mirror of
https://github.com/fatedier/frp.git
synced 2026-03-20 16:59:18 +08:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a7efc81c9 | ||
|
|
12c5c553c3 | ||
|
|
988e9b1de3 | ||
|
|
db6bbc5187 | ||
|
|
c67b4e7b94 | ||
|
|
b7a73d3469 | ||
|
|
7f9d88c10a | ||
|
|
79237d2b94 | ||
|
|
9c4ec56491 | ||
|
|
74a8752570 | ||
|
|
a8ab4c5003 | ||
|
|
9cee263c91 | ||
|
|
c6bf6f59e6 | ||
|
|
4b7aef2196 | ||
|
|
f6d0046b5a | ||
|
|
84363266d2 | ||
|
|
9ac8f2a047 | ||
|
|
b2b55533b8 | ||
|
|
a4cfab689a | ||
|
|
c7df39074c | ||
|
|
fdcdccb0c2 | ||
|
|
e945c1667a | ||
|
|
87a4de4370 | ||
|
|
e1e2913b77 | ||
|
|
9be24db410 | ||
|
|
6b61cb3742 | ||
|
|
90b7f2080f | ||
|
|
d1f1c72a55 | ||
|
|
1925847ef8 | ||
|
|
8b216b0ca9 | ||
|
|
dbfeea99f3 | ||
|
|
5e64bbfa7c | ||
|
|
e691a40260 | ||
|
|
d812488767 | ||
|
|
3c03690ab7 | ||
|
|
3df27b9c04 | ||
|
|
ba45d29b7c | ||
|
|
3cf83f57a8 | ||
|
|
03e4318d79 | ||
|
|
178d134f46 | ||
|
|
cbf9c731a0 | ||
|
|
de4bfcc43c | ||
|
|
9737978f28 | ||
|
|
5bc7fe2cea | ||
|
|
65d8fe37c5 | ||
|
|
1723d7b651 | ||
|
|
2481dfab64 | ||
|
|
95a881a7d3 | ||
|
|
fe403ab328 | ||
|
|
66555dbb00 | ||
|
|
7f9ea48405 | ||
|
|
96d7e2da6f | ||
|
|
d879b8208b | ||
|
|
3585e456d4 | ||
|
|
1de8c3fc87 | ||
|
|
bbab3fe9ca | ||
|
|
48990da22e |
@@ -2,8 +2,8 @@ sudo: false
|
|||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.10.x
|
|
||||||
- 1.11.x
|
- 1.11.x
|
||||||
|
- 1.12.x
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- make
|
- make
|
||||||
|
|||||||
12
Makefile
12
Makefile
@@ -6,11 +6,12 @@ build: frps frpc
|
|||||||
|
|
||||||
# compile assets into binary file
|
# compile assets into binary file
|
||||||
file:
|
file:
|
||||||
rm -rf ./assets/static/*
|
rm -rf ./assets/frps/static/*
|
||||||
cp -rf ./web/frps/dist/* ./assets/static
|
rm -rf ./assets/frpc/static/*
|
||||||
go get -d github.com/rakyll/statik
|
cp -rf ./web/frps/dist/* ./assets/frps/static
|
||||||
go install github.com/rakyll/statik
|
cp -rf ./web/frpc/dist/* ./assets/frpc/static
|
||||||
rm -rf ./assets/statik
|
rm -rf ./assets/frps/statik
|
||||||
|
rm -rf ./assets/frpc/statik
|
||||||
go generate ./assets/...
|
go generate ./assets/...
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
@@ -18,7 +19,6 @@ fmt:
|
|||||||
|
|
||||||
frps:
|
frps:
|
||||||
go build -o bin/frps ./cmd/frps
|
go build -o bin/frps ./cmd/frps
|
||||||
@cp -rf ./assets/static ./bin
|
|
||||||
|
|
||||||
frpc:
|
frpc:
|
||||||
go build -o bin/frpc ./cmd/frpc
|
go build -o bin/frpc ./cmd/frpc
|
||||||
|
|||||||
85
README.md
85
README.md
@@ -22,14 +22,17 @@ 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)
|
||||||
* [Configuration File](#configuration-file)
|
* [Configuration File](#configuration-file)
|
||||||
* [Configuration file template](#configuration-file-template)
|
* [Configuration file template](#configuration-file-template)
|
||||||
* [Dashboard](#dashboard)
|
* [Dashboard](#dashboard)
|
||||||
|
* [Admin UI](#admin-ui)
|
||||||
* [Authentication](#authentication)
|
* [Authentication](#authentication)
|
||||||
* [Encryption and Compression](#encryption-and-compression)
|
* [Encryption and Compression](#encryption-and-compression)
|
||||||
|
* [TLS](#tls)
|
||||||
* [Hot-Reload frpc configuration](#hot-reload-frpc-configuration)
|
* [Hot-Reload frpc configuration](#hot-reload-frpc-configuration)
|
||||||
* [Get proxy status from client](#get-proxy-status-from-client)
|
* [Get proxy status from client](#get-proxy-status-from-client)
|
||||||
* [Port White List](#port-white-list)
|
* [Port White List](#port-white-list)
|
||||||
@@ -42,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)
|
||||||
@@ -241,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.
|
||||||
|
|
||||||
@@ -389,6 +417,22 @@ Then visit `http://[server_addr]:7500` to see dashboard, default username and pa
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### Admin UI
|
||||||
|
|
||||||
|
Admin UI help you check and manage frpc's configure.
|
||||||
|
|
||||||
|
Configure a address for admin UI to enable this feature:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[common]
|
||||||
|
admin_addr = 127.0.0.1
|
||||||
|
admin_port = 7400
|
||||||
|
admin_user = admin
|
||||||
|
admin_pwd = admin
|
||||||
|
```
|
||||||
|
|
||||||
|
Then visit `http://127.0.0.1:7400` to see admin UI, default username and password are both `admin`.
|
||||||
|
|
||||||
### Authentication
|
### Authentication
|
||||||
|
|
||||||
`token` in frps.ini and frpc.ini should be same.
|
`token` in frps.ini and frpc.ini should be same.
|
||||||
@@ -407,6 +451,14 @@ use_encryption = true
|
|||||||
use_compression = true
|
use_compression = true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### TLS
|
||||||
|
|
||||||
|
frp support TLS protocol between frpc and frps since v0.25.0.
|
||||||
|
|
||||||
|
Config `tls_enable = true` in `common` section to frpc.ini to enable this feature.
|
||||||
|
|
||||||
|
For port multiplexing, frp send a first byte 0x17 to dial a TLS connection.
|
||||||
|
|
||||||
### Hot-Reload frpc configuration
|
### Hot-Reload frpc configuration
|
||||||
|
|
||||||
First you need to set admin port in frpc's configure file to let it provide HTTP API for more features.
|
First you need to set admin port in frpc's configure file to let it provide HTTP API for more features.
|
||||||
@@ -458,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:
|
||||||
@@ -592,7 +642,7 @@ custom_domains = test.yourdomain.com
|
|||||||
host_header_rewrite = dev.yourdomain.com
|
host_header_rewrite = dev.yourdomain.com
|
||||||
```
|
```
|
||||||
|
|
||||||
If `host_header_rewrite` is specified, the host header will be rewritten to match the hostname portion of the forwarding address.
|
The `Host` request header will be rewritten to `Host: dev.yourdomain.com` before it reach your local http server.
|
||||||
|
|
||||||
### Set Headers In HTTP Request
|
### Set Headers In HTTP Request
|
||||||
|
|
||||||
@@ -613,9 +663,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
|
||||||
|
|
||||||
@@ -736,8 +809,6 @@ plugin_http_passwd = abc
|
|||||||
## Development Plan
|
## Development Plan
|
||||||
|
|
||||||
* Log http request information in frps.
|
* Log http request information in frps.
|
||||||
* Direct reverse proxy, like haproxy.
|
|
||||||
* kubernetes ingress support.
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
91
README_zh.md
91
README_zh.md
@@ -18,14 +18,17 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
|
|||||||
* [转发 DNS 查询请求](#转发-dns-查询请求)
|
* [转发 DNS 查询请求](#转发-dns-查询请求)
|
||||||
* [转发 Unix域套接字](#转发-unix域套接字)
|
* [转发 Unix域套接字](#转发-unix域套接字)
|
||||||
* [对外提供简单的文件访问服务](#对外提供简单的文件访问服务)
|
* [对外提供简单的文件访问服务](#对外提供简单的文件访问服务)
|
||||||
|
* [为本地 HTTP 服务启用 HTTPS](#为本地-http-服务启用-https)
|
||||||
* [安全地暴露内网服务](#安全地暴露内网服务)
|
* [安全地暴露内网服务](#安全地暴露内网服务)
|
||||||
* [点对点内网穿透](#点对点内网穿透)
|
* [点对点内网穿透](#点对点内网穿透)
|
||||||
* [功能说明](#功能说明)
|
* [功能说明](#功能说明)
|
||||||
* [配置文件](#配置文件)
|
* [配置文件](#配置文件)
|
||||||
* [配置文件模版渲染](#配置文件模版渲染)
|
* [配置文件模版渲染](#配置文件模版渲染)
|
||||||
* [Dashboard](#dashboard)
|
* [Dashboard](#dashboard)
|
||||||
|
* [Admin UI](#admin-ui)
|
||||||
* [身份验证](#身份验证)
|
* [身份验证](#身份验证)
|
||||||
* [加密与压缩](#加密与压缩)
|
* [加密与压缩](#加密与压缩)
|
||||||
|
* [TLS](#tls)
|
||||||
* [客户端热加载配置文件](#客户端热加载配置文件)
|
* [客户端热加载配置文件](#客户端热加载配置文件)
|
||||||
* [客户端查看代理状态](#客户端查看代理状态)
|
* [客户端查看代理状态](#客户端查看代理状态)
|
||||||
* [端口白名单](#端口白名单)
|
* [端口白名单](#端口白名单)
|
||||||
@@ -38,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-路由)
|
||||||
@@ -47,6 +52,7 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
|
|||||||
* [开发计划](#开发计划)
|
* [开发计划](#开发计划)
|
||||||
* [为 frp 做贡献](#为-frp-做贡献)
|
* [为 frp 做贡献](#为-frp-做贡献)
|
||||||
* [捐助](#捐助)
|
* [捐助](#捐助)
|
||||||
|
* [知识星球](#知识星球)
|
||||||
* [支付宝扫码捐赠](#支付宝扫码捐赠)
|
* [支付宝扫码捐赠](#支付宝扫码捐赠)
|
||||||
* [微信支付捐赠](#微信支付捐赠)
|
* [微信支付捐赠](#微信支付捐赠)
|
||||||
* [Paypal 捐赠](#paypal-捐赠)
|
* [Paypal 捐赠](#paypal-捐赠)
|
||||||
@@ -241,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` 即可。
|
||||||
|
|
||||||
### 安全地暴露内网服务
|
### 安全地暴露内网服务
|
||||||
|
|
||||||
对于某些服务来说如果直接暴露于公网上将会存在安全隐患。
|
对于某些服务来说如果直接暴露于公网上将会存在安全隐患。
|
||||||
@@ -404,6 +437,24 @@ dashboard_pwd = admin
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### Admin UI
|
||||||
|
|
||||||
|
Admin UI 可以帮助用户通过浏览器来查询和管理客户端的 proxy 状态和配置。
|
||||||
|
|
||||||
|
需要在 frpc.ini 中指定 admin 服务使用的端口,即可开启此功能:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[common]
|
||||||
|
admin_addr = 127.0.0.1
|
||||||
|
admin_port = 7400
|
||||||
|
admin_user = admin
|
||||||
|
admin_pwd = admin
|
||||||
|
```
|
||||||
|
|
||||||
|
打开浏览器通过 `http://127.0.0.1:7400` 访问 Admin UI,用户名密码默认为 `admin`。
|
||||||
|
|
||||||
|
如果想要在外网环境访问 Admin UI,将 7400 端口映射出去即可,但需要重视安全风险。
|
||||||
|
|
||||||
### 身份验证
|
### 身份验证
|
||||||
|
|
||||||
服务端和客户端的 common 配置中的 `token` 参数一致则身份验证通过。
|
服务端和客户端的 common 配置中的 `token` 参数一致则身份验证通过。
|
||||||
@@ -426,6 +477,14 @@ use_compression = true
|
|||||||
|
|
||||||
如果传输的报文长度较长,通过设置 `use_compression = true` 对传输内容进行压缩,可以有效减小 frpc 与 frps 之间的网络流量,加快流量转发速度,但是会额外消耗一些 cpu 资源。
|
如果传输的报文长度较长,通过设置 `use_compression = true` 对传输内容进行压缩,可以有效减小 frpc 与 frps 之间的网络流量,加快流量转发速度,但是会额外消耗一些 cpu 资源。
|
||||||
|
|
||||||
|
#### TLS
|
||||||
|
|
||||||
|
从 v0.25.0 版本开始 frpc 和 frps 之间支持通过 TLS 协议加密传输。通过在 `frpc.ini` 的 `common` 中配置 `tls_enable = true` 来启用此功能,安全性更高。
|
||||||
|
|
||||||
|
为了端口复用,frp 建立 TLS 连接的第一个字节为 0x17。
|
||||||
|
|
||||||
|
**注意: 启用此功能后除 xtcp 外,不需要再设置 use_encryption。**
|
||||||
|
|
||||||
### 客户端热加载配置文件
|
### 客户端热加载配置文件
|
||||||
|
|
||||||
当修改了 frpc 中的代理配置,可以通过 `frpc reload` 命令来动态加载配置文件,通常会在 10 秒内完成代理的更新。
|
当修改了 frpc 中的代理配置,可以通过 `frpc reload` 命令来动态加载配置文件,通常会在 10 秒内完成代理的更新。
|
||||||
@@ -485,7 +544,7 @@ tcp_mux = false
|
|||||||
|
|
||||||
### 底层通信可选 kcp 协议
|
### 底层通信可选 kcp 协议
|
||||||
|
|
||||||
从 v0.12.0 版本开始,底层通信协议支持选择 kcp 协议,在弱网环境下传输效率提升明显,但是会有一些额外的流量消耗。
|
底层通信协议支持选择 kcp 协议,在弱网环境下传输效率提升明显,但是会有一些额外的流量消耗。
|
||||||
|
|
||||||
开启 kcp 协议支持:
|
开启 kcp 协议支持:
|
||||||
|
|
||||||
@@ -537,6 +596,7 @@ tcp_mux = false
|
|||||||
### 负载均衡
|
### 负载均衡
|
||||||
|
|
||||||
可以将多个相同类型的 proxy 加入到同一个 group 中,从而实现负载均衡的功能。
|
可以将多个相同类型的 proxy 加入到同一个 group 中,从而实现负载均衡的功能。
|
||||||
|
|
||||||
目前只支持 tcp 类型的 proxy。
|
目前只支持 tcp 类型的 proxy。
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
@@ -639,7 +699,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 服务
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,10 @@
|
|||||||
|
|
||||||
package assets
|
package assets
|
||||||
|
|
||||||
//go:generate statik -src=./static
|
//go:generate statik -src=./frps/static -dest=./frps
|
||||||
//go:generate go fmt statik/statik.go
|
//go:generate statik -src=./frpc/static -dest=./frpc
|
||||||
|
//go:generate go fmt ./frps/statik/statik.go
|
||||||
|
//go:generate go fmt ./frpc/statik/statik.go
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -24,8 +26,6 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/rakyll/statik/fs"
|
"github.com/rakyll/statik/fs"
|
||||||
|
|
||||||
_ "github.com/fatedier/frp/assets/statik"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
1
assets/frpc/static/index.html
Normal file
1
assets/frpc/static/index.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<!doctype html> <html lang=en> <head> <meta charset=utf-8> <title>frp client admin UI</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?d2cd6337d30c7b22e836"></script><script type="text/javascript" src="vendor.js?edb271e1d9c81f857840"></script></body> </html>
|
||||||
1
assets/frpc/static/manifest.js
Normal file
1
assets/frpc/static/manifest.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
!function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,c,u){for(var i,a,f,l=0,s=[];l<t.length;l++)a=t[l],o[a]&&s.push(o[a][0]),o[a]=0;for(i in c)Object.prototype.hasOwnProperty.call(c,i)&&(e[i]=c[i]);for(r&&r(t,c,u);s.length;)s.shift()();if(u)for(l=0;l<u.length;l++)f=n(n.s=u[l]);return f};var t={},o={1:0};n.e=function(e){function r(){i.onerror=i.onload=null,clearTimeout(a);var n=o[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}var t=o[e];if(0===t)return new Promise(function(e){e()});if(t)return t[2];var c=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=c;var u=document.getElementsByTagName("head")[0],i=document.createElement("script");i.type="text/javascript",i.charset="utf-8",i.async=!0,i.timeout=12e4,n.nc&&i.setAttribute("nonce",n.nc),i.src=n.p+""+e+".js?"+{0:"edb271e1d9c81f857840"}[e];var a=setTimeout(r,12e4);return i.onerror=i.onload=r,u.appendChild(i),c},n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n.oe=function(e){throw console.error(e),e}}([]);
|
||||||
1
assets/frpc/static/vendor.js
Normal file
1
assets/frpc/static/vendor.js
Normal file
File diff suppressed because one or more lines are too long
10
assets/frpc/statik/statik.go
Normal file
10
assets/frpc/statik/statik.go
Normal file
File diff suppressed because one or more lines are too long
BIN
assets/frps/static/6f0a76321d30f3c8120915e57f7bd77e.ttf
Normal file
BIN
assets/frps/static/6f0a76321d30f3c8120915e57f7bd77e.ttf
Normal file
Binary file not shown.
BIN
assets/frps/static/favicon.ico
Normal file
BIN
assets/frps/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
10
assets/frps/statik/statik.go
Normal file
10
assets/frps/statik/statik.go
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -20,6 +20,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/assets"
|
||||||
"github.com/fatedier/frp/g"
|
"github.com/fatedier/frp/g"
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
|
||||||
@@ -41,6 +42,15 @@ func (svr *Service) RunAdminServer(addr string, port int) (err error) {
|
|||||||
// api, see dashboard_api.go
|
// api, see dashboard_api.go
|
||||||
router.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
|
router.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
|
||||||
router.HandleFunc("/api/status", svr.apiStatus).Methods("GET")
|
router.HandleFunc("/api/status", svr.apiStatus).Methods("GET")
|
||||||
|
router.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET")
|
||||||
|
router.HandleFunc("/api/config", svr.apiPutConfig).Methods("PUT")
|
||||||
|
|
||||||
|
// view
|
||||||
|
router.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
|
||||||
|
router.PathPrefix("/static/").Handler(frpNet.MakeHttpGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
|
||||||
|
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||||
|
})
|
||||||
|
|
||||||
address := fmt.Sprintf("%s:%d", addr, port)
|
address := fmt.Sprintf("%s:%d", addr, port)
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ package client
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -28,57 +29,53 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type GeneralResponse struct {
|
type GeneralResponse struct {
|
||||||
Code int64 `json:"code"`
|
Code int
|
||||||
Msg string `json:"msg"`
|
Msg string
|
||||||
}
|
}
|
||||||
|
|
||||||
// api/reload
|
// GET api/reload
|
||||||
type ReloadResp struct {
|
|
||||||
GeneralResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
res := GeneralResponse{Code: 200}
|
||||||
buf []byte
|
|
||||||
res ReloadResp
|
|
||||||
)
|
|
||||||
defer func() {
|
|
||||||
log.Info("Http response [/api/reload]: code [%d]", res.Code)
|
|
||||||
buf, _ = json.Marshal(&res)
|
|
||||||
w.Write(buf)
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.Info("Http request: [/api/reload]")
|
log.Info("Http request [/api/reload]")
|
||||||
|
defer func() {
|
||||||
|
log.Info("Http response [/api/reload], code [%d]", res.Code)
|
||||||
|
w.WriteHeader(res.Code)
|
||||||
|
if len(res.Msg) > 0 {
|
||||||
|
w.Write([]byte(res.Msg))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile)
|
content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Code = 1
|
res.Code = 400
|
||||||
res.Msg = err.Error()
|
res.Msg = err.Error()
|
||||||
log.Error("reload frpc config file error: %v", err)
|
log.Warn("reload frpc config file error: %s", res.Msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newCommonCfg, err := config.UnmarshalClientConfFromIni(nil, content)
|
newCommonCfg, err := config.UnmarshalClientConfFromIni(nil, content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Code = 2
|
res.Code = 400
|
||||||
res.Msg = err.Error()
|
res.Msg = err.Error()
|
||||||
log.Error("reload frpc common section error: %v", err)
|
log.Warn("reload frpc common section error: %s", res.Msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, newCommonCfg.Start)
|
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, newCommonCfg.Start)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Code = 3
|
res.Code = 400
|
||||||
res.Msg = err.Error()
|
res.Msg = err.Error()
|
||||||
log.Error("reload frpc proxy config error: %v", err)
|
log.Warn("reload frpc proxy config error: %s", res.Msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = svr.ReloadConf(pxyCfgs, visitorCfgs)
|
err = svr.ReloadConf(pxyCfgs, visitorCfgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Code = 4
|
res.Code = 500
|
||||||
res.Msg = err.Error()
|
res.Msg = err.Error()
|
||||||
log.Error("reload frpc proxy config error: %v", err)
|
log.Warn("reload frpc proxy config error: %s", res.Msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Info("success reload conf")
|
log.Info("success reload conf")
|
||||||
@@ -163,7 +160,7 @@ func NewProxyStatusResp(status *proxy.ProxyStatus) ProxyStatusResp {
|
|||||||
return psr
|
return psr
|
||||||
}
|
}
|
||||||
|
|
||||||
// api/status
|
// GET api/status
|
||||||
func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
var (
|
||||||
buf []byte
|
buf []byte
|
||||||
@@ -175,14 +172,14 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
|||||||
res.Https = make([]ProxyStatusResp, 0)
|
res.Https = make([]ProxyStatusResp, 0)
|
||||||
res.Stcp = make([]ProxyStatusResp, 0)
|
res.Stcp = make([]ProxyStatusResp, 0)
|
||||||
res.Xtcp = make([]ProxyStatusResp, 0)
|
res.Xtcp = make([]ProxyStatusResp, 0)
|
||||||
|
|
||||||
|
log.Info("Http request [/api/status]")
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Info("Http response [/api/status]")
|
log.Info("Http response [/api/status]")
|
||||||
buf, _ = json.Marshal(&res)
|
buf, _ = json.Marshal(&res)
|
||||||
w.Write(buf)
|
w.Write(buf)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
log.Info("Http request: [/api/status]")
|
|
||||||
|
|
||||||
ps := svr.ctl.pm.GetAllProxyStatus()
|
ps := svr.ctl.pm.GetAllProxyStatus()
|
||||||
for _, status := range ps {
|
for _, status := range ps {
|
||||||
switch status.Type {
|
switch status.Type {
|
||||||
@@ -208,3 +205,122 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
|||||||
sort.Sort(ByProxyStatusResp(res.Xtcp))
|
sort.Sort(ByProxyStatusResp(res.Xtcp))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET api/config
|
||||||
|
func (svr *Service) apiGetConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
|
res := GeneralResponse{Code: 200}
|
||||||
|
|
||||||
|
log.Info("Http get request [/api/config]")
|
||||||
|
defer func() {
|
||||||
|
log.Info("Http get response [/api/config], code [%d]", res.Code)
|
||||||
|
w.WriteHeader(res.Code)
|
||||||
|
if len(res.Msg) > 0 {
|
||||||
|
w.Write([]byte(res.Msg))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if g.GlbClientCfg.CfgFile == "" {
|
||||||
|
res.Code = 400
|
||||||
|
res.Msg = "frpc has no config file path"
|
||||||
|
log.Warn("%s", res.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile)
|
||||||
|
if err != nil {
|
||||||
|
res.Code = 400
|
||||||
|
res.Msg = err.Error()
|
||||||
|
log.Warn("load frpc config file error: %s", res.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rows := strings.Split(content, "\n")
|
||||||
|
newRows := make([]string, 0, len(rows))
|
||||||
|
for _, row := range rows {
|
||||||
|
row = strings.TrimSpace(row)
|
||||||
|
if strings.HasPrefix(row, "token") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newRows = append(newRows, row)
|
||||||
|
}
|
||||||
|
res.Msg = strings.Join(newRows, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT api/config
|
||||||
|
func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
|
res := GeneralResponse{Code: 200}
|
||||||
|
|
||||||
|
log.Info("Http put request [/api/config]")
|
||||||
|
defer func() {
|
||||||
|
log.Info("Http put response [/api/config], code [%d]", res.Code)
|
||||||
|
w.WriteHeader(res.Code)
|
||||||
|
if len(res.Msg) > 0 {
|
||||||
|
w.Write([]byte(res.Msg))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// get new config content
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
res.Code = 400
|
||||||
|
res.Msg = fmt.Sprintf("read request body error: %v", err)
|
||||||
|
log.Warn("%s", res.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(body) == 0 {
|
||||||
|
res.Code = 400
|
||||||
|
res.Msg = "body can't be empty"
|
||||||
|
log.Warn("%s", res.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get token from origin content
|
||||||
|
token := ""
|
||||||
|
b, err := ioutil.ReadFile(g.GlbClientCfg.CfgFile)
|
||||||
|
if err != nil {
|
||||||
|
res.Code = 400
|
||||||
|
res.Msg = err.Error()
|
||||||
|
log.Warn("load frpc config file error: %s", res.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
content := string(b)
|
||||||
|
|
||||||
|
for _, row := range strings.Split(content, "\n") {
|
||||||
|
row = strings.TrimSpace(row)
|
||||||
|
if strings.HasPrefix(row, "token") {
|
||||||
|
token = row
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpRows := make([]string, 0)
|
||||||
|
for _, row := range strings.Split(string(body), "\n") {
|
||||||
|
row = strings.TrimSpace(row)
|
||||||
|
if strings.HasPrefix(row, "token") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tmpRows = append(tmpRows, row)
|
||||||
|
}
|
||||||
|
|
||||||
|
newRows := make([]string, 0)
|
||||||
|
if token != "" {
|
||||||
|
for _, row := range tmpRows {
|
||||||
|
newRows = append(newRows, row)
|
||||||
|
if strings.HasPrefix(row, "[common]") {
|
||||||
|
newRows = append(newRows, token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newRows = tmpRows
|
||||||
|
}
|
||||||
|
content = strings.Join(newRows, "\n")
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(g.GlbClientCfg.CfgFile, []byte(content), 0644)
|
||||||
|
if err != nil {
|
||||||
|
res.Code = 500
|
||||||
|
res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)
|
||||||
|
log.Warn("%s", res.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
@@ -130,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) {
|
||||||
@@ -147,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,8 +170,14 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
|
|||||||
}
|
}
|
||||||
conn = frpNet.WrapConn(stream)
|
conn = frpNet.WrapConn(stream)
|
||||||
} else {
|
} else {
|
||||||
conn, err = frpNet.ConnectServerByProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
|
var tlsConfig *tls.Config
|
||||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort))
|
if g.GlbClientCfg.TLSEnable {
|
||||||
|
tlsConfig = &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn, err = frpNet.ConnectServerByProxyWithTLS(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
|
||||||
|
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort), tlsConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctl.Warn("start new connection to server error: %v", err)
|
ctl.Warn("start new connection to server error: %v", err)
|
||||||
return
|
return
|
||||||
@@ -195,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 {
|
||||||
@@ -293,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
@@ -170,6 +172,8 @@ func (monitor *HealthCheckMonitor) doHttpCheck(ctx context.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
io.Copy(ioutil.Discard, resp.Body)
|
||||||
|
|
||||||
if resp.StatusCode/100 != 2 {
|
if resp.StatusCode/100 != 2 {
|
||||||
return fmt.Errorf("do http health check, StatusCode is [%d] not 2xx", resp.StatusCode)
|
return fmt.Errorf("do http health check, StatusCode is [%d] not 2xx", resp.StatusCode)
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -33,6 +36,8 @@ import (
|
|||||||
"github.com/fatedier/golib/errors"
|
"github.com/fatedier/golib/errors"
|
||||||
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"
|
||||||
|
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.
|
||||||
@@ -40,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
|
||||||
@@ -53,32 +58,32 @@ func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) {
|
|||||||
switch cfg := pxyConf.(type) {
|
switch cfg := pxyConf.(type) {
|
||||||
case *config.TcpProxyConf:
|
case *config.TcpProxyConf:
|
||||||
pxy = &TcpProxy{
|
pxy = &TcpProxy{
|
||||||
BaseProxy: baseProxy,
|
BaseProxy: &baseProxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
case *config.UdpProxyConf:
|
case *config.UdpProxyConf:
|
||||||
pxy = &UdpProxy{
|
pxy = &UdpProxy{
|
||||||
BaseProxy: baseProxy,
|
BaseProxy: &baseProxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
case *config.HttpProxyConf:
|
case *config.HttpProxyConf:
|
||||||
pxy = &HttpProxy{
|
pxy = &HttpProxy{
|
||||||
BaseProxy: baseProxy,
|
BaseProxy: &baseProxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
case *config.HttpsProxyConf:
|
case *config.HttpsProxyConf:
|
||||||
pxy = &HttpsProxy{
|
pxy = &HttpsProxy{
|
||||||
BaseProxy: baseProxy,
|
BaseProxy: &baseProxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
case *config.StcpProxyConf:
|
case *config.StcpProxyConf:
|
||||||
pxy = &StcpProxy{
|
pxy = &StcpProxy{
|
||||||
BaseProxy: baseProxy,
|
BaseProxy: &baseProxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
case *config.XtcpProxyConf:
|
case *config.XtcpProxyConf:
|
||||||
pxy = &XtcpProxy{
|
pxy = &XtcpProxy{
|
||||||
BaseProxy: baseProxy,
|
BaseProxy: &baseProxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,7 +98,7 @@ type BaseProxy struct {
|
|||||||
|
|
||||||
// TCP
|
// TCP
|
||||||
type TcpProxy struct {
|
type TcpProxy struct {
|
||||||
BaseProxy
|
*BaseProxy
|
||||||
|
|
||||||
cfg *config.TcpProxyConf
|
cfg *config.TcpProxyConf
|
||||||
proxyPlugin plugin.Plugin
|
proxyPlugin plugin.Plugin
|
||||||
@@ -115,14 +120,14 @@ 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
|
||||||
type HttpProxy struct {
|
type HttpProxy struct {
|
||||||
BaseProxy
|
*BaseProxy
|
||||||
|
|
||||||
cfg *config.HttpProxyConf
|
cfg *config.HttpProxyConf
|
||||||
proxyPlugin plugin.Plugin
|
proxyPlugin plugin.Plugin
|
||||||
@@ -144,14 +149,14 @@ 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
|
||||||
type HttpsProxy struct {
|
type HttpsProxy struct {
|
||||||
BaseProxy
|
*BaseProxy
|
||||||
|
|
||||||
cfg *config.HttpsProxyConf
|
cfg *config.HttpsProxyConf
|
||||||
proxyPlugin plugin.Plugin
|
proxyPlugin plugin.Plugin
|
||||||
@@ -173,14 +178,14 @@ 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
|
||||||
type StcpProxy struct {
|
type StcpProxy struct {
|
||||||
BaseProxy
|
*BaseProxy
|
||||||
|
|
||||||
cfg *config.StcpProxyConf
|
cfg *config.StcpProxyConf
|
||||||
proxyPlugin plugin.Plugin
|
proxyPlugin plugin.Plugin
|
||||||
@@ -202,14 +207,14 @@ 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
|
||||||
type XtcpProxy struct {
|
type XtcpProxy struct {
|
||||||
BaseProxy
|
*BaseProxy
|
||||||
|
|
||||||
cfg *config.XtcpProxyConf
|
cfg *config.XtcpProxyConf
|
||||||
proxyPlugin plugin.Plugin
|
proxyPlugin plugin.Plugin
|
||||||
@@ -231,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)
|
||||||
@@ -278,37 +283,102 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
|
pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s] visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
|
||||||
|
|
||||||
// Send sid to visitor udp address.
|
// Send detect message
|
||||||
time.Sleep(time.Second)
|
array := strings.Split(natHoleRespMsg.VisitorAddr, ":")
|
||||||
|
if len(array) <= 1 {
|
||||||
|
pxy.Error("get NatHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
||||||
|
}
|
||||||
laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String())
|
laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String())
|
||||||
daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.VisitorAddr)
|
/*
|
||||||
|
for i := 1000; i < 65000; i++ {
|
||||||
|
pxy.sendDetectMsg(array[0], int64(i), laddr, "a")
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
port, err := strconv.ParseInt(array[1], 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pxy.Error("resolve visitor udp address error: %v", err)
|
pxy.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
pxy.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid))
|
||||||
|
pxy.Trace("send all detect msg done")
|
||||||
|
|
||||||
lConn, err := net.DialUDP("udp", laddr, daddr)
|
msg.WriteMsg(conn, &msg.NatHoleClientDetectOK{})
|
||||||
|
|
||||||
|
// Listen for clientConn's address and wait for visitor connection
|
||||||
|
lConn, err := net.ListenUDP("udp", laddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pxy.Error("dial visitor udp address error: %v", err)
|
pxy.Error("listen on visitorConn's local adress error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
lConn.Write([]byte(natHoleRespMsg.Sid))
|
defer lConn.Close()
|
||||||
|
|
||||||
kcpConn, err := frpNet.NewKcpConnFromUdp(lConn, true, natHoleRespMsg.VisitorAddr)
|
lConn.SetReadDeadline(time.Now().Add(8 * time.Second))
|
||||||
|
sidBuf := pool.GetBuf(1024)
|
||||||
|
var uAddr *net.UDPAddr
|
||||||
|
n, uAddr, err = lConn.ReadFromUDP(sidBuf)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Warn("get sid from visitor error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lConn.SetReadDeadline(time.Time{})
|
||||||
|
if string(sidBuf[:n]) != natHoleRespMsg.Sid {
|
||||||
|
pxy.Warn("incorrect sid from visitor")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pool.PutBuf(sidBuf)
|
||||||
|
pxy.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
|
||||||
|
|
||||||
|
lConn.WriteToUDP(sidBuf[:n], uAddr)
|
||||||
|
|
||||||
|
kcpConn, err := frpNet.NewKcpConnFromUdp(lConn, false, natHoleRespMsg.VisitorAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pxy.Error("create kcp connection from udp connection error: %v", err)
|
pxy.Error("create kcp connection from udp connection error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmuxCfg := fmux.DefaultConfig()
|
||||||
|
fmuxCfg.KeepAliveInterval = 5 * time.Second
|
||||||
|
fmuxCfg.LogOutput = ioutil.Discard
|
||||||
|
sess, err := fmux.Server(kcpConn, fmuxCfg)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("create yamux server from kcp connection error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer sess.Close()
|
||||||
|
muxConn, err := sess.Accept()
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("accept for yamux connection error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf,
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf,
|
||||||
frpNet.WrapConn(kcpConn), []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) {
|
||||||
|
daddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addr, port))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tConn, err := net.DialUDP("udp", laddr, daddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//uConn := ipv4.NewConn(tConn)
|
||||||
|
//uConn.SetTTL(3)
|
||||||
|
|
||||||
|
tConn.Write(content)
|
||||||
|
tConn.Close()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UDP
|
// UDP
|
||||||
type UdpProxy struct {
|
type UdpProxy struct {
|
||||||
BaseProxy
|
*BaseProxy
|
||||||
|
|
||||||
cfg *config.UdpProxyConf
|
cfg *config.UdpProxyConf
|
||||||
|
|
||||||
@@ -346,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()
|
||||||
@@ -413,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
|
||||||
@@ -449,6 +519,37 @@ 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())
|
||||||
|
|
||||||
|
// check if we need to send proxy protocol info
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
h.WriteTo(localConn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -22,6 +23,7 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/assets"
|
||||||
"github.com/fatedier/frp/g"
|
"github.com/fatedier/frp/g"
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/models/msg"
|
"github.com/fatedier/frp/models/msg"
|
||||||
@@ -49,7 +51,14 @@ type Service struct {
|
|||||||
closedCh chan int
|
closedCh chan int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (svr *Service) {
|
func NewService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (svr *Service, err error) {
|
||||||
|
// Init assets
|
||||||
|
err = assets.Load("")
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Load assets error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
svr = &Service{
|
svr = &Service{
|
||||||
pxyCfgs: pxyCfgs,
|
pxyCfgs: pxyCfgs,
|
||||||
visitorCfgs: visitorCfgs,
|
visitorCfgs: visitorCfgs,
|
||||||
@@ -143,8 +152,14 @@ func (svr *Service) keepControllerWorking() {
|
|||||||
// conn: control connection
|
// conn: control connection
|
||||||
// session: if it's not nil, using tcp mux
|
// session: if it's not nil, using tcp mux
|
||||||
func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) {
|
func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) {
|
||||||
conn, err = frpNet.ConnectServerByProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
|
var tlsConfig *tls.Config
|
||||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort))
|
if g.GlbClientCfg.TLSEnable {
|
||||||
|
tlsConfig = &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn, err = frpNet.ConnectServerByProxyWithTLS(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
|
||||||
|
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort), tlsConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,14 +18,11 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/net/ipv4"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/g"
|
"github.com/fatedier/frp/g"
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/models/msg"
|
"github.com/fatedier/frp/models/msg"
|
||||||
@@ -35,6 +32,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"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Visitor is used for forward traffics from local port tot remote service.
|
// Visitor is used for forward traffics from local port tot remote service.
|
||||||
@@ -52,12 +50,12 @@ func NewVisitor(ctl *Control, cfg config.VisitorConf) (visitor Visitor) {
|
|||||||
switch cfg := cfg.(type) {
|
switch cfg := cfg.(type) {
|
||||||
case *config.StcpVisitorConf:
|
case *config.StcpVisitorConf:
|
||||||
visitor = &StcpVisitor{
|
visitor = &StcpVisitor{
|
||||||
BaseVisitor: baseVisitor,
|
BaseVisitor: &baseVisitor,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
case *config.XtcpVisitorConf:
|
case *config.XtcpVisitorConf:
|
||||||
visitor = &XtcpVisitor{
|
visitor = &XtcpVisitor{
|
||||||
BaseVisitor: baseVisitor,
|
BaseVisitor: &baseVisitor,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,7 +71,7 @@ type BaseVisitor struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StcpVisitor struct {
|
type StcpVisitor struct {
|
||||||
BaseVisitor
|
*BaseVisitor
|
||||||
|
|
||||||
cfg *config.StcpVisitorConf
|
cfg *config.StcpVisitorConf
|
||||||
}
|
}
|
||||||
@@ -160,7 +158,7 @@ func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type XtcpVisitor struct {
|
type XtcpVisitor struct {
|
||||||
BaseVisitor
|
*BaseVisitor
|
||||||
|
|
||||||
cfg *config.XtcpVisitorConf
|
cfg *config.XtcpVisitorConf
|
||||||
}
|
}
|
||||||
@@ -249,40 +247,31 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sv.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
|
sv.Trace("get natHoleRespMsg, sid [%s], client address [%s], visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
|
||||||
|
|
||||||
// Close visitorConn, so we can use it's local address.
|
// Close visitorConn, so we can use it's local address.
|
||||||
visitorConn.Close()
|
visitorConn.Close()
|
||||||
|
|
||||||
// Send detect message.
|
// send sid message to client
|
||||||
array := strings.Split(natHoleRespMsg.ClientAddr, ":")
|
|
||||||
if len(array) <= 1 {
|
|
||||||
sv.Error("get natHoleResp client address error: %s", natHoleRespMsg.ClientAddr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
laddr, _ := net.ResolveUDPAddr("udp", visitorConn.LocalAddr().String())
|
laddr, _ := net.ResolveUDPAddr("udp", visitorConn.LocalAddr().String())
|
||||||
/*
|
daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.ClientAddr)
|
||||||
for i := 1000; i < 65000; i++ {
|
|
||||||
sv.sendDetectMsg(array[0], int64(i), laddr, "a")
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
port, err := strconv.ParseInt(array[1], 10, 64)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sv.Error("get natHoleResp client address error: %s", natHoleRespMsg.ClientAddr)
|
sv.Error("resolve client udp address error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sv.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid))
|
lConn, err := net.DialUDP("udp", laddr, daddr)
|
||||||
sv.Trace("send all detect msg done")
|
if err != nil {
|
||||||
|
sv.Error("dial client udp address error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer lConn.Close()
|
||||||
|
|
||||||
// Listen for visitorConn's address and wait for client connection.
|
lConn.Write([]byte(natHoleRespMsg.Sid))
|
||||||
lConn, err := net.ListenUDP("udp", laddr)
|
|
||||||
if err != nil {
|
// read ack sid from client
|
||||||
sv.Error("listen on visitorConn's local adress error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
lConn.SetReadDeadline(time.Now().Add(5 * time.Second))
|
|
||||||
sidBuf := pool.GetBuf(1024)
|
sidBuf := pool.GetBuf(1024)
|
||||||
n, _, err = lConn.ReadFromUDP(sidBuf)
|
lConn.SetReadDeadline(time.Now().Add(8 * time.Second))
|
||||||
|
n, err = lConn.Read(sidBuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sv.Warn("get sid from client error: %v", err)
|
sv.Warn("get sid from client error: %v", err)
|
||||||
return
|
return
|
||||||
@@ -292,11 +281,13 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
|||||||
sv.Warn("incorrect sid from client")
|
sv.Warn("incorrect sid from client")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sv.Info("nat hole connection make success, sid [%s]", string(sidBuf[:n]))
|
|
||||||
pool.PutBuf(sidBuf)
|
pool.PutBuf(sidBuf)
|
||||||
|
|
||||||
|
sv.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
|
||||||
|
|
||||||
|
// wrap kcp connection
|
||||||
var remote io.ReadWriteCloser
|
var remote io.ReadWriteCloser
|
||||||
remote, err = frpNet.NewKcpConnFromUdp(lConn, false, natHoleRespMsg.ClientAddr)
|
remote, err = frpNet.NewKcpConnFromUdp(lConn, true, natHoleRespMsg.ClientAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sv.Error("create kcp connection from udp connection error: %v", err)
|
sv.Error("create kcp connection from udp connection error: %v", err)
|
||||||
return
|
return
|
||||||
@@ -314,25 +305,21 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
|||||||
remote = frpIo.WithCompression(remote)
|
remote = frpIo.WithCompression(remote)
|
||||||
}
|
}
|
||||||
|
|
||||||
frpIo.Join(userConn, remote)
|
fmuxCfg := fmux.DefaultConfig()
|
||||||
|
fmuxCfg.KeepAliveInterval = 5 * time.Second
|
||||||
|
fmuxCfg.LogOutput = ioutil.Discard
|
||||||
|
sess, err := fmux.Client(remote, fmuxCfg)
|
||||||
|
if err != nil {
|
||||||
|
sv.Error("create yamux session error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer sess.Close()
|
||||||
|
muxConn, err := sess.Open()
|
||||||
|
if err != nil {
|
||||||
|
sv.Error("open yamux stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
frpIo.Join(userConn, muxConn)
|
||||||
sv.Debug("join connections closed")
|
sv.Debug("join connections closed")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sv *XtcpVisitor) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) {
|
|
||||||
daddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addr, port))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tConn, err := net.DialUDP("udp", laddr, daddr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
uConn := ipv4.NewConn(tConn)
|
|
||||||
uConn.SetTTL(3)
|
|
||||||
|
|
||||||
tConn.Write(content)
|
|
||||||
tConn.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,9 +12,10 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package main // "github.com/fatedier/frp/cmd/frpc"
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
_ "github.com/fatedier/frp/assets/frpc/statik"
|
||||||
"github.com/fatedier/frp/cmd/frpc/sub"
|
"github.com/fatedier/frp/cmd/frpc/sub"
|
||||||
|
|
||||||
"github.com/fatedier/golib/crypto"
|
"github.com/fatedier/golib/crypto"
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ package sub
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -25,7 +24,6 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/fatedier/frp/client"
|
|
||||||
"github.com/fatedier/frp/g"
|
"github.com/fatedier/frp/g"
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
)
|
)
|
||||||
@@ -79,21 +77,16 @@ func reload() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode == 200 {
|
||||||
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
res := &client.GeneralResponse{}
|
return fmt.Errorf("code [%d], %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
||||||
err = json.Unmarshal(body, &res)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
|
||||||
} else if res.Code != 0 {
|
|
||||||
return fmt.Errorf(res.Msg)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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{})
|
||||||
@@ -205,7 +205,11 @@ func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]co
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
svr := client.NewService(pxyCfgs, visitorCfgs)
|
svr, errRet := client.NewService(pxyCfgs, visitorCfgs)
|
||||||
|
if errRet != nil {
|
||||||
|
err = errRet
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Capture the exit signal if we use kcp.
|
// Capture the exit signal if we use kcp.
|
||||||
if g.GlbClientCfg.Protocol == "kcp" {
|
if g.GlbClientCfg.Protocol == "kcp" {
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ var xtcpCmd = &cobra.Command{
|
|||||||
if role == "server" {
|
if role == "server" {
|
||||||
cfg := &config.XtcpProxyConf{}
|
cfg := &config.XtcpProxyConf{}
|
||||||
cfg.ProxyName = prefix + proxyName
|
cfg.ProxyName = prefix + proxyName
|
||||||
cfg.ProxyType = consts.StcpProxy
|
cfg.ProxyType = consts.XtcpProxy
|
||||||
cfg.UseEncryption = useEncryption
|
cfg.UseEncryption = useEncryption
|
||||||
cfg.UseCompression = useCompression
|
cfg.UseCompression = useCompression
|
||||||
cfg.Role = role
|
cfg.Role = role
|
||||||
@@ -84,7 +84,7 @@ var xtcpCmd = &cobra.Command{
|
|||||||
} else if role == "visitor" {
|
} else if role == "visitor" {
|
||||||
cfg := &config.XtcpVisitorConf{}
|
cfg := &config.XtcpVisitorConf{}
|
||||||
cfg.ProxyName = prefix + proxyName
|
cfg.ProxyName = prefix + proxyName
|
||||||
cfg.ProxyType = consts.StcpProxy
|
cfg.ProxyType = consts.XtcpProxy
|
||||||
cfg.UseEncryption = useEncryption
|
cfg.UseEncryption = useEncryption
|
||||||
cfg.UseCompression = useCompression
|
cfg.UseCompression = useCompression
|
||||||
cfg.Role = role
|
cfg.Role = role
|
||||||
|
|||||||
@@ -12,10 +12,12 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package main // "github.com/fatedier/frp/cmd/frps"
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/fatedier/golib/crypto"
|
"github.com/fatedier/golib/crypto"
|
||||||
|
|
||||||
|
_ "github.com/fatedier/frp/assets/frps/statik"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -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")
|
||||||
@@ -187,9 +187,9 @@ func parseServerCommonCfgFromCmd() (err error) {
|
|||||||
g.GlbServerCfg.MaxPortsPerClient = maxPortsPerClient
|
g.GlbServerCfg.MaxPortsPerClient = maxPortsPerClient
|
||||||
|
|
||||||
if logFile == "console" {
|
if logFile == "console" {
|
||||||
g.GlbClientCfg.LogWay = "console"
|
g.GlbServerCfg.LogWay = "console"
|
||||||
} else {
|
} else {
|
||||||
g.GlbClientCfg.LogWay = "file"
|
g.GlbServerCfg.LogWay = "file"
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ login_fail_exit = true
|
|||||||
# now it supports tcp and kcp and websocket, default is tcp
|
# now it supports tcp and kcp and websocket, default is tcp
|
||||||
protocol = tcp
|
protocol = tcp
|
||||||
|
|
||||||
|
# if tls_enable is true, frpc will connect frps by tls
|
||||||
|
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
|
||||||
|
|
||||||
@@ -151,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
|
||||||
@@ -184,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
|
||||||
|
|||||||
14
conf/systemd/frpc.service
Normal file
14
conf/systemd/frpc.service
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Frp Client Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=nobody
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5s
|
||||||
|
ExecStart=/usr/bin/frpc -c /etc/frp/frpc.ini
|
||||||
|
ExecReload=/usr/bin/frpc reload -c /etc/frp/frpc.ini
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
14
conf/systemd/frpc@.service
Normal file
14
conf/systemd/frpc@.service
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Frp Client Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=idle
|
||||||
|
User=nobody
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5s
|
||||||
|
ExecStart=/usr/bin/frpc -c /etc/frp/%i.ini
|
||||||
|
ExecReload=/usr/bin/frpc reload -c /etc/frp/%i.ini
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
13
conf/systemd/frps.service
Normal file
13
conf/systemd/frps.service
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Frp Server Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=nobody
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5s
|
||||||
|
ExecStart=/usr/bin/frps -c /etc/frp/frps.ini
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
13
conf/systemd/frps@.service
Normal file
13
conf/systemd/frps@.service
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Frp Server Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=nobody
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5s
|
||||||
|
ExecStart=/usr/bin/frps -c /etc/frp/%i.ini
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
8
go.mod
8
go.mod
@@ -7,14 +7,17 @@ require (
|
|||||||
github.com/davecgh/go-spew v1.1.0 // indirect
|
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 v0.0.0-20171023144637-cd167d2f15f4
|
github.com/fatedier/kcp-go v2.0.4-0.20190317085623-2063a803e6fe+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/context v1.1.1 // indirect
|
||||||
github.com/gorilla/mux v1.6.2
|
github.com/gorilla/mux v1.6.2
|
||||||
github.com/gorilla/websocket v1.2.0
|
github.com/gorilla/websocket v1.2.0
|
||||||
github.com/hashicorp/yamux v0.0.0-20180314200745-2658be15c5f0
|
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/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/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/rakyll/statik v0.1.1
|
github.com/rakyll/statik v0.1.1
|
||||||
@@ -23,7 +26,6 @@ require (
|
|||||||
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.2.1
|
||||||
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 // indirect
|
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 // indirect
|
||||||
github.com/templexxx/reedsolomon v0.0.0-20170926020725-5e06b81a1c76 // 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
|
||||||
|
|||||||
13
go.sum
13
go.sum
@@ -3,17 +3,25 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||||||
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 v0.0.0-20171023144637-cd167d2f15f4/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
|
github.com/fatedier/kcp-go v2.0.4-0.20190317085623-2063a803e6fe+incompatible h1:pNNeBKz1jtMDupiwvtEGFTujA3J86xoEXGSkwVeYFsw=
|
||||||
|
github.com/fatedier/kcp-go v2.0.4-0.20190317085623-2063a803e6fe+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
|
||||||
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/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
github.com/gorilla/mux v1.6.2/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.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
github.com/hashicorp/yamux v0.0.0-20180314200745-2658be15c5f0/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
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/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
|
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
|
||||||
|
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
|
github.com/klauspost/reedsolomon v1.9.1 h1:kYrT1MlR4JH6PqOpC+okdb9CDTcwEC/BqpzK4WFyXL8=
|
||||||
|
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/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/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
|
github.com/rakyll/statik v0.1.1/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
|
||||||
@@ -22,7 +30,6 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3
|
|||||||
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/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
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/reedsolomon v0.0.0-20170926020725-5e06b81a1c76/go.mod h1:ToWcj2sZ6xHl14JjZiVDktYpFtrFZJXBlsu7TV23lNg=
|
|
||||||
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/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/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw=
|
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw=
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ type ClientCommonConf struct {
|
|||||||
LoginFailExit bool `json:"login_fail_exit"`
|
LoginFailExit bool `json:"login_fail_exit"`
|
||||||
Start map[string]struct{} `json:"start"`
|
Start map[string]struct{} `json:"start"`
|
||||||
Protocol string `json:"protocol"`
|
Protocol string `json:"protocol"`
|
||||||
|
TLSEnable bool `json:"tls_enable"`
|
||||||
HeartBeatInterval int64 `json:"heartbeat_interval"`
|
HeartBeatInterval int64 `json:"heartbeat_interval"`
|
||||||
HeartBeatTimeout int64 `json:"heartbeat_timeout"`
|
HeartBeatTimeout int64 `json:"heartbeat_timeout"`
|
||||||
}
|
}
|
||||||
@@ -69,6 +70,7 @@ func GetDefaultClientConf() *ClientCommonConf {
|
|||||||
LoginFailExit: true,
|
LoginFailExit: true,
|
||||||
Start: make(map[string]struct{}),
|
Start: make(map[string]struct{}),
|
||||||
Protocol: "tcp",
|
Protocol: "tcp",
|
||||||
|
TLSEnable: false,
|
||||||
HeartBeatInterval: 30,
|
HeartBeatInterval: 30,
|
||||||
HeartBeatTimeout: 90,
|
HeartBeatTimeout: 90,
|
||||||
}
|
}
|
||||||
@@ -194,6 +196,12 @@ func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (c
|
|||||||
cfg.Protocol = tmpStr
|
cfg.Protocol = tmpStr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok = conf.Get("common", "tls_enable"); ok && tmpStr == "true" {
|
||||||
|
cfg.TLSEnable = true
|
||||||
|
} else {
|
||||||
|
cfg.TLSEnable = false
|
||||||
|
}
|
||||||
|
|
||||||
if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
|
if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
|
||||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||||
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout")
|
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout")
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ type ServerCommonConf struct {
|
|||||||
VhostHttpPort int `json:"vhost_http_port"`
|
VhostHttpPort int `json:"vhost_http_port"`
|
||||||
|
|
||||||
// if VhostHttpsPort equals 0, don't listen a public port for https protocol
|
// if VhostHttpsPort equals 0, don't listen a public port for https protocol
|
||||||
VhostHttpsPort int `json:"vhost_http_port"`
|
VhostHttpsPort int `json:"vhost_https_port"`
|
||||||
|
|
||||||
VhostHttpTimeout int64 `json:"vhost_http_timeout"`
|
VhostHttpTimeout int64 `json:"vhost_http_timeout"`
|
||||||
|
|
||||||
|
|||||||
@@ -17,44 +17,46 @@ package msg
|
|||||||
import "net"
|
import "net"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TypeLogin = 'o'
|
TypeLogin = 'o'
|
||||||
TypeLoginResp = '1'
|
TypeLoginResp = '1'
|
||||||
TypeNewProxy = 'p'
|
TypeNewProxy = 'p'
|
||||||
TypeNewProxyResp = '2'
|
TypeNewProxyResp = '2'
|
||||||
TypeCloseProxy = 'c'
|
TypeCloseProxy = 'c'
|
||||||
TypeNewWorkConn = 'w'
|
TypeNewWorkConn = 'w'
|
||||||
TypeReqWorkConn = 'r'
|
TypeReqWorkConn = 'r'
|
||||||
TypeStartWorkConn = 's'
|
TypeStartWorkConn = 's'
|
||||||
TypeNewVisitorConn = 'v'
|
TypeNewVisitorConn = 'v'
|
||||||
TypeNewVisitorConnResp = '3'
|
TypeNewVisitorConnResp = '3'
|
||||||
TypePing = 'h'
|
TypePing = 'h'
|
||||||
TypePong = '4'
|
TypePong = '4'
|
||||||
TypeUdpPacket = 'u'
|
TypeUdpPacket = 'u'
|
||||||
TypeNatHoleVisitor = 'i'
|
TypeNatHoleVisitor = 'i'
|
||||||
TypeNatHoleClient = 'n'
|
TypeNatHoleClient = 'n'
|
||||||
TypeNatHoleResp = 'm'
|
TypeNatHoleResp = 'm'
|
||||||
TypeNatHoleSid = '5'
|
TypeNatHoleClientDetectOK = 'd'
|
||||||
|
TypeNatHoleSid = '5'
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
msgTypeMap = map[byte]interface{}{
|
msgTypeMap = map[byte]interface{}{
|
||||||
TypeLogin: Login{},
|
TypeLogin: Login{},
|
||||||
TypeLoginResp: LoginResp{},
|
TypeLoginResp: LoginResp{},
|
||||||
TypeNewProxy: NewProxy{},
|
TypeNewProxy: NewProxy{},
|
||||||
TypeNewProxyResp: NewProxyResp{},
|
TypeNewProxyResp: NewProxyResp{},
|
||||||
TypeCloseProxy: CloseProxy{},
|
TypeCloseProxy: CloseProxy{},
|
||||||
TypeNewWorkConn: NewWorkConn{},
|
TypeNewWorkConn: NewWorkConn{},
|
||||||
TypeReqWorkConn: ReqWorkConn{},
|
TypeReqWorkConn: ReqWorkConn{},
|
||||||
TypeStartWorkConn: StartWorkConn{},
|
TypeStartWorkConn: StartWorkConn{},
|
||||||
TypeNewVisitorConn: NewVisitorConn{},
|
TypeNewVisitorConn: NewVisitorConn{},
|
||||||
TypeNewVisitorConnResp: NewVisitorConnResp{},
|
TypeNewVisitorConnResp: NewVisitorConnResp{},
|
||||||
TypePing: Ping{},
|
TypePing: Ping{},
|
||||||
TypePong: Pong{},
|
TypePong: Pong{},
|
||||||
TypeUdpPacket: UdpPacket{},
|
TypeUdpPacket: UdpPacket{},
|
||||||
TypeNatHoleVisitor: NatHoleVisitor{},
|
TypeNatHoleVisitor: NatHoleVisitor{},
|
||||||
TypeNatHoleClient: NatHoleClient{},
|
TypeNatHoleClient: NatHoleClient{},
|
||||||
TypeNatHoleResp: NatHoleResp{},
|
TypeNatHoleResp: NatHoleResp{},
|
||||||
TypeNatHoleSid: NatHoleSid{},
|
TypeNatHoleClientDetectOK: NatHoleClientDetectOK{},
|
||||||
|
TypeNatHoleSid: NatHoleSid{},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -124,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 {
|
||||||
@@ -169,6 +175,9 @@ type NatHoleResp struct {
|
|||||||
Error string `json:"error"`
|
Error string `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NatHoleClientDetectOK struct {
|
||||||
|
}
|
||||||
|
|
||||||
type NatHoleSid struct {
|
type NatHoleSid struct {
|
||||||
Sid string `json:"sid"`
|
Sid string `json:"sid"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ import (
|
|||||||
// Timeout seconds.
|
// Timeout seconds.
|
||||||
var NatHoleTimeout int64 = 10
|
var NatHoleTimeout int64 = 10
|
||||||
|
|
||||||
|
type SidRequest struct {
|
||||||
|
Sid string
|
||||||
|
NotifyCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
type NatHoleController struct {
|
type NatHoleController struct {
|
||||||
listener *net.UDPConn
|
listener *net.UDPConn
|
||||||
|
|
||||||
@@ -44,11 +49,11 @@ func NewNatHoleController(udpBindAddr string) (nc *NatHoleController, err error)
|
|||||||
return nc, nil
|
return nc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nc *NatHoleController) ListenClient(name string, sk string) (sidCh chan string) {
|
func (nc *NatHoleController) ListenClient(name string, sk string) (sidCh chan *SidRequest) {
|
||||||
clientCfg := &NatHoleClientCfg{
|
clientCfg := &NatHoleClientCfg{
|
||||||
Name: name,
|
Name: name,
|
||||||
Sk: sk,
|
Sk: sk,
|
||||||
SidCh: make(chan string),
|
SidCh: make(chan *SidRequest),
|
||||||
}
|
}
|
||||||
nc.mu.Lock()
|
nc.mu.Lock()
|
||||||
nc.clientCfgs[name] = clientCfg
|
nc.clientCfgs[name] = clientCfg
|
||||||
@@ -132,7 +137,10 @@ func (nc *NatHoleController) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDP
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
err := errors.PanicToError(func() {
|
err := errors.PanicToError(func() {
|
||||||
clientCfg.SidCh <- sid
|
clientCfg.SidCh <- &SidRequest{
|
||||||
|
Sid: sid,
|
||||||
|
NotifyCh: session.NotifyCh,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -158,7 +166,6 @@ func (nc *NatHoleController) HandleClient(m *msg.NatHoleClient, raddr *net.UDPAd
|
|||||||
}
|
}
|
||||||
log.Trace("handle client message, sid [%s]", session.Sid)
|
log.Trace("handle client message, sid [%s]", session.Sid)
|
||||||
session.ClientAddr = raddr
|
session.ClientAddr = raddr
|
||||||
session.NotifyCh <- struct{}{}
|
|
||||||
|
|
||||||
resp := nc.GenNatHoleResponse(session, "")
|
resp := nc.GenNatHoleResponse(session, "")
|
||||||
log.Trace("send nat hole response to client")
|
log.Trace("send nat hole response to client")
|
||||||
@@ -201,5 +208,5 @@ type NatHoleSession struct {
|
|||||||
type NatHoleClientCfg struct {
|
type NatHoleClientCfg struct {
|
||||||
Name string
|
Name string
|
||||||
Sk string
|
Sk string
|
||||||
SidCh chan string
|
SidCh chan *SidRequest
|
||||||
}
|
}
|
||||||
|
|||||||
119
models/plugin/https2http.go
Normal file
119
models/plugin/https2http.go
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
// 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) {
|
||||||
|
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
|
p.l.PutConn(wrapConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HTTPS2HTTPPlugin) handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("hello"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HTTPS2HTTPPlugin) Name() string {
|
||||||
|
return PluginHTTPS2HTTP
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HTTPS2HTTPPlugin) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -67,7 +67,6 @@ func ForwardUserConn(udpConn *net.UDPConn, readCh <-chan *msg.UdpPacket, sendCh
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<- msg.Message) {
|
func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<- msg.Message) {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ for os in $os_all; do
|
|||||||
mv ./frps_${os}_${arch} ${frp_path}/frps
|
mv ./frps_${os}_${arch} ${frp_path}/frps
|
||||||
fi
|
fi
|
||||||
cp ./LICENSE ${frp_path}
|
cp ./LICENSE ${frp_path}
|
||||||
cp ./conf/* ${frp_path}
|
cp -rf ./conf/* ${frp_path}
|
||||||
|
|
||||||
# packages
|
# packages
|
||||||
cd ./packages
|
cd ./packages
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -28,13 +28,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type GeneralResponse struct {
|
type GeneralResponse struct {
|
||||||
Code int64 `json:"code"`
|
Code int
|
||||||
Msg string `json:"msg"`
|
Msg string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServerInfoResp struct {
|
type ServerInfoResp struct {
|
||||||
GeneralResponse
|
|
||||||
|
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
BindPort int `json:"bind_port"`
|
BindPort int `json:"bind_port"`
|
||||||
BindUdpPort int `json:"bind_udp_port"`
|
BindUdpPort int `json:"bind_udp_port"`
|
||||||
@@ -55,18 +53,19 @@ type ServerInfoResp struct {
|
|||||||
|
|
||||||
// api/serverinfo
|
// api/serverinfo
|
||||||
func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
res := GeneralResponse{Code: 200}
|
||||||
buf []byte
|
|
||||||
res ServerInfoResp
|
|
||||||
)
|
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||||
|
w.WriteHeader(res.Code)
|
||||||
|
if len(res.Msg) > 0 {
|
||||||
|
w.Write([]byte(res.Msg))
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
log.Info("Http request: [%s]", r.URL.Path)
|
log.Info("Http request: [%s]", r.URL.Path)
|
||||||
cfg := &g.GlbServerCfg.ServerCommonConf
|
cfg := &g.GlbServerCfg.ServerCommonConf
|
||||||
serverStats := svr.statsCollector.GetServer()
|
serverStats := svr.statsCollector.GetServer()
|
||||||
res = ServerInfoResp{
|
svrResp := ServerInfoResp{
|
||||||
Version: version.Full(),
|
Version: version.Full(),
|
||||||
BindPort: cfg.BindPort,
|
BindPort: cfg.BindPort,
|
||||||
BindUdpPort: cfg.BindUdpPort,
|
BindUdpPort: cfg.BindUdpPort,
|
||||||
@@ -85,8 +84,8 @@ func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) {
|
|||||||
ProxyTypeCounts: serverStats.ProxyTypeCounts,
|
ProxyTypeCounts: serverStats.ProxyTypeCounts,
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, _ = json.Marshal(&res)
|
buf, _ := json.Marshal(&svrResp)
|
||||||
w.Write(buf)
|
res.Msg = string(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
type BaseOutConf struct {
|
type BaseOutConf struct {
|
||||||
@@ -155,31 +154,29 @@ type ProxyStatsInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GetProxyInfoResp struct {
|
type GetProxyInfoResp struct {
|
||||||
GeneralResponse
|
|
||||||
Proxies []*ProxyStatsInfo `json:"proxies"`
|
Proxies []*ProxyStatsInfo `json:"proxies"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// api/proxy/:type
|
// api/proxy/:type
|
||||||
func (svr *Service) ApiProxyByType(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) ApiProxyByType(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
res := GeneralResponse{Code: 200}
|
||||||
buf []byte
|
|
||||||
res GetProxyInfoResp
|
|
||||||
)
|
|
||||||
params := mux.Vars(r)
|
params := mux.Vars(r)
|
||||||
proxyType := params["type"]
|
proxyType := params["type"]
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||||
log.Info(r.URL.Path)
|
w.WriteHeader(res.Code)
|
||||||
log.Info(r.URL.RawPath)
|
if len(res.Msg) > 0 {
|
||||||
|
w.Write([]byte(res.Msg))
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
log.Info("Http request: [%s]", r.URL.Path)
|
log.Info("Http request: [%s]", r.URL.Path)
|
||||||
|
|
||||||
res.Proxies = svr.getProxyStatsByType(proxyType)
|
proxyInfoResp := GetProxyInfoResp{}
|
||||||
|
proxyInfoResp.Proxies = svr.getProxyStatsByType(proxyType)
|
||||||
buf, _ = json.Marshal(&res)
|
|
||||||
w.Write(buf)
|
|
||||||
|
|
||||||
|
buf, _ := json.Marshal(&proxyInfoResp)
|
||||||
|
res.Msg = string(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
|
func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
|
||||||
@@ -215,8 +212,6 @@ func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxySt
|
|||||||
|
|
||||||
// Get proxy info by name.
|
// Get proxy info by name.
|
||||||
type GetProxyStatsResp struct {
|
type GetProxyStatsResp struct {
|
||||||
GeneralResponse
|
|
||||||
|
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Conf interface{} `json:"conf"`
|
Conf interface{} `json:"conf"`
|
||||||
TodayTrafficIn int64 `json:"today_traffic_in"`
|
TodayTrafficIn int64 `json:"today_traffic_in"`
|
||||||
@@ -229,45 +224,50 @@ type GetProxyStatsResp struct {
|
|||||||
|
|
||||||
// api/proxy/:type/:name
|
// api/proxy/:type/:name
|
||||||
func (svr *Service) ApiProxyByTypeAndName(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) ApiProxyByTypeAndName(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
res := GeneralResponse{Code: 200}
|
||||||
buf []byte
|
|
||||||
res GetProxyStatsResp
|
|
||||||
)
|
|
||||||
params := mux.Vars(r)
|
params := mux.Vars(r)
|
||||||
proxyType := params["type"]
|
proxyType := params["type"]
|
||||||
name := params["name"]
|
name := params["name"]
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||||
|
w.WriteHeader(res.Code)
|
||||||
|
if len(res.Msg) > 0 {
|
||||||
|
w.Write([]byte(res.Msg))
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
log.Info("Http request: [%s]", r.URL.Path)
|
log.Info("Http request: [%s]", r.URL.Path)
|
||||||
|
|
||||||
res = svr.getProxyStatsByTypeAndName(proxyType, name)
|
proxyStatsResp := GetProxyStatsResp{}
|
||||||
|
proxyStatsResp, res.Code, res.Msg = svr.getProxyStatsByTypeAndName(proxyType, name)
|
||||||
|
if res.Code != 200 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
buf, _ = json.Marshal(&res)
|
buf, _ := json.Marshal(&proxyStatsResp)
|
||||||
w.Write(buf)
|
res.Msg = string(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp) {
|
func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp, code int, msg string) {
|
||||||
proxyInfo.Name = proxyName
|
proxyInfo.Name = proxyName
|
||||||
ps := svr.statsCollector.GetProxiesByTypeAndName(proxyType, proxyName)
|
ps := svr.statsCollector.GetProxiesByTypeAndName(proxyType, proxyName)
|
||||||
if ps == nil {
|
if ps == nil {
|
||||||
proxyInfo.Code = 1
|
code = 404
|
||||||
proxyInfo.Msg = "no proxy info found"
|
msg = "no proxy info found"
|
||||||
} else {
|
} else {
|
||||||
if pxy, ok := svr.pxyManager.GetByName(proxyName); ok {
|
if pxy, ok := svr.pxyManager.GetByName(proxyName); ok {
|
||||||
content, err := json.Marshal(pxy.GetConf())
|
content, err := json.Marshal(pxy.GetConf())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("marshal proxy [%s] conf info error: %v", ps.Name, err)
|
log.Warn("marshal proxy [%s] conf info error: %v", ps.Name, err)
|
||||||
proxyInfo.Code = 2
|
code = 400
|
||||||
proxyInfo.Msg = "parse conf error"
|
msg = "parse conf error"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
proxyInfo.Conf = getConfByType(ps.Type)
|
proxyInfo.Conf = getConfByType(ps.Type)
|
||||||
if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil {
|
if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil {
|
||||||
log.Warn("unmarshal proxy [%s] conf info error: %v", ps.Name, err)
|
log.Warn("unmarshal proxy [%s] conf info error: %v", ps.Name, err)
|
||||||
proxyInfo.Code = 2
|
code = 400
|
||||||
proxyInfo.Msg = "parse conf error"
|
msg = "parse conf error"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
proxyInfo.Status = consts.Online
|
proxyInfo.Status = consts.Online
|
||||||
@@ -286,36 +286,38 @@ func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName strin
|
|||||||
|
|
||||||
// api/traffic/:name
|
// api/traffic/:name
|
||||||
type GetProxyTrafficResp struct {
|
type GetProxyTrafficResp struct {
|
||||||
GeneralResponse
|
|
||||||
|
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
TrafficIn []int64 `json:"traffic_in"`
|
TrafficIn []int64 `json:"traffic_in"`
|
||||||
TrafficOut []int64 `json:"traffic_out"`
|
TrafficOut []int64 `json:"traffic_out"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svr *Service) ApiProxyTraffic(w http.ResponseWriter, r *http.Request) {
|
func (svr *Service) ApiProxyTraffic(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
res := GeneralResponse{Code: 200}
|
||||||
buf []byte
|
|
||||||
res GetProxyTrafficResp
|
|
||||||
)
|
|
||||||
params := mux.Vars(r)
|
params := mux.Vars(r)
|
||||||
name := params["name"]
|
name := params["name"]
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||||
|
w.WriteHeader(res.Code)
|
||||||
|
if len(res.Msg) > 0 {
|
||||||
|
w.Write([]byte(res.Msg))
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
log.Info("Http request: [%s]", r.URL.Path)
|
log.Info("Http request: [%s]", r.URL.Path)
|
||||||
|
|
||||||
res.Name = name
|
trafficResp := GetProxyTrafficResp{}
|
||||||
|
trafficResp.Name = name
|
||||||
proxyTrafficInfo := svr.statsCollector.GetProxyTraffic(name)
|
proxyTrafficInfo := svr.statsCollector.GetProxyTraffic(name)
|
||||||
|
|
||||||
if proxyTrafficInfo == nil {
|
if proxyTrafficInfo == nil {
|
||||||
res.Code = 1
|
res.Code = 404
|
||||||
res.Msg = "no proxy info found"
|
res.Msg = "no proxy info found"
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
res.TrafficIn = proxyTrafficInfo.TrafficIn
|
trafficResp.TrafficIn = proxyTrafficInfo.TrafficIn
|
||||||
res.TrafficOut = proxyTrafficInfo.TrafficOut
|
trafficResp.TrafficOut = proxyTrafficInfo.TrafficOut
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, _ = json.Marshal(&res)
|
buf, _ := json.Marshal(&trafficResp)
|
||||||
w.Write(buf)
|
res.Msg = string(buf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ package proxy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/fatedier/frp/g"
|
"github.com/fatedier/frp/g"
|
||||||
@@ -29,7 +30,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type HttpProxy struct {
|
type HttpProxy struct {
|
||||||
BaseProxy
|
*BaseProxy
|
||||||
cfg *config.HttpProxyConf
|
cfg *config.HttpProxyConf
|
||||||
|
|
||||||
closeFuncs []func()
|
closeFuncs []func()
|
||||||
@@ -51,6 +52,10 @@ func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
|
|||||||
|
|
||||||
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
|
||||||
@@ -93,8 +98,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
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type HttpsProxy struct {
|
type HttpsProxy struct {
|
||||||
BaseProxy
|
*BaseProxy
|
||||||
cfg *config.HttpsProxyConf
|
cfg *config.HttpsProxyConf
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,6 +33,10 @@ func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
|
|||||||
|
|
||||||
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,7 @@ func (pxy *BaseProxy) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *BaseProxy) GetWorkConnFromPool() (workConn frpNet.Conn, err error) {
|
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 +82,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)
|
||||||
@@ -135,33 +158,33 @@ func NewProxy(runId string, rc *controller.ResourceController, statsCollector st
|
|||||||
case *config.TcpProxyConf:
|
case *config.TcpProxyConf:
|
||||||
basePxy.usedPortsNum = 1
|
basePxy.usedPortsNum = 1
|
||||||
pxy = &TcpProxy{
|
pxy = &TcpProxy{
|
||||||
BaseProxy: basePxy,
|
BaseProxy: &basePxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
case *config.HttpProxyConf:
|
case *config.HttpProxyConf:
|
||||||
pxy = &HttpProxy{
|
pxy = &HttpProxy{
|
||||||
BaseProxy: basePxy,
|
BaseProxy: &basePxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
case *config.HttpsProxyConf:
|
case *config.HttpsProxyConf:
|
||||||
pxy = &HttpsProxy{
|
pxy = &HttpsProxy{
|
||||||
BaseProxy: basePxy,
|
BaseProxy: &basePxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
case *config.UdpProxyConf:
|
case *config.UdpProxyConf:
|
||||||
basePxy.usedPortsNum = 1
|
basePxy.usedPortsNum = 1
|
||||||
pxy = &UdpProxy{
|
pxy = &UdpProxy{
|
||||||
BaseProxy: basePxy,
|
BaseProxy: &basePxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
case *config.StcpProxyConf:
|
case *config.StcpProxyConf:
|
||||||
pxy = &StcpProxy{
|
pxy = &StcpProxy{
|
||||||
BaseProxy: basePxy,
|
BaseProxy: &basePxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
case *config.XtcpProxyConf:
|
case *config.XtcpProxyConf:
|
||||||
pxy = &XtcpProxy{
|
pxy = &XtcpProxy{
|
||||||
BaseProxy: basePxy,
|
BaseProxy: &basePxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -177,7 +200,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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type StcpProxy struct {
|
type StcpProxy struct {
|
||||||
BaseProxy
|
*BaseProxy
|
||||||
cfg *config.StcpProxyConf
|
cfg *config.StcpProxyConf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type TcpProxy struct {
|
type TcpProxy struct {
|
||||||
BaseProxy
|
*BaseProxy
|
||||||
cfg *config.TcpProxyConf
|
cfg *config.TcpProxyConf
|
||||||
|
|
||||||
realPort int
|
realPort int
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type UdpProxy struct {
|
type UdpProxy struct {
|
||||||
BaseProxy
|
*BaseProxy
|
||||||
cfg *config.UdpProxyConf
|
cfg *config.UdpProxyConf
|
||||||
|
|
||||||
realPort int
|
realPort int
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type XtcpProxy struct {
|
type XtcpProxy struct {
|
||||||
BaseProxy
|
*BaseProxy
|
||||||
cfg *config.XtcpProxyConf
|
cfg *config.XtcpProxyConf
|
||||||
|
|
||||||
closeCh chan struct{}
|
closeCh chan struct{}
|
||||||
@@ -42,18 +42,40 @@ func (pxy *XtcpProxy) Run() (remoteAddr string, err error) {
|
|||||||
select {
|
select {
|
||||||
case <-pxy.closeCh:
|
case <-pxy.closeCh:
|
||||||
break
|
break
|
||||||
case sid := <-sidCh:
|
case sidRequest := <-sidCh:
|
||||||
workConn, errRet := pxy.GetWorkConnFromPool()
|
sr := sidRequest
|
||||||
|
workConn, errRet := pxy.GetWorkConnFromPool(nil, nil)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
m := &msg.NatHoleSid{
|
m := &msg.NatHoleSid{
|
||||||
Sid: sid,
|
Sid: sr.Sid,
|
||||||
}
|
}
|
||||||
errRet = msg.WriteMsg(workConn, m)
|
errRet = msg.WriteMsg(workConn, m)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
pxy.Warn("write nat hole sid package error, %v", errRet)
|
pxy.Warn("write nat hole sid package error, %v", errRet)
|
||||||
|
workConn.Close()
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
raw, errRet := msg.ReadMsg(workConn)
|
||||||
|
if errRet != nil {
|
||||||
|
pxy.Warn("read nat hole client ok package error: %v", errRet)
|
||||||
|
workConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := raw.(*msg.NatHoleClientDetectOK); !ok {
|
||||||
|
pxy.Warn("read nat hole client ok package format error")
|
||||||
|
workConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case sr.NotifyCh <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -16,8 +16,14 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
@@ -61,6 +67,9 @@ type Service struct {
|
|||||||
// Accept connections using websocket
|
// Accept connections using websocket
|
||||||
websocketListener frpNet.Listener
|
websocketListener frpNet.Listener
|
||||||
|
|
||||||
|
// Accept frp tls connections
|
||||||
|
tlsListener frpNet.Listener
|
||||||
|
|
||||||
// Manage all controllers
|
// Manage all controllers
|
||||||
ctlManager *ControlManager
|
ctlManager *ControlManager
|
||||||
|
|
||||||
@@ -72,6 +81,8 @@ type Service struct {
|
|||||||
|
|
||||||
// stats collector to store server and proxies stats info
|
// stats collector to store server and proxies stats info
|
||||||
statsCollector stats.Collector
|
statsCollector stats.Collector
|
||||||
|
|
||||||
|
tlsConfig *tls.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService() (svr *Service, err error) {
|
func NewService() (svr *Service, err error) {
|
||||||
@@ -84,12 +95,13 @@ 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(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init group controller
|
// Init group controller
|
||||||
svr.rc.TcpGroupCtl = group.NewTcpGroupCtl(svr.rc.TcpPortManager)
|
svr.rc.TcpGroupCtl = group.NewTcpGroupCtl(svr.rc.TcpPortManager)
|
||||||
|
|
||||||
// Init assets.
|
// Init assets
|
||||||
err = assets.Load(cfg.AssetsDir)
|
err = assets.Load(cfg.AssetsDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("Load assets error: %v", err)
|
err = fmt.Errorf("Load assets error: %v", err)
|
||||||
@@ -187,6 +199,12 @@ func NewService() (svr *Service, err error) {
|
|||||||
log.Info("https service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort)
|
log.Info("https service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// frp tls listener
|
||||||
|
tlsListener := svr.muxer.Listen(1, 1, func(data []byte) bool {
|
||||||
|
return int(data[0]) == frpNet.FRP_TLS_HEAD_BYTE
|
||||||
|
})
|
||||||
|
svr.tlsListener = frpNet.WrapLogListener(tlsListener)
|
||||||
|
|
||||||
// Create nat hole controller.
|
// Create nat hole controller.
|
||||||
if cfg.BindUdpPort > 0 {
|
if cfg.BindUdpPort > 0 {
|
||||||
var nc *nathole.NatHoleController
|
var nc *nathole.NatHoleController
|
||||||
@@ -225,6 +243,7 @@ func (svr *Service) Run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
go svr.HandleListener(svr.websocketListener)
|
go svr.HandleListener(svr.websocketListener)
|
||||||
|
go svr.HandleListener(svr.tlsListener)
|
||||||
|
|
||||||
svr.HandleListener(svr.listener)
|
svr.HandleListener(svr.listener)
|
||||||
}
|
}
|
||||||
@@ -237,6 +256,7 @@ 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)
|
||||||
|
|
||||||
// Start a new goroutine for dealing connections.
|
// Start a new goroutine for dealing connections.
|
||||||
go func(frpConn frpNet.Conn) {
|
go func(frpConn frpNet.Conn) {
|
||||||
@@ -373,3 +393,24 @@ func (svr *Service) RegisterVisitorConn(visitorConn frpNet.Conn, newMsg *msg.New
|
|||||||
return svr.rc.VisitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey,
|
return svr.rc.VisitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey,
|
||||||
newMsg.UseEncryption, newMsg.UseCompression)
|
newMsg.UseEncryption, newMsg.UseCompression)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup a bare-bones TLS config for the server
|
||||||
|
func generateTLSConfig() *tls.Config {
|
||||||
|
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
template := x509.Certificate{SerialNumber: big.NewInt(1)}
|
||||||
|
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
|
||||||
|
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
||||||
|
|
||||||
|
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return &tls.Config{Certificates: []tls.Certificate{tlsCert}}
|
||||||
|
}
|
||||||
|
|||||||
@@ -127,6 +127,12 @@ custom_domains = test6.frp.com
|
|||||||
host_header_rewrite = test6.frp.com
|
host_header_rewrite = test6.frp.com
|
||||||
header_X-From-Where = frp
|
header_X-From-Where = frp
|
||||||
|
|
||||||
|
[wildcard_http]
|
||||||
|
type = http
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 10704
|
||||||
|
custom_domains = *.frp1.com
|
||||||
|
|
||||||
[subhost01]
|
[subhost01]
|
||||||
type = http
|
type = http
|
||||||
local_ip = 127.0.0.1
|
local_ip = 127.0.0.1
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func TestCmdTcp(t *testing.T) {
|
|||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
defer s.Stop()
|
defer s.Stop()
|
||||||
}
|
}
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"tcp", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
|
c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"tcp", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
|
||||||
"-l", "10701", "-r", "20801", "-n", "tcp_test"})
|
"-l", "10701", "-r", "20801", "-n", "tcp_test"})
|
||||||
@@ -27,7 +27,7 @@ func TestCmdTcp(t *testing.T) {
|
|||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
defer c.Stop()
|
defer c.Stop()
|
||||||
}
|
}
|
||||||
time.Sleep(250 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
@@ -43,7 +43,7 @@ func TestCmdUdp(t *testing.T) {
|
|||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
defer s.Stop()
|
defer s.Stop()
|
||||||
}
|
}
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"udp", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
|
c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"udp", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
|
||||||
"-l", "10702", "-r", "20802", "-n", "udp_test"})
|
"-l", "10702", "-r", "20802", "-n", "udp_test"})
|
||||||
@@ -51,7 +51,7 @@ func TestCmdUdp(t *testing.T) {
|
|||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
defer c.Stop()
|
defer c.Stop()
|
||||||
}
|
}
|
||||||
time.Sleep(250 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
res, err := util.SendUdpMsg("127.0.0.1:20802", consts.TEST_UDP_ECHO_STR)
|
res, err := util.SendUdpMsg("127.0.0.1:20802", consts.TEST_UDP_ECHO_STR)
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
@@ -67,7 +67,7 @@ func TestCmdHttp(t *testing.T) {
|
|||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
defer s.Stop()
|
defer s.Stop()
|
||||||
}
|
}
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"http", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
|
c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"http", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
|
||||||
"-n", "udp_test", "-l", "10704", "--custom_domain", "127.0.0.1"})
|
"-n", "udp_test", "-l", "10704", "--custom_domain", "127.0.0.1"})
|
||||||
@@ -75,7 +75,7 @@ func TestCmdHttp(t *testing.T) {
|
|||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
defer c.Stop()
|
defer c.Stop()
|
||||||
}
|
}
|
||||||
time.Sleep(250 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
code, body, _, err := util.SendHttpMsg("GET", "http://127.0.0.1:20001", "", nil, "")
|
code, body, _, err := util.SendHttpMsg("GET", "http://127.0.0.1:20001", "", nil, "")
|
||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
|
|||||||
@@ -182,6 +182,21 @@ func TestHttp(t *testing.T) {
|
|||||||
assert.Equal("true", header.Get("X-Header-Set"))
|
assert.Equal("true", header.Get("X-Header-Set"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wildcard_http
|
||||||
|
// test.frp1.com match *.frp1.com
|
||||||
|
code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "test.frp1.com", nil, "")
|
||||||
|
if assert.NoError(err) {
|
||||||
|
assert.Equal(200, code)
|
||||||
|
assert.Equal(consts.TEST_HTTP_NORMAL_STR, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// new.test.frp1.com also match *.frp1.com
|
||||||
|
code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "new.test.frp1.com", nil, "")
|
||||||
|
if assert.NoError(err) {
|
||||||
|
assert.Equal(200, code)
|
||||||
|
assert.Equal(consts.TEST_HTTP_NORMAL_STR, body)
|
||||||
|
}
|
||||||
|
|
||||||
// subhost01
|
// subhost01
|
||||||
code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "test01.sub.com", nil, "")
|
code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "test01.sub.com", nil, "")
|
||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
|
|||||||
@@ -56,14 +56,14 @@ func TestReconnect(t *testing.T) {
|
|||||||
defer frpsProcess.Stop()
|
defer frpsProcess.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
|
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
|
||||||
err = frpcProcess.Start()
|
err = frpcProcess.Start()
|
||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
defer frpcProcess.Stop()
|
defer frpcProcess.Stop()
|
||||||
}
|
}
|
||||||
time.Sleep(250 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
// test tcp
|
// test tcp
|
||||||
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||||
@@ -72,7 +72,7 @@ func TestReconnect(t *testing.T) {
|
|||||||
|
|
||||||
// stop frpc
|
// stop frpc
|
||||||
frpcProcess.Stop()
|
frpcProcess.Stop()
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
// test tcp, expect failed
|
// test tcp, expect failed
|
||||||
_, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
_, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||||
@@ -84,7 +84,7 @@ func TestReconnect(t *testing.T) {
|
|||||||
if assert.NoError(err) {
|
if assert.NoError(err) {
|
||||||
defer newFrpcProcess.Stop()
|
defer newFrpcProcess.Stop()
|
||||||
}
|
}
|
||||||
time.Sleep(250 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
// test tcp
|
// test tcp
|
||||||
res, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
res, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||||
@@ -93,7 +93,7 @@ func TestReconnect(t *testing.T) {
|
|||||||
|
|
||||||
// stop frps
|
// stop frps
|
||||||
frpsProcess.Stop()
|
frpsProcess.Stop()
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
// test tcp, expect failed
|
// test tcp, expect failed
|
||||||
_, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
_, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ func TestReload(t *testing.T) {
|
|||||||
defer frpsProcess.Stop()
|
defer frpsProcess.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
|
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
|
||||||
err = frpcProcess.Start()
|
err = frpcProcess.Start()
|
||||||
@@ -102,7 +102,7 @@ func TestReload(t *testing.T) {
|
|||||||
defer frpcProcess.Stop()
|
defer frpcProcess.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(250 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
// test tcp1
|
// test tcp1
|
||||||
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ func TestConfTemplate(t *testing.T) {
|
|||||||
defer frpsProcess.Stop()
|
defer frpsProcess.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
frpcProcess := util.NewProcess("env", []string{"FRP_TOKEN=123456", "TCP_REMOTE_PORT=20801", consts.FRPC_BIN_PATH, "-c", frpcCfgPath})
|
frpcProcess := util.NewProcess("env", []string{"FRP_TOKEN=123456", "TCP_REMOTE_PORT=20801", consts.FRPC_BIN_PATH, "-c", frpcCfgPath})
|
||||||
err = frpcProcess.Start()
|
err = frpcProcess.Start()
|
||||||
@@ -63,7 +63,7 @@ func TestConfTemplate(t *testing.T) {
|
|||||||
defer frpcProcess.Stop()
|
defer frpcProcess.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(250 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
// test tcp1
|
// test tcp1
|
||||||
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||||
|
|||||||
188
tests/ci/tls_test.go
Normal file
188
tests/ci/tls_test.go
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
package ci
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/tests/config"
|
||||||
|
"github.com/fatedier/frp/tests/consts"
|
||||||
|
"github.com/fatedier/frp/tests/util"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
const FRPS_TLS_TCP_CONF = `
|
||||||
|
[common]
|
||||||
|
bind_addr = 0.0.0.0
|
||||||
|
bind_port = 20000
|
||||||
|
log_file = console
|
||||||
|
log_level = debug
|
||||||
|
token = 123456
|
||||||
|
`
|
||||||
|
|
||||||
|
const FRPC_TLS_TCP_CONF = `
|
||||||
|
[common]
|
||||||
|
server_addr = 127.0.0.1
|
||||||
|
server_port = 20000
|
||||||
|
log_file = console
|
||||||
|
log_level = debug
|
||||||
|
token = 123456
|
||||||
|
protocol = tcp
|
||||||
|
tls_enable = true
|
||||||
|
|
||||||
|
[tcp]
|
||||||
|
type = tcp
|
||||||
|
local_port = 10701
|
||||||
|
remote_port = 20801
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestTlsOverTCP(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_TLS_TCP_CONF)
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer os.Remove(frpsCfgPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_TLS_TCP_CONF)
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer os.Remove(frpcCfgPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
frpsProcess := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", frpsCfgPath})
|
||||||
|
err = frpsProcess.Start()
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer frpsProcess.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
|
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
|
||||||
|
err = frpcProcess.Start()
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer frpcProcess.Stop()
|
||||||
|
}
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
|
// test tcp
|
||||||
|
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
const FRPS_TLS_KCP_CONF = `
|
||||||
|
[common]
|
||||||
|
bind_addr = 0.0.0.0
|
||||||
|
bind_port = 20000
|
||||||
|
kcp_bind_port = 20000
|
||||||
|
log_file = console
|
||||||
|
log_level = debug
|
||||||
|
token = 123456
|
||||||
|
`
|
||||||
|
|
||||||
|
const FRPC_TLS_KCP_CONF = `
|
||||||
|
[common]
|
||||||
|
server_addr = 127.0.0.1
|
||||||
|
server_port = 20000
|
||||||
|
log_file = console
|
||||||
|
log_level = debug
|
||||||
|
token = 123456
|
||||||
|
protocol = kcp
|
||||||
|
tls_enable = true
|
||||||
|
|
||||||
|
[tcp]
|
||||||
|
type = tcp
|
||||||
|
local_port = 10701
|
||||||
|
remote_port = 20801
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestTLSOverKCP(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_TLS_KCP_CONF)
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer os.Remove(frpsCfgPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_TLS_KCP_CONF)
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer os.Remove(frpcCfgPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
frpsProcess := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", frpsCfgPath})
|
||||||
|
err = frpsProcess.Start()
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer frpsProcess.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
|
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
|
||||||
|
err = frpcProcess.Start()
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer frpcProcess.Stop()
|
||||||
|
}
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
|
// test tcp
|
||||||
|
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
const FRPS_TLS_WS_CONF = `
|
||||||
|
[common]
|
||||||
|
bind_addr = 0.0.0.0
|
||||||
|
bind_port = 20000
|
||||||
|
log_file = console
|
||||||
|
log_level = debug
|
||||||
|
token = 123456
|
||||||
|
`
|
||||||
|
|
||||||
|
const FRPC_TLS_WS_CONF = `
|
||||||
|
[common]
|
||||||
|
server_addr = 127.0.0.1
|
||||||
|
server_port = 20000
|
||||||
|
log_file = console
|
||||||
|
log_level = debug
|
||||||
|
token = 123456
|
||||||
|
protocol = websocket
|
||||||
|
tls_enable = true
|
||||||
|
|
||||||
|
[tcp]
|
||||||
|
type = tcp
|
||||||
|
local_port = 10701
|
||||||
|
remote_port = 20801
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestTLSOverWebsocket(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_TLS_WS_CONF)
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer os.Remove(frpsCfgPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_TLS_WS_CONF)
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer os.Remove(frpcCfgPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
frpsProcess := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", frpsCfgPath})
|
||||||
|
err = frpsProcess.Start()
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer frpsProcess.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
|
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
|
||||||
|
err = frpcProcess.Start()
|
||||||
|
if assert.NoError(err) {
|
||||||
|
defer frpcProcess.Stop()
|
||||||
|
}
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
|
// test tcp
|
||||||
|
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
|
||||||
|
}
|
||||||
@@ -88,8 +88,10 @@ func handleHttp(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(r.Host, "127.0.0.1") || strings.Contains(r.Host, "test2.frp.com") ||
|
if strings.HasPrefix(r.Host, "127.0.0.1") || strings.HasPrefix(r.Host, "test2.frp.com") ||
|
||||||
strings.Contains(r.Host, "test5.frp.com") || strings.Contains(r.Host, "test6.frp.com") {
|
strings.HasPrefix(r.Host, "test5.frp.com") || strings.HasPrefix(r.Host, "test6.frp.com") ||
|
||||||
|
strings.HasPrefix(r.Host, "test.frp1.com") || strings.HasPrefix(r.Host, "new.test.frp1.com") {
|
||||||
|
|
||||||
w.WriteHeader(200)
|
w.WriteHeader(200)
|
||||||
w.Write([]byte(consts.TEST_HTTP_NORMAL_STR))
|
w.Write([]byte(consts.TEST_HTTP_NORMAL_STR))
|
||||||
} else if strings.Contains(r.Host, "test3.frp.com") {
|
} else if strings.Contains(r.Host, "test3.frp.com") {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/fatedier/beego/logs"
|
"github.com/fatedier/beego/logs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Log is the under log object
|
||||||
var Log *logs.BeeLogger
|
var Log *logs.BeeLogger
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -33,6 +34,7 @@ func InitLog(logWay string, logFile string, logLevel string, maxdays int64) {
|
|||||||
SetLogLevel(logLevel)
|
SetLogLevel(logLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetLogFile to configure log params
|
||||||
// logWay: file or console
|
// logWay: file or console
|
||||||
func SetLogFile(logWay string, logFile string, maxdays int64) {
|
func SetLogFile(logWay string, logFile string, maxdays int64) {
|
||||||
if logWay == "console" {
|
if logWay == "console" {
|
||||||
@@ -43,6 +45,7 @@ func SetLogFile(logWay string, logFile string, maxdays int64) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetLogLevel set log level, default is warning
|
||||||
// value: error, warning, info, debug, trace
|
// value: error, warning, info, debug, trace
|
||||||
func SetLogLevel(logLevel string) {
|
func SetLogLevel(logLevel string) {
|
||||||
level := 4 // warning
|
level := 4 // warning
|
||||||
@@ -85,7 +88,7 @@ func Trace(format string, v ...interface{}) {
|
|||||||
Log.Trace(format, v...)
|
Log.Trace(format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logger
|
// Logger is the log interface
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
AddLogPrefix(string)
|
AddLogPrefix(string)
|
||||||
GetPrefixStr() string
|
GetPrefixStr() string
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
package net
|
package net
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -207,3 +208,17 @@ func ConnectServerByProxy(proxyUrl string, protocol string, addr string) (c Conn
|
|||||||
return nil, fmt.Errorf("unsupport protocol: %s", protocol)
|
return nil, fmt.Errorf("unsupport protocol: %s", protocol)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ConnectServerByProxyWithTLS(proxyUrl string, protocol string, addr string, tlsConfig *tls.Config) (c Conn, err error) {
|
||||||
|
c, err = ConnectServerByProxy(proxyUrl, protocol, addr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if tlsConfig == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c = WrapTLSClientConn(c, tlsConfig)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
44
utils/net/tls.go
Normal file
44
utils/net/tls.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// 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 net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
gnet "github.com/fatedier/golib/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
FRP_TLS_HEAD_BYTE = 0x17
|
||||||
|
)
|
||||||
|
|
||||||
|
func WrapTLSClientConn(c net.Conn, tlsConfig *tls.Config) (out Conn) {
|
||||||
|
c.Write([]byte{byte(FRP_TLS_HEAD_BYTE)})
|
||||||
|
out = WrapConn(tls.Client(c, tlsConfig))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckAndEnableTLSServerConn(c net.Conn, tlsConfig *tls.Config) (out Conn) {
|
||||||
|
sc, r := gnet.NewSharedConnSize(c, 1)
|
||||||
|
buf := make([]byte, 1)
|
||||||
|
n, _ := r.Read(buf)
|
||||||
|
if n == 1 && int(buf[0]) == FRP_TLS_HEAD_BYTE {
|
||||||
|
out = WrapConn(tls.Server(c, tlsConfig))
|
||||||
|
} else {
|
||||||
|
out = WrapConn(sc)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -31,6 +31,7 @@ type WebsocketListener struct {
|
|||||||
httpMutex *http.ServeMux
|
httpMutex *http.ServeMux
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewWebsocketListener to handle websocket connections
|
||||||
// ln: tcp listener for websocket connections
|
// ln: tcp listener for websocket connections
|
||||||
func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) {
|
func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) {
|
||||||
wl = &WebsocketListener{
|
wl = &WebsocketListener{
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.23.3"
|
var version string = "0.26.0"
|
||||||
|
|
||||||
func Full() string {
|
func Full() string {
|
||||||
return version
|
return version
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -15,221 +15,211 @@
|
|||||||
package vhost
|
package vhost
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"log"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpLog "github.com/fatedier/frp/utils/log"
|
||||||
|
|
||||||
gnet "github.com/fatedier/golib/net"
|
|
||||||
"github.com/fatedier/golib/pool"
|
"github.com/fatedier/golib/pool"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HttpMuxer struct {
|
var (
|
||||||
*VhostMuxer
|
ErrRouterConfigConflict = errors.New("router config conflict")
|
||||||
|
ErrNoDomain = errors.New("no such domain")
|
||||||
|
)
|
||||||
|
|
||||||
|
func getHostFromAddr(addr string) (host string) {
|
||||||
|
strs := strings.Split(addr, ":")
|
||||||
|
if len(strs) > 1 {
|
||||||
|
host = strs[0]
|
||||||
|
} else {
|
||||||
|
host = addr
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetHttpRequestInfo(c frpNet.Conn) (_ frpNet.Conn, _ map[string]string, err error) {
|
type HttpReverseProxyOptions struct {
|
||||||
reqInfoMap := make(map[string]string, 0)
|
ResponseHeaderTimeoutS int64
|
||||||
sc, rd := gnet.NewSharedConn(c)
|
|
||||||
|
|
||||||
request, err := http.ReadRequest(bufio.NewReader(rd))
|
|
||||||
if err != nil {
|
|
||||||
return nil, reqInfoMap, err
|
|
||||||
}
|
|
||||||
// hostName
|
|
||||||
tmpArr := strings.Split(request.Host, ":")
|
|
||||||
reqInfoMap["Host"] = tmpArr[0]
|
|
||||||
reqInfoMap["Path"] = request.URL.Path
|
|
||||||
reqInfoMap["Scheme"] = request.URL.Scheme
|
|
||||||
|
|
||||||
// Authorization
|
|
||||||
authStr := request.Header.Get("Authorization")
|
|
||||||
if authStr != "" {
|
|
||||||
reqInfoMap["Authorization"] = authStr
|
|
||||||
}
|
|
||||||
request.Body.Close()
|
|
||||||
return frpNet.WrapConn(sc), reqInfoMap, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHttpMuxer(listener frpNet.Listener, timeout time.Duration) (*HttpMuxer, error) {
|
type HttpReverseProxy struct {
|
||||||
mux, err := NewVhostMuxer(listener, GetHttpRequestInfo, HttpAuthFunc, ModifyHttpRequest, timeout)
|
proxy *ReverseProxy
|
||||||
return &HttpMuxer{mux}, err
|
|
||||||
|
vhostRouter *VhostRouters
|
||||||
|
|
||||||
|
responseHeaderTimeout time.Duration
|
||||||
|
cfgMu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func ModifyHttpRequest(c frpNet.Conn, rewriteHost string) (_ frpNet.Conn, err error) {
|
func NewHttpReverseProxy(option HttpReverseProxyOptions) *HttpReverseProxy {
|
||||||
sc, rd := gnet.NewSharedConn(c)
|
if option.ResponseHeaderTimeoutS <= 0 {
|
||||||
var buff []byte
|
option.ResponseHeaderTimeoutS = 60
|
||||||
remoteIP := strings.Split(c.RemoteAddr().String(), ":")[0]
|
|
||||||
if buff, err = hostNameRewrite(rd, rewriteHost, remoteIP); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
err = sc.ResetBuf(buff)
|
rp := &HttpReverseProxy{
|
||||||
return frpNet.WrapConn(sc), err
|
responseHeaderTimeout: time.Duration(option.ResponseHeaderTimeoutS) * time.Second,
|
||||||
}
|
vhostRouter: NewVhostRouters(),
|
||||||
|
|
||||||
func hostNameRewrite(request io.Reader, rewriteHost string, remoteIP string) (_ []byte, err error) {
|
|
||||||
buf := pool.GetBuf(1024)
|
|
||||||
defer pool.PutBuf(buf)
|
|
||||||
|
|
||||||
var n int
|
|
||||||
n, err = request.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
retBuffer, err := parseRequest(buf[:n], rewriteHost, remoteIP)
|
proxy := &ReverseProxy{
|
||||||
return retBuffer, err
|
Director: func(req *http.Request) {
|
||||||
}
|
req.URL.Scheme = "http"
|
||||||
|
url := req.Context().Value("url").(string)
|
||||||
func parseRequest(org []byte, rewriteHost string, remoteIP string) (ret []byte, err error) {
|
oldHost := getHostFromAddr(req.Context().Value("host").(string))
|
||||||
tp := bytes.NewBuffer(org)
|
host := rp.GetRealHost(oldHost, url)
|
||||||
// First line: GET /index.html HTTP/1.0
|
if host != "" {
|
||||||
var b []byte
|
req.Host = host
|
||||||
if b, err = tp.ReadBytes('\n'); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
req := new(http.Request)
|
|
||||||
// we invoked ReadRequest in GetHttpHostname before, so we ignore error
|
|
||||||
req.Method, req.RequestURI, req.Proto, _ = parseRequestLine(string(b))
|
|
||||||
rawurl := req.RequestURI
|
|
||||||
// CONNECT www.google.com:443 HTTP/1.1
|
|
||||||
justAuthority := req.Method == "CONNECT" && !strings.HasPrefix(rawurl, "/")
|
|
||||||
if justAuthority {
|
|
||||||
rawurl = "http://" + rawurl
|
|
||||||
}
|
|
||||||
req.URL, _ = url.ParseRequestURI(rawurl)
|
|
||||||
if justAuthority {
|
|
||||||
// Strip the bogus "http://" back off.
|
|
||||||
req.URL.Scheme = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC2616: first case
|
|
||||||
// GET /index.html HTTP/1.1
|
|
||||||
// Host: www.google.com
|
|
||||||
if req.URL.Host == "" {
|
|
||||||
var changedBuf []byte
|
|
||||||
if rewriteHost != "" {
|
|
||||||
changedBuf, err = changeHostName(tp, rewriteHost)
|
|
||||||
}
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
buf.Write(b)
|
|
||||||
buf.WriteString(fmt.Sprintf("X-Forwarded-For: %s\r\n", remoteIP))
|
|
||||||
buf.WriteString(fmt.Sprintf("X-Real-IP: %s\r\n", remoteIP))
|
|
||||||
if len(changedBuf) == 0 {
|
|
||||||
tp.WriteTo(buf)
|
|
||||||
} else {
|
|
||||||
buf.Write(changedBuf)
|
|
||||||
}
|
|
||||||
return buf.Bytes(), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC2616: second case
|
|
||||||
// GET http://www.google.com/index.html HTTP/1.1
|
|
||||||
// Host: doesntmatter
|
|
||||||
// In this case, any Host line is ignored.
|
|
||||||
if rewriteHost != "" {
|
|
||||||
hostPort := strings.Split(req.URL.Host, ":")
|
|
||||||
if len(hostPort) == 1 {
|
|
||||||
req.URL.Host = rewriteHost
|
|
||||||
} else if len(hostPort) == 2 {
|
|
||||||
req.URL.Host = fmt.Sprintf("%s:%s", rewriteHost, hostPort[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
firstLine := req.Method + " " + req.URL.String() + " " + req.Proto
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
buf.WriteString(firstLine)
|
|
||||||
buf.WriteString(fmt.Sprintf("X-Forwarded-For: %s\r\n", remoteIP))
|
|
||||||
buf.WriteString(fmt.Sprintf("X-Real-IP: %s\r\n", remoteIP))
|
|
||||||
tp.WriteTo(buf)
|
|
||||||
return buf.Bytes(), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseRequestLine parses "GET /foo HTTP/1.1" into its three parts.
|
|
||||||
func parseRequestLine(line string) (method, requestURI, proto string, ok bool) {
|
|
||||||
s1 := strings.Index(line, " ")
|
|
||||||
s2 := strings.Index(line[s1+1:], " ")
|
|
||||||
if s1 < 0 || s2 < 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s2 += s1 + 1
|
|
||||||
return line[:s1], line[s1+1 : s2], line[s2+1:], true
|
|
||||||
}
|
|
||||||
|
|
||||||
func changeHostName(buff *bytes.Buffer, rewriteHost string) (_ []byte, err error) {
|
|
||||||
retBuf := new(bytes.Buffer)
|
|
||||||
|
|
||||||
peek := buff.Bytes()
|
|
||||||
for len(peek) > 0 {
|
|
||||||
i := bytes.IndexByte(peek, '\n')
|
|
||||||
if i < 3 {
|
|
||||||
// Not present (-1) or found within the next few bytes,
|
|
||||||
// implying we're at the end ("\r\n\r\n" or "\n\n")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
kv := peek[:i]
|
|
||||||
j := bytes.IndexByte(kv, ':')
|
|
||||||
if j < 0 {
|
|
||||||
return nil, fmt.Errorf("malformed MIME header line: " + string(kv))
|
|
||||||
}
|
|
||||||
if strings.Contains(strings.ToLower(string(kv[:j])), "host") {
|
|
||||||
var hostHeader string
|
|
||||||
portPos := bytes.IndexByte(kv[j+1:], ':')
|
|
||||||
if portPos == -1 {
|
|
||||||
hostHeader = fmt.Sprintf("Host: %s\r\n", rewriteHost)
|
|
||||||
} else {
|
|
||||||
hostHeader = fmt.Sprintf("Host: %s:%s\r\n", rewriteHost, kv[j+portPos+2:])
|
|
||||||
}
|
}
|
||||||
retBuf.WriteString(hostHeader)
|
req.URL.Host = req.Host
|
||||||
peek = peek[i+1:]
|
|
||||||
break
|
headers := rp.GetHeaders(oldHost, url)
|
||||||
} else {
|
for k, v := range headers {
|
||||||
retBuf.Write(peek[:i])
|
req.Header.Set(k, v)
|
||||||
retBuf.WriteByte('\n')
|
}
|
||||||
|
},
|
||||||
|
Transport: &http.Transport{
|
||||||
|
ResponseHeaderTimeout: rp.responseHeaderTimeout,
|
||||||
|
DisableKeepAlives: true,
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
url := ctx.Value("url").(string)
|
||||||
|
host := getHostFromAddr(ctx.Value("host").(string))
|
||||||
|
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))
|
||||||
|
remote := ctx.Value("remote").(string)
|
||||||
|
return rp.CreateConnection(host, url, remote)
|
||||||
|
},
|
||||||
|
BufferPool: newWrapPool(),
|
||||||
|
ErrorLog: log.New(newWrapLogger(), "", 0),
|
||||||
|
}
|
||||||
|
rp.proxy = proxy
|
||||||
|
return rp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rp *HttpReverseProxy) Register(routeCfg VhostRouteConfig) error {
|
||||||
|
rp.cfgMu.Lock()
|
||||||
|
defer rp.cfgMu.Unlock()
|
||||||
|
_, ok := rp.vhostRouter.Exist(routeCfg.Domain, routeCfg.Location)
|
||||||
|
if ok {
|
||||||
|
return ErrRouterConfigConflict
|
||||||
|
} else {
|
||||||
|
rp.vhostRouter.Add(routeCfg.Domain, routeCfg.Location, &routeCfg)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rp *HttpReverseProxy) UnRegister(domain string, location string) {
|
||||||
|
rp.cfgMu.Lock()
|
||||||
|
defer rp.cfgMu.Unlock()
|
||||||
|
rp.vhostRouter.Del(domain, location)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rp *HttpReverseProxy) GetRealHost(domain string, location string) (host string) {
|
||||||
|
vr, ok := rp.getVhost(domain, location)
|
||||||
|
if ok {
|
||||||
|
host = vr.payload.(*VhostRouteConfig).RewriteHost
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rp *HttpReverseProxy) GetHeaders(domain string, location string) (headers map[string]string) {
|
||||||
|
vr, ok := rp.getVhost(domain, location)
|
||||||
|
if ok {
|
||||||
|
headers = vr.payload.(*VhostRouteConfig).Headers
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rp *HttpReverseProxy) CreateConnection(domain string, location string, remoteAddr string) (net.Conn, error) {
|
||||||
|
vr, ok := rp.getVhost(domain, location)
|
||||||
|
if ok {
|
||||||
|
fn := vr.payload.(*VhostRouteConfig).CreateConnFn
|
||||||
|
if fn != nil {
|
||||||
|
return fn(remoteAddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("%v: %s %s", ErrNoDomain, domain, location)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rp *HttpReverseProxy) CheckAuth(domain, location, user, passwd string) bool {
|
||||||
|
vr, ok := rp.getVhost(domain, location)
|
||||||
|
if ok {
|
||||||
|
checkUser := vr.payload.(*VhostRouteConfig).Username
|
||||||
|
checkPasswd := vr.payload.(*VhostRouteConfig).Password
|
||||||
|
if (checkUser != "" || checkPasswd != "") && (checkUser != user || checkPasswd != passwd) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rp *HttpReverseProxy) getVhost(domain string, location string) (vr *VhostRouter, ok bool) {
|
||||||
|
rp.cfgMu.RLock()
|
||||||
|
defer rp.cfgMu.RUnlock()
|
||||||
|
|
||||||
|
// first we check the full hostname
|
||||||
|
// if not exist, then check the wildcard_domain such as *.example.com
|
||||||
|
vr, ok = rp.vhostRouter.Get(domain, location)
|
||||||
|
if ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
domainSplit := strings.Split(domain, ".")
|
||||||
|
if len(domainSplit) < 3 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
if len(domainSplit) < 3 {
|
||||||
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
peek = peek[i+1:]
|
domainSplit[0] = "*"
|
||||||
|
domain = strings.Join(domainSplit, ".")
|
||||||
|
vr, ok = rp.vhostRouter.Get(domain, location)
|
||||||
|
if ok {
|
||||||
|
return vr, true
|
||||||
|
}
|
||||||
|
domainSplit = domainSplit[1:]
|
||||||
}
|
}
|
||||||
retBuf.Write(peek)
|
return
|
||||||
return retBuf.Bytes(), err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func HttpAuthFunc(c frpNet.Conn, userName, passWord, authorization string) (bAccess bool, err error) {
|
func (rp *HttpReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
s := strings.SplitN(authorization, " ", 2)
|
domain := getHostFromAddr(req.Host)
|
||||||
if len(s) != 2 {
|
location := req.URL.Path
|
||||||
res := noAuthResponse()
|
user, passwd, _ := req.BasicAuth()
|
||||||
res.Write(c)
|
if !rp.CheckAuth(domain, location, user, passwd) {
|
||||||
|
rw.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||||
|
http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
b, err := base64.StdEncoding.DecodeString(s[1])
|
rp.proxy.ServeHTTP(rw, req)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pair := strings.SplitN(string(b), ":", 2)
|
|
||||||
if len(pair) != 2 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if pair[0] != userName || pair[1] != passWord {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func noAuthResponse() *http.Response {
|
type wrapPool struct{}
|
||||||
header := make(map[string][]string)
|
|
||||||
header["WWW-Authenticate"] = []string{`Basic realm="Restricted"`}
|
func newWrapPool() *wrapPool { return &wrapPool{} }
|
||||||
res := &http.Response{
|
|
||||||
Status: "401 Not authorized",
|
func (p *wrapPool) Get() []byte { return pool.GetBuf(32 * 1024) }
|
||||||
StatusCode: 401,
|
|
||||||
Proto: "HTTP/1.1",
|
func (p *wrapPool) Put(buf []byte) { pool.PutBuf(buf) }
|
||||||
ProtoMajor: 1,
|
|
||||||
ProtoMinor: 1,
|
type wrapLogger struct{}
|
||||||
Header: header,
|
|
||||||
}
|
func newWrapLogger() *wrapLogger { return &wrapLogger{} }
|
||||||
return res
|
|
||||||
|
func (l *wrapLogger) Write(p []byte) (n int, err error) {
|
||||||
|
frpLog.Warn("%s", string(bytes.TrimRight(p, "\n")))
|
||||||
|
return len(p), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,211 +0,0 @@
|
|||||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package vhost
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
frpLog "github.com/fatedier/frp/utils/log"
|
|
||||||
|
|
||||||
"github.com/fatedier/golib/pool"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrRouterConfigConflict = errors.New("router config conflict")
|
|
||||||
ErrNoDomain = errors.New("no such domain")
|
|
||||||
)
|
|
||||||
|
|
||||||
func getHostFromAddr(addr string) (host string) {
|
|
||||||
strs := strings.Split(addr, ":")
|
|
||||||
if len(strs) > 1 {
|
|
||||||
host = strs[0]
|
|
||||||
} else {
|
|
||||||
host = addr
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type HttpReverseProxyOptions struct {
|
|
||||||
ResponseHeaderTimeoutS int64
|
|
||||||
}
|
|
||||||
|
|
||||||
type HttpReverseProxy struct {
|
|
||||||
proxy *ReverseProxy
|
|
||||||
|
|
||||||
vhostRouter *VhostRouters
|
|
||||||
|
|
||||||
responseHeaderTimeout time.Duration
|
|
||||||
cfgMu sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHttpReverseProxy(option HttpReverseProxyOptions) *HttpReverseProxy {
|
|
||||||
if option.ResponseHeaderTimeoutS <= 0 {
|
|
||||||
option.ResponseHeaderTimeoutS = 60
|
|
||||||
}
|
|
||||||
rp := &HttpReverseProxy{
|
|
||||||
responseHeaderTimeout: time.Duration(option.ResponseHeaderTimeoutS) * time.Second,
|
|
||||||
vhostRouter: NewVhostRouters(),
|
|
||||||
}
|
|
||||||
proxy := &ReverseProxy{
|
|
||||||
Director: func(req *http.Request) {
|
|
||||||
req.URL.Scheme = "http"
|
|
||||||
url := req.Context().Value("url").(string)
|
|
||||||
oldHost := getHostFromAddr(req.Context().Value("host").(string))
|
|
||||||
host := rp.GetRealHost(oldHost, url)
|
|
||||||
if host != "" {
|
|
||||||
req.Host = host
|
|
||||||
}
|
|
||||||
req.URL.Host = req.Host
|
|
||||||
|
|
||||||
headers := rp.GetHeaders(oldHost, url)
|
|
||||||
for k, v := range headers {
|
|
||||||
req.Header.Set(k, v)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Transport: &http.Transport{
|
|
||||||
ResponseHeaderTimeout: rp.responseHeaderTimeout,
|
|
||||||
DisableKeepAlives: true,
|
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
url := ctx.Value("url").(string)
|
|
||||||
host := getHostFromAddr(ctx.Value("host").(string))
|
|
||||||
return rp.CreateConnection(host, url)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
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(),
|
|
||||||
ErrorLog: log.New(newWrapLogger(), "", 0),
|
|
||||||
}
|
|
||||||
rp.proxy = proxy
|
|
||||||
return rp
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rp *HttpReverseProxy) Register(routeCfg VhostRouteConfig) error {
|
|
||||||
rp.cfgMu.Lock()
|
|
||||||
defer rp.cfgMu.Unlock()
|
|
||||||
_, ok := rp.vhostRouter.Exist(routeCfg.Domain, routeCfg.Location)
|
|
||||||
if ok {
|
|
||||||
return ErrRouterConfigConflict
|
|
||||||
} else {
|
|
||||||
rp.vhostRouter.Add(routeCfg.Domain, routeCfg.Location, &routeCfg)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rp *HttpReverseProxy) UnRegister(domain string, location string) {
|
|
||||||
rp.cfgMu.Lock()
|
|
||||||
defer rp.cfgMu.Unlock()
|
|
||||||
rp.vhostRouter.Del(domain, location)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rp *HttpReverseProxy) GetRealHost(domain string, location string) (host string) {
|
|
||||||
vr, ok := rp.getVhost(domain, location)
|
|
||||||
if ok {
|
|
||||||
host = vr.payload.(*VhostRouteConfig).RewriteHost
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rp *HttpReverseProxy) GetHeaders(domain string, location string) (headers map[string]string) {
|
|
||||||
vr, ok := rp.getVhost(domain, location)
|
|
||||||
if ok {
|
|
||||||
headers = vr.payload.(*VhostRouteConfig).Headers
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rp *HttpReverseProxy) CreateConnection(domain string, location string) (net.Conn, error) {
|
|
||||||
vr, ok := rp.getVhost(domain, location)
|
|
||||||
if ok {
|
|
||||||
fn := vr.payload.(*VhostRouteConfig).CreateConnFn
|
|
||||||
if fn != nil {
|
|
||||||
return fn()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, ErrNoDomain
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rp *HttpReverseProxy) CheckAuth(domain, location, user, passwd string) bool {
|
|
||||||
vr, ok := rp.getVhost(domain, location)
|
|
||||||
if ok {
|
|
||||||
checkUser := vr.payload.(*VhostRouteConfig).Username
|
|
||||||
checkPasswd := vr.payload.(*VhostRouteConfig).Password
|
|
||||||
if (checkUser != "" || checkPasswd != "") && (checkUser != user || checkPasswd != passwd) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rp *HttpReverseProxy) getVhost(domain string, location string) (vr *VhostRouter, ok bool) {
|
|
||||||
rp.cfgMu.RLock()
|
|
||||||
defer rp.cfgMu.RUnlock()
|
|
||||||
|
|
||||||
// first we check the full hostname
|
|
||||||
// if not exist, then check the wildcard_domain such as *.example.com
|
|
||||||
vr, ok = rp.vhostRouter.Get(domain, location)
|
|
||||||
if ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
domainSplit := strings.Split(domain, ".")
|
|
||||||
if len(domainSplit) < 3 {
|
|
||||||
return vr, false
|
|
||||||
}
|
|
||||||
domainSplit[0] = "*"
|
|
||||||
domain = strings.Join(domainSplit, ".")
|
|
||||||
vr, ok = rp.vhostRouter.Get(domain, location)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rp *HttpReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|
||||||
domain := getHostFromAddr(req.Host)
|
|
||||||
location := req.URL.Path
|
|
||||||
user, passwd, _ := req.BasicAuth()
|
|
||||||
if !rp.CheckAuth(domain, location, user, passwd) {
|
|
||||||
rw.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
|
||||||
http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rp.proxy.ServeHTTP(rw, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
type wrapPool struct{}
|
|
||||||
|
|
||||||
func newWrapPool() *wrapPool { return &wrapPool{} }
|
|
||||||
|
|
||||||
func (p *wrapPool) Get() []byte { return pool.GetBuf(32 * 1024) }
|
|
||||||
|
|
||||||
func (p *wrapPool) Put(buf []byte) { pool.PutBuf(buf) }
|
|
||||||
|
|
||||||
type wrapLogger struct{}
|
|
||||||
|
|
||||||
func newWrapLogger() *wrapLogger { return &wrapLogger{} }
|
|
||||||
|
|
||||||
func (l *wrapLogger) Write(p []byte) (n int, err error) {
|
|
||||||
frpLog.Warn("%s", string(bytes.TrimRight(p, "\n")))
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
@@ -61,3 +61,17 @@ func notFoundResponse() *http.Response {
|
|||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func noAuthResponse() *http.Response {
|
||||||
|
header := make(map[string][]string)
|
||||||
|
header["WWW-Authenticate"] = []string{`Basic realm="Restricted"`}
|
||||||
|
res := &http.Response{
|
||||||
|
Status: "401 Not authorized",
|
||||||
|
StatusCode: 401,
|
||||||
|
Proto: "HTTP/1.1",
|
||||||
|
ProtoMajor: 1,
|
||||||
|
ProtoMinor: 1,
|
||||||
|
Header: header,
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ func (p *ReverseProxy) serveWebSocket(rw http.ResponseWriter, req *http.Request)
|
|||||||
|
|
||||||
req = req.WithContext(context.WithValue(req.Context(), "url", req.URL.Path))
|
req = req.WithContext(context.WithValue(req.Context(), "url", req.URL.Path))
|
||||||
req = req.WithContext(context.WithValue(req.Context(), "host", req.Host))
|
req = req.WithContext(context.WithValue(req.Context(), "host", req.Host))
|
||||||
|
req = req.WithContext(context.WithValue(req.Context(), "remote", req.RemoteAddr))
|
||||||
|
|
||||||
targetConn, err := p.WebSocketDialContext(req.Context(), "tcp", "")
|
targetConn, err := p.WebSocketDialContext(req.Context(), "tcp", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -215,6 +216,7 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
// Modify 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
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ 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)
|
||||||
|
|
||||||
type VhostRouteConfig struct {
|
type VhostRouteConfig struct {
|
||||||
Domain string
|
Domain string
|
||||||
@@ -102,17 +102,24 @@ func (v *VhostMuxer) getListener(name, path string) (l *Listener, exist bool) {
|
|||||||
|
|
||||||
domainSplit := strings.Split(name, ".")
|
domainSplit := strings.Split(name, ".")
|
||||||
if len(domainSplit) < 3 {
|
if len(domainSplit) < 3 {
|
||||||
return l, false
|
|
||||||
}
|
|
||||||
domainSplit[0] = "*"
|
|
||||||
name = strings.Join(domainSplit, ".")
|
|
||||||
|
|
||||||
vr, found = v.registryRouter.Get(name, path)
|
|
||||||
if !found {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return vr.payload.(*Listener), true
|
for {
|
||||||
|
if len(domainSplit) < 3 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
domainSplit[0] = "*"
|
||||||
|
name = strings.Join(domainSplit, ".")
|
||||||
|
|
||||||
|
vr, found = v.registryRouter.Get(name, path)
|
||||||
|
if found {
|
||||||
|
return vr.payload.(*Listener), true
|
||||||
|
}
|
||||||
|
domainSplit = domainSplit[1:]
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VhostMuxer) run() {
|
func (v *VhostMuxer) run() {
|
||||||
|
|||||||
4
vendor/github.com/fatedier/kcp-go/.travis.yml
generated
vendored
4
vendor/github.com/fatedier/kcp-go/.travis.yml
generated
vendored
@@ -1,6 +1,8 @@
|
|||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.9
|
- 1.9.x
|
||||||
|
- 1.10.x
|
||||||
|
- 1.11.x
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- go get -t -v ./...
|
- go get -t -v ./...
|
||||||
|
|||||||
98
vendor/github.com/fatedier/kcp-go/README.md
generated
vendored
98
vendor/github.com/fatedier/kcp-go/README.md
generated
vendored
@@ -20,24 +20,20 @@
|
|||||||
|
|
||||||
**kcp-go** is a **Production-Grade Reliable-UDP** library for [golang](https://golang.org/).
|
**kcp-go** is a **Production-Grade Reliable-UDP** library for [golang](https://golang.org/).
|
||||||
|
|
||||||
It provides **fast, ordered and error-checked** delivery of streams over **UDP** packets, has been well tested with opensource project [kcptun](https://github.com/xtaci/kcptun). Millions of devices(from low-end MIPS routers to high-end servers) are running with **kcp-go** at present, including applications like **online games, live broadcasting, file synchronization and network acceleration**.
|
This library intents to provide a **smooth, resilient, ordered, error-checked and anonymous** delivery of streams over **UDP** packets, it has been battle-tested with opensource project [kcptun](https://github.com/xtaci/kcptun). Millions of devices(from low-end MIPS routers to high-end servers) have deployed **kcp-go** powered program in a variety of forms like **online games, live broadcasting, file synchronization and network acceleration**.
|
||||||
|
|
||||||
[Lastest Release](https://github.com/xtaci/kcp-go/releases)
|
[Lastest Release](https://github.com/xtaci/kcp-go/releases)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
1. Optimized for **Realtime Online Games, Audio/Video Streaming and Latency-Sensitive Distributed Consensus**.
|
1. Designed for **Latency-sensitive** scenarios.
|
||||||
1. Compatible with [skywind3000's](https://github.com/skywind3000) C version with language specific optimizations.
|
|
||||||
1. **Cache friendly** and **Memory optimized** design, offers extremely **High Performance** core.
|
1. **Cache friendly** and **Memory optimized** design, offers extremely **High Performance** core.
|
||||||
1. Handles **>5K concurrent connections** on a single commodity server.
|
1. Handles **>5K concurrent connections** on a single commodity server.
|
||||||
1. Compatible with [net.Conn](https://golang.org/pkg/net/#Conn) and [net.Listener](https://golang.org/pkg/net/#Listener), a drop-in replacement for [net.TCPConn](https://golang.org/pkg/net/#TCPConn).
|
1. Compatible with [net.Conn](https://golang.org/pkg/net/#Conn) and [net.Listener](https://golang.org/pkg/net/#Listener), a drop-in replacement for [net.TCPConn](https://golang.org/pkg/net/#TCPConn).
|
||||||
1. [FEC(Forward Error Correction)](https://en.wikipedia.org/wiki/Forward_error_correction) Support with [Reed-Solomon Codes](https://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction)
|
1. [FEC(Forward Error Correction)](https://en.wikipedia.org/wiki/Forward_error_correction) Support with [Reed-Solomon Codes](https://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction)
|
||||||
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.
|
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. **Fixed number of goroutines** created for the entire server application, minimized goroutine context switch.
|
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.
|
||||||
## Conventions
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
@@ -80,47 +76,59 @@ Server: [full demo](https://github.com/xtaci/kcptun/blob/master/server/main.go
|
|||||||
lis, err := kcp.ListenWithOptions(":10000", nil, 10, 3)
|
lis, err := kcp.ListenWithOptions(":10000", nil, 10, 3)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Performance
|
## Benchmark
|
||||||
```
|
```
|
||||||
Model Name: MacBook Pro
|
Model Name: MacBook Pro
|
||||||
Model Identifier: MacBookPro12,1
|
Model Identifier: MacBookPro14,1
|
||||||
Processor Name: Intel Core i5
|
Processor Name: Intel Core i5
|
||||||
Processor Speed: 2.7 GHz
|
Processor Speed: 3.1 GHz
|
||||||
Number of Processors: 1
|
Number of Processors: 1
|
||||||
Total Number of Cores: 2
|
Total Number of Cores: 2
|
||||||
L2 Cache (per Core): 256 KB
|
L2 Cache (per Core): 256 KB
|
||||||
L3 Cache: 3 MB
|
L3 Cache: 4 MB
|
||||||
Memory: 8 GB
|
Memory: 8 GB
|
||||||
```
|
```
|
||||||
```
|
```
|
||||||
$ go test -v -run=^$ -bench .
|
$ go test -v -run=^$ -bench .
|
||||||
beginning tests, encryption:salsa20, fec:10/3
|
beginning tests, encryption:salsa20, fec:10/3
|
||||||
BenchmarkAES128-4 200000 8256 ns/op 363.33 MB/s 0 B/op 0 allocs/op
|
goos: darwin
|
||||||
BenchmarkAES192-4 200000 9153 ns/op 327.74 MB/s 0 B/op 0 allocs/op
|
goarch: amd64
|
||||||
BenchmarkAES256-4 200000 10079 ns/op 297.64 MB/s 0 B/op 0 allocs/op
|
pkg: github.com/xtaci/kcp-go
|
||||||
BenchmarkTEA-4 100000 18643 ns/op 160.91 MB/s 0 B/op 0 allocs/op
|
BenchmarkSM4-4 50000 32180 ns/op 93.23 MB/s 0 B/op 0 allocs/op
|
||||||
BenchmarkXOR-4 5000000 316 ns/op 9486.46 MB/s 0 B/op 0 allocs/op
|
BenchmarkAES128-4 500000 3285 ns/op 913.21 MB/s 0 B/op 0 allocs/op
|
||||||
BenchmarkBlowfish-4 50000 35643 ns/op 84.17 MB/s 0 B/op 0 allocs/op
|
BenchmarkAES192-4 300000 3623 ns/op 827.85 MB/s 0 B/op 0 allocs/op
|
||||||
BenchmarkNone-4 30000000 56.2 ns/op 53371.83 MB/s 0 B/op 0 allocs/op
|
BenchmarkAES256-4 300000 3874 ns/op 774.20 MB/s 0 B/op 0 allocs/op
|
||||||
BenchmarkCast5-4 30000 44744 ns/op 67.05 MB/s 0 B/op 0 allocs/op
|
BenchmarkTEA-4 100000 15384 ns/op 195.00 MB/s 0 B/op 0 allocs/op
|
||||||
Benchmark3DES-4 2000 639839 ns/op 4.69 MB/s 2 B/op 0 allocs/op
|
BenchmarkXOR-4 20000000 89.9 ns/op 33372.00 MB/s 0 B/op 0 allocs/op
|
||||||
BenchmarkTwofish-4 30000 43368 ns/op 69.17 MB/s 0 B/op 0 allocs/op
|
BenchmarkBlowfish-4 50000 26927 ns/op 111.41 MB/s 0 B/op 0 allocs/op
|
||||||
BenchmarkXTEA-4 30000 57673 ns/op 52.02 MB/s 0 B/op 0 allocs/op
|
BenchmarkNone-4 30000000 45.7 ns/op 65597.94 MB/s 0 B/op 0 allocs/op
|
||||||
BenchmarkSalsa20-4 300000 3917 ns/op 765.80 MB/s 0 B/op 0 allocs/op
|
BenchmarkCast5-4 50000 34258 ns/op 87.57 MB/s 0 B/op 0 allocs/op
|
||||||
BenchmarkFlush-4 10000000 226 ns/op 0 B/op 0 allocs/op
|
Benchmark3DES-4 10000 117149 ns/op 25.61 MB/s 0 B/op 0 allocs/op
|
||||||
BenchmarkEchoSpeed4K-4 5000 300030 ns/op 13.65 MB/s 5672 B/op 177 allocs/op
|
BenchmarkTwofish-4 50000 33538 ns/op 89.45 MB/s 0 B/op 0 allocs/op
|
||||||
BenchmarkEchoSpeed64K-4 500 3202335 ns/op 20.47 MB/s 73295 B/op 2198 allocs/op
|
BenchmarkXTEA-4 30000 45666 ns/op 65.69 MB/s 0 B/op 0 allocs/op
|
||||||
BenchmarkEchoSpeed512K-4 50 24926924 ns/op 21.03 MB/s 659339 B/op 17602 allocs/op
|
BenchmarkSalsa20-4 500000 3308 ns/op 906.76 MB/s 0 B/op 0 allocs/op
|
||||||
BenchmarkEchoSpeed1M-4 20 64857821 ns/op 16.17 MB/s 1772437 B/op 42869 allocs/op
|
BenchmarkCRC32-4 20000000 65.2 ns/op 15712.43 MB/s
|
||||||
BenchmarkSinkSpeed4K-4 30000 50230 ns/op 81.54 MB/s 2058 B/op 48 allocs/op
|
BenchmarkCsprngSystem-4 1000000 1150 ns/op 13.91 MB/s
|
||||||
BenchmarkSinkSpeed64K-4 2000 648718 ns/op 101.02 MB/s 31165 B/op 687 allocs/op
|
BenchmarkCsprngMD5-4 10000000 145 ns/op 110.26 MB/s
|
||||||
BenchmarkSinkSpeed256K-4 300 4635905 ns/op 113.09 MB/s 286229 B/op 5516 allocs/op
|
BenchmarkCsprngSHA1-4 10000000 158 ns/op 126.54 MB/s
|
||||||
BenchmarkSinkSpeed1M-4 200 9566933 ns/op 109.60 MB/s 463771 B/op 10701 allocs/op
|
BenchmarkCsprngNonceMD5-4 10000000 153 ns/op 104.22 MB/s
|
||||||
|
BenchmarkCsprngNonceAES128-4 100000000 19.1 ns/op 837.81 MB/s
|
||||||
|
BenchmarkFECDecode-4 1000000 1119 ns/op 1339.61 MB/s 1606 B/op 2 allocs/op
|
||||||
|
BenchmarkFECEncode-4 2000000 832 ns/op 1801.83 MB/s 17 B/op 0 allocs/op
|
||||||
|
BenchmarkFlush-4 5000000 272 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkEchoSpeed4K-4 5000 259617 ns/op 15.78 MB/s 5451 B/op 149 allocs/op
|
||||||
|
BenchmarkEchoSpeed64K-4 1000 1706084 ns/op 38.41 MB/s 56002 B/op 1604 allocs/op
|
||||||
|
BenchmarkEchoSpeed512K-4 100 14345505 ns/op 36.55 MB/s 482597 B/op 13045 allocs/op
|
||||||
|
BenchmarkEchoSpeed1M-4 30 34859104 ns/op 30.08 MB/s 1143773 B/op 27186 allocs/op
|
||||||
|
BenchmarkSinkSpeed4K-4 50000 31369 ns/op 130.57 MB/s 1566 B/op 30 allocs/op
|
||||||
|
BenchmarkSinkSpeed64K-4 5000 329065 ns/op 199.16 MB/s 21529 B/op 453 allocs/op
|
||||||
|
BenchmarkSinkSpeed256K-4 500 2373354 ns/op 220.91 MB/s 166332 B/op 3554 allocs/op
|
||||||
|
BenchmarkSinkSpeed1M-4 300 5117927 ns/op 204.88 MB/s 310378 B/op 6988 allocs/op
|
||||||
PASS
|
PASS
|
||||||
ok _/Users/xtaci/.godeps/src/github.com/xtaci/kcp-go 39.689s
|
ok github.com/xtaci/kcp-go 50.349s
|
||||||
```
|
```
|
||||||
|
|
||||||
## Design Considerations
|
## Key Design Considerations
|
||||||
|
|
||||||
1. slice vs. container/list
|
1. slice vs. container/list
|
||||||
|
|
||||||
@@ -139,7 +147,9 @@ List structure introduces **heavy cache misses** compared to slice which owns be
|
|||||||
|
|
||||||
2. Timing accuracy vs. syscall clock_gettime
|
2. Timing accuracy vs. syscall clock_gettime
|
||||||
|
|
||||||
Timing is **critical** to **RTT estimator**, inaccurate timing introduces false retransmissions in KCP, but calling `time.Now()` costs 42 cycles(10.5ns on 4GHz CPU, 15.6ns on my MacBook Pro 2.7GHz), the benchmark for time.Now():
|
Timing is **critical** to **RTT estimator**, inaccurate timing leads to false retransmissions in KCP, but calling `time.Now()` costs 42 cycles(10.5ns on 4GHz CPU, 15.6ns on my MacBook Pro 2.7GHz).
|
||||||
|
|
||||||
|
The benchmark for time.Now() lies here:
|
||||||
|
|
||||||
https://github.com/xtaci/notes/blob/master/golang/benchmark2/syscall_test.go
|
https://github.com/xtaci/notes/blob/master/golang/benchmark2/syscall_test.go
|
||||||
|
|
||||||
@@ -147,14 +157,17 @@ https://github.com/xtaci/notes/blob/master/golang/benchmark2/syscall_test.go
|
|||||||
BenchmarkNow-4 100000000 15.6 ns/op
|
BenchmarkNow-4 100000000 15.6 ns/op
|
||||||
```
|
```
|
||||||
|
|
||||||
In kcp-go, after each `kcp.output()` function call, current time will be updated upon return, and each `kcp.flush()` will get current time once. For most of the time, 5000 connections costs 5000 * 15.6ns = 78us(no packet needs to be sent by `kcp.output()`), 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**.
|
||||||
|
|
||||||
|
## Connection Termination
|
||||||
|
|
||||||
## Tuning
|
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.
|
||||||
|
|
||||||
Q: I'm handling >5K connections on my server. the CPU utilization is high.
|
## FAQ
|
||||||
|
|
||||||
A: A standalone `agent` or `gate` server for kcp-go is suggested, not only for CPU utilization, but also important to the **precision** of RTT measurements which indirectly affects retransmission. By increasing update `interval` with `SetNoDelay` like `conn.SetNoDelay(1, 40, 1, 1)` will dramatically reduce system load.
|
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.
|
||||||
|
|
||||||
## Who is using this?
|
## Who is using this?
|
||||||
|
|
||||||
@@ -163,10 +176,9 @@ A: A standalone `agent` or `gate` server for kcp-go is suggested, not only for C
|
|||||||
3. https://github.com/smallnest/rpcx -- A RPC service framework based on net/rpc like alibaba Dubbo and weibo Motan.
|
3. https://github.com/smallnest/rpcx -- A RPC service framework based on net/rpc like alibaba Dubbo and weibo Motan.
|
||||||
4. https://github.com/gonet2/agent -- A gateway for games with stream multiplexing.
|
4. https://github.com/gonet2/agent -- A gateway for games with stream multiplexing.
|
||||||
5. https://github.com/syncthing/syncthing -- Open Source Continuous File Synchronization.
|
5. https://github.com/syncthing/syncthing -- Open Source Continuous File Synchronization.
|
||||||
6. https://play.google.com/store/apps/details?id=com.k17game.k3 -- Battle Zone - Earth 2048, a world-wide strategy game.
|
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
1. https://github.com/xtaci/libkcp -- FEC enhanced KCP session library for iOS/Android in C++
|
1. https://github.com/xtaci/libkcp -- FEC enhanced KCP session library for iOS/Android in C++
|
||||||
2. https://github.com/skywind3000/kcp -- A Fast and Reliable ARQ Protocol
|
2. https://github.com/skywind3000/kcp -- A Fast and Reliable ARQ Protocol
|
||||||
3. https://github.com/templexxx/reedsolomon -- Reed-Solomon Erasure Coding in Go
|
3. https://github.com/klauspost/reedsolomon -- Reed-Solomon Erasure Coding in Go
|
||||||
|
|||||||
601
vendor/github.com/fatedier/kcp-go/crypt.go
generated
vendored
601
vendor/github.com/fatedier/kcp-go/crypt.go
generated
vendored
@@ -57,8 +57,8 @@ func (c *salsa20BlockCrypt) Decrypt(dst, src []byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type sm4BlockCrypt struct {
|
type sm4BlockCrypt struct {
|
||||||
encbuf []byte
|
encbuf [sm4.BlockSize]byte
|
||||||
decbuf []byte
|
decbuf [2 * sm4.BlockSize]byte
|
||||||
block cipher.Block
|
block cipher.Block
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,17 +70,15 @@ func NewSM4BlockCrypt(key []byte) (BlockCrypt, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
c.block = block
|
c.block = block
|
||||||
c.encbuf = make([]byte, sm4.BlockSize)
|
|
||||||
c.decbuf = make([]byte, 2*sm4.BlockSize)
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *sm4BlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf) }
|
func (c *sm4BlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf[:]) }
|
||||||
func (c *sm4BlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf) }
|
func (c *sm4BlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf[:]) }
|
||||||
|
|
||||||
type twofishBlockCrypt struct {
|
type twofishBlockCrypt struct {
|
||||||
encbuf []byte
|
encbuf [twofish.BlockSize]byte
|
||||||
decbuf []byte
|
decbuf [2 * twofish.BlockSize]byte
|
||||||
block cipher.Block
|
block cipher.Block
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,17 +90,15 @@ func NewTwofishBlockCrypt(key []byte) (BlockCrypt, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
c.block = block
|
c.block = block
|
||||||
c.encbuf = make([]byte, twofish.BlockSize)
|
|
||||||
c.decbuf = make([]byte, 2*twofish.BlockSize)
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *twofishBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf) }
|
func (c *twofishBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf[:]) }
|
||||||
func (c *twofishBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf) }
|
func (c *twofishBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf[:]) }
|
||||||
|
|
||||||
type tripleDESBlockCrypt struct {
|
type tripleDESBlockCrypt struct {
|
||||||
encbuf []byte
|
encbuf [des.BlockSize]byte
|
||||||
decbuf []byte
|
decbuf [2 * des.BlockSize]byte
|
||||||
block cipher.Block
|
block cipher.Block
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,17 +110,15 @@ func NewTripleDESBlockCrypt(key []byte) (BlockCrypt, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
c.block = block
|
c.block = block
|
||||||
c.encbuf = make([]byte, des.BlockSize)
|
|
||||||
c.decbuf = make([]byte, 2*des.BlockSize)
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *tripleDESBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf) }
|
func (c *tripleDESBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf[:]) }
|
||||||
func (c *tripleDESBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf) }
|
func (c *tripleDESBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf[:]) }
|
||||||
|
|
||||||
type cast5BlockCrypt struct {
|
type cast5BlockCrypt struct {
|
||||||
encbuf []byte
|
encbuf [cast5.BlockSize]byte
|
||||||
decbuf []byte
|
decbuf [2 * cast5.BlockSize]byte
|
||||||
block cipher.Block
|
block cipher.Block
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,17 +130,15 @@ func NewCast5BlockCrypt(key []byte) (BlockCrypt, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
c.block = block
|
c.block = block
|
||||||
c.encbuf = make([]byte, cast5.BlockSize)
|
|
||||||
c.decbuf = make([]byte, 2*cast5.BlockSize)
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cast5BlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf) }
|
func (c *cast5BlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf[:]) }
|
||||||
func (c *cast5BlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf) }
|
func (c *cast5BlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf[:]) }
|
||||||
|
|
||||||
type blowfishBlockCrypt struct {
|
type blowfishBlockCrypt struct {
|
||||||
encbuf []byte
|
encbuf [blowfish.BlockSize]byte
|
||||||
decbuf []byte
|
decbuf [2 * blowfish.BlockSize]byte
|
||||||
block cipher.Block
|
block cipher.Block
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,17 +150,15 @@ func NewBlowfishBlockCrypt(key []byte) (BlockCrypt, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
c.block = block
|
c.block = block
|
||||||
c.encbuf = make([]byte, blowfish.BlockSize)
|
|
||||||
c.decbuf = make([]byte, 2*blowfish.BlockSize)
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *blowfishBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf) }
|
func (c *blowfishBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf[:]) }
|
||||||
func (c *blowfishBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf) }
|
func (c *blowfishBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf[:]) }
|
||||||
|
|
||||||
type aesBlockCrypt struct {
|
type aesBlockCrypt struct {
|
||||||
encbuf []byte
|
encbuf [aes.BlockSize]byte
|
||||||
decbuf []byte
|
decbuf [2 * aes.BlockSize]byte
|
||||||
block cipher.Block
|
block cipher.Block
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,17 +170,15 @@ func NewAESBlockCrypt(key []byte) (BlockCrypt, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
c.block = block
|
c.block = block
|
||||||
c.encbuf = make([]byte, aes.BlockSize)
|
|
||||||
c.decbuf = make([]byte, 2*aes.BlockSize)
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *aesBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf) }
|
func (c *aesBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf[:]) }
|
||||||
func (c *aesBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf) }
|
func (c *aesBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf[:]) }
|
||||||
|
|
||||||
type teaBlockCrypt struct {
|
type teaBlockCrypt struct {
|
||||||
encbuf []byte
|
encbuf [tea.BlockSize]byte
|
||||||
decbuf []byte
|
decbuf [2 * tea.BlockSize]byte
|
||||||
block cipher.Block
|
block cipher.Block
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,17 +190,15 @@ func NewTEABlockCrypt(key []byte) (BlockCrypt, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
c.block = block
|
c.block = block
|
||||||
c.encbuf = make([]byte, tea.BlockSize)
|
|
||||||
c.decbuf = make([]byte, 2*tea.BlockSize)
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *teaBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf) }
|
func (c *teaBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf[:]) }
|
||||||
func (c *teaBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf) }
|
func (c *teaBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf[:]) }
|
||||||
|
|
||||||
type xteaBlockCrypt struct {
|
type xteaBlockCrypt struct {
|
||||||
encbuf []byte
|
encbuf [xtea.BlockSize]byte
|
||||||
decbuf []byte
|
decbuf [2 * xtea.BlockSize]byte
|
||||||
block cipher.Block
|
block cipher.Block
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,13 +210,11 @@ func NewXTEABlockCrypt(key []byte) (BlockCrypt, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
c.block = block
|
c.block = block
|
||||||
c.encbuf = make([]byte, xtea.BlockSize)
|
|
||||||
c.decbuf = make([]byte, 2*xtea.BlockSize)
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *xteaBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf) }
|
func (c *xteaBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf[:]) }
|
||||||
func (c *xteaBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf) }
|
func (c *xteaBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf[:]) }
|
||||||
|
|
||||||
type simpleXORBlockCrypt struct {
|
type simpleXORBlockCrypt struct {
|
||||||
xortbl []byte
|
xortbl []byte
|
||||||
@@ -258,31 +242,544 @@ func (c *noneBlockCrypt) Decrypt(dst, src []byte) { copy(dst, src) }
|
|||||||
|
|
||||||
// packet encryption with local CFB mode
|
// packet encryption with local CFB mode
|
||||||
func encrypt(block cipher.Block, dst, src, buf []byte) {
|
func encrypt(block cipher.Block, dst, src, buf []byte) {
|
||||||
|
switch block.BlockSize() {
|
||||||
|
case 8:
|
||||||
|
encrypt8(block, dst, src, buf)
|
||||||
|
case 16:
|
||||||
|
encrypt16(block, dst, src, buf)
|
||||||
|
default:
|
||||||
|
encryptVariant(block, dst, src, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// optimized encryption for the ciphers which works in 8-bytes
|
||||||
|
func encrypt8(block cipher.Block, dst, src, buf []byte) {
|
||||||
|
tbl := buf[:8]
|
||||||
|
block.Encrypt(tbl, initialVector)
|
||||||
|
n := len(src) / 8
|
||||||
|
base := 0
|
||||||
|
repeat := n / 8
|
||||||
|
left := n % 8
|
||||||
|
for i := 0; i < repeat; i++ {
|
||||||
|
s := src[base:][0:64]
|
||||||
|
d := dst[base:][0:64]
|
||||||
|
// 1
|
||||||
|
xor.BytesSrc1(d[0:8], s[0:8], tbl)
|
||||||
|
block.Encrypt(tbl, d[0:8])
|
||||||
|
// 2
|
||||||
|
xor.BytesSrc1(d[8:16], s[8:16], tbl)
|
||||||
|
block.Encrypt(tbl, d[8:16])
|
||||||
|
// 3
|
||||||
|
xor.BytesSrc1(d[16:24], s[16:24], tbl)
|
||||||
|
block.Encrypt(tbl, d[16:24])
|
||||||
|
// 4
|
||||||
|
xor.BytesSrc1(d[24:32], s[24:32], tbl)
|
||||||
|
block.Encrypt(tbl, d[24:32])
|
||||||
|
// 5
|
||||||
|
xor.BytesSrc1(d[32:40], s[32:40], tbl)
|
||||||
|
block.Encrypt(tbl, d[32:40])
|
||||||
|
// 6
|
||||||
|
xor.BytesSrc1(d[40:48], s[40:48], tbl)
|
||||||
|
block.Encrypt(tbl, d[40:48])
|
||||||
|
// 7
|
||||||
|
xor.BytesSrc1(d[48:56], s[48:56], tbl)
|
||||||
|
block.Encrypt(tbl, d[48:56])
|
||||||
|
// 8
|
||||||
|
xor.BytesSrc1(d[56:64], s[56:64], tbl)
|
||||||
|
block.Encrypt(tbl, d[56:64])
|
||||||
|
base += 64
|
||||||
|
}
|
||||||
|
|
||||||
|
switch left {
|
||||||
|
case 7:
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += 8
|
||||||
|
fallthrough
|
||||||
|
case 6:
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += 8
|
||||||
|
fallthrough
|
||||||
|
case 5:
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += 8
|
||||||
|
fallthrough
|
||||||
|
case 4:
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += 8
|
||||||
|
fallthrough
|
||||||
|
case 3:
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += 8
|
||||||
|
fallthrough
|
||||||
|
case 2:
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += 8
|
||||||
|
fallthrough
|
||||||
|
case 1:
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += 8
|
||||||
|
fallthrough
|
||||||
|
case 0:
|
||||||
|
xor.BytesSrc0(dst[base:], src[base:], tbl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// optimized encryption for the ciphers which works in 16-bytes
|
||||||
|
func encrypt16(block cipher.Block, dst, src, buf []byte) {
|
||||||
|
tbl := buf[:16]
|
||||||
|
block.Encrypt(tbl, initialVector)
|
||||||
|
n := len(src) / 16
|
||||||
|
base := 0
|
||||||
|
repeat := n / 8
|
||||||
|
left := n % 8
|
||||||
|
for i := 0; i < repeat; i++ {
|
||||||
|
s := src[base:][0:128]
|
||||||
|
d := dst[base:][0:128]
|
||||||
|
// 1
|
||||||
|
xor.BytesSrc1(d[0:16], s[0:16], tbl)
|
||||||
|
block.Encrypt(tbl, d[0:16])
|
||||||
|
// 2
|
||||||
|
xor.BytesSrc1(d[16:32], s[16:32], tbl)
|
||||||
|
block.Encrypt(tbl, d[16:32])
|
||||||
|
// 3
|
||||||
|
xor.BytesSrc1(d[32:48], s[32:48], tbl)
|
||||||
|
block.Encrypt(tbl, d[32:48])
|
||||||
|
// 4
|
||||||
|
xor.BytesSrc1(d[48:64], s[48:64], tbl)
|
||||||
|
block.Encrypt(tbl, d[48:64])
|
||||||
|
// 5
|
||||||
|
xor.BytesSrc1(d[64:80], s[64:80], tbl)
|
||||||
|
block.Encrypt(tbl, d[64:80])
|
||||||
|
// 6
|
||||||
|
xor.BytesSrc1(d[80:96], s[80:96], tbl)
|
||||||
|
block.Encrypt(tbl, d[80:96])
|
||||||
|
// 7
|
||||||
|
xor.BytesSrc1(d[96:112], s[96:112], tbl)
|
||||||
|
block.Encrypt(tbl, d[96:112])
|
||||||
|
// 8
|
||||||
|
xor.BytesSrc1(d[112:128], s[112:128], tbl)
|
||||||
|
block.Encrypt(tbl, d[112:128])
|
||||||
|
base += 128
|
||||||
|
}
|
||||||
|
|
||||||
|
switch left {
|
||||||
|
case 7:
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += 16
|
||||||
|
fallthrough
|
||||||
|
case 6:
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += 16
|
||||||
|
fallthrough
|
||||||
|
case 5:
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += 16
|
||||||
|
fallthrough
|
||||||
|
case 4:
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += 16
|
||||||
|
fallthrough
|
||||||
|
case 3:
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += 16
|
||||||
|
fallthrough
|
||||||
|
case 2:
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += 16
|
||||||
|
fallthrough
|
||||||
|
case 1:
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += 16
|
||||||
|
fallthrough
|
||||||
|
case 0:
|
||||||
|
xor.BytesSrc0(dst[base:], src[base:], tbl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encryptVariant(block cipher.Block, dst, src, buf []byte) {
|
||||||
blocksize := block.BlockSize()
|
blocksize := block.BlockSize()
|
||||||
tbl := buf[:blocksize]
|
tbl := buf[:blocksize]
|
||||||
block.Encrypt(tbl, initialVector)
|
block.Encrypt(tbl, initialVector)
|
||||||
n := len(src) / blocksize
|
n := len(src) / blocksize
|
||||||
base := 0
|
base := 0
|
||||||
for i := 0; i < n; i++ {
|
repeat := n / 8
|
||||||
|
left := n % 8
|
||||||
|
for i := 0; i < repeat; i++ {
|
||||||
|
// 1
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += blocksize
|
||||||
|
|
||||||
|
// 2
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += blocksize
|
||||||
|
|
||||||
|
// 3
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += blocksize
|
||||||
|
|
||||||
|
// 4
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += blocksize
|
||||||
|
|
||||||
|
// 5
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += blocksize
|
||||||
|
|
||||||
|
// 6
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += blocksize
|
||||||
|
|
||||||
|
// 7
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += blocksize
|
||||||
|
|
||||||
|
// 8
|
||||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
block.Encrypt(tbl, dst[base:])
|
block.Encrypt(tbl, dst[base:])
|
||||||
base += blocksize
|
base += blocksize
|
||||||
}
|
}
|
||||||
xor.BytesSrc0(dst[base:], src[base:], tbl)
|
|
||||||
|
switch left {
|
||||||
|
case 7:
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += blocksize
|
||||||
|
fallthrough
|
||||||
|
case 6:
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += blocksize
|
||||||
|
fallthrough
|
||||||
|
case 5:
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += blocksize
|
||||||
|
fallthrough
|
||||||
|
case 4:
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += blocksize
|
||||||
|
fallthrough
|
||||||
|
case 3:
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += blocksize
|
||||||
|
fallthrough
|
||||||
|
case 2:
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += blocksize
|
||||||
|
fallthrough
|
||||||
|
case 1:
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
block.Encrypt(tbl, dst[base:])
|
||||||
|
base += blocksize
|
||||||
|
fallthrough
|
||||||
|
case 0:
|
||||||
|
xor.BytesSrc0(dst[base:], src[base:], tbl)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// decryption
|
||||||
func decrypt(block cipher.Block, dst, src, buf []byte) {
|
func decrypt(block cipher.Block, dst, src, buf []byte) {
|
||||||
|
switch block.BlockSize() {
|
||||||
|
case 8:
|
||||||
|
decrypt8(block, dst, src, buf)
|
||||||
|
case 16:
|
||||||
|
decrypt16(block, dst, src, buf)
|
||||||
|
default:
|
||||||
|
decryptVariant(block, dst, src, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decrypt8(block cipher.Block, dst, src, buf []byte) {
|
||||||
|
tbl := buf[0:8]
|
||||||
|
next := buf[8:16]
|
||||||
|
block.Encrypt(tbl, initialVector)
|
||||||
|
n := len(src) / 8
|
||||||
|
base := 0
|
||||||
|
repeat := n / 8
|
||||||
|
left := n % 8
|
||||||
|
for i := 0; i < repeat; i++ {
|
||||||
|
s := src[base:][0:64]
|
||||||
|
d := dst[base:][0:64]
|
||||||
|
// 1
|
||||||
|
block.Encrypt(next, s[0:8])
|
||||||
|
xor.BytesSrc1(d[0:8], s[0:8], tbl)
|
||||||
|
// 2
|
||||||
|
block.Encrypt(tbl, s[8:16])
|
||||||
|
xor.BytesSrc1(d[8:16], s[8:16], next)
|
||||||
|
// 3
|
||||||
|
block.Encrypt(next, s[16:24])
|
||||||
|
xor.BytesSrc1(d[16:24], s[16:24], tbl)
|
||||||
|
// 4
|
||||||
|
block.Encrypt(tbl, s[24:32])
|
||||||
|
xor.BytesSrc1(d[24:32], s[24:32], next)
|
||||||
|
// 5
|
||||||
|
block.Encrypt(next, s[32:40])
|
||||||
|
xor.BytesSrc1(d[32:40], s[32:40], tbl)
|
||||||
|
// 6
|
||||||
|
block.Encrypt(tbl, s[40:48])
|
||||||
|
xor.BytesSrc1(d[40:48], s[40:48], next)
|
||||||
|
// 7
|
||||||
|
block.Encrypt(next, s[48:56])
|
||||||
|
xor.BytesSrc1(d[48:56], s[48:56], tbl)
|
||||||
|
// 8
|
||||||
|
block.Encrypt(tbl, s[56:64])
|
||||||
|
xor.BytesSrc1(d[56:64], s[56:64], next)
|
||||||
|
base += 64
|
||||||
|
}
|
||||||
|
|
||||||
|
switch left {
|
||||||
|
case 7:
|
||||||
|
block.Encrypt(next, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
tbl, next = next, tbl
|
||||||
|
base += 8
|
||||||
|
fallthrough
|
||||||
|
case 6:
|
||||||
|
block.Encrypt(next, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
tbl, next = next, tbl
|
||||||
|
base += 8
|
||||||
|
fallthrough
|
||||||
|
case 5:
|
||||||
|
block.Encrypt(next, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
tbl, next = next, tbl
|
||||||
|
base += 8
|
||||||
|
fallthrough
|
||||||
|
case 4:
|
||||||
|
block.Encrypt(next, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
tbl, next = next, tbl
|
||||||
|
base += 8
|
||||||
|
fallthrough
|
||||||
|
case 3:
|
||||||
|
block.Encrypt(next, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
tbl, next = next, tbl
|
||||||
|
base += 8
|
||||||
|
fallthrough
|
||||||
|
case 2:
|
||||||
|
block.Encrypt(next, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
tbl, next = next, tbl
|
||||||
|
base += 8
|
||||||
|
fallthrough
|
||||||
|
case 1:
|
||||||
|
block.Encrypt(next, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
tbl, next = next, tbl
|
||||||
|
base += 8
|
||||||
|
fallthrough
|
||||||
|
case 0:
|
||||||
|
xor.BytesSrc0(dst[base:], src[base:], tbl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decrypt16(block cipher.Block, dst, src, buf []byte) {
|
||||||
|
tbl := buf[0:16]
|
||||||
|
next := buf[16:32]
|
||||||
|
block.Encrypt(tbl, initialVector)
|
||||||
|
n := len(src) / 16
|
||||||
|
base := 0
|
||||||
|
repeat := n / 8
|
||||||
|
left := n % 8
|
||||||
|
for i := 0; i < repeat; i++ {
|
||||||
|
s := src[base:][0:128]
|
||||||
|
d := dst[base:][0:128]
|
||||||
|
// 1
|
||||||
|
block.Encrypt(next, s[0:16])
|
||||||
|
xor.BytesSrc1(d[0:16], s[0:16], tbl)
|
||||||
|
// 2
|
||||||
|
block.Encrypt(tbl, s[16:32])
|
||||||
|
xor.BytesSrc1(d[16:32], s[16:32], next)
|
||||||
|
// 3
|
||||||
|
block.Encrypt(next, s[32:48])
|
||||||
|
xor.BytesSrc1(d[32:48], s[32:48], tbl)
|
||||||
|
// 4
|
||||||
|
block.Encrypt(tbl, s[48:64])
|
||||||
|
xor.BytesSrc1(d[48:64], s[48:64], next)
|
||||||
|
// 5
|
||||||
|
block.Encrypt(next, s[64:80])
|
||||||
|
xor.BytesSrc1(d[64:80], s[64:80], tbl)
|
||||||
|
// 6
|
||||||
|
block.Encrypt(tbl, s[80:96])
|
||||||
|
xor.BytesSrc1(d[80:96], s[80:96], next)
|
||||||
|
// 7
|
||||||
|
block.Encrypt(next, s[96:112])
|
||||||
|
xor.BytesSrc1(d[96:112], s[96:112], tbl)
|
||||||
|
// 8
|
||||||
|
block.Encrypt(tbl, s[112:128])
|
||||||
|
xor.BytesSrc1(d[112:128], s[112:128], next)
|
||||||
|
base += 128
|
||||||
|
}
|
||||||
|
|
||||||
|
switch left {
|
||||||
|
case 7:
|
||||||
|
block.Encrypt(next, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
tbl, next = next, tbl
|
||||||
|
base += 16
|
||||||
|
fallthrough
|
||||||
|
case 6:
|
||||||
|
block.Encrypt(next, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
tbl, next = next, tbl
|
||||||
|
base += 16
|
||||||
|
fallthrough
|
||||||
|
case 5:
|
||||||
|
block.Encrypt(next, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
tbl, next = next, tbl
|
||||||
|
base += 16
|
||||||
|
fallthrough
|
||||||
|
case 4:
|
||||||
|
block.Encrypt(next, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
tbl, next = next, tbl
|
||||||
|
base += 16
|
||||||
|
fallthrough
|
||||||
|
case 3:
|
||||||
|
block.Encrypt(next, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
tbl, next = next, tbl
|
||||||
|
base += 16
|
||||||
|
fallthrough
|
||||||
|
case 2:
|
||||||
|
block.Encrypt(next, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
tbl, next = next, tbl
|
||||||
|
base += 16
|
||||||
|
fallthrough
|
||||||
|
case 1:
|
||||||
|
block.Encrypt(next, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
tbl, next = next, tbl
|
||||||
|
base += 16
|
||||||
|
fallthrough
|
||||||
|
case 0:
|
||||||
|
xor.BytesSrc0(dst[base:], src[base:], tbl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decryptVariant(block cipher.Block, dst, src, buf []byte) {
|
||||||
blocksize := block.BlockSize()
|
blocksize := block.BlockSize()
|
||||||
tbl := buf[:blocksize]
|
tbl := buf[:blocksize]
|
||||||
next := buf[blocksize:]
|
next := buf[blocksize:]
|
||||||
block.Encrypt(tbl, initialVector)
|
block.Encrypt(tbl, initialVector)
|
||||||
n := len(src) / blocksize
|
n := len(src) / blocksize
|
||||||
base := 0
|
base := 0
|
||||||
for i := 0; i < n; i++ {
|
repeat := n / 8
|
||||||
|
left := n % 8
|
||||||
|
for i := 0; i < repeat; i++ {
|
||||||
|
// 1
|
||||||
|
block.Encrypt(next, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
base += blocksize
|
||||||
|
|
||||||
|
// 2
|
||||||
|
block.Encrypt(tbl, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], next)
|
||||||
|
base += blocksize
|
||||||
|
|
||||||
|
// 3
|
||||||
|
block.Encrypt(next, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
base += blocksize
|
||||||
|
|
||||||
|
// 4
|
||||||
|
block.Encrypt(tbl, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], next)
|
||||||
|
base += blocksize
|
||||||
|
|
||||||
|
// 5
|
||||||
|
block.Encrypt(next, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
base += blocksize
|
||||||
|
|
||||||
|
// 6
|
||||||
|
block.Encrypt(tbl, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], next)
|
||||||
|
base += blocksize
|
||||||
|
|
||||||
|
// 7
|
||||||
|
block.Encrypt(next, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
base += blocksize
|
||||||
|
|
||||||
|
// 8
|
||||||
|
block.Encrypt(tbl, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], next)
|
||||||
|
base += blocksize
|
||||||
|
}
|
||||||
|
|
||||||
|
switch left {
|
||||||
|
case 7:
|
||||||
block.Encrypt(next, src[base:])
|
block.Encrypt(next, src[base:])
|
||||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
tbl, next = next, tbl
|
tbl, next = next, tbl
|
||||||
base += blocksize
|
base += blocksize
|
||||||
|
fallthrough
|
||||||
|
case 6:
|
||||||
|
block.Encrypt(next, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
tbl, next = next, tbl
|
||||||
|
base += blocksize
|
||||||
|
fallthrough
|
||||||
|
case 5:
|
||||||
|
block.Encrypt(next, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
tbl, next = next, tbl
|
||||||
|
base += blocksize
|
||||||
|
fallthrough
|
||||||
|
case 4:
|
||||||
|
block.Encrypt(next, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
tbl, next = next, tbl
|
||||||
|
base += blocksize
|
||||||
|
fallthrough
|
||||||
|
case 3:
|
||||||
|
block.Encrypt(next, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
tbl, next = next, tbl
|
||||||
|
base += blocksize
|
||||||
|
fallthrough
|
||||||
|
case 2:
|
||||||
|
block.Encrypt(next, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
tbl, next = next, tbl
|
||||||
|
base += blocksize
|
||||||
|
fallthrough
|
||||||
|
case 1:
|
||||||
|
block.Encrypt(next, src[base:])
|
||||||
|
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||||
|
tbl, next = next, tbl
|
||||||
|
base += blocksize
|
||||||
|
fallthrough
|
||||||
|
case 0:
|
||||||
|
xor.BytesSrc0(dst[base:], src[base:], tbl)
|
||||||
}
|
}
|
||||||
xor.BytesSrc0(dst[base:], src[base:], tbl)
|
|
||||||
}
|
}
|
||||||
|
|||||||
52
vendor/github.com/fatedier/kcp-go/entropy.go
generated
vendored
Normal file
52
vendor/github.com/fatedier/kcp-go/entropy.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package kcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/rand"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Entropy defines a entropy source
|
||||||
|
type Entropy interface {
|
||||||
|
Init()
|
||||||
|
Fill(nonce []byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
// nonceMD5 nonce generator for packet header
|
||||||
|
type nonceMD5 struct {
|
||||||
|
seed [md5.Size]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nonceMD5) Init() { /*nothing required*/ }
|
||||||
|
|
||||||
|
func (n *nonceMD5) Fill(nonce []byte) {
|
||||||
|
if n.seed[0] == 0 { // entropy update
|
||||||
|
io.ReadFull(rand.Reader, n.seed[:])
|
||||||
|
}
|
||||||
|
n.seed = md5.Sum(n.seed[:])
|
||||||
|
copy(nonce, n.seed[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// nonceAES128 nonce generator for packet headers
|
||||||
|
type nonceAES128 struct {
|
||||||
|
seed [aes.BlockSize]byte
|
||||||
|
block cipher.Block
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nonceAES128) Init() {
|
||||||
|
var key [16]byte //aes-128
|
||||||
|
io.ReadFull(rand.Reader, key[:])
|
||||||
|
io.ReadFull(rand.Reader, n.seed[:])
|
||||||
|
block, _ := aes.NewCipher(key[:])
|
||||||
|
n.block = block
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nonceAES128) Fill(nonce []byte) {
|
||||||
|
if n.seed[0] == 0 { // entropy update
|
||||||
|
io.ReadFull(rand.Reader, n.seed[:])
|
||||||
|
}
|
||||||
|
n.block.Encrypt(n.seed[:], n.seed[:])
|
||||||
|
copy(nonce, n.seed[:])
|
||||||
|
}
|
||||||
88
vendor/github.com/fatedier/kcp-go/fec.go
generated
vendored
88
vendor/github.com/fatedier/kcp-go/fec.go
generated
vendored
@@ -4,7 +4,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/templexxx/reedsolomon"
|
"github.com/klauspost/reedsolomon"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -34,6 +34,9 @@ type (
|
|||||||
decodeCache [][]byte
|
decodeCache [][]byte
|
||||||
flagCache []bool
|
flagCache []bool
|
||||||
|
|
||||||
|
// zeros
|
||||||
|
zeros []byte
|
||||||
|
|
||||||
// RS decoder
|
// RS decoder
|
||||||
codec reedsolomon.Encoder
|
codec reedsolomon.Encoder
|
||||||
}
|
}
|
||||||
@@ -47,19 +50,20 @@ func newFECDecoder(rxlimit, dataShards, parityShards int) *fecDecoder {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fec := new(fecDecoder)
|
dec := new(fecDecoder)
|
||||||
fec.rxlimit = rxlimit
|
dec.rxlimit = rxlimit
|
||||||
fec.dataShards = dataShards
|
dec.dataShards = dataShards
|
||||||
fec.parityShards = parityShards
|
dec.parityShards = parityShards
|
||||||
fec.shardSize = dataShards + parityShards
|
dec.shardSize = dataShards + parityShards
|
||||||
enc, err := reedsolomon.New(dataShards, parityShards)
|
codec, err := reedsolomon.New(dataShards, parityShards)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
fec.codec = enc
|
dec.codec = codec
|
||||||
fec.decodeCache = make([][]byte, fec.shardSize)
|
dec.decodeCache = make([][]byte, dec.shardSize)
|
||||||
fec.flagCache = make([]bool, fec.shardSize)
|
dec.flagCache = make([]bool, dec.shardSize)
|
||||||
return fec
|
dec.zeros = make([]byte, mtuLimit)
|
||||||
|
return dec
|
||||||
}
|
}
|
||||||
|
|
||||||
// decodeBytes a fec packet
|
// decodeBytes a fec packet
|
||||||
@@ -116,7 +120,7 @@ func (dec *fecDecoder) decode(pkt fecPacket) (recovered [][]byte) {
|
|||||||
if searchEnd-searchBegin+1 >= dec.dataShards {
|
if searchEnd-searchBegin+1 >= dec.dataShards {
|
||||||
var numshard, numDataShard, first, maxlen int
|
var numshard, numDataShard, first, maxlen int
|
||||||
|
|
||||||
// zero cache
|
// zero caches
|
||||||
shards := dec.decodeCache
|
shards := dec.decodeCache
|
||||||
shardsflag := dec.flagCache
|
shardsflag := dec.flagCache
|
||||||
for k := range dec.decodeCache {
|
for k := range dec.decodeCache {
|
||||||
@@ -146,15 +150,15 @@ func (dec *fecDecoder) decode(pkt fecPacket) (recovered [][]byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if numDataShard == dec.dataShards {
|
if numDataShard == dec.dataShards {
|
||||||
// case 1: no lost data shards
|
// case 1: no loss on data shards
|
||||||
dec.rx = dec.freeRange(first, numshard, dec.rx)
|
dec.rx = dec.freeRange(first, numshard, dec.rx)
|
||||||
} else if numshard >= dec.dataShards {
|
} else if numshard >= dec.dataShards {
|
||||||
// case 2: data shard lost, but recoverable from parity shard
|
// case 2: loss on data shards, but it's recoverable from parity shards
|
||||||
for k := range shards {
|
for k := range shards {
|
||||||
if shards[k] != nil {
|
if shards[k] != nil {
|
||||||
dlen := len(shards[k])
|
dlen := len(shards[k])
|
||||||
shards[k] = shards[k][:maxlen]
|
shards[k] = shards[k][:maxlen]
|
||||||
xorBytes(shards[k][dlen:], shards[k][dlen:], shards[k][dlen:])
|
copy(shards[k][dlen:], dec.zeros)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := dec.codec.ReconstructData(shards); err == nil {
|
if err := dec.codec.ReconstructData(shards); err == nil {
|
||||||
@@ -170,7 +174,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 { // record 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)
|
||||||
@@ -180,7 +184,7 @@ func (dec *fecDecoder) decode(pkt fecPacket) (recovered [][]byte) {
|
|||||||
|
|
||||||
// free a range of fecPacket, and zero for GC recycling
|
// free a range of fecPacket, and zero for GC recycling
|
||||||
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++ { // free
|
for i := first; i < first+n; i++ { // recycle buffer
|
||||||
xmitBuf.Put(q[i].data)
|
xmitBuf.Put(q[i].data)
|
||||||
}
|
}
|
||||||
copy(q[first:], q[first+n:])
|
copy(q[first:], q[first+n:])
|
||||||
@@ -200,7 +204,7 @@ type (
|
|||||||
next uint32 // next seqid
|
next uint32 // next seqid
|
||||||
|
|
||||||
shardCount int // count the number of datashards collected
|
shardCount int // count the number of datashards collected
|
||||||
maxSize int // record maximum data length in datashard
|
maxSize int // track maximum data length in datashard
|
||||||
|
|
||||||
headerOffset int // FEC header offset
|
headerOffset int // FEC header offset
|
||||||
payloadOffset int // FEC payload offset
|
payloadOffset int // FEC payload offset
|
||||||
@@ -209,6 +213,9 @@ type (
|
|||||||
shardCache [][]byte
|
shardCache [][]byte
|
||||||
encodeCache [][]byte
|
encodeCache [][]byte
|
||||||
|
|
||||||
|
// zeros
|
||||||
|
zeros []byte
|
||||||
|
|
||||||
// RS encoder
|
// RS encoder
|
||||||
codec reedsolomon.Encoder
|
codec reedsolomon.Encoder
|
||||||
}
|
}
|
||||||
@@ -218,31 +225,32 @@ func newFECEncoder(dataShards, parityShards, offset int) *fecEncoder {
|
|||||||
if dataShards <= 0 || parityShards <= 0 {
|
if dataShards <= 0 || parityShards <= 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
fec := new(fecEncoder)
|
enc := new(fecEncoder)
|
||||||
fec.dataShards = dataShards
|
enc.dataShards = dataShards
|
||||||
fec.parityShards = parityShards
|
enc.parityShards = parityShards
|
||||||
fec.shardSize = dataShards + parityShards
|
enc.shardSize = dataShards + parityShards
|
||||||
fec.paws = (0xffffffff/uint32(fec.shardSize) - 1) * uint32(fec.shardSize)
|
enc.paws = (0xffffffff/uint32(enc.shardSize) - 1) * uint32(enc.shardSize)
|
||||||
fec.headerOffset = offset
|
enc.headerOffset = offset
|
||||||
fec.payloadOffset = fec.headerOffset + fecHeaderSize
|
enc.payloadOffset = enc.headerOffset + fecHeaderSize
|
||||||
|
|
||||||
enc, err := reedsolomon.New(dataShards, parityShards)
|
codec, err := reedsolomon.New(dataShards, parityShards)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
fec.codec = enc
|
enc.codec = codec
|
||||||
|
|
||||||
// caches
|
// caches
|
||||||
fec.encodeCache = make([][]byte, fec.shardSize)
|
enc.encodeCache = make([][]byte, enc.shardSize)
|
||||||
fec.shardCache = make([][]byte, fec.shardSize)
|
enc.shardCache = make([][]byte, enc.shardSize)
|
||||||
for k := range fec.shardCache {
|
for k := range enc.shardCache {
|
||||||
fec.shardCache[k] = make([]byte, mtuLimit)
|
enc.shardCache[k] = make([]byte, mtuLimit)
|
||||||
}
|
}
|
||||||
return fec
|
enc.zeros = make([]byte, mtuLimit)
|
||||||
|
return enc
|
||||||
}
|
}
|
||||||
|
|
||||||
// encode the packet, output parity shards if we have enough datashards
|
// encodes the packet, outputs parity shards if we have collected quorum datashards
|
||||||
// the content of returned parityshards will change in next encode
|
// 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) {
|
||||||
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:])))
|
||||||
@@ -253,18 +261,18 @@ func (enc *fecEncoder) encode(b []byte) (ps [][]byte) {
|
|||||||
copy(enc.shardCache[enc.shardCount], b)
|
copy(enc.shardCache[enc.shardCount], b)
|
||||||
enc.shardCount++
|
enc.shardCount++
|
||||||
|
|
||||||
// record max datashard length
|
// track max datashard length
|
||||||
if sz > enc.maxSize {
|
if sz > enc.maxSize {
|
||||||
enc.maxSize = sz
|
enc.maxSize = sz
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate Reed-Solomon Erasure Code
|
// Generation of Reed-Solomon Erasure Code
|
||||||
if enc.shardCount == enc.dataShards {
|
if enc.shardCount == enc.dataShards {
|
||||||
// bzero each datashard's tail
|
// fill '0' into the tail of each datashard
|
||||||
for i := 0; i < enc.dataShards; i++ {
|
for i := 0; i < enc.dataShards; i++ {
|
||||||
shard := enc.shardCache[i]
|
shard := enc.shardCache[i]
|
||||||
slen := len(shard)
|
slen := len(shard)
|
||||||
xorBytes(shard[slen:enc.maxSize], shard[slen:enc.maxSize], shard[slen:enc.maxSize])
|
copy(shard[slen:enc.maxSize], enc.zeros)
|
||||||
}
|
}
|
||||||
|
|
||||||
// construct equal-sized slice with stripped header
|
// construct equal-sized slice with stripped header
|
||||||
@@ -273,7 +281,7 @@ func (enc *fecEncoder) encode(b []byte) (ps [][]byte) {
|
|||||||
cache[k] = enc.shardCache[k][enc.payloadOffset:enc.maxSize]
|
cache[k] = enc.shardCache[k][enc.payloadOffset:enc.maxSize]
|
||||||
}
|
}
|
||||||
|
|
||||||
// rs encode
|
// encoding
|
||||||
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 {
|
||||||
@@ -282,7 +290,7 @@ func (enc *fecEncoder) encode(b []byte) (ps [][]byte) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset counters to zero
|
// counters resetting
|
||||||
enc.shardCount = 0
|
enc.shardCount = 0
|
||||||
enc.maxSize = 0
|
enc.maxSize = 0
|
||||||
}
|
}
|
||||||
|
|||||||
186
vendor/github.com/fatedier/kcp-go/kcp.go
generated
vendored
186
vendor/github.com/fatedier/kcp-go/kcp.go
generated
vendored
@@ -104,6 +104,7 @@ type segment struct {
|
|||||||
xmit uint32
|
xmit uint32
|
||||||
resendts uint32
|
resendts uint32
|
||||||
fastack uint32
|
fastack uint32
|
||||||
|
acked uint32 // mark if the seg has acked
|
||||||
data []byte
|
data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,8 +182,11 @@ func (kcp *KCP) newSegment(size int) (seg segment) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// delSegment recycles a KCP segment
|
// delSegment recycles a KCP segment
|
||||||
func (kcp *KCP) delSegment(seg segment) {
|
func (kcp *KCP) delSegment(seg *segment) {
|
||||||
xmitBuf.Put(seg.data)
|
if seg.data != nil {
|
||||||
|
xmitBuf.Put(seg.data)
|
||||||
|
seg.data = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PeekSize checks the size of next message in the recv queue
|
// PeekSize checks the size of next message in the recv queue
|
||||||
@@ -238,7 +242,7 @@ func (kcp *KCP) Recv(buffer []byte) (n int) {
|
|||||||
buffer = buffer[len(seg.data):]
|
buffer = buffer[len(seg.data):]
|
||||||
n += len(seg.data)
|
n += len(seg.data)
|
||||||
count++
|
count++
|
||||||
kcp.delSegment(*seg)
|
kcp.delSegment(seg)
|
||||||
if seg.frg == 0 {
|
if seg.frg == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -382,10 +386,8 @@ 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 {
|
||||||
kcp.delSegment(*seg)
|
seg.acked = 1
|
||||||
copy(kcp.snd_buf[k:], kcp.snd_buf[k+1:])
|
kcp.delSegment(seg)
|
||||||
kcp.snd_buf[len(kcp.snd_buf)-1] = segment{}
|
|
||||||
kcp.snd_buf = kcp.snd_buf[:len(kcp.snd_buf)-1]
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if _itimediff(sn, seg.sn) < 0 {
|
if _itimediff(sn, seg.sn) < 0 {
|
||||||
@@ -394,7 +396,7 @@ func (kcp *KCP) parse_ack(sn uint32) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (kcp *KCP) parse_fastack(sn uint32) {
|
func (kcp *KCP) parse_fastack(sn, ts uint32) {
|
||||||
if _itimediff(sn, kcp.snd_una) < 0 || _itimediff(sn, kcp.snd_nxt) >= 0 {
|
if _itimediff(sn, kcp.snd_una) < 0 || _itimediff(sn, kcp.snd_nxt) >= 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -403,7 +405,7 @@ func (kcp *KCP) parse_fastack(sn uint32) {
|
|||||||
seg := &kcp.snd_buf[k]
|
seg := &kcp.snd_buf[k]
|
||||||
if _itimediff(sn, seg.sn) < 0 {
|
if _itimediff(sn, seg.sn) < 0 {
|
||||||
break
|
break
|
||||||
} else if sn != seg.sn {
|
} else if sn != seg.sn && _itimediff(seg.ts, ts) <= 0 {
|
||||||
seg.fastack++
|
seg.fastack++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -414,7 +416,7 @@ func (kcp *KCP) parse_una(una uint32) {
|
|||||||
for k := range kcp.snd_buf {
|
for k := range kcp.snd_buf {
|
||||||
seg := &kcp.snd_buf[k]
|
seg := &kcp.snd_buf[k]
|
||||||
if _itimediff(una, seg.sn) > 0 {
|
if _itimediff(una, seg.sn) > 0 {
|
||||||
kcp.delSegment(*seg)
|
kcp.delSegment(seg)
|
||||||
count++
|
count++
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
@@ -430,12 +432,12 @@ func (kcp *KCP) ack_push(sn, ts uint32) {
|
|||||||
kcp.acklist = append(kcp.acklist, ackItem{sn, ts})
|
kcp.acklist = append(kcp.acklist, ackItem{sn, ts})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (kcp *KCP) parse_data(newseg segment) {
|
// returns true if data has repeated
|
||||||
|
func (kcp *KCP) parse_data(newseg segment) bool {
|
||||||
sn := newseg.sn
|
sn := newseg.sn
|
||||||
if _itimediff(sn, kcp.rcv_nxt+kcp.rcv_wnd) >= 0 ||
|
if _itimediff(sn, kcp.rcv_nxt+kcp.rcv_wnd) >= 0 ||
|
||||||
_itimediff(sn, kcp.rcv_nxt) < 0 {
|
_itimediff(sn, kcp.rcv_nxt) < 0 {
|
||||||
kcp.delSegment(newseg)
|
return true
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
n := len(kcp.rcv_buf) - 1
|
n := len(kcp.rcv_buf) - 1
|
||||||
@@ -445,7 +447,6 @@ func (kcp *KCP) parse_data(newseg segment) {
|
|||||||
seg := &kcp.rcv_buf[i]
|
seg := &kcp.rcv_buf[i]
|
||||||
if seg.sn == sn {
|
if seg.sn == sn {
|
||||||
repeat = true
|
repeat = true
|
||||||
atomic.AddUint64(&DefaultSnmp.RepeatSegs, 1)
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if _itimediff(sn, seg.sn) > 0 {
|
if _itimediff(sn, seg.sn) > 0 {
|
||||||
@@ -455,6 +456,11 @@ func (kcp *KCP) parse_data(newseg segment) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !repeat {
|
if !repeat {
|
||||||
|
// replicate the content if it's new
|
||||||
|
dataCopy := xmitBuf.Get().([]byte)[:len(newseg.data)]
|
||||||
|
copy(dataCopy, newseg.data)
|
||||||
|
newseg.data = dataCopy
|
||||||
|
|
||||||
if insert_idx == n+1 {
|
if insert_idx == n+1 {
|
||||||
kcp.rcv_buf = append(kcp.rcv_buf, newseg)
|
kcp.rcv_buf = append(kcp.rcv_buf, newseg)
|
||||||
} else {
|
} else {
|
||||||
@@ -462,8 +468,6 @@ func (kcp *KCP) parse_data(newseg segment) {
|
|||||||
copy(kcp.rcv_buf[insert_idx+1:], kcp.rcv_buf[insert_idx:])
|
copy(kcp.rcv_buf[insert_idx+1:], kcp.rcv_buf[insert_idx:])
|
||||||
kcp.rcv_buf[insert_idx] = newseg
|
kcp.rcv_buf[insert_idx] = newseg
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
kcp.delSegment(newseg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// move available data from rcv_buf -> rcv_queue
|
// move available data from rcv_buf -> rcv_queue
|
||||||
@@ -481,18 +485,19 @@ func (kcp *KCP) parse_data(newseg segment) {
|
|||||||
kcp.rcv_queue = append(kcp.rcv_queue, kcp.rcv_buf[:count]...)
|
kcp.rcv_queue = append(kcp.rcv_queue, kcp.rcv_buf[:count]...)
|
||||||
kcp.rcv_buf = kcp.remove_front(kcp.rcv_buf, count)
|
kcp.rcv_buf = kcp.remove_front(kcp.rcv_buf, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return repeat
|
||||||
}
|
}
|
||||||
|
|
||||||
// Input when you received a low level packet (eg. UDP packet), call it
|
// Input when you received a low level packet (eg. UDP packet), call it
|
||||||
// regular indicates a regular packet has received(not from FEC)
|
// regular indicates a regular packet has received(not from FEC)
|
||||||
func (kcp *KCP) Input(data []byte, regular, ackNoDelay bool) int {
|
func (kcp *KCP) Input(data []byte, regular, ackNoDelay bool) int {
|
||||||
una := kcp.snd_una
|
snd_una := kcp.snd_una
|
||||||
if len(data) < IKCP_OVERHEAD {
|
if len(data) < IKCP_OVERHEAD {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
var maxack uint32
|
var latest uint32 // the latest ack packet
|
||||||
var lastackts uint32
|
|
||||||
var flag int
|
var flag int
|
||||||
var inSegs uint64
|
var inSegs uint64
|
||||||
|
|
||||||
@@ -535,19 +540,15 @@ func (kcp *KCP) Input(data []byte, regular, ackNoDelay bool) int {
|
|||||||
|
|
||||||
if cmd == IKCP_CMD_ACK {
|
if cmd == IKCP_CMD_ACK {
|
||||||
kcp.parse_ack(sn)
|
kcp.parse_ack(sn)
|
||||||
kcp.shrink_buf()
|
kcp.parse_fastack(sn, ts)
|
||||||
if flag == 0 {
|
flag |= 1
|
||||||
flag = 1
|
latest = ts
|
||||||
maxack = sn
|
|
||||||
} else if _itimediff(sn, maxack) > 0 {
|
|
||||||
maxack = sn
|
|
||||||
}
|
|
||||||
lastackts = ts
|
|
||||||
} else if cmd == IKCP_CMD_PUSH {
|
} else if cmd == IKCP_CMD_PUSH {
|
||||||
|
repeat := true
|
||||||
if _itimediff(sn, kcp.rcv_nxt+kcp.rcv_wnd) < 0 {
|
if _itimediff(sn, kcp.rcv_nxt+kcp.rcv_wnd) < 0 {
|
||||||
kcp.ack_push(sn, ts)
|
kcp.ack_push(sn, ts)
|
||||||
if _itimediff(sn, kcp.rcv_nxt) >= 0 {
|
if _itimediff(sn, kcp.rcv_nxt) >= 0 {
|
||||||
seg := kcp.newSegment(int(length))
|
var seg segment
|
||||||
seg.conv = conv
|
seg.conv = conv
|
||||||
seg.cmd = cmd
|
seg.cmd = cmd
|
||||||
seg.frg = frg
|
seg.frg = frg
|
||||||
@@ -555,12 +556,11 @@ func (kcp *KCP) Input(data []byte, regular, ackNoDelay bool) int {
|
|||||||
seg.ts = ts
|
seg.ts = ts
|
||||||
seg.sn = sn
|
seg.sn = sn
|
||||||
seg.una = una
|
seg.una = una
|
||||||
copy(seg.data, data[:length])
|
seg.data = data[:length] // delayed data copying
|
||||||
kcp.parse_data(seg)
|
repeat = kcp.parse_data(seg)
|
||||||
} else {
|
|
||||||
atomic.AddUint64(&DefaultSnmp.RepeatSegs, 1)
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
if regular && repeat {
|
||||||
atomic.AddUint64(&DefaultSnmp.RepeatSegs, 1)
|
atomic.AddUint64(&DefaultSnmp.RepeatSegs, 1)
|
||||||
}
|
}
|
||||||
} else if cmd == IKCP_CMD_WASK {
|
} else if cmd == IKCP_CMD_WASK {
|
||||||
@@ -578,40 +578,42 @@ func (kcp *KCP) Input(data []byte, regular, ackNoDelay bool) int {
|
|||||||
}
|
}
|
||||||
atomic.AddUint64(&DefaultSnmp.InSegs, inSegs)
|
atomic.AddUint64(&DefaultSnmp.InSegs, inSegs)
|
||||||
|
|
||||||
|
// update rtt with the latest ts
|
||||||
|
// ignore the FEC packet
|
||||||
if flag != 0 && regular {
|
if flag != 0 && regular {
|
||||||
kcp.parse_fastack(maxack)
|
|
||||||
current := currentMs()
|
current := currentMs()
|
||||||
if _itimediff(current, lastackts) >= 0 {
|
if _itimediff(current, latest) >= 0 {
|
||||||
kcp.update_ack(_itimediff(current, lastackts))
|
kcp.update_ack(_itimediff(current, latest))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _itimediff(kcp.snd_una, una) > 0 {
|
// cwnd update when packet arrived
|
||||||
if kcp.cwnd < kcp.rmt_wnd {
|
if kcp.nocwnd == 0 {
|
||||||
mss := kcp.mss
|
if _itimediff(kcp.snd_una, snd_una) > 0 {
|
||||||
if kcp.cwnd < kcp.ssthresh {
|
if kcp.cwnd < kcp.rmt_wnd {
|
||||||
kcp.cwnd++
|
mss := kcp.mss
|
||||||
kcp.incr += mss
|
if kcp.cwnd < kcp.ssthresh {
|
||||||
} else {
|
|
||||||
if kcp.incr < mss {
|
|
||||||
kcp.incr = mss
|
|
||||||
}
|
|
||||||
kcp.incr += (mss*mss)/kcp.incr + (mss / 16)
|
|
||||||
if (kcp.cwnd+1)*mss <= kcp.incr {
|
|
||||||
kcp.cwnd++
|
kcp.cwnd++
|
||||||
|
kcp.incr += mss
|
||||||
|
} else {
|
||||||
|
if kcp.incr < mss {
|
||||||
|
kcp.incr = mss
|
||||||
|
}
|
||||||
|
kcp.incr += (mss*mss)/kcp.incr + (mss / 16)
|
||||||
|
if (kcp.cwnd+1)*mss <= kcp.incr {
|
||||||
|
kcp.cwnd++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if kcp.cwnd > kcp.rmt_wnd {
|
||||||
|
kcp.cwnd = kcp.rmt_wnd
|
||||||
|
kcp.incr = kcp.rmt_wnd * mss
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if kcp.cwnd > kcp.rmt_wnd {
|
|
||||||
kcp.cwnd = kcp.rmt_wnd
|
|
||||||
kcp.incr = kcp.rmt_wnd * mss
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ackNoDelay && len(kcp.acklist) > 0 { // ack immediately
|
if ackNoDelay && len(kcp.acklist) > 0 { // ack immediately
|
||||||
kcp.flush(true)
|
kcp.flush(true)
|
||||||
} else if kcp.rmt_wnd == 0 && len(kcp.acklist) > 0 { // window zero
|
|
||||||
kcp.flush(true)
|
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -624,7 +626,7 @@ func (kcp *KCP) wnd_unused() uint16 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// flush pending data
|
// flush pending data
|
||||||
func (kcp *KCP) flush(ackOnly bool) {
|
func (kcp *KCP) flush(ackOnly bool) uint32 {
|
||||||
var seg segment
|
var seg segment
|
||||||
seg.conv = kcp.conv
|
seg.conv = kcp.conv
|
||||||
seg.cmd = IKCP_CMD_ACK
|
seg.cmd = IKCP_CMD_ACK
|
||||||
@@ -653,7 +655,7 @@ func (kcp *KCP) flush(ackOnly bool) {
|
|||||||
if size > 0 {
|
if size > 0 {
|
||||||
kcp.output(buffer, size)
|
kcp.output(buffer, size)
|
||||||
}
|
}
|
||||||
return
|
return kcp.interval
|
||||||
}
|
}
|
||||||
|
|
||||||
// probe window size (if remote window size equals zero)
|
// probe window size (if remote window size equals zero)
|
||||||
@@ -723,7 +725,6 @@ func (kcp *KCP) flush(ackOnly bool) {
|
|||||||
kcp.snd_buf = append(kcp.snd_buf, newseg)
|
kcp.snd_buf = append(kcp.snd_buf, newseg)
|
||||||
kcp.snd_nxt++
|
kcp.snd_nxt++
|
||||||
newSegsCount++
|
newSegsCount++
|
||||||
kcp.snd_queue[k].data = nil
|
|
||||||
}
|
}
|
||||||
if newSegsCount > 0 {
|
if newSegsCount > 0 {
|
||||||
kcp.snd_queue = kcp.remove_front(kcp.snd_queue, newSegsCount)
|
kcp.snd_queue = kcp.remove_front(kcp.snd_queue, newSegsCount)
|
||||||
@@ -738,9 +739,15 @@ func (kcp *KCP) flush(ackOnly bool) {
|
|||||||
// check for retransmissions
|
// check for retransmissions
|
||||||
current := currentMs()
|
current := currentMs()
|
||||||
var change, lost, lostSegs, fastRetransSegs, earlyRetransSegs uint64
|
var change, lost, lostSegs, fastRetransSegs, earlyRetransSegs uint64
|
||||||
for k := range kcp.snd_buf {
|
minrto := int32(kcp.interval)
|
||||||
segment := &kcp.snd_buf[k]
|
|
||||||
|
ref := kcp.snd_buf[:len(kcp.snd_buf)] // for bounds check elimination
|
||||||
|
for k := range ref {
|
||||||
|
segment := &ref[k]
|
||||||
needsend := false
|
needsend := false
|
||||||
|
if segment.acked == 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if segment.xmit == 0 { // initial transmit
|
if segment.xmit == 0 { // initial transmit
|
||||||
needsend = true
|
needsend = true
|
||||||
segment.rto = kcp.rx_rto
|
segment.rto = kcp.rx_rto
|
||||||
@@ -772,6 +779,7 @@ func (kcp *KCP) flush(ackOnly bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if needsend {
|
if needsend {
|
||||||
|
current = currentMs() // time update for a blocking call
|
||||||
segment.xmit++
|
segment.xmit++
|
||||||
segment.ts = current
|
segment.ts = current
|
||||||
segment.wnd = seg.wnd
|
segment.wnd = seg.wnd
|
||||||
@@ -782,7 +790,6 @@ func (kcp *KCP) flush(ackOnly bool) {
|
|||||||
|
|
||||||
if size+need > int(kcp.mtu) {
|
if size+need > int(kcp.mtu) {
|
||||||
kcp.output(buffer, size)
|
kcp.output(buffer, size)
|
||||||
current = currentMs() // time update for a blocking call
|
|
||||||
ptr = buffer
|
ptr = buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -794,6 +801,11 @@ func (kcp *KCP) flush(ackOnly bool) {
|
|||||||
kcp.state = 0xFFFFFFFF
|
kcp.state = 0xFFFFFFFF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get the nearest rto
|
||||||
|
if rto := _itimediff(segment.resendts, current); rto > 0 && rto < minrto {
|
||||||
|
minrto = rto
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// flash remain segments
|
// flash remain segments
|
||||||
@@ -819,32 +831,37 @@ func (kcp *KCP) flush(ackOnly bool) {
|
|||||||
atomic.AddUint64(&DefaultSnmp.RetransSegs, sum)
|
atomic.AddUint64(&DefaultSnmp.RetransSegs, sum)
|
||||||
}
|
}
|
||||||
|
|
||||||
// update ssthresh
|
// cwnd update
|
||||||
// rate halving, https://tools.ietf.org/html/rfc6937
|
if kcp.nocwnd == 0 {
|
||||||
if change > 0 {
|
// update ssthresh
|
||||||
inflight := kcp.snd_nxt - kcp.snd_una
|
// rate halving, https://tools.ietf.org/html/rfc6937
|
||||||
kcp.ssthresh = inflight / 2
|
if change > 0 {
|
||||||
if kcp.ssthresh < IKCP_THRESH_MIN {
|
inflight := kcp.snd_nxt - kcp.snd_una
|
||||||
kcp.ssthresh = IKCP_THRESH_MIN
|
kcp.ssthresh = inflight / 2
|
||||||
|
if kcp.ssthresh < IKCP_THRESH_MIN {
|
||||||
|
kcp.ssthresh = IKCP_THRESH_MIN
|
||||||
|
}
|
||||||
|
kcp.cwnd = kcp.ssthresh + resent
|
||||||
|
kcp.incr = kcp.cwnd * kcp.mss
|
||||||
|
}
|
||||||
|
|
||||||
|
// congestion control, https://tools.ietf.org/html/rfc5681
|
||||||
|
if lost > 0 {
|
||||||
|
kcp.ssthresh = cwnd / 2
|
||||||
|
if kcp.ssthresh < IKCP_THRESH_MIN {
|
||||||
|
kcp.ssthresh = IKCP_THRESH_MIN
|
||||||
|
}
|
||||||
|
kcp.cwnd = 1
|
||||||
|
kcp.incr = kcp.mss
|
||||||
|
}
|
||||||
|
|
||||||
|
if kcp.cwnd < 1 {
|
||||||
|
kcp.cwnd = 1
|
||||||
|
kcp.incr = kcp.mss
|
||||||
}
|
}
|
||||||
kcp.cwnd = kcp.ssthresh + resent
|
|
||||||
kcp.incr = kcp.cwnd * kcp.mss
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// congestion control, https://tools.ietf.org/html/rfc5681
|
return uint32(minrto)
|
||||||
if lost > 0 {
|
|
||||||
kcp.ssthresh = cwnd / 2
|
|
||||||
if kcp.ssthresh < IKCP_THRESH_MIN {
|
|
||||||
kcp.ssthresh = IKCP_THRESH_MIN
|
|
||||||
}
|
|
||||||
kcp.cwnd = 1
|
|
||||||
kcp.incr = kcp.mss
|
|
||||||
}
|
|
||||||
|
|
||||||
if kcp.cwnd < 1 {
|
|
||||||
kcp.cwnd = 1
|
|
||||||
kcp.incr = kcp.mss
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@@ -991,8 +1008,5 @@ func (kcp *KCP) WaitSnd() int {
|
|||||||
// remove front n elements from queue
|
// remove front n elements from queue
|
||||||
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:])
|
newn := copy(q, q[n:])
|
||||||
for i := newn; i < len(q); i++ {
|
|
||||||
q[i] = segment{} // manual set nil for GC
|
|
||||||
}
|
|
||||||
return q[:newn]
|
return q[:newn]
|
||||||
}
|
}
|
||||||
|
|||||||
472
vendor/github.com/fatedier/kcp-go/sess.go
generated
vendored
472
vendor/github.com/fatedier/kcp-go/sess.go
generated
vendored
@@ -4,7 +4,6 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"hash/crc32"
|
"hash/crc32"
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -12,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/net/ipv4"
|
"golang.org/x/net/ipv4"
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
)
|
)
|
||||||
|
|
||||||
type errTimeout struct {
|
type errTimeout struct {
|
||||||
@@ -23,7 +23,7 @@ func (errTimeout) Temporary() bool { return true }
|
|||||||
func (errTimeout) Error() string { return "i/o timeout" }
|
func (errTimeout) Error() string { return "i/o timeout" }
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// 16-bytes magic number for each packet
|
// 16-bytes nonce for each packet
|
||||||
nonceSize = 16
|
nonceSize = 16
|
||||||
|
|
||||||
// 4-bytes packet checksum
|
// 4-bytes packet checksum
|
||||||
@@ -40,9 +40,6 @@ const (
|
|||||||
|
|
||||||
// accept backlog
|
// accept backlog
|
||||||
acceptBacklog = 128
|
acceptBacklog = 128
|
||||||
|
|
||||||
// prerouting(to session) queue
|
|
||||||
qlen = 128
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -51,8 +48,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// global packet buffer
|
// a system-wide packet buffer shared among sending, receiving and FEC
|
||||||
// shared among sending/receiving/FEC
|
// to mitigate high-frequency memory allocation for packets
|
||||||
xmitBuf sync.Pool
|
xmitBuf sync.Pool
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -68,17 +65,17 @@ type (
|
|||||||
updaterIdx int // record slice index in updater
|
updaterIdx int // record slice index in updater
|
||||||
conn net.PacketConn // the underlying packet connection
|
conn net.PacketConn // the underlying packet connection
|
||||||
kcp *KCP // KCP ARQ protocol
|
kcp *KCP // KCP ARQ protocol
|
||||||
l *Listener // point to the Listener if it's accepted by Listener
|
l *Listener // pointing to the Listener object if it's been accepted by a Listener
|
||||||
block BlockCrypt // block encryption
|
block BlockCrypt // block encryption object
|
||||||
|
|
||||||
// kcp receiving is based on packets
|
// kcp receiving is based on packets
|
||||||
// recvbuf turns packets into stream
|
// recvbuf turns packets into stream
|
||||||
recvbuf []byte
|
recvbuf []byte
|
||||||
bufptr []byte
|
bufptr []byte
|
||||||
// extended output buffer(with header)
|
// header extended output buffer, if has header
|
||||||
ext []byte
|
ext []byte
|
||||||
|
|
||||||
// FEC
|
// FEC codec
|
||||||
fecDecoder *fecDecoder
|
fecDecoder *fecDecoder
|
||||||
fecEncoder *fecEncoder
|
fecEncoder *fecEncoder
|
||||||
|
|
||||||
@@ -86,16 +83,20 @@ type (
|
|||||||
remote net.Addr // remote peer address
|
remote net.Addr // remote peer address
|
||||||
rd time.Time // read deadline
|
rd time.Time // read deadline
|
||||||
wd time.Time // write deadline
|
wd time.Time // write deadline
|
||||||
headerSize int // the overall header size added before KCP frame
|
headerSize int // the header size additional to a KCP frame
|
||||||
ackNoDelay bool // send ack immediately for each incoming packet
|
ackNoDelay bool // send ack immediately for each incoming packet(testing purpose)
|
||||||
writeDelay bool // delay kcp.flush() for Write() for bulk transfer
|
writeDelay bool // delay kcp.flush() for Write() for bulk transfer
|
||||||
dup int // duplicate udp packets
|
dup int // duplicate udp packets(testing purpose)
|
||||||
|
|
||||||
// notifications
|
// notifications
|
||||||
die chan struct{} // notify session has Closed
|
die chan struct{} // notify current session has Closed
|
||||||
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
|
||||||
chErrorEvent chan error // notify Read() have an error
|
chReadError chan error // notify PacketConn.Read() have an error
|
||||||
|
chWriteError chan error // notify PacketConn.Write() have an error
|
||||||
|
|
||||||
|
// nonce generator
|
||||||
|
nonce Entropy
|
||||||
|
|
||||||
isClosed bool // flag the session has Closed
|
isClosed bool // flag the session has Closed
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
@@ -114,16 +115,19 @@ type (
|
|||||||
func newUDPSession(conv uint32, dataShards, parityShards int, l *Listener, conn net.PacketConn, remote net.Addr, block BlockCrypt) *UDPSession {
|
func newUDPSession(conv uint32, dataShards, parityShards int, l *Listener, conn net.PacketConn, remote net.Addr, block BlockCrypt) *UDPSession {
|
||||||
sess := new(UDPSession)
|
sess := new(UDPSession)
|
||||||
sess.die = make(chan struct{})
|
sess.die = make(chan struct{})
|
||||||
|
sess.nonce = new(nonceAES128)
|
||||||
|
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.chErrorEvent = make(chan error, 1)
|
sess.chReadError = make(chan error, 1)
|
||||||
|
sess.chWriteError = make(chan error, 1)
|
||||||
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)
|
||||||
|
|
||||||
// FEC 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 {
|
||||||
sess.fecEncoder = newFECEncoder(dataShards, parityShards, cryptHeaderSize)
|
sess.fecEncoder = newFECEncoder(dataShards, parityShards, cryptHeaderSize)
|
||||||
@@ -131,7 +135,7 @@ func newUDPSession(conv uint32, dataShards, parityShards int, l *Listener, conn
|
|||||||
sess.fecEncoder = newFECEncoder(dataShards, parityShards, 0)
|
sess.fecEncoder = newFECEncoder(dataShards, parityShards, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate header size
|
// calculate additional header size introduced by FEC and encryption
|
||||||
if sess.block != nil {
|
if sess.block != nil {
|
||||||
sess.headerSize += cryptHeaderSize
|
sess.headerSize += cryptHeaderSize
|
||||||
}
|
}
|
||||||
@@ -139,8 +143,7 @@ func newUDPSession(conv uint32, dataShards, parityShards int, l *Listener, conn
|
|||||||
sess.headerSize += fecHeaderSizePlus2
|
sess.headerSize += fecHeaderSizePlus2
|
||||||
}
|
}
|
||||||
|
|
||||||
// only allocate extended packet buffer
|
// we only need to allocate extended packet buffer if we have the additional header
|
||||||
// when the extra header is required
|
|
||||||
if sess.headerSize > 0 {
|
if sess.headerSize > 0 {
|
||||||
sess.ext = make([]byte, mtuLimit)
|
sess.ext = make([]byte, mtuLimit)
|
||||||
}
|
}
|
||||||
@@ -152,8 +155,8 @@ func newUDPSession(conv uint32, dataShards, parityShards int, l *Listener, conn
|
|||||||
})
|
})
|
||||||
sess.kcp.SetMtu(IKCP_MTU_DEF - sess.headerSize)
|
sess.kcp.SetMtu(IKCP_MTU_DEF - sess.headerSize)
|
||||||
|
|
||||||
// add current session to the global updater,
|
// register current session to the global updater,
|
||||||
// which periodically calls sess.update()
|
// which call sess.update() periodically.
|
||||||
updater.addSession(sess)
|
updater.addSession(sess)
|
||||||
|
|
||||||
if sess.l == nil { // it's a client connection
|
if sess.l == nil { // it's a client connection
|
||||||
@@ -179,6 +182,7 @@ func (s *UDPSession) Read(b []byte) (n int, err error) {
|
|||||||
n = copy(b, s.bufptr)
|
n = copy(b, s.bufptr)
|
||||||
s.bufptr = s.bufptr[n:]
|
s.bufptr = s.bufptr[n:]
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
atomic.AddUint64(&DefaultSnmp.BytesReceived, uint64(n))
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,29 +192,29 @@ func (s *UDPSession) Read(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if size := s.kcp.PeekSize(); size > 0 { // peek data size from kcp
|
if size := s.kcp.PeekSize(); size > 0 { // peek data size from kcp
|
||||||
atomic.AddUint64(&DefaultSnmp.BytesReceived, uint64(size))
|
if len(b) >= size { // receive data into 'b' directly
|
||||||
if len(b) >= size { // direct write to b
|
|
||||||
s.kcp.Recv(b)
|
s.kcp.Recv(b)
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
atomic.AddUint64(&DefaultSnmp.BytesReceived, uint64(size))
|
||||||
return size, nil
|
return size, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// resize kcp receive buffer
|
// if necessary resize the stream buffer to guarantee a sufficent buffer space
|
||||||
// to make sure recvbuf has enough capacity
|
|
||||||
if cap(s.recvbuf) < size {
|
if cap(s.recvbuf) < size {
|
||||||
s.recvbuf = make([]byte, size)
|
s.recvbuf = make([]byte, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
// resize recvbuf slice length
|
// resize the length of recvbuf to correspond to data size
|
||||||
s.recvbuf = s.recvbuf[:size]
|
s.recvbuf = s.recvbuf[:size]
|
||||||
s.kcp.Recv(s.recvbuf)
|
s.kcp.Recv(s.recvbuf)
|
||||||
n = copy(b, s.recvbuf) // copy to b
|
n = copy(b, s.recvbuf) // copy to 'b'
|
||||||
s.bufptr = s.recvbuf[n:] // update pointer
|
s.bufptr = s.recvbuf[n:] // pointer update
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
atomic.AddUint64(&DefaultSnmp.BytesReceived, uint64(n))
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// read deadline
|
// deadline for current reading operation
|
||||||
var timeout *time.Timer
|
var timeout *time.Timer
|
||||||
var c <-chan time.Time
|
var c <-chan time.Time
|
||||||
if !s.rd.IsZero() {
|
if !s.rd.IsZero() {
|
||||||
@@ -230,7 +234,7 @@ func (s *UDPSession) Read(b []byte) (n int, err error) {
|
|||||||
case <-s.chReadEvent:
|
case <-s.chReadEvent:
|
||||||
case <-c:
|
case <-c:
|
||||||
case <-s.die:
|
case <-s.die:
|
||||||
case err = <-s.chErrorEvent:
|
case err = <-s.chReadError:
|
||||||
if timeout != nil {
|
if timeout != nil {
|
||||||
timeout.Stop()
|
timeout.Stop()
|
||||||
}
|
}
|
||||||
@@ -252,7 +256,8 @@ func (s *UDPSession) Write(b []byte) (n int, err error) {
|
|||||||
return 0, errors.New(errBrokenPipe)
|
return 0, errors.New(errBrokenPipe)
|
||||||
}
|
}
|
||||||
|
|
||||||
// api flow control
|
// controls how much data will be sent to kcp core
|
||||||
|
// 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)
|
n = len(b)
|
||||||
for {
|
for {
|
||||||
@@ -265,7 +270,8 @@ func (s *UDPSession) Write(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !s.writeDelay {
|
// flush immediately if the queue is full
|
||||||
|
if s.kcp.WaitSnd() >= int(s.kcp.snd_wnd) || !s.writeDelay {
|
||||||
s.kcp.flush(false)
|
s.kcp.flush(false)
|
||||||
}
|
}
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
@@ -273,7 +279,7 @@ func (s *UDPSession) Write(b []byte) (n int, err error) {
|
|||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// write deadline
|
// 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() {
|
||||||
@@ -292,6 +298,11 @@ func (s *UDPSession) Write(b []byte) (n int, err error) {
|
|||||||
case <-s.chWriteEvent:
|
case <-s.chWriteEvent:
|
||||||
case <-c:
|
case <-c:
|
||||||
case <-s.die:
|
case <-s.die:
|
||||||
|
case err = <-s.chWriteError:
|
||||||
|
if timeout != nil {
|
||||||
|
timeout.Stop()
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if timeout != nil {
|
if timeout != nil {
|
||||||
@@ -302,13 +313,10 @@ func (s *UDPSession) Write(b []byte) (n int, err error) {
|
|||||||
|
|
||||||
// Close closes the connection.
|
// Close closes the connection.
|
||||||
func (s *UDPSession) Close() error {
|
func (s *UDPSession) Close() error {
|
||||||
// remove this session from updater & listener(if necessary)
|
// remove current session from updater & listener(if necessary)
|
||||||
updater.removeSession(s)
|
updater.removeSession(s)
|
||||||
if s.l != nil { // notify listener
|
if s.l != nil { // notify listener
|
||||||
s.l.closeSession(sessionKey{
|
s.l.closeSession(s.remote)
|
||||||
addr: s.remote.String(),
|
|
||||||
convID: s.kcp.conv,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
@@ -337,6 +345,8 @@ func (s *UDPSession) SetDeadline(t time.Time) error {
|
|||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
s.rd = t
|
s.rd = t
|
||||||
s.wd = t
|
s.wd = t
|
||||||
|
s.notifyReadEvent()
|
||||||
|
s.notifyWriteEvent()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,6 +355,7 @@ func (s *UDPSession) SetReadDeadline(t time.Time) error {
|
|||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
s.rd = t
|
s.rd = t
|
||||||
|
s.notifyReadEvent()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,6 +364,7 @@ func (s *UDPSession) SetWriteDeadline(t time.Time) error {
|
|||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
s.wd = t
|
s.wd = t
|
||||||
|
s.notifyWriteEvent()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,10 +432,11 @@ 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.(*connectedUDPConn); ok {
|
if nc, ok := s.conn.(net.Conn); ok {
|
||||||
return ipv4.NewConn(nc.UDPConn).SetTOS(dscp << 2)
|
if err := ipv4.NewConn(nc).SetTOS(dscp << 2); err != nil {
|
||||||
} else if nc, ok := s.conn.(net.Conn); ok {
|
return ipv6.NewConn(nc).SetTrafficClass(dscp)
|
||||||
return ipv4.NewConn(nc).SetTOS(dscp << 2)
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return errors.New(errInvalidOperation)
|
return errors.New(errInvalidOperation)
|
||||||
@@ -453,11 +466,11 @@ func (s *UDPSession) SetWriteBuffer(bytes int) error {
|
|||||||
return errors.New(errInvalidOperation)
|
return errors.New(errInvalidOperation)
|
||||||
}
|
}
|
||||||
|
|
||||||
// output pipeline entry
|
// post-processing for sending a packet from kcp core
|
||||||
// steps for output data processing:
|
// steps:
|
||||||
// 0. Header extends
|
// 0. Header extending
|
||||||
// 1. FEC
|
// 1. FEC packet generation
|
||||||
// 2. CRC32
|
// 2. CRC32 integrity
|
||||||
// 3. Encryption
|
// 3. Encryption
|
||||||
// 4. WriteTo kernel
|
// 4. WriteTo kernel
|
||||||
func (s *UDPSession) output(buf []byte) {
|
func (s *UDPSession) output(buf []byte) {
|
||||||
@@ -477,13 +490,13 @@ func (s *UDPSession) output(buf []byte) {
|
|||||||
|
|
||||||
// 2&3. crc32 & encryption
|
// 2&3. crc32 & encryption
|
||||||
if s.block != nil {
|
if s.block != nil {
|
||||||
io.ReadFull(rand.Reader, ext[:nonceSize])
|
s.nonce.Fill(ext[:nonceSize])
|
||||||
checksum := crc32.ChecksumIEEE(ext[cryptHeaderSize:])
|
checksum := crc32.ChecksumIEEE(ext[cryptHeaderSize:])
|
||||||
binary.LittleEndian.PutUint32(ext[nonceSize:], checksum)
|
binary.LittleEndian.PutUint32(ext[nonceSize:], checksum)
|
||||||
s.block.Encrypt(ext, ext)
|
s.block.Encrypt(ext, ext)
|
||||||
|
|
||||||
for k := range ecc {
|
for k := range ecc {
|
||||||
io.ReadFull(rand.Reader, ecc[k][:nonceSize])
|
s.nonce.Fill(ecc[k][:nonceSize])
|
||||||
checksum := crc32.ChecksumIEEE(ecc[k][cryptHeaderSize:])
|
checksum := crc32.ChecksumIEEE(ecc[k][cryptHeaderSize:])
|
||||||
binary.LittleEndian.PutUint32(ecc[k][nonceSize:], checksum)
|
binary.LittleEndian.PutUint32(ecc[k][nonceSize:], checksum)
|
||||||
s.block.Encrypt(ecc[k], ecc[k])
|
s.block.Encrypt(ecc[k], ecc[k])
|
||||||
@@ -497,6 +510,8 @@ func (s *UDPSession) output(buf []byte) {
|
|||||||
if n, err := s.conn.WriteTo(ext, s.remote); err == nil {
|
if n, err := s.conn.WriteTo(ext, s.remote); err == nil {
|
||||||
nbytes += n
|
nbytes += n
|
||||||
npkts++
|
npkts++
|
||||||
|
} else {
|
||||||
|
s.notifyWriteError(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,6 +519,8 @@ func (s *UDPSession) output(buf []byte) {
|
|||||||
if n, err := s.conn.WriteTo(ecc[k], s.remote); err == nil {
|
if n, err := s.conn.WriteTo(ecc[k], s.remote); err == nil {
|
||||||
nbytes += n
|
nbytes += n
|
||||||
npkts++
|
npkts++
|
||||||
|
} else {
|
||||||
|
s.notifyWriteError(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
atomic.AddUint64(&DefaultSnmp.OutPkts, uint64(npkts))
|
atomic.AddUint64(&DefaultSnmp.OutPkts, uint64(npkts))
|
||||||
@@ -513,11 +530,11 @@ func (s *UDPSession) output(buf []byte) {
|
|||||||
// kcp update, returns interval for next calling
|
// kcp update, returns interval for next calling
|
||||||
func (s *UDPSession) update() (interval time.Duration) {
|
func (s *UDPSession) update() (interval time.Duration) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.kcp.flush(false)
|
waitsnd := s.kcp.WaitSnd()
|
||||||
if s.kcp.WaitSnd() < int(s.kcp.snd_wnd) {
|
interval = time.Duration(s.kcp.flush(false)) * time.Millisecond
|
||||||
|
if s.kcp.WaitSnd() < waitsnd {
|
||||||
s.notifyWriteEvent()
|
s.notifyWriteEvent()
|
||||||
}
|
}
|
||||||
interval = time.Duration(s.kcp.interval) * time.Millisecond
|
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -539,56 +556,77 @@ func (s *UDPSession) notifyWriteEvent() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *UDPSession) notifyWriteError(err error) {
|
||||||
|
select {
|
||||||
|
case s.chWriteError <- err:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *UDPSession) kcpInput(data []byte) {
|
func (s *UDPSession) kcpInput(data []byte) {
|
||||||
var kcpInErrors, fecErrs, fecRecovered, fecParityShards uint64
|
var kcpInErrors, fecErrs, fecRecovered, fecParityShards uint64
|
||||||
|
|
||||||
if s.fecDecoder != nil {
|
if s.fecDecoder != nil {
|
||||||
f := s.fecDecoder.decodeBytes(data)
|
if len(data) > fecHeaderSize { // must be larger than fec header size
|
||||||
s.mu.Lock()
|
f := s.fecDecoder.decodeBytes(data)
|
||||||
if f.flag == typeData {
|
if f.flag == typeData || f.flag == typeFEC { // header check
|
||||||
if ret := s.kcp.Input(data[fecHeaderSizePlus2:], true, s.ackNoDelay); ret != 0 {
|
if f.flag == typeFEC {
|
||||||
kcpInErrors++
|
fecParityShards++
|
||||||
}
|
}
|
||||||
}
|
recovers := s.fecDecoder.decode(f)
|
||||||
|
|
||||||
if f.flag == typeData || f.flag == typeFEC {
|
s.mu.Lock()
|
||||||
if f.flag == typeFEC {
|
waitsnd := s.kcp.WaitSnd()
|
||||||
fecParityShards++
|
if f.flag == typeData {
|
||||||
}
|
if ret := s.kcp.Input(data[fecHeaderSizePlus2:], true, s.ackNoDelay); ret != 0 {
|
||||||
|
kcpInErrors++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
recovers := s.fecDecoder.decode(f)
|
for _, r := range recovers {
|
||||||
for _, r := range recovers {
|
if len(r) >= 2 { // must be larger than 2bytes
|
||||||
if len(r) >= 2 { // must be larger than 2bytes
|
sz := binary.LittleEndian.Uint16(r)
|
||||||
sz := binary.LittleEndian.Uint16(r)
|
if int(sz) <= len(r) && sz >= 2 {
|
||||||
if int(sz) <= len(r) && sz >= 2 {
|
if ret := s.kcp.Input(r[2:sz], false, s.ackNoDelay); ret == 0 {
|
||||||
if ret := s.kcp.Input(r[2:sz], false, s.ackNoDelay); ret == 0 {
|
fecRecovered++
|
||||||
fecRecovered++
|
} else {
|
||||||
|
kcpInErrors++
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
kcpInErrors++
|
fecErrs++
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fecErrs++
|
fecErrs++
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
fecErrs++
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// notify reader
|
// to notify the readers to receive the data
|
||||||
if n := s.kcp.PeekSize(); n > 0 {
|
if n := s.kcp.PeekSize(); n > 0 {
|
||||||
s.notifyReadEvent()
|
s.notifyReadEvent()
|
||||||
|
}
|
||||||
|
// to notify the writers when queue is shorter(e.g. ACKed)
|
||||||
|
if s.kcp.WaitSnd() < waitsnd {
|
||||||
|
s.notifyWriteEvent()
|
||||||
|
}
|
||||||
|
s.mu.Unlock()
|
||||||
|
} else {
|
||||||
|
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||||
}
|
}
|
||||||
s.mu.Unlock()
|
|
||||||
} else {
|
} else {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
|
waitsnd := s.kcp.WaitSnd()
|
||||||
if ret := s.kcp.Input(data, true, s.ackNoDelay); ret != 0 {
|
if ret := s.kcp.Input(data, true, s.ackNoDelay); ret != 0 {
|
||||||
kcpInErrors++
|
kcpInErrors++
|
||||||
}
|
}
|
||||||
// notify reader
|
|
||||||
if n := s.kcp.PeekSize(); n > 0 {
|
if n := s.kcp.PeekSize(); n > 0 {
|
||||||
s.notifyReadEvent()
|
s.notifyReadEvent()
|
||||||
}
|
}
|
||||||
|
if s.kcp.WaitSnd() < waitsnd {
|
||||||
|
s.notifyWriteEvent()
|
||||||
|
}
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -608,65 +646,52 @@ func (s *UDPSession) kcpInput(data []byte) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *UDPSession) receiver(ch chan<- []byte) {
|
// the read loop for a client session
|
||||||
for {
|
|
||||||
data := xmitBuf.Get().([]byte)[:mtuLimit]
|
|
||||||
if n, _, err := s.conn.ReadFrom(data); err == nil && n >= s.headerSize+IKCP_OVERHEAD {
|
|
||||||
select {
|
|
||||||
case ch <- data[:n]:
|
|
||||||
case <-s.die:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
s.chErrorEvent <- err
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// read loop for client session
|
|
||||||
func (s *UDPSession) readLoop() {
|
func (s *UDPSession) readLoop() {
|
||||||
chPacket := make(chan []byte, qlen)
|
buf := make([]byte, mtuLimit)
|
||||||
go s.receiver(chPacket)
|
var src string
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
if n, addr, err := s.conn.ReadFrom(buf); err == nil {
|
||||||
case data := <-chPacket:
|
// make sure the packet is from the same source
|
||||||
raw := data
|
if src == "" { // set source address
|
||||||
dataValid := false
|
src = addr.String()
|
||||||
if s.block != nil {
|
} else if addr.String() != src {
|
||||||
s.block.Decrypt(data, data)
|
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||||
data = data[nonceSize:]
|
continue
|
||||||
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 {
|
if n >= s.headerSize+IKCP_OVERHEAD {
|
||||||
s.kcpInput(data)
|
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)
|
||||||
}
|
}
|
||||||
xmitBuf.Put(raw)
|
} else {
|
||||||
case <-s.die:
|
s.chReadError <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
sessionKey struct {
|
// Listener defines a server which will be waiting to accept incoming connections
|
||||||
addr string
|
|
||||||
convID uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listener defines a server listening for connections
|
|
||||||
Listener struct {
|
Listener struct {
|
||||||
block BlockCrypt // block encryption
|
block BlockCrypt // block encryption
|
||||||
dataShards int // FEC data shard
|
dataShards int // FEC data shard
|
||||||
@@ -674,120 +699,93 @@ type (
|
|||||||
fecDecoder *fecDecoder // FEC mock initialization
|
fecDecoder *fecDecoder // FEC mock initialization
|
||||||
conn net.PacketConn // the underlying packet connection
|
conn net.PacketConn // the underlying packet connection
|
||||||
|
|
||||||
sessions map[sessionKey]*UDPSession // all sessions accepted by this Listener
|
sessions map[string]*UDPSession // all sessions accepted by this Listener
|
||||||
chAccepts chan *UDPSession // Listen() backlog
|
sessionLock sync.Mutex
|
||||||
chSessionClosed chan sessionKey // session close queue
|
chAccepts chan *UDPSession // Listen() backlog
|
||||||
headerSize int // the overall header size added before KCP frame
|
chSessionClosed chan net.Addr // session close queue
|
||||||
die chan struct{} // notify the listener has closed
|
headerSize int // the additional header to a KCP frame
|
||||||
rd atomic.Value // read deadline for Accept()
|
die chan struct{} // notify the listener has closed
|
||||||
|
rd atomic.Value // read deadline for Accept()
|
||||||
wd atomic.Value
|
wd atomic.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// incoming packet
|
|
||||||
inPacket struct {
|
|
||||||
from net.Addr
|
|
||||||
data []byte
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// monitor incoming data for all connections of server
|
// monitor incoming data for all connections of server
|
||||||
func (l *Listener) monitor() {
|
func (l *Listener) monitor() {
|
||||||
// cache last session
|
// a cache for session object last used
|
||||||
var lastKey sessionKey
|
var lastAddr string
|
||||||
var lastSession *UDPSession
|
var lastSession *UDPSession
|
||||||
|
buf := make([]byte, mtuLimit)
|
||||||
chPacket := make(chan inPacket, qlen)
|
|
||||||
go l.receiver(chPacket)
|
|
||||||
for {
|
for {
|
||||||
select {
|
if n, from, err := l.conn.ReadFrom(buf); err == nil {
|
||||||
case p := <-chPacket:
|
if n >= l.headerSize+IKCP_OVERHEAD {
|
||||||
raw := p.data
|
data := buf[:n]
|
||||||
data := p.data
|
dataValid := false
|
||||||
from := p.from
|
if l.block != nil {
|
||||||
dataValid := false
|
l.block.Decrypt(data, data)
|
||||||
if l.block != nil {
|
data = data[nonceSize:]
|
||||||
l.block.Decrypt(data, data)
|
checksum := crc32.ChecksumIEEE(data[crcSize:])
|
||||||
data = data[nonceSize:]
|
if checksum == binary.LittleEndian.Uint32(data) {
|
||||||
checksum := crc32.ChecksumIEEE(data[crcSize:])
|
data = data[crcSize:]
|
||||||
if checksum == binary.LittleEndian.Uint32(data) {
|
dataValid = true
|
||||||
data = data[crcSize:]
|
} else {
|
||||||
|
atomic.AddUint64(&DefaultSnmp.InCsumErrors, 1)
|
||||||
|
}
|
||||||
|
} else if l.block == nil {
|
||||||
dataValid = true
|
dataValid = true
|
||||||
} else {
|
|
||||||
atomic.AddUint64(&DefaultSnmp.InCsumErrors, 1)
|
|
||||||
}
|
|
||||||
} else if l.block == nil {
|
|
||||||
dataValid = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if dataValid {
|
|
||||||
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 {
|
if dataValid {
|
||||||
key := sessionKey{
|
addr := from.String()
|
||||||
addr: from.String(),
|
|
||||||
convID: conv,
|
|
||||||
}
|
|
||||||
var s *UDPSession
|
var s *UDPSession
|
||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
// packets received from an address always come in batch.
|
// the packets received from an address always come in batch,
|
||||||
// cache the session for next packet, without querying map.
|
// cache the session for next packet, without querying map.
|
||||||
if key == lastKey {
|
if addr == lastAddr {
|
||||||
s, ok = lastSession, true
|
s, ok = lastSession, true
|
||||||
} else if s, ok = l.sessions[key]; ok {
|
} else {
|
||||||
lastSession = s
|
l.sessionLock.Lock()
|
||||||
lastKey = key
|
if s, ok = l.sessions[addr]; ok {
|
||||||
|
lastSession = s
|
||||||
|
lastAddr = addr
|
||||||
|
}
|
||||||
|
l.sessionLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ok { // new session
|
if !ok { // new session
|
||||||
if len(l.chAccepts) < cap(l.chAccepts) && len(l.sessions) < 4096 { // do not let new session overwhelm accept queue and connection count
|
if len(l.chAccepts) < cap(l.chAccepts) { // do not let the new sessions overwhelm accept queue
|
||||||
s := newUDPSession(conv, l.dataShards, l.parityShards, l, l.conn, from, l.block)
|
var conv uint32
|
||||||
s.kcpInput(data)
|
convValid := false
|
||||||
l.sessions[key] = s
|
if l.fecDecoder != nil {
|
||||||
l.chAccepts <- s
|
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 {
|
} else {
|
||||||
s.kcpInput(data)
|
s.kcpInput(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
xmitBuf.Put(raw)
|
|
||||||
case key := <-l.chSessionClosed:
|
|
||||||
if key == lastKey {
|
|
||||||
lastKey = sessionKey{}
|
|
||||||
}
|
|
||||||
delete(l.sessions, key)
|
|
||||||
case <-l.die:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Listener) receiver(ch chan<- inPacket) {
|
|
||||||
for {
|
|
||||||
data := xmitBuf.Get().([]byte)[:mtuLimit]
|
|
||||||
if n, from, err := l.conn.ReadFrom(data); err == nil && n >= l.headerSize+IKCP_OVERHEAD {
|
|
||||||
select {
|
|
||||||
case ch <- inPacket{from, data[:n]}:
|
|
||||||
case <-l.die:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
return
|
|
||||||
} else {
|
} else {
|
||||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -811,7 +809,10 @@ func (l *Listener) SetWriteBuffer(bytes int) error {
|
|||||||
// SetDSCP sets the 6bit DSCP field of IP header
|
// SetDSCP sets the 6bit DSCP field of IP 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 {
|
||||||
return ipv4.NewConn(nc).SetTOS(dscp << 2)
|
if err := ipv4.NewConn(nc).SetTOS(dscp << 2); err != nil {
|
||||||
|
return ipv6.NewConn(nc).SetTrafficClass(dscp)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return errors.New(errInvalidOperation)
|
return errors.New(errInvalidOperation)
|
||||||
}
|
}
|
||||||
@@ -864,13 +865,14 @@ func (l *Listener) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// closeSession notify the listener that a session has closed
|
// closeSession notify the listener that a session has closed
|
||||||
func (l *Listener) closeSession(key sessionKey) bool {
|
func (l *Listener) closeSession(remote net.Addr) (ret bool) {
|
||||||
select {
|
l.sessionLock.Lock()
|
||||||
case l.chSessionClosed <- key:
|
defer l.sessionLock.Unlock()
|
||||||
|
if _, ok := l.sessions[remote.String()]; ok {
|
||||||
|
delete(l.sessions, remote.String())
|
||||||
return true
|
return true
|
||||||
case <-l.die:
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Addr returns the listener's network address, The Addr returned is shared by all invocations of Addr, so do not modify it.
|
// Addr returns the listener's network address, The Addr returned is shared by all invocations of Addr, so do not modify it.
|
||||||
@@ -898,9 +900,9 @@ func ListenWithOptions(laddr string, block BlockCrypt, dataShards, parityShards
|
|||||||
func ServeConn(block BlockCrypt, dataShards, parityShards int, conn net.PacketConn) (*Listener, error) {
|
func ServeConn(block BlockCrypt, dataShards, parityShards int, conn net.PacketConn) (*Listener, error) {
|
||||||
l := new(Listener)
|
l := new(Listener)
|
||||||
l.conn = conn
|
l.conn = conn
|
||||||
l.sessions = make(map[sessionKey]*UDPSession)
|
l.sessions = make(map[string]*UDPSession)
|
||||||
l.chAccepts = make(chan *UDPSession, acceptBacklog)
|
l.chAccepts = make(chan *UDPSession, acceptBacklog)
|
||||||
l.chSessionClosed = make(chan sessionKey)
|
l.chSessionClosed = make(chan net.Addr)
|
||||||
l.die = make(chan struct{})
|
l.die = make(chan struct{})
|
||||||
l.dataShards = dataShards
|
l.dataShards = dataShards
|
||||||
l.parityShards = parityShards
|
l.parityShards = parityShards
|
||||||
@@ -924,17 +926,22 @@ func Dial(raddr string) (net.Conn, error) { return DialWithOptions(raddr, nil, 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
|
||||||
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
|
||||||
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.Wrap(err, "net.ResolveUDPAddr")
|
||||||
}
|
}
|
||||||
|
network := "udp4"
|
||||||
|
if udpaddr.IP.To4() == nil {
|
||||||
|
network = "udp"
|
||||||
|
}
|
||||||
|
|
||||||
udpconn, err := net.DialUDP("udp", nil, udpaddr)
|
conn, err := net.ListenUDP(network, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "net.DialUDP")
|
return nil, errors.Wrap(err, "net.DialUDP")
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewConn(raddr, block, dataShards, parityShards, &connectedUDPConn{udpconn})
|
return NewConn(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.
|
||||||
@@ -949,6 +956,12 @@ func NewConn(raddr string, block BlockCrypt, dataShards, parityShards int, conn
|
|||||||
return newUDPSession(convid, dataShards, parityShards, nil, conn, udpaddr, block), nil
|
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 {
|
||||||
@@ -963,9 +976,6 @@ func NewConnEx(convid uint32, connected bool, raddr string, block BlockCrypt, da
|
|||||||
return newUDPSession(convid, dataShards, parityShards, nil, pConn, udpaddr, block), nil
|
return newUDPSession(convid, dataShards, parityShards, nil, pConn, udpaddr, block), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns current time in milliseconds
|
|
||||||
func currentMs() uint32 { return uint32(time.Now().UnixNano() / int64(time.Millisecond)) }
|
|
||||||
|
|
||||||
// connectedUDPConn is a wrapper for net.UDPConn which converts WriteTo syscalls
|
// connectedUDPConn is a wrapper for net.UDPConn which converts WriteTo syscalls
|
||||||
// to Write syscalls that are 4 times faster on some OS'es. This should only be
|
// to Write syscalls that are 4 times faster on some OS'es. This should only be
|
||||||
// used for connections that were produced by a net.Dial* call.
|
// used for connections that were produced by a net.Dial* call.
|
||||||
|
|||||||
13
vendor/github.com/fatedier/kcp-go/updater.go
generated
vendored
13
vendor/github.com/fatedier/kcp-go/updater.go
generated
vendored
@@ -85,20 +85,19 @@ func (h *updateHeap) updateTask() {
|
|||||||
|
|
||||||
h.mu.Lock()
|
h.mu.Lock()
|
||||||
hlen := h.Len()
|
hlen := h.Len()
|
||||||
now := time.Now()
|
|
||||||
for i := 0; i < hlen; i++ {
|
for i := 0; i < hlen; i++ {
|
||||||
entry := heap.Pop(h).(entry)
|
entry := &h.entries[0]
|
||||||
if now.After(entry.ts) {
|
if time.Now().After(entry.ts) {
|
||||||
entry.ts = now.Add(entry.s.update())
|
interval := entry.s.update()
|
||||||
heap.Push(h, entry)
|
entry.ts = time.Now().Add(interval)
|
||||||
|
heap.Fix(h, 0)
|
||||||
} else {
|
} else {
|
||||||
heap.Push(h, entry)
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if hlen > 0 {
|
if hlen > 0 {
|
||||||
timer = time.After(h.entries[0].ts.Sub(now))
|
timer = time.After(h.entries[0].ts.Sub(time.Now()))
|
||||||
}
|
}
|
||||||
h.mu.Unlock()
|
h.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|||||||
110
vendor/github.com/fatedier/kcp-go/xor.go
generated
vendored
110
vendor/github.com/fatedier/kcp-go/xor.go
generated
vendored
@@ -1,110 +0,0 @@
|
|||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package kcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const wordSize = int(unsafe.Sizeof(uintptr(0)))
|
|
||||||
const supportsUnaligned = runtime.GOARCH == "386" || runtime.GOARCH == "amd64" || runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "s390x"
|
|
||||||
|
|
||||||
// fastXORBytes xors in bulk. It only works on architectures that
|
|
||||||
// support unaligned read/writes.
|
|
||||||
func fastXORBytes(dst, a, b []byte) int {
|
|
||||||
n := len(a)
|
|
||||||
if len(b) < n {
|
|
||||||
n = len(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
w := n / wordSize
|
|
||||||
if w > 0 {
|
|
||||||
wordBytes := w * wordSize
|
|
||||||
fastXORWords(dst[:wordBytes], a[:wordBytes], b[:wordBytes])
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := (n - n%wordSize); i < n; i++ {
|
|
||||||
dst[i] = a[i] ^ b[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
func safeXORBytes(dst, a, b []byte) int {
|
|
||||||
n := len(a)
|
|
||||||
if len(b) < n {
|
|
||||||
n = len(b)
|
|
||||||
}
|
|
||||||
ex := n % 8
|
|
||||||
for i := 0; i < ex; i++ {
|
|
||||||
dst[i] = a[i] ^ b[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := ex; i < n; i += 8 {
|
|
||||||
_dst := dst[i : i+8]
|
|
||||||
_a := a[i : i+8]
|
|
||||||
_b := b[i : i+8]
|
|
||||||
_dst[0] = _a[0] ^ _b[0]
|
|
||||||
_dst[1] = _a[1] ^ _b[1]
|
|
||||||
_dst[2] = _a[2] ^ _b[2]
|
|
||||||
_dst[3] = _a[3] ^ _b[3]
|
|
||||||
|
|
||||||
_dst[4] = _a[4] ^ _b[4]
|
|
||||||
_dst[5] = _a[5] ^ _b[5]
|
|
||||||
_dst[6] = _a[6] ^ _b[6]
|
|
||||||
_dst[7] = _a[7] ^ _b[7]
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
// xorBytes xors the bytes in a and b. The destination is assumed to have enough
|
|
||||||
// space. Returns the number of bytes xor'd.
|
|
||||||
func xorBytes(dst, a, b []byte) int {
|
|
||||||
if supportsUnaligned {
|
|
||||||
return fastXORBytes(dst, a, b)
|
|
||||||
}
|
|
||||||
// TODO(hanwen): if (dst, a, b) have common alignment
|
|
||||||
// we could still try fastXORBytes. It is not clear
|
|
||||||
// how often this happens, and it's only worth it if
|
|
||||||
// the block encryption itself is hardware
|
|
||||||
// accelerated.
|
|
||||||
return safeXORBytes(dst, a, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fastXORWords XORs multiples of 4 or 8 bytes (depending on architecture.)
|
|
||||||
// The arguments are assumed to be of equal length.
|
|
||||||
func fastXORWords(dst, a, b []byte) {
|
|
||||||
dw := *(*[]uintptr)(unsafe.Pointer(&dst))
|
|
||||||
aw := *(*[]uintptr)(unsafe.Pointer(&a))
|
|
||||||
bw := *(*[]uintptr)(unsafe.Pointer(&b))
|
|
||||||
n := len(b) / wordSize
|
|
||||||
ex := n % 8
|
|
||||||
for i := 0; i < ex; i++ {
|
|
||||||
dw[i] = aw[i] ^ bw[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := ex; i < n; i += 8 {
|
|
||||||
_dw := dw[i : i+8]
|
|
||||||
_aw := aw[i : i+8]
|
|
||||||
_bw := bw[i : i+8]
|
|
||||||
_dw[0] = _aw[0] ^ _bw[0]
|
|
||||||
_dw[1] = _aw[1] ^ _bw[1]
|
|
||||||
_dw[2] = _aw[2] ^ _bw[2]
|
|
||||||
_dw[3] = _aw[3] ^ _bw[3]
|
|
||||||
_dw[4] = _aw[4] ^ _bw[4]
|
|
||||||
_dw[5] = _aw[5] ^ _bw[5]
|
|
||||||
_dw[6] = _aw[6] ^ _bw[6]
|
|
||||||
_dw[7] = _aw[7] ^ _bw[7]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func xorWords(dst, a, b []byte) {
|
|
||||||
if supportsUnaligned {
|
|
||||||
fastXORWords(dst, a, b)
|
|
||||||
} else {
|
|
||||||
safeXORBytes(dst, a, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1
vendor/github.com/hashicorp/yamux/go.mod
generated
vendored
Normal file
1
vendor/github.com/hashicorp/yamux/go.mod
generated
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
module github.com/hashicorp/yamux
|
||||||
13
vendor/github.com/hashicorp/yamux/mux.go
generated
vendored
13
vendor/github.com/hashicorp/yamux/mux.go
generated
vendored
@@ -3,6 +3,7 @@ package yamux
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -30,8 +31,13 @@ type Config struct {
|
|||||||
// window size that we allow for a stream.
|
// window size that we allow for a stream.
|
||||||
MaxStreamWindowSize uint32
|
MaxStreamWindowSize uint32
|
||||||
|
|
||||||
// LogOutput is used to control the log destination
|
// LogOutput is used to control the log destination. Either Logger or
|
||||||
|
// LogOutput can be set, not both.
|
||||||
LogOutput io.Writer
|
LogOutput io.Writer
|
||||||
|
|
||||||
|
// Logger is used to pass in the logger to be used. Either Logger or
|
||||||
|
// LogOutput can be set, not both.
|
||||||
|
Logger *log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultConfig is used to return a default configuration
|
// DefaultConfig is used to return a default configuration
|
||||||
@@ -57,6 +63,11 @@ func VerifyConfig(config *Config) error {
|
|||||||
if config.MaxStreamWindowSize < initialStreamWindow {
|
if config.MaxStreamWindowSize < initialStreamWindow {
|
||||||
return fmt.Errorf("MaxStreamWindowSize must be larger than %d", initialStreamWindow)
|
return fmt.Errorf("MaxStreamWindowSize must be larger than %d", initialStreamWindow)
|
||||||
}
|
}
|
||||||
|
if config.LogOutput != nil && config.Logger != nil {
|
||||||
|
return fmt.Errorf("both Logger and LogOutput may not be set, select one")
|
||||||
|
} else if config.LogOutput == nil && config.Logger == nil {
|
||||||
|
return fmt.Errorf("one of Logger or LogOutput must be set, select one")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
13
vendor/github.com/hashicorp/yamux/session.go
generated
vendored
13
vendor/github.com/hashicorp/yamux/session.go
generated
vendored
@@ -86,9 +86,14 @@ type sendReady struct {
|
|||||||
|
|
||||||
// newSession is used to construct a new session
|
// newSession is used to construct a new session
|
||||||
func newSession(config *Config, conn io.ReadWriteCloser, client bool) *Session {
|
func newSession(config *Config, conn io.ReadWriteCloser, client bool) *Session {
|
||||||
|
logger := config.Logger
|
||||||
|
if logger == nil {
|
||||||
|
logger = log.New(config.LogOutput, "", log.LstdFlags)
|
||||||
|
}
|
||||||
|
|
||||||
s := &Session{
|
s := &Session{
|
||||||
config: config,
|
config: config,
|
||||||
logger: log.New(config.LogOutput, "", log.LstdFlags),
|
logger: logger,
|
||||||
conn: conn,
|
conn: conn,
|
||||||
bufRead: bufio.NewReader(conn),
|
bufRead: bufio.NewReader(conn),
|
||||||
pings: make(map[uint32]chan struct{}),
|
pings: make(map[uint32]chan struct{}),
|
||||||
@@ -309,8 +314,10 @@ func (s *Session) keepalive() {
|
|||||||
case <-time.After(s.config.KeepAliveInterval):
|
case <-time.After(s.config.KeepAliveInterval):
|
||||||
_, err := s.Ping()
|
_, err := s.Ping()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Printf("[ERR] yamux: keepalive failed: %v", err)
|
if err != ErrSessionShutdown {
|
||||||
s.exitErr(ErrKeepAliveTimeout)
|
s.logger.Printf("[ERR] yamux: keepalive failed: %v", err)
|
||||||
|
s.exitErr(ErrKeepAliveTimeout)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case <-s.shutdownCh:
|
case <-s.shutdownCh:
|
||||||
|
|||||||
@@ -22,19 +22,3 @@ _testmain.go
|
|||||||
*.exe
|
*.exe
|
||||||
*.test
|
*.test
|
||||||
*.prof
|
*.prof
|
||||||
/.idea
|
|
||||||
/backup
|
|
||||||
/loopunroll/
|
|
||||||
cpu.out
|
|
||||||
mathtool/galois/
|
|
||||||
mathtool/matrix/
|
|
||||||
mem.out
|
|
||||||
/examples/
|
|
||||||
/.DS_Store
|
|
||||||
/mathtool/cntinverse
|
|
||||||
/invert
|
|
||||||
/bakcup
|
|
||||||
/buf.svg
|
|
||||||
*.svg
|
|
||||||
*.out
|
|
||||||
/escape
|
|
||||||
23
vendor/github.com/klauspost/cpuid/.travis.yml
generated
vendored
Normal file
23
vendor/github.com/klauspost/cpuid/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
- osx
|
||||||
|
go:
|
||||||
|
- 1.8.x
|
||||||
|
- 1.9.x
|
||||||
|
- 1.10.x
|
||||||
|
- master
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go vet ./...
|
||||||
|
- go test -v ./...
|
||||||
|
- go test -race ./...
|
||||||
|
- diff <(gofmt -d .) <("")
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- go: 'master'
|
||||||
|
fast_finish: true
|
||||||
35
vendor/github.com/klauspost/cpuid/CONTRIBUTING.txt
generated
vendored
Normal file
35
vendor/github.com/klauspost/cpuid/CONTRIBUTING.txt
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
Developer Certificate of Origin
|
||||||
|
Version 1.1
|
||||||
|
|
||||||
|
Copyright (C) 2015- Klaus Post & Contributors.
|
||||||
|
Email: klauspost@gmail.com
|
||||||
|
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies of this
|
||||||
|
license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
Developer's Certificate of Origin 1.1
|
||||||
|
|
||||||
|
By making a contribution to this project, I certify that:
|
||||||
|
|
||||||
|
(a) The contribution was created in whole or in part by me and I
|
||||||
|
have the right to submit it under the open source license
|
||||||
|
indicated in the file; or
|
||||||
|
|
||||||
|
(b) The contribution is based upon previous work that, to the best
|
||||||
|
of my knowledge, is covered under an appropriate open source
|
||||||
|
license and I have the right under that license to submit that
|
||||||
|
work with modifications, whether created in whole or in part
|
||||||
|
by me, under the same open source license (unless I am
|
||||||
|
permitted to submit under a different license), as indicated
|
||||||
|
in the file; or
|
||||||
|
|
||||||
|
(c) The contribution was provided directly to me by some other
|
||||||
|
person who certified (a), (b) or (c) and I have not modified
|
||||||
|
it.
|
||||||
|
|
||||||
|
(d) I understand and agree that this project and the contribution
|
||||||
|
are public and that a record of the contribution (including all
|
||||||
|
personal information I submit with it, including my sign-off) is
|
||||||
|
maintained indefinitely and may be redistributed consistent with
|
||||||
|
this project or the open source license(s) involved.
|
||||||
22
vendor/github.com/klauspost/cpuid/LICENSE
generated
vendored
Normal file
22
vendor/github.com/klauspost/cpuid/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Klaus Post
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
145
vendor/github.com/klauspost/cpuid/README.md
generated
vendored
Normal file
145
vendor/github.com/klauspost/cpuid/README.md
generated
vendored
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
# cpuid
|
||||||
|
Package cpuid provides information about the CPU running the current program.
|
||||||
|
|
||||||
|
CPU features are detected on startup, and kept for fast access through the life of the application.
|
||||||
|
Currently x86 / x64 (AMD64) is supported, and no external C (cgo) code is used, which should make the library very easy to use.
|
||||||
|
|
||||||
|
You can access the CPU information by accessing the shared CPU variable of the cpuid library.
|
||||||
|
|
||||||
|
Package home: https://github.com/klauspost/cpuid
|
||||||
|
|
||||||
|
[![GoDoc][1]][2] [![Build Status][3]][4]
|
||||||
|
|
||||||
|
[1]: https://godoc.org/github.com/klauspost/cpuid?status.svg
|
||||||
|
[2]: https://godoc.org/github.com/klauspost/cpuid
|
||||||
|
[3]: https://travis-ci.org/klauspost/cpuid.svg
|
||||||
|
[4]: https://travis-ci.org/klauspost/cpuid
|
||||||
|
|
||||||
|
# features
|
||||||
|
## CPU Instructions
|
||||||
|
* **CMOV** (i686 CMOV)
|
||||||
|
* **NX** (NX (No-Execute) bit)
|
||||||
|
* **AMD3DNOW** (AMD 3DNOW)
|
||||||
|
* **AMD3DNOWEXT** (AMD 3DNowExt)
|
||||||
|
* **MMX** (standard MMX)
|
||||||
|
* **MMXEXT** (SSE integer functions or AMD MMX ext)
|
||||||
|
* **SSE** (SSE functions)
|
||||||
|
* **SSE2** (P4 SSE functions)
|
||||||
|
* **SSE3** (Prescott SSE3 functions)
|
||||||
|
* **SSSE3** (Conroe SSSE3 functions)
|
||||||
|
* **SSE4** (Penryn SSE4.1 functions)
|
||||||
|
* **SSE4A** (AMD Barcelona microarchitecture SSE4a instructions)
|
||||||
|
* **SSE42** (Nehalem SSE4.2 functions)
|
||||||
|
* **AVX** (AVX functions)
|
||||||
|
* **AVX2** (AVX2 functions)
|
||||||
|
* **FMA3** (Intel FMA 3)
|
||||||
|
* **FMA4** (Bulldozer FMA4 functions)
|
||||||
|
* **XOP** (Bulldozer XOP functions)
|
||||||
|
* **F16C** (Half-precision floating-point conversion)
|
||||||
|
* **BMI1** (Bit Manipulation Instruction Set 1)
|
||||||
|
* **BMI2** (Bit Manipulation Instruction Set 2)
|
||||||
|
* **TBM** (AMD Trailing Bit Manipulation)
|
||||||
|
* **LZCNT** (LZCNT instruction)
|
||||||
|
* **POPCNT** (POPCNT instruction)
|
||||||
|
* **AESNI** (Advanced Encryption Standard New Instructions)
|
||||||
|
* **CLMUL** (Carry-less Multiplication)
|
||||||
|
* **HTT** (Hyperthreading (enabled))
|
||||||
|
* **HLE** (Hardware Lock Elision)
|
||||||
|
* **RTM** (Restricted Transactional Memory)
|
||||||
|
* **RDRAND** (RDRAND instruction is available)
|
||||||
|
* **RDSEED** (RDSEED instruction is available)
|
||||||
|
* **ADX** (Intel ADX (Multi-Precision Add-Carry Instruction Extensions))
|
||||||
|
* **SHA** (Intel SHA Extensions)
|
||||||
|
* **AVX512F** (AVX-512 Foundation)
|
||||||
|
* **AVX512DQ** (AVX-512 Doubleword and Quadword Instructions)
|
||||||
|
* **AVX512IFMA** (AVX-512 Integer Fused Multiply-Add Instructions)
|
||||||
|
* **AVX512PF** (AVX-512 Prefetch Instructions)
|
||||||
|
* **AVX512ER** (AVX-512 Exponential and Reciprocal Instructions)
|
||||||
|
* **AVX512CD** (AVX-512 Conflict Detection Instructions)
|
||||||
|
* **AVX512BW** (AVX-512 Byte and Word Instructions)
|
||||||
|
* **AVX512VL** (AVX-512 Vector Length Extensions)
|
||||||
|
* **AVX512VBMI** (AVX-512 Vector Bit Manipulation Instructions)
|
||||||
|
* **MPX** (Intel MPX (Memory Protection Extensions))
|
||||||
|
* **ERMS** (Enhanced REP MOVSB/STOSB)
|
||||||
|
* **RDTSCP** (RDTSCP Instruction)
|
||||||
|
* **CX16** (CMPXCHG16B Instruction)
|
||||||
|
* **SGX** (Software Guard Extensions, with activation details)
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
* **RDTSCP()** Returns current cycle count. Can be used for benchmarking.
|
||||||
|
* **SSE2SLOW** (SSE2 is supported, but usually not faster)
|
||||||
|
* **SSE3SLOW** (SSE3 is supported, but usually not faster)
|
||||||
|
* **ATOM** (Atom processor, some SSSE3 instructions are slower)
|
||||||
|
* **Cache line** (Probable size of a cache line).
|
||||||
|
* **L1, L2, L3 Cache size** on newer Intel/AMD CPUs.
|
||||||
|
|
||||||
|
## Cpu Vendor/VM
|
||||||
|
* **Intel**
|
||||||
|
* **AMD**
|
||||||
|
* **VIA**
|
||||||
|
* **Transmeta**
|
||||||
|
* **NSC**
|
||||||
|
* **KVM** (Kernel-based Virtual Machine)
|
||||||
|
* **MSVM** (Microsoft Hyper-V or Windows Virtual PC)
|
||||||
|
* **VMware**
|
||||||
|
* **XenHVM**
|
||||||
|
|
||||||
|
# installing
|
||||||
|
|
||||||
|
```go get github.com/klauspost/cpuid```
|
||||||
|
|
||||||
|
# example
|
||||||
|
|
||||||
|
```Go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/klauspost/cpuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Print basic CPU information:
|
||||||
|
fmt.Println("Name:", cpuid.CPU.BrandName)
|
||||||
|
fmt.Println("PhysicalCores:", cpuid.CPU.PhysicalCores)
|
||||||
|
fmt.Println("ThreadsPerCore:", cpuid.CPU.ThreadsPerCore)
|
||||||
|
fmt.Println("LogicalCores:", cpuid.CPU.LogicalCores)
|
||||||
|
fmt.Println("Family", cpuid.CPU.Family, "Model:", cpuid.CPU.Model)
|
||||||
|
fmt.Println("Features:", cpuid.CPU.Features)
|
||||||
|
fmt.Println("Cacheline bytes:", cpuid.CPU.CacheLine)
|
||||||
|
fmt.Println("L1 Data Cache:", cpuid.CPU.Cache.L1D, "bytes")
|
||||||
|
fmt.Println("L1 Instruction Cache:", cpuid.CPU.Cache.L1D, "bytes")
|
||||||
|
fmt.Println("L2 Cache:", cpuid.CPU.Cache.L2, "bytes")
|
||||||
|
fmt.Println("L3 Cache:", cpuid.CPU.Cache.L3, "bytes")
|
||||||
|
|
||||||
|
// Test if we have a specific feature:
|
||||||
|
if cpuid.CPU.SSE() {
|
||||||
|
fmt.Println("We have Streaming SIMD Extensions")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Sample output:
|
||||||
|
```
|
||||||
|
>go run main.go
|
||||||
|
Name: Intel(R) Core(TM) i5-2540M CPU @ 2.60GHz
|
||||||
|
PhysicalCores: 2
|
||||||
|
ThreadsPerCore: 2
|
||||||
|
LogicalCores: 4
|
||||||
|
Family 6 Model: 42
|
||||||
|
Features: CMOV,MMX,MMXEXT,SSE,SSE2,SSE3,SSSE3,SSE4.1,SSE4.2,AVX,AESNI,CLMUL
|
||||||
|
Cacheline bytes: 64
|
||||||
|
We have Streaming SIMD Extensions
|
||||||
|
```
|
||||||
|
|
||||||
|
# private package
|
||||||
|
|
||||||
|
In the "private" folder you can find an autogenerated version of the library you can include in your own packages.
|
||||||
|
|
||||||
|
For this purpose all exports are removed, and functions and constants are lowercased.
|
||||||
|
|
||||||
|
This is not a recommended way of using the library, but provided for convenience, if it is difficult for you to use external packages.
|
||||||
|
|
||||||
|
# license
|
||||||
|
|
||||||
|
This code is published under an MIT license. See LICENSE file for more information.
|
||||||
1040
vendor/github.com/klauspost/cpuid/cpuid.go
generated
vendored
Normal file
1040
vendor/github.com/klauspost/cpuid/cpuid.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
42
vendor/github.com/klauspost/cpuid/cpuid_386.s
generated
vendored
Normal file
42
vendor/github.com/klauspost/cpuid/cpuid_386.s
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
|
||||||
|
|
||||||
|
// +build 386,!gccgo
|
||||||
|
|
||||||
|
// func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32)
|
||||||
|
TEXT ·asmCpuid(SB), 7, $0
|
||||||
|
XORL CX, CX
|
||||||
|
MOVL op+0(FP), AX
|
||||||
|
CPUID
|
||||||
|
MOVL AX, eax+4(FP)
|
||||||
|
MOVL BX, ebx+8(FP)
|
||||||
|
MOVL CX, ecx+12(FP)
|
||||||
|
MOVL DX, edx+16(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
// func asmCpuidex(op, op2 uint32) (eax, ebx, ecx, edx uint32)
|
||||||
|
TEXT ·asmCpuidex(SB), 7, $0
|
||||||
|
MOVL op+0(FP), AX
|
||||||
|
MOVL op2+4(FP), CX
|
||||||
|
CPUID
|
||||||
|
MOVL AX, eax+8(FP)
|
||||||
|
MOVL BX, ebx+12(FP)
|
||||||
|
MOVL CX, ecx+16(FP)
|
||||||
|
MOVL DX, edx+20(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
// func xgetbv(index uint32) (eax, edx uint32)
|
||||||
|
TEXT ·asmXgetbv(SB), 7, $0
|
||||||
|
MOVL index+0(FP), CX
|
||||||
|
BYTE $0x0f; BYTE $0x01; BYTE $0xd0 // XGETBV
|
||||||
|
MOVL AX, eax+4(FP)
|
||||||
|
MOVL DX, edx+8(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
// func asmRdtscpAsm() (eax, ebx, ecx, edx uint32)
|
||||||
|
TEXT ·asmRdtscpAsm(SB), 7, $0
|
||||||
|
BYTE $0x0F; BYTE $0x01; BYTE $0xF9 // RDTSCP
|
||||||
|
MOVL AX, eax+0(FP)
|
||||||
|
MOVL BX, ebx+4(FP)
|
||||||
|
MOVL CX, ecx+8(FP)
|
||||||
|
MOVL DX, edx+12(FP)
|
||||||
|
RET
|
||||||
42
vendor/github.com/klauspost/cpuid/cpuid_amd64.s
generated
vendored
Normal file
42
vendor/github.com/klauspost/cpuid/cpuid_amd64.s
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
|
||||||
|
|
||||||
|
//+build amd64,!gccgo
|
||||||
|
|
||||||
|
// func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32)
|
||||||
|
TEXT ·asmCpuid(SB), 7, $0
|
||||||
|
XORQ CX, CX
|
||||||
|
MOVL op+0(FP), AX
|
||||||
|
CPUID
|
||||||
|
MOVL AX, eax+8(FP)
|
||||||
|
MOVL BX, ebx+12(FP)
|
||||||
|
MOVL CX, ecx+16(FP)
|
||||||
|
MOVL DX, edx+20(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
// func asmCpuidex(op, op2 uint32) (eax, ebx, ecx, edx uint32)
|
||||||
|
TEXT ·asmCpuidex(SB), 7, $0
|
||||||
|
MOVL op+0(FP), AX
|
||||||
|
MOVL op2+4(FP), CX
|
||||||
|
CPUID
|
||||||
|
MOVL AX, eax+8(FP)
|
||||||
|
MOVL BX, ebx+12(FP)
|
||||||
|
MOVL CX, ecx+16(FP)
|
||||||
|
MOVL DX, edx+20(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
// func asmXgetbv(index uint32) (eax, edx uint32)
|
||||||
|
TEXT ·asmXgetbv(SB), 7, $0
|
||||||
|
MOVL index+0(FP), CX
|
||||||
|
BYTE $0x0f; BYTE $0x01; BYTE $0xd0 // XGETBV
|
||||||
|
MOVL AX, eax+8(FP)
|
||||||
|
MOVL DX, edx+12(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
// func asmRdtscpAsm() (eax, ebx, ecx, edx uint32)
|
||||||
|
TEXT ·asmRdtscpAsm(SB), 7, $0
|
||||||
|
BYTE $0x0F; BYTE $0x01; BYTE $0xF9 // RDTSCP
|
||||||
|
MOVL AX, eax+0(FP)
|
||||||
|
MOVL BX, ebx+4(FP)
|
||||||
|
MOVL CX, ecx+8(FP)
|
||||||
|
MOVL DX, edx+12(FP)
|
||||||
|
RET
|
||||||
17
vendor/github.com/klauspost/cpuid/detect_intel.go
generated
vendored
Normal file
17
vendor/github.com/klauspost/cpuid/detect_intel.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
|
||||||
|
|
||||||
|
// +build 386,!gccgo amd64,!gccgo
|
||||||
|
|
||||||
|
package cpuid
|
||||||
|
|
||||||
|
func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32)
|
||||||
|
func asmCpuidex(op, op2 uint32) (eax, ebx, ecx, edx uint32)
|
||||||
|
func asmXgetbv(index uint32) (eax, edx uint32)
|
||||||
|
func asmRdtscpAsm() (eax, ebx, ecx, edx uint32)
|
||||||
|
|
||||||
|
func initCPU() {
|
||||||
|
cpuid = asmCpuid
|
||||||
|
cpuidex = asmCpuidex
|
||||||
|
xgetbv = asmXgetbv
|
||||||
|
rdtscpAsm = asmRdtscpAsm
|
||||||
|
}
|
||||||
23
vendor/github.com/klauspost/cpuid/detect_ref.go
generated
vendored
Normal file
23
vendor/github.com/klauspost/cpuid/detect_ref.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
|
||||||
|
|
||||||
|
// +build !amd64,!386 gccgo
|
||||||
|
|
||||||
|
package cpuid
|
||||||
|
|
||||||
|
func initCPU() {
|
||||||
|
cpuid = func(op uint32) (eax, ebx, ecx, edx uint32) {
|
||||||
|
return 0, 0, 0, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
cpuidex = func(op, op2 uint32) (eax, ebx, ecx, edx uint32) {
|
||||||
|
return 0, 0, 0, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
xgetbv = func(index uint32) (eax, edx uint32) {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
rdtscpAsm = func() (eax, ebx, ecx, edx uint32) {
|
||||||
|
return 0, 0, 0, 0
|
||||||
|
}
|
||||||
|
}
|
||||||
4
vendor/github.com/klauspost/cpuid/generate.go
generated
vendored
Normal file
4
vendor/github.com/klauspost/cpuid/generate.go
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
package cpuid
|
||||||
|
|
||||||
|
//go:generate go run private-gen.go
|
||||||
|
//go:generate gofmt -w ./private
|
||||||
476
vendor/github.com/klauspost/cpuid/private-gen.go
generated
vendored
Normal file
476
vendor/github.com/klauspost/cpuid/private-gen.go
generated
vendored
Normal file
@@ -0,0 +1,476 @@
|
|||||||
|
// +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
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user