mirror of
https://github.com/fatedier/frp.git
synced 2026-04-05 08:39:17 +08:00
Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a384bf5580 | ||
|
|
92046a7ca2 | ||
|
|
4cc5ddc012 | ||
|
|
46358d466d | ||
|
|
7da61f004b | ||
|
|
63037f1c65 | ||
|
|
cc160995da | ||
|
|
de48d97cb2 | ||
|
|
1a6a179b68 | ||
|
|
3a2946a2ff | ||
|
|
ae9a4623d9 | ||
|
|
bd1e9a3010 | ||
|
|
92fff5c191 | ||
|
|
8c65b337ca | ||
|
|
0f1005ff61 | ||
|
|
ad858a0d32 | ||
|
|
1e905839f0 | ||
|
|
bf50f932d9 | ||
|
|
673047be2c | ||
|
|
fa2b9a836c | ||
|
|
9e0fd0c4ef | ||
|
|
0559865fe5 | ||
|
|
4fc85a36c2 | ||
|
|
3f1174a519 | ||
|
|
bcbdfcb99b | ||
|
|
df046bdeeb | ||
|
|
f83447c652 | ||
|
|
9ae69b4aac | ||
|
|
c48a89731a | ||
|
|
36b58ab60c | ||
|
|
6320f15a7c | ||
|
|
066172e9c1 | ||
|
|
d5931758b6 | ||
|
|
c75c3acd21 | ||
|
|
0208ecd1d9 | ||
|
|
23e9845e65 | ||
|
|
2b1ba3a946 | ||
|
|
ee9ddf52cd | ||
|
|
d246400a71 | ||
|
|
f63a4f0cdd | ||
|
|
b743b5aaed | ||
|
|
9d9416ab94 | ||
|
|
c081df40e1 | ||
|
|
fe32a7c4bb | ||
|
|
7bb8c10647 | ||
|
|
0752508469 | ||
|
|
4cc1663a5f | ||
|
|
b55a24a27e | ||
|
|
aede4e54f8 | ||
|
|
b811a620c3 | ||
|
|
07fe05a9d5 | ||
|
|
171bc8dd22 | ||
|
|
9c175d4eb5 | ||
|
|
9f736558e2 | ||
|
|
8f071dd2c2 | ||
|
|
bcaf51a6ad | ||
|
|
ad3cf9a64a | ||
|
|
e3fc73dbc5 | ||
|
|
f884e894f2 | ||
|
|
d57ed7d3d8 | ||
|
|
a2c318d24c | ||
|
|
32f8745d61 | ||
|
|
66120fe49d | ||
|
|
fca7f42b37 | ||
|
|
5b303f5148 | ||
|
|
2a044c9d6d | ||
|
|
70e2aee46d | ||
|
|
6742fa2ea8 | ||
|
|
511503d34c | ||
|
|
1eaf17fd05 | ||
|
|
04f4fd0a81 | ||
|
|
3a4d769bb3 | ||
|
|
84341b7fcc | ||
|
|
80ba931326 | ||
|
|
7ebcc7503a | ||
|
|
74cf57feb3 |
4
.github/ISSUE_TEMPLATE
vendored
4
.github/ISSUE_TEMPLATE
vendored
@@ -1,4 +1,5 @@
|
|||||||
Issue is only used for submiting bug report and documents typo. If there are same issues or answers can be found in documents, we will close it directly.
|
Issue is only used for submiting bug report and documents typo. If there are same issues or answers can be found in documents, we will close it directly.
|
||||||
|
(为了节约时间,提高处理问题的效率,不按照格式填写的 issue 将会直接关闭。)
|
||||||
|
|
||||||
Use the commands below to provide key information from your environment:
|
Use the commands below to provide key information from your environment:
|
||||||
You do NOT have to include this information if this is a FEATURE REQUEST
|
You do NOT have to include this information if this is a FEATURE REQUEST
|
||||||
@@ -9,6 +10,9 @@ You do NOT have to include this information if this is a FEATURE REQUEST
|
|||||||
**What operating system and processor architecture are you using (`go env`)?**
|
**What operating system and processor architecture are you using (`go env`)?**
|
||||||
|
|
||||||
|
|
||||||
|
**Configures you used:**
|
||||||
|
|
||||||
|
|
||||||
**Steps to reproduce the issue:**
|
**Steps to reproduce the issue:**
|
||||||
1.
|
1.
|
||||||
2.
|
2.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ language: go
|
|||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.8.x
|
- 1.8.x
|
||||||
|
- 1.x
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- make
|
- make
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ RUN cd /go/src/github.com/fatedier/frp \
|
|||||||
&& make \
|
&& make \
|
||||||
&& mv bin/frpc /frpc \
|
&& mv bin/frpc /frpc \
|
||||||
&& mv bin/frps /frps \
|
&& mv bin/frps /frps \
|
||||||
&& mv conf/frpc_min.ini /frpc.ini \
|
&& mv conf/frpc.ini /frpc.ini \
|
||||||
&& mv conf/frps_min.ini /frps.ini \
|
&& mv conf/frps.ini /frps.ini \
|
||||||
&& make clean
|
&& make clean
|
||||||
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
|
|||||||
21
Dockerfile_multiple_build
Normal file
21
Dockerfile_multiple_build
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
FROM golang:1.8 as frpBuild
|
||||||
|
|
||||||
|
COPY . /go/src/github.com/fatedier/frp
|
||||||
|
|
||||||
|
ENV CGO_ENABLED=0
|
||||||
|
|
||||||
|
RUN cd /go/src/github.com/fatedier/frp \
|
||||||
|
&& make
|
||||||
|
|
||||||
|
FROM alpine:3.6
|
||||||
|
|
||||||
|
COPY --from=frpBuild /go/src/github.com/fatedier/frp/bin/frpc /
|
||||||
|
COPY --from=frpBuild /go/src/github.com/fatedier/frp/conf/frpc.ini /
|
||||||
|
COPY --from=frpBuild /go/src/github.com/fatedier/frp/bin/frps /
|
||||||
|
COPY --from=frpBuild /go/src/github.com/fatedier/frp/conf/frps.ini /
|
||||||
|
|
||||||
|
EXPOSE 80 443 6000 7000 7500
|
||||||
|
|
||||||
|
WORKDIR /
|
||||||
|
|
||||||
|
CMD ["/frps","-c","frps.ini"]
|
||||||
67
Godeps/Godeps.json
generated
67
Godeps/Godeps.json
generated
@@ -1,67 +0,0 @@
|
|||||||
{
|
|
||||||
"ImportPath": "github.com/fatedier/frp",
|
|
||||||
"GoVersion": "go1.8",
|
|
||||||
"GodepVersion": "v79",
|
|
||||||
"Packages": [
|
|
||||||
"./..."
|
|
||||||
],
|
|
||||||
"Deps": [
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/davecgh/go-spew/spew",
|
|
||||||
"Comment": "v1.1.0",
|
|
||||||
"Rev": "346938d642f2ec3594ed81d874461961cd0faa76"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/docopt/docopt-go",
|
|
||||||
"Comment": "0.6.2",
|
|
||||||
"Rev": "784ddc588536785e7299f7272f39101f7faccc3f"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/fatedier/beego/logs",
|
|
||||||
"Comment": "v1.7.2-72-gf73c369",
|
|
||||||
"Rev": "f73c3692bbd70a83728cb59b2c0423ff95e4ecea"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/golang/snappy",
|
|
||||||
"Rev": "5979233c5d6225d4a8e438cdd0b411888449ddab"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/julienschmidt/httprouter",
|
|
||||||
"Comment": "v1.1-41-g8a45e95",
|
|
||||||
"Rev": "8a45e95fc75cb77048068a62daed98cc22fdac7c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/pkg/errors",
|
|
||||||
"Comment": "v0.8.0-5-gc605e28",
|
|
||||||
"Rev": "c605e284fe17294bda444b34710735b29d1a9d90"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/pmezard/go-difflib/difflib",
|
|
||||||
"Comment": "v1.0.0",
|
|
||||||
"Rev": "792786c7400a136282c1664665ae0a8db921c6c2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/rakyll/statik/fs",
|
|
||||||
"Comment": "v0.1.0",
|
|
||||||
"Rev": "274df120e9065bdd08eb1120e0375e3dc1ae8465"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/stretchr/testify/assert",
|
|
||||||
"Comment": "v1.1.4-25-g2402e8e",
|
|
||||||
"Rev": "2402e8e7a02fc811447d11f881aa9746cdc57983"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/vaughan0/go-ini",
|
|
||||||
"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/xtaci/smux",
|
|
||||||
"Comment": "v1.0.5-8-g2de5471",
|
|
||||||
"Rev": "2de5471dfcbc029f5fe1392b83fe784127c4943e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/crypto/pbkdf2",
|
|
||||||
"Rev": "1f22c0103821b9390939b6776727195525381532"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
7
Makefile
7
Makefile
@@ -15,7 +15,12 @@ file:
|
|||||||
go generate ./assets/...
|
go generate ./assets/...
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
go fmt ./...
|
go fmt ./assets/...
|
||||||
|
go fmt ./client/...
|
||||||
|
go fmt ./cmd/...
|
||||||
|
go fmt ./models/...
|
||||||
|
go fmt ./server/...
|
||||||
|
go fmt ./utils/...
|
||||||
|
|
||||||
frps:
|
frps:
|
||||||
go build -o bin/frps ./cmd/frps
|
go build -o bin/frps ./cmd/frps
|
||||||
|
|||||||
249
README.md
249
README.md
@@ -11,6 +11,7 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
|
|||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
<!-- vim-markdown-toc GFM -->
|
<!-- vim-markdown-toc GFM -->
|
||||||
|
|
||||||
* [What can I do with frp?](#what-can-i-do-with-frp)
|
* [What can I do with frp?](#what-can-i-do-with-frp)
|
||||||
* [Status](#status)
|
* [Status](#status)
|
||||||
* [Architecture](#architecture)
|
* [Architecture](#architecture)
|
||||||
@@ -18,24 +19,33 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
|
|||||||
* [Access your computer in LAN by SSH](#access-your-computer-in-lan-by-ssh)
|
* [Access your computer in LAN by SSH](#access-your-computer-in-lan-by-ssh)
|
||||||
* [Visit your web service in LAN by custom domains](#visit-your-web-service-in-lan-by-custom-domains)
|
* [Visit your web service in LAN by custom domains](#visit-your-web-service-in-lan-by-custom-domains)
|
||||||
* [Forward DNS query request](#forward-dns-query-request)
|
* [Forward DNS query request](#forward-dns-query-request)
|
||||||
|
* [Forward unix domain socket](#forward-unix-domain-socket)
|
||||||
|
* [Expose your service in security](#expose-your-service-in-security)
|
||||||
|
* [P2P Mode](#p2p-mode)
|
||||||
|
* [Connect website through frpc's network](#connect-website-through-frpcs-network)
|
||||||
* [Features](#features)
|
* [Features](#features)
|
||||||
|
* [Configuration File](#configuration-file)
|
||||||
* [Dashboard](#dashboard)
|
* [Dashboard](#dashboard)
|
||||||
* [Authentication](#authentication)
|
* [Authentication](#authentication)
|
||||||
* [Encryption and Compression](#encryption-and-compression)
|
* [Encryption and Compression](#encryption-and-compression)
|
||||||
* [Reload configures without frps stopped](#reload-configures-without-frps-stopped)
|
* [Hot-Reload frpc configuration](#hot-reload-frpc-configuration)
|
||||||
* [Privilege Mode](#privilege-mode)
|
* [Privilege Mode](#privilege-mode)
|
||||||
* [Port White List](#port-white-list)
|
* [Port White List](#port-white-list)
|
||||||
* [TCP Stream Multiplexing](#tcp-stream-multiplexing)
|
* [TCP Stream Multiplexing](#tcp-stream-multiplexing)
|
||||||
|
* [Support KCP Protocol](#support-kcp-protocol)
|
||||||
* [Connection Pool](#connection-pool)
|
* [Connection Pool](#connection-pool)
|
||||||
* [Rewriting the Host Header](#rewriting-the-host-header)
|
* [Rewriting the Host Header](#rewriting-the-host-header)
|
||||||
|
* [Get Real IP](#get-real-ip)
|
||||||
* [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)
|
||||||
* [Connect frps by HTTP PROXY](#connect-frps-by-http-proxy)
|
* [Connect frps by HTTP PROXY](#connect-frps-by-http-proxy)
|
||||||
|
* [Plugin](#plugin)
|
||||||
* [Development Plan](#development-plan)
|
* [Development Plan](#development-plan)
|
||||||
* [Contributing](#contributing)
|
* [Contributing](#contributing)
|
||||||
* [Donation](#donation)
|
* [Donation](#donation)
|
||||||
* [AliPay](#alipay)
|
* [AliPay](#alipay)
|
||||||
|
* [Wechat Pay](#wechat-pay)
|
||||||
* [Paypal](#paypal)
|
* [Paypal](#paypal)
|
||||||
|
|
||||||
<!-- vim-markdown-toc -->
|
<!-- vim-markdown-toc -->
|
||||||
@@ -143,7 +153,7 @@ However, we can expose a http or https service using frp.
|
|||||||
|
|
||||||
### Forward DNS query request
|
### Forward DNS query request
|
||||||
|
|
||||||
1. Modify frps.ini, configure a reverse proxy named [dns]:
|
1. Modify frps.ini:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frps.ini
|
# frps.ini
|
||||||
@@ -176,10 +186,155 @@ However, we can expose a http or https service using frp.
|
|||||||
|
|
||||||
5. Send dns query request by dig:
|
5. Send dns query request by dig:
|
||||||
|
|
||||||
`dig @x.x.x.x -p 6000 www.goolge.com`
|
`dig @x.x.x.x -p 6000 www.google.com`
|
||||||
|
|
||||||
|
### Forward unix domain socket
|
||||||
|
|
||||||
|
Using tcp port to connect unix domain socket like docker daemon.
|
||||||
|
|
||||||
|
Configure frps same as above.
|
||||||
|
|
||||||
|
1. Start frpc with configurations:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[unix_domain_socket]
|
||||||
|
type = tcp
|
||||||
|
remote_port = 6000
|
||||||
|
plugin = unix_domain_socket
|
||||||
|
plugin_unix_path = /var/run/docker.sock
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Get docker version by curl command:
|
||||||
|
|
||||||
|
`curl http://x.x.x.x:6000/version`
|
||||||
|
|
||||||
|
### Expose your service in security
|
||||||
|
|
||||||
|
For some services, if expose them to the public network directly will be a security risk.
|
||||||
|
|
||||||
|
**stcp(secret tcp)** help you create a proxy avoiding any one can access it.
|
||||||
|
|
||||||
|
Configure frps same as above.
|
||||||
|
|
||||||
|
1. Start frpc, forward ssh port and `remote_port` is useless:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[secret_ssh]
|
||||||
|
type = stcp
|
||||||
|
sk = abcdefg
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 22
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Start another frpc in which you want to connect this ssh server:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[secret_ssh_visitor]
|
||||||
|
type = stcp
|
||||||
|
role = visitor
|
||||||
|
server_name = secret_ssh
|
||||||
|
sk = abcdefg
|
||||||
|
bind_addr = 127.0.0.1
|
||||||
|
bind_port = 6000
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Connect to server in LAN by ssh assuming that username is test:
|
||||||
|
|
||||||
|
`ssh -oPort=6000 test@127.0.0.1`
|
||||||
|
|
||||||
|
### P2P Mode
|
||||||
|
|
||||||
|
**xtcp** is designed for transmitting a large amount of data directly between two client.
|
||||||
|
|
||||||
|
Now it can't penetrate all types of NAT devices. You can try **stcp** if **xtcp** doesn't work.
|
||||||
|
|
||||||
|
1. Configure a udp port for xtcp:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
bind_udp_port = 7001
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Start frpc, forward ssh port and `remote_port` is useless:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[p2p_ssh]
|
||||||
|
type = xtcp
|
||||||
|
sk = abcdefg
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 22
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Start another frpc in which you want to connect this ssh server:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[p2p_ssh_visitor]
|
||||||
|
type = xtcp
|
||||||
|
role = visitor
|
||||||
|
server_name = p2p_ssh
|
||||||
|
sk = abcdefg
|
||||||
|
bind_addr = 127.0.0.1
|
||||||
|
bind_port = 6000
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Connect to server in LAN by ssh assuming that username is test:
|
||||||
|
|
||||||
|
`ssh -oPort=6000 test@127.0.0.1`
|
||||||
|
|
||||||
|
### Connect website through frpc's network
|
||||||
|
|
||||||
|
Configure frps same as above.
|
||||||
|
|
||||||
|
1. Start frpc with configurations:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[http_proxy]
|
||||||
|
type = tcp
|
||||||
|
remote_port = 6000
|
||||||
|
plugin = http_proxy # or socks5
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Set http proxy or socks5 proxy `x.x.x.x:6000` in your browser and visit website through frpc's network.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
### Configuration File
|
||||||
|
|
||||||
|
You can find features which this document not metioned from full example configuration files.
|
||||||
|
|
||||||
|
[frps full configuration file](./conf/frps_full.ini)
|
||||||
|
|
||||||
|
[frpc full configuration file](./conf/frpc_full.ini)
|
||||||
|
|
||||||
### Dashboard
|
### Dashboard
|
||||||
|
|
||||||
Check frp's status and proxies's statistics information by Dashboard.
|
Check frp's status and proxies's statistics information by Dashboard.
|
||||||
@@ -220,9 +375,20 @@ use_encryption = true
|
|||||||
use_compression = true
|
use_compression = true
|
||||||
```
|
```
|
||||||
|
|
||||||
### Reload configures without frps stopped
|
### Hot-Reload frpc configuration
|
||||||
|
|
||||||
This feature is removed since v0.10.0.
|
First you need to set admin port in frpc's configure file to let it provide HTTP API for more features.
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
admin_addr = 127.0.0.1
|
||||||
|
admin_port = 7400
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run command `frpc -c ./frpc.ini --reload` and wait for about 10 seconds to let frpc create or update or delete proxies.
|
||||||
|
|
||||||
|
**Note that parameters in [common] section won't be modified except 'start' now.**
|
||||||
|
|
||||||
### Privilege Mode
|
### Privilege Mode
|
||||||
|
|
||||||
@@ -252,6 +418,35 @@ You can disable this feature by modify frps.ini and frpc.ini:
|
|||||||
tcp_mux = false
|
tcp_mux = false
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
Using kcp in frp:
|
||||||
|
|
||||||
|
1. Enable kcp protocol in frps:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frps.ini
|
||||||
|
[common]
|
||||||
|
bind_port = 7000
|
||||||
|
# kcp needs to bind a udp port, it can be same with 'bind_port'
|
||||||
|
kcp_bind_port = 7000
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Configure the protocol used in frpc to connect frps:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
# specify the 'kcp_bind_port' in frps
|
||||||
|
server_port = 7000
|
||||||
|
protocol = kcp
|
||||||
|
```
|
||||||
|
|
||||||
### Connection Pool
|
### Connection Pool
|
||||||
|
|
||||||
By default, frps send message to frpc for create a new connection to backward service when getting an user request.If a proxy's connection pool is enabled, there will be a specified number of connections pre-established.
|
By default, frps send message to frpc for create a new connection to backward service when getting an user request.If a proxy's connection pool is enabled, there will be a specified number of connections pre-established.
|
||||||
@@ -289,6 +484,14 @@ host_header_rewrite = dev.yourdomain.com
|
|||||||
|
|
||||||
If `host_header_rewrite` is specified, the Host header will be rewritten to match the hostname portion of the forwarding address.
|
If `host_header_rewrite` is specified, the Host header will be rewritten to match the hostname portion of the forwarding address.
|
||||||
|
|
||||||
|
### Get Real IP
|
||||||
|
|
||||||
|
Features for http proxy only.
|
||||||
|
|
||||||
|
You can get user's real IP from http request header `X-Forwarded-For` and `X-Real-IP`.
|
||||||
|
|
||||||
|
**Note that now you can only get these two headers in first request of each user connection.**
|
||||||
|
|
||||||
### Password protecting your web service
|
### Password protecting your web service
|
||||||
|
|
||||||
Anyone who can guess your tunnel URL can access your local web server unless you protect it with a password.
|
Anyone who can guess your tunnel URL can access your local web server unless you protect it with a password.
|
||||||
@@ -358,22 +561,46 @@ Http requests with url prefix `/news` and `/about` will be forwarded to **web02*
|
|||||||
|
|
||||||
frpc can connect frps using HTTP PROXY if you set os environment `HTTP_PROXY` or configure `http_proxy` param in frpc.ini file.
|
frpc can connect frps using HTTP PROXY if you set os environment `HTTP_PROXY` or configure `http_proxy` param in frpc.ini file.
|
||||||
|
|
||||||
|
It only works when protocol is tcp.
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
|
[common]
|
||||||
server_addr = x.x.x.x
|
server_addr = x.x.x.x
|
||||||
server_port = 7000
|
server_port = 7000
|
||||||
http_proxy = http://user:pwd@192.168.1.128:8080
|
http_proxy = http://user:pwd@192.168.1.128:8080
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Plugin
|
||||||
|
|
||||||
|
frpc only forward request to local tcp or udp port by default.
|
||||||
|
|
||||||
|
Plugin is used for providing rich features. There are built-in plugins such as **unix_domain_socket**, **http_proxy**, **socks5** and you can see [example usage](#example-usage).
|
||||||
|
|
||||||
|
Specify which plugin to use by `plugin` parameter. Configuration parameters of plugin should be started with `plugin_`. `local_ip` and `local_port` is useless for plugin.
|
||||||
|
|
||||||
|
Using plugin **http_proxy**:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[http_proxy]
|
||||||
|
type = tcp
|
||||||
|
remote_port = 6000
|
||||||
|
plugin = http_proxy
|
||||||
|
plugin_http_user = abc
|
||||||
|
plugin_http_passwd = abc
|
||||||
|
```
|
||||||
|
|
||||||
|
`plugin_http_user` and `plugin_http_passwd` are configuration parameters used in `http_proxy` plugin.
|
||||||
|
|
||||||
|
|
||||||
## Development Plan
|
## Development Plan
|
||||||
|
|
||||||
* Log http request information in frps.
|
* Log http request information in frps.
|
||||||
* Direct reverse proxy, like haproxy.
|
* Direct reverse proxy, like haproxy.
|
||||||
* Load balance to different service in frpc.
|
* Load balance to different service in frpc.
|
||||||
* Frpc can directly be a webserver for static files.
|
* Frpc can directly be a webserver for static files.
|
||||||
* Full control mode, dynamically modify frpc's configure with dashboard in frps.
|
* P2p communicate by making udp hole to penetrate NAT.
|
||||||
* P2p communicate by make udp hole to penetrate NAT.
|
|
||||||
* Client Plugin (http proxy).
|
|
||||||
* kubernetes ingress support.
|
* kubernetes ingress support.
|
||||||
|
|
||||||
|
|
||||||
@@ -384,7 +611,7 @@ Interested in getting involved? We would like to help you!
|
|||||||
* Take a look at our [issues list](https://github.com/fatedier/frp/issues) and consider sending a Pull Request to **dev branch**.
|
* Take a look at our [issues list](https://github.com/fatedier/frp/issues) and consider sending a Pull Request to **dev branch**.
|
||||||
* If you want to add a new feature, please create an issue first to describe the new feature, as well as the implementation approach. Once a proposal is accepted, create an implementation of the new features and submit it as a pull request.
|
* If you want to add a new feature, please create an issue first to describe the new feature, as well as the implementation approach. Once a proposal is accepted, create an implementation of the new features and submit it as a pull request.
|
||||||
* Sorry for my poor english and improvement for this document is welcome even some typo fix.
|
* Sorry for my poor english and improvement for this document is welcome even some typo fix.
|
||||||
* If you have some wanderful ideas, send email to fatedier@gmail.com.
|
* If you have some wonderful ideas, send email to fatedier@gmail.com.
|
||||||
|
|
||||||
**Note: We prefer you to give your advise in [issues](https://github.com/fatedier/frp/issues), so others with a same question can search it quickly and we don't need to answer them repeatly.**
|
**Note: We prefer you to give your advise in [issues](https://github.com/fatedier/frp/issues), so others with a same question can search it quickly and we don't need to answer them repeatly.**
|
||||||
|
|
||||||
@@ -398,6 +625,10 @@ frp QQ group: 606194980
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### Wechat Pay
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
### Paypal
|
### Paypal
|
||||||
|
|
||||||
Donate money by [paypal](https://www.paypal.me/fatedier) to my account **fatedier@gmail.com**.
|
Donate money by [paypal](https://www.paypal.me/fatedier) to my account **fatedier@gmail.com**.
|
||||||
|
|||||||
260
README_zh.md
260
README_zh.md
@@ -9,6 +9,7 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
|
|||||||
## 目录
|
## 目录
|
||||||
|
|
||||||
<!-- vim-markdown-toc GFM -->
|
<!-- vim-markdown-toc GFM -->
|
||||||
|
|
||||||
* [frp 的作用](#frp-的作用)
|
* [frp 的作用](#frp-的作用)
|
||||||
* [开发状态](#开发状态)
|
* [开发状态](#开发状态)
|
||||||
* [架构](#架构)
|
* [架构](#架构)
|
||||||
@@ -16,24 +17,33 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
|
|||||||
* [通过 ssh 访问公司内网机器](#通过-ssh-访问公司内网机器)
|
* [通过 ssh 访问公司内网机器](#通过-ssh-访问公司内网机器)
|
||||||
* [通过自定义域名访问部署于内网的 web 服务](#通过自定义域名访问部署于内网的-web-服务)
|
* [通过自定义域名访问部署于内网的 web 服务](#通过自定义域名访问部署于内网的-web-服务)
|
||||||
* [转发 DNS 查询请求](#转发-dns-查询请求)
|
* [转发 DNS 查询请求](#转发-dns-查询请求)
|
||||||
|
* [转发 Unix域套接字](#转发-unix域套接字)
|
||||||
|
* [安全地暴露内网服务](#安全地暴露内网服务)
|
||||||
|
* [点对点内网穿透](#点对点内网穿透)
|
||||||
|
* [通过 frpc 所在机器访问外网](#通过-frpc-所在机器访问外网)
|
||||||
* [功能说明](#功能说明)
|
* [功能说明](#功能说明)
|
||||||
|
* [配置文件](#配置文件)
|
||||||
* [Dashboard](#dashboard)
|
* [Dashboard](#dashboard)
|
||||||
* [身份验证](#身份验证)
|
* [身份验证](#身份验证)
|
||||||
* [加密与压缩](#加密与压缩)
|
* [加密与压缩](#加密与压缩)
|
||||||
* [服务器端热加载配置文件](#服务器端热加载配置文件)
|
* [客户端热加载配置文件](#客户端热加载配置文件)
|
||||||
* [特权模式](#特权模式)
|
* [特权模式](#特权模式)
|
||||||
* [端口白名单](#端口白名单)
|
* [端口白名单](#端口白名单)
|
||||||
* [TCP 多路复用](#tcp-多路复用)
|
* [TCP 多路复用](#tcp-多路复用)
|
||||||
|
* [底层通信可选 kcp 协议](#底层通信可选-kcp-协议)
|
||||||
* [连接池](#连接池)
|
* [连接池](#连接池)
|
||||||
* [修改 Host Header](#修改-host-header)
|
* [修改 Host Header](#修改-host-header)
|
||||||
|
* [获取用户真实 IP](#获取用户真实-ip)
|
||||||
* [通过密码保护你的 web 服务](#通过密码保护你的-web-服务)
|
* [通过密码保护你的 web 服务](#通过密码保护你的-web-服务)
|
||||||
* [自定义二级域名](#自定义二级域名)
|
* [自定义二级域名](#自定义二级域名)
|
||||||
* [URL 路由](#url-路由)
|
* [URL 路由](#url-路由)
|
||||||
* [通过代理连接 frps](#通过代理连接-frps)
|
* [通过代理连接 frps](#通过代理连接-frps)
|
||||||
|
* [插件](#插件)
|
||||||
* [开发计划](#开发计划)
|
* [开发计划](#开发计划)
|
||||||
* [为 frp 做贡献](#为-frp-做贡献)
|
* [为 frp 做贡献](#为-frp-做贡献)
|
||||||
* [捐助](#捐助)
|
* [捐助](#捐助)
|
||||||
* [支付宝扫码捐赠](#支付宝扫码捐赠)
|
* [支付宝扫码捐赠](#支付宝扫码捐赠)
|
||||||
|
* [微信支付捐赠](#微信支付捐赠)
|
||||||
* [Paypal 捐赠](#paypal-捐赠)
|
* [Paypal 捐赠](#paypal-捐赠)
|
||||||
|
|
||||||
<!-- vim-markdown-toc -->
|
<!-- vim-markdown-toc -->
|
||||||
@@ -177,10 +187,169 @@ DNS 查询请求通常使用 UDP 协议,frp 支持对内网 UDP 服务的穿
|
|||||||
|
|
||||||
5. 通过 dig 测试 UDP 包转发是否成功,预期会返回 `www.google.com` 域名的解析结果:
|
5. 通过 dig 测试 UDP 包转发是否成功,预期会返回 `www.google.com` 域名的解析结果:
|
||||||
|
|
||||||
`dig @x.x.x.x -p 6000 www.goolge.com`
|
`dig @x.x.x.x -p 6000 www.google.com`
|
||||||
|
|
||||||
|
### 转发 Unix域套接字
|
||||||
|
|
||||||
|
通过 tcp 端口访问内网的 unix域套接字(和 docker daemon 通信)。
|
||||||
|
|
||||||
|
frps 的部署步骤同上。
|
||||||
|
|
||||||
|
1. 启动 frpc,启用 unix_domain_socket 插件,配置如下:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[unix_domain_socket]
|
||||||
|
type = tcp
|
||||||
|
remote_port = 6000
|
||||||
|
plugin = unix_domain_socket
|
||||||
|
plugin_unix_path = /var/run/docker.sock
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 通过 curl 命令查看 docker 版本信息
|
||||||
|
|
||||||
|
`curl http://x.x.x.x:6000/version`
|
||||||
|
|
||||||
|
### 安全地暴露内网服务
|
||||||
|
|
||||||
|
对于某些服务来说如果直接暴露于公网上将会存在安全隐患。
|
||||||
|
|
||||||
|
使用 **stcp(secret tcp)** 类型的代理可以避免让任何人都能访问到要穿透的服务,但是访问者也需要运行另外一个 frpc。
|
||||||
|
|
||||||
|
以下示例将会创建一个只有自己能访问到的 ssh 服务代理。
|
||||||
|
|
||||||
|
frps 的部署步骤同上。
|
||||||
|
|
||||||
|
1. 启动 frpc,转发内网的 ssh 服务,配置如下,不需要指定远程端口:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[secret_ssh]
|
||||||
|
type = stcp
|
||||||
|
# 只有 sk 一致的用户才能访问到此服务
|
||||||
|
sk = abcdefg
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 22
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 在要访问这个服务的机器上启动另外一个 frpc,配置如下:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[secret_ssh_visitor]
|
||||||
|
type = stcp
|
||||||
|
# stcp 的访问者
|
||||||
|
role = visitor
|
||||||
|
# 要访问的 stcp 代理的名字
|
||||||
|
server_name = secret_ssh
|
||||||
|
sk = abcdefg
|
||||||
|
# 绑定本地端口用于访问 ssh 服务
|
||||||
|
bind_addr = 127.0.0.1
|
||||||
|
bind_port = 6000
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 通过 ssh 访问内网机器,假设用户名为 test:
|
||||||
|
|
||||||
|
`ssh -oPort=6000 test@127.0.0.1`
|
||||||
|
|
||||||
|
### 点对点内网穿透
|
||||||
|
|
||||||
|
frp 提供了一种新的代理类型 **xtcp** 用于应对在希望传输大量数据且流量不经过服务器的场景。
|
||||||
|
|
||||||
|
使用方式同 **stcp** 类似,需要在两边都部署上 frpc 用于建立直接的连接。
|
||||||
|
|
||||||
|
目前处于开发的初级阶段,并不能穿透所有类型的 NAT 设备,所以穿透成功率较低。穿透失败时可以尝试 **stcp** 的方式。
|
||||||
|
|
||||||
|
1. frps 除正常配置外需要额外配置一个 udp 端口用于支持该类型的客户端:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
bind_udp_port = 7001
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 启动 frpc,转发内网的 ssh 服务,配置如下,不需要指定远程端口:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[p2p_ssh]
|
||||||
|
type = xtcp
|
||||||
|
# 只有 sk 一致的用户才能访问到此服务
|
||||||
|
sk = abcdefg
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 22
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 在要访问这个服务的机器上启动另外一个 frpc,配置如下:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[p2p_ssh_visitor]
|
||||||
|
type = xtcp
|
||||||
|
# xtcp 的访问者
|
||||||
|
role = visitor
|
||||||
|
# 要访问的 xtcp 代理的名字
|
||||||
|
server_name = p2p_ssh
|
||||||
|
sk = abcdefg
|
||||||
|
# 绑定本地端口用于访问 ssh 服务
|
||||||
|
bind_addr = 127.0.0.1
|
||||||
|
bind_port = 6000
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 通过 ssh 访问内网机器,假设用户名为 test:
|
||||||
|
|
||||||
|
`ssh -oPort=6000 test@127.0.0.1`
|
||||||
|
|
||||||
|
### 通过 frpc 所在机器访问外网
|
||||||
|
|
||||||
|
frpc 内置了 http proxy 和 socks5 插件,可以使其他机器通过 frpc 的网络访问互联网。
|
||||||
|
|
||||||
|
frps 的部署步骤同上。
|
||||||
|
|
||||||
|
1. 启动 frpc,启用 http_proxy 或 socks5 插件(plugin 换为 socks5 即可), 配置如下:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[http_proxy]
|
||||||
|
type = tcp
|
||||||
|
remote_port = 6000
|
||||||
|
plugin = http_proxy
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 浏览器设置 http 或 socks5 代理地址为 `x.x.x.x:6000`,通过 frpc 机器的网络访问互联网。
|
||||||
|
|
||||||
## 功能说明
|
## 功能说明
|
||||||
|
|
||||||
|
### 配置文件
|
||||||
|
|
||||||
|
由于 frp 目前支持的功能和配置项较多,未在文档中列出的功能可以从完整的示例配置文件中发现。
|
||||||
|
|
||||||
|
[frps 完整配置文件](./conf/frps_full.ini)
|
||||||
|
|
||||||
|
[frpc 完整配置文件](./conf/frpc_full.ini)
|
||||||
|
|
||||||
### Dashboard
|
### Dashboard
|
||||||
|
|
||||||
通过浏览器查看 frp 的状态以及代理统计信息展示。
|
通过浏览器查看 frp 的状态以及代理统计信息展示。
|
||||||
@@ -225,9 +394,26 @@ use_compression = true
|
|||||||
|
|
||||||
如果传输的报文长度较长,通过设置 `use_compression = true` 对传输内容进行压缩,可以有效减小 frpc 与 frps 之间的网络流量,加快流量转发速度,但是会额外消耗一些 cpu 资源。
|
如果传输的报文长度较长,通过设置 `use_compression = true` 对传输内容进行压缩,可以有效减小 frpc 与 frps 之间的网络流量,加快流量转发速度,但是会额外消耗一些 cpu 资源。
|
||||||
|
|
||||||
### 服务器端热加载配置文件
|
### 客户端热加载配置文件
|
||||||
|
|
||||||
由于从 v0.10.0 版本开始,所有 proxy 都在客户端配置,这个功能暂时移除。
|
当修改了 frpc 中的代理配置,可以通过 `frpc --reload` 命令来动态加载配置文件,通常会在 10 秒内完成代理的更新。
|
||||||
|
|
||||||
|
启用此功能需要在 frpc 中启用 admin 端口,用于提供 API 服务。配置如下:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
admin_addr = 127.0.0.1
|
||||||
|
admin_port = 7400
|
||||||
|
```
|
||||||
|
|
||||||
|
之后执行重启命令:
|
||||||
|
|
||||||
|
`frpc -c ./frpc.ini --reload`
|
||||||
|
|
||||||
|
等待一段时间后客户端会根据新的配置文件创建、更新、删除代理。
|
||||||
|
|
||||||
|
**需要注意的是,[common] 中的参数除了 start 外目前无法被修改。**
|
||||||
|
|
||||||
### 特权模式
|
### 特权模式
|
||||||
|
|
||||||
@@ -257,6 +443,35 @@ privilege_allow_ports 可以配置允许使用的某个指定端口或者是一
|
|||||||
tcp_mux = false
|
tcp_mux = false
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 底层通信可选 kcp 协议
|
||||||
|
|
||||||
|
从 v0.12.0 版本开始,底层通信协议支持选择 kcp 协议,在弱网环境下传输效率提升明显,但是会有一些额外的流量消耗。
|
||||||
|
|
||||||
|
开启 kcp 协议支持:
|
||||||
|
|
||||||
|
1. 在 frps.ini 中启用 kcp 协议支持,指定一个 udp 端口用于接收客户端请求:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frps.ini
|
||||||
|
[common]
|
||||||
|
bind_port = 7000
|
||||||
|
# kcp 绑定的是 udp 端口,可以和 bind_port 一样
|
||||||
|
kcp_bind_port = 7000
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 在 frpc.ini 指定需要使用的协议类型,目前只支持 tcp 和 kcp。其他代理配置不需要变更:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
# server_port 指定为 frps 的 kcp_bind_port
|
||||||
|
server_port = 7000
|
||||||
|
protocol = kcp
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 像之前一样使用 frp,需要注意开放相关机器上的 udp 的端口的访问权限。
|
||||||
|
|
||||||
### 连接池
|
### 连接池
|
||||||
|
|
||||||
默认情况下,当用户请求建立连接后,frps 才会请求 frpc 主动与后端服务建立一个连接。当为指定的代理启用连接池后,frp 会预先和后端服务建立起指定数量的连接,每次接收到用户请求后,会从连接池中取出一个连接和用户连接关联起来,避免了等待与后端服务建立连接以及 frpc 和 frps 之间传递控制信息的时间。
|
默认情况下,当用户请求建立连接后,frps 才会请求 frpc 主动与后端服务建立一个连接。当为指定的代理启用连接池后,frp 会预先和后端服务建立起指定数量的连接,每次接收到用户请求后,会从连接池中取出一个连接和用户连接关联起来,避免了等待与后端服务建立连接以及 frpc 和 frps 之间传递控制信息的时间。
|
||||||
@@ -294,6 +509,12 @@ host_header_rewrite = dev.yourdomain.com
|
|||||||
|
|
||||||
原来 http 请求中的 host 字段 `test.yourdomain.com` 转发到后端服务时会被替换为 `dev.yourdomain.com`。
|
原来 http 请求中的 host 字段 `test.yourdomain.com` 转发到后端服务时会被替换为 `dev.yourdomain.com`。
|
||||||
|
|
||||||
|
### 获取用户真实 IP
|
||||||
|
|
||||||
|
目前只有 **http** 类型的代理支持这一功能,可以通过用户请求的 header 中的 `X-Forwarded-For` 和 `X-Real-IP` 来获取用户真实 IP。
|
||||||
|
|
||||||
|
**需要注意的是,目前只在每一个用户连接的第一个 HTTP 请求中添加了这两个 header。**
|
||||||
|
|
||||||
### 通过密码保护你的 web 服务
|
### 通过密码保护你的 web 服务
|
||||||
|
|
||||||
由于所有客户端共用一个 frps 的 http 服务端口,任何知道你的域名和 url 的人都能访问到你部署在内网的 web 服务,但是在某些场景下需要确保只有限定的用户才能访问。
|
由于所有客户端共用一个 frps 的 http 服务端口,任何知道你的域名和 url 的人都能访问到你部署在内网的 web 服务,但是在某些场景下需要确保只有限定的用户才能访问。
|
||||||
@@ -373,13 +594,38 @@ locations = /news,/about
|
|||||||
|
|
||||||
可以通过设置 `HTTP_PROXY` 系统环境变量或者通过在 frpc 的配置文件中设置 `http_proxy` 参数来使用此功能。
|
可以通过设置 `HTTP_PROXY` 系统环境变量或者通过在 frpc 的配置文件中设置 `http_proxy` 参数来使用此功能。
|
||||||
|
|
||||||
|
仅在 `protocol = tcp` 时生效。
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
# frpc.ini
|
# frpc.ini
|
||||||
|
[common]
|
||||||
server_addr = x.x.x.x
|
server_addr = x.x.x.x
|
||||||
server_port = 7000
|
server_port = 7000
|
||||||
http_proxy = http://user:pwd@192.168.1.128:8080
|
http_proxy = http://user:pwd@192.168.1.128:8080
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 插件
|
||||||
|
|
||||||
|
默认情况下,frpc 只会转发请求到本地 tcp 或 udp 端口。
|
||||||
|
|
||||||
|
插件模式是为了在客户端提供更加丰富的功能,目前内置的插件有 **unix_domain_socket**、**http_proxy**、**socks5**。具体使用方式请查看[使用示例](#使用示例)。
|
||||||
|
|
||||||
|
通过 `plugin` 指定需要使用的插件,插件的配置参数都以 `plugin_` 开头。使用插件后 `local_ip` 和 `local_port` 不再需要配置。
|
||||||
|
|
||||||
|
使用 **http_proxy** 插件的示例:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[http_proxy]
|
||||||
|
type = tcp
|
||||||
|
remote_port = 6000
|
||||||
|
plugin = http_proxy
|
||||||
|
plugin_http_user = abc
|
||||||
|
plugin_http_passwd = abc
|
||||||
|
```
|
||||||
|
|
||||||
|
`plugin_http_user` 和 `plugin_http_passwd` 即为 `http_proxy` 插件可选的配置参数。
|
||||||
|
|
||||||
## 开发计划
|
## 开发计划
|
||||||
|
|
||||||
计划在后续版本中加入的功能与优化,排名不分先后,如果有其他功能建议欢迎在 [issues](https://github.com/fatedier/frp/issues) 中反馈。
|
计划在后续版本中加入的功能与优化,排名不分先后,如果有其他功能建议欢迎在 [issues](https://github.com/fatedier/frp/issues) 中反馈。
|
||||||
@@ -388,9 +634,7 @@ http_proxy = http://user:pwd@192.168.1.128:8080
|
|||||||
* frps 支持直接反向代理,类似 haproxy。
|
* frps 支持直接反向代理,类似 haproxy。
|
||||||
* frpc 支持负载均衡到后端不同服务。
|
* frpc 支持负载均衡到后端不同服务。
|
||||||
* frpc 支持直接作为 webserver 访问指定静态页面。
|
* frpc 支持直接作为 webserver 访问指定静态页面。
|
||||||
* frpc 完全控制模式,通过 dashboard 对 frpc 进行在线操作。
|
|
||||||
* 支持 udp 打洞的方式,提供两边内网机器直接通信,流量不经过服务器转发。
|
* 支持 udp 打洞的方式,提供两边内网机器直接通信,流量不经过服务器转发。
|
||||||
* 支持 plugin,frpc 获取到的连接可以交给指定 plugin 处理,例如 http 代理,简单的 web server。
|
|
||||||
* 集成对 k8s 等平台的支持。
|
* 集成对 k8s 等平台的支持。
|
||||||
|
|
||||||
## 为 frp 做贡献
|
## 为 frp 做贡献
|
||||||
@@ -416,6 +660,10 @@ frp 交流群:606194980 (QQ 群号)
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### 微信支付捐赠
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
### Paypal 捐赠
|
### Paypal 捐赠
|
||||||
|
|
||||||
海外用户推荐通过 [Paypal](https://www.paypal.me/fatedier) 向我的账户 **fatedier@gmail.com** 进行捐赠。
|
海外用户推荐通过 [Paypal](https://www.paypal.me/fatedier) 向我的账户 **fatedier@gmail.com** 进行捐赠。
|
||||||
|
|||||||
60
client/admin.go
Normal file
60
client/admin.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
httpServerReadTimeout = 10 * time.Second
|
||||||
|
httpServerWriteTimeout = 10 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
func (svr *Service) RunAdminServer(addr string, port int64) (err error) {
|
||||||
|
// url router
|
||||||
|
router := httprouter.New()
|
||||||
|
|
||||||
|
user, passwd := config.ClientCommonCfg.AdminUser, config.ClientCommonCfg.AdminPwd
|
||||||
|
|
||||||
|
// api, see dashboard_api.go
|
||||||
|
router.GET("/api/reload", frpNet.HttprouterBasicAuth(svr.apiReload, user, passwd))
|
||||||
|
|
||||||
|
address := fmt.Sprintf("%s:%d", addr, port)
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: address,
|
||||||
|
Handler: router,
|
||||||
|
ReadTimeout: httpServerReadTimeout,
|
||||||
|
WriteTimeout: httpServerWriteTimeout,
|
||||||
|
}
|
||||||
|
if address == "" {
|
||||||
|
address = ":http"
|
||||||
|
}
|
||||||
|
ln, err := net.Listen("tcp", address)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go server.Serve(ln)
|
||||||
|
return
|
||||||
|
}
|
||||||
78
client/admin_api.go
Normal file
78
client/admin_api.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
ini "github.com/vaughan0/go-ini"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/utils/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GeneralResponse struct {
|
||||||
|
Code int64 `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// api/reload
|
||||||
|
type ReloadResp struct {
|
||||||
|
GeneralResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
|
var (
|
||||||
|
buf []byte
|
||||||
|
res ReloadResp
|
||||||
|
)
|
||||||
|
defer func() {
|
||||||
|
log.Info("Http response [/api/reload]: code [%d]", res.Code)
|
||||||
|
buf, _ = json.Marshal(&res)
|
||||||
|
w.Write(buf)
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Info("Http request: [/api/reload]")
|
||||||
|
|
||||||
|
conf, err := ini.LoadFile(config.ClientCommonCfg.ConfigFile)
|
||||||
|
if err != nil {
|
||||||
|
res.Code = 1
|
||||||
|
res.Msg = err.Error()
|
||||||
|
log.Error("reload frpc config file error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newCommonCfg, err := config.LoadClientCommonConf(conf)
|
||||||
|
if err != nil {
|
||||||
|
res.Code = 2
|
||||||
|
res.Msg = err.Error()
|
||||||
|
log.Error("reload frpc common section error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pxyCfgs, visitorCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, newCommonCfg.Start)
|
||||||
|
if err != nil {
|
||||||
|
res.Code = 3
|
||||||
|
res.Msg = err.Error()
|
||||||
|
log.Error("reload frpc proxy config error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
svr.ctl.reloadConf(pxyCfgs, visitorCfgs)
|
||||||
|
log.Info("success reload conf")
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -24,8 +24,9 @@ import (
|
|||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/models/msg"
|
"github.com/fatedier/frp/models/msg"
|
||||||
"github.com/fatedier/frp/utils/crypto"
|
"github.com/fatedier/frp/utils/crypto"
|
||||||
|
"github.com/fatedier/frp/utils/errors"
|
||||||
"github.com/fatedier/frp/utils/log"
|
"github.com/fatedier/frp/utils/log"
|
||||||
"github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
"github.com/fatedier/frp/utils/util"
|
"github.com/fatedier/frp/utils/util"
|
||||||
"github.com/fatedier/frp/utils/version"
|
"github.com/fatedier/frp/utils/version"
|
||||||
"github.com/xtaci/smux"
|
"github.com/xtaci/smux"
|
||||||
@@ -48,8 +49,14 @@ type Control struct {
|
|||||||
// proxies
|
// proxies
|
||||||
proxies map[string]Proxy
|
proxies map[string]Proxy
|
||||||
|
|
||||||
|
// visitor configures
|
||||||
|
visitorCfgs map[string]config.ProxyConf
|
||||||
|
|
||||||
|
// visitors
|
||||||
|
visitors map[string]Visitor
|
||||||
|
|
||||||
// control connection
|
// control connection
|
||||||
conn net.Conn
|
conn frpNet.Conn
|
||||||
|
|
||||||
// tcp stream multiplexing, if enabled
|
// tcp stream multiplexing, if enabled
|
||||||
session *smux.Session
|
session *smux.Session
|
||||||
@@ -63,8 +70,8 @@ type Control struct {
|
|||||||
// run id got from server
|
// run id got from server
|
||||||
runId string
|
runId string
|
||||||
|
|
||||||
// connection or other error happens , control will try to reconnect to server
|
// if we call close() in control, do not reconnect to server
|
||||||
closed int32
|
exit bool
|
||||||
|
|
||||||
// goroutines can block by reading from this channel, it will be closed only in reader() when control connection is closed
|
// goroutines can block by reading from this channel, it will be closed only in reader() when control connection is closed
|
||||||
closedCh chan int
|
closedCh chan int
|
||||||
@@ -77,7 +84,7 @@ type Control struct {
|
|||||||
log.Logger
|
log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf) *Control {
|
func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) *Control {
|
||||||
loginMsg := &msg.Login{
|
loginMsg := &msg.Login{
|
||||||
Arch: runtime.GOARCH,
|
Arch: runtime.GOARCH,
|
||||||
Os: runtime.GOOS,
|
Os: runtime.GOOS,
|
||||||
@@ -86,14 +93,16 @@ func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf) *Control {
|
|||||||
Version: version.Full(),
|
Version: version.Full(),
|
||||||
}
|
}
|
||||||
return &Control{
|
return &Control{
|
||||||
svr: svr,
|
svr: svr,
|
||||||
loginMsg: loginMsg,
|
loginMsg: loginMsg,
|
||||||
pxyCfgs: pxyCfgs,
|
pxyCfgs: pxyCfgs,
|
||||||
proxies: make(map[string]Proxy),
|
visitorCfgs: visitorCfgs,
|
||||||
sendCh: make(chan msg.Message, 10),
|
proxies: make(map[string]Proxy),
|
||||||
readCh: make(chan msg.Message, 10),
|
visitors: make(map[string]Visitor),
|
||||||
closedCh: make(chan int),
|
sendCh: make(chan msg.Message, 10),
|
||||||
Logger: log.NewPrefixLogger(""),
|
readCh: make(chan msg.Message, 10),
|
||||||
|
closedCh: make(chan int),
|
||||||
|
Logger: log.NewPrefixLogger(""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,16 +114,17 @@ func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf) *Control {
|
|||||||
// 6. In controler(): ini readCh, sendCh, closedCh
|
// 6. In controler(): ini readCh, sendCh, closedCh
|
||||||
// 7. In controler(): start new reader(), writer(), manager()
|
// 7. In controler(): start new reader(), writer(), manager()
|
||||||
// controler() will keep running
|
// controler() will keep running
|
||||||
func (ctl *Control) Run() error {
|
func (ctl *Control) Run() (err error) {
|
||||||
for {
|
for {
|
||||||
err := ctl.login()
|
err = ctl.login()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
ctl.Warn("login to server failed: %v", err)
|
||||||
|
|
||||||
// if login_fail_exit is true, just exit this program
|
// if login_fail_exit is true, just exit this program
|
||||||
// otherwise sleep a while and continues relogin to server
|
// otherwise sleep a while and continues relogin to server
|
||||||
if config.ClientCommonCfg.LoginFailExit {
|
if config.ClientCommonCfg.LoginFailExit {
|
||||||
return err
|
return
|
||||||
} else {
|
} else {
|
||||||
ctl.Warn("login to server fail: %v", err)
|
|
||||||
time.Sleep(30 * time.Second)
|
time.Sleep(30 * time.Second)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -127,6 +137,18 @@ func (ctl *Control) Run() error {
|
|||||||
go ctl.writer()
|
go ctl.writer()
|
||||||
go ctl.reader()
|
go ctl.reader()
|
||||||
|
|
||||||
|
// start all local visitors
|
||||||
|
for _, cfg := range ctl.visitorCfgs {
|
||||||
|
visitor := NewVisitor(ctl, cfg)
|
||||||
|
err = visitor.Run()
|
||||||
|
if err != nil {
|
||||||
|
visitor.Warn("start error: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ctl.visitors[cfg.GetName()] = visitor
|
||||||
|
visitor.Info("start visitor success")
|
||||||
|
}
|
||||||
|
|
||||||
// send NewProxy message for all configured proxies
|
// send NewProxy message for all configured proxies
|
||||||
for _, cfg := range ctl.pxyCfgs {
|
for _, cfg := range ctl.pxyCfgs {
|
||||||
var newProxyMsg msg.NewProxy
|
var newProxyMsg msg.NewProxy
|
||||||
@@ -137,29 +159,13 @@ func (ctl *Control) Run() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ctl *Control) NewWorkConn() {
|
func (ctl *Control) NewWorkConn() {
|
||||||
var (
|
workConn, err := ctl.connectServer()
|
||||||
workConn net.Conn
|
if err != nil {
|
||||||
err error
|
return
|
||||||
)
|
|
||||||
if config.ClientCommonCfg.TcpMux {
|
|
||||||
stream, err := ctl.session.OpenStream()
|
|
||||||
if err != nil {
|
|
||||||
ctl.Warn("start new work connection error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
workConn = net.WrapConn(stream)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
workConn, err = net.ConnectTcpServerByHttpProxy(config.ClientCommonCfg.HttpProxy,
|
|
||||||
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerPort))
|
|
||||||
if err != nil {
|
|
||||||
ctl.Warn("start new work connection error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m := &msg.NewWorkConn{
|
m := &msg.NewWorkConn{
|
||||||
RunId: ctl.runId,
|
RunId: ctl.getRunId(),
|
||||||
}
|
}
|
||||||
if err = msg.WriteMsg(workConn, m); err != nil {
|
if err = msg.WriteMsg(workConn, m); err != nil {
|
||||||
ctl.Warn("work connection write to server error: %v", err)
|
ctl.Warn("work connection write to server error: %v", err)
|
||||||
@@ -176,7 +182,8 @@ func (ctl *Control) NewWorkConn() {
|
|||||||
workConn.AddLogPrefix(startMsg.ProxyName)
|
workConn.AddLogPrefix(startMsg.ProxyName)
|
||||||
|
|
||||||
// dispatch this work connection to related proxy
|
// dispatch this work connection to related proxy
|
||||||
if pxy, ok := ctl.proxies[startMsg.ProxyName]; ok {
|
pxy, ok := ctl.getProxy(startMsg.ProxyName)
|
||||||
|
if ok {
|
||||||
workConn.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
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)
|
||||||
} else {
|
} else {
|
||||||
@@ -184,6 +191,20 @@ func (ctl *Control) NewWorkConn() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) Close() error {
|
||||||
|
ctl.mu.Lock()
|
||||||
|
ctl.exit = true
|
||||||
|
err := errors.PanicToError(func() {
|
||||||
|
for name, _ := range ctl.proxies {
|
||||||
|
ctl.sendCh <- &msg.CloseProxy{
|
||||||
|
ProxyName: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
ctl.mu.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (ctl *Control) init() {
|
func (ctl *Control) init() {
|
||||||
ctl.sendCh = make(chan msg.Message, 10)
|
ctl.sendCh = make(chan msg.Message, 10)
|
||||||
ctl.readCh = make(chan msg.Message, 10)
|
ctl.readCh = make(chan msg.Message, 10)
|
||||||
@@ -199,7 +220,7 @@ func (ctl *Control) login() (err error) {
|
|||||||
ctl.session.Close()
|
ctl.session.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := net.ConnectTcpServerByHttpProxy(config.ClientCommonCfg.HttpProxy,
|
conn, err := frpNet.ConnectServerByHttpProxy(config.ClientCommonCfg.HttpProxy, config.ClientCommonCfg.Protocol,
|
||||||
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerPort))
|
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -221,14 +242,14 @@ func (ctl *Control) login() (err error) {
|
|||||||
session.Close()
|
session.Close()
|
||||||
return errRet
|
return errRet
|
||||||
}
|
}
|
||||||
conn = net.WrapConn(stream)
|
conn = frpNet.WrapConn(stream)
|
||||||
ctl.session = session
|
ctl.session = session
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
ctl.loginMsg.PrivilegeKey = util.GetAuthKey(config.ClientCommonCfg.PrivilegeToken, now)
|
ctl.loginMsg.PrivilegeKey = util.GetAuthKey(config.ClientCommonCfg.PrivilegeToken, now)
|
||||||
ctl.loginMsg.Timestamp = now
|
ctl.loginMsg.Timestamp = now
|
||||||
ctl.loginMsg.RunId = ctl.runId
|
ctl.loginMsg.RunId = ctl.getRunId()
|
||||||
|
|
||||||
if err = msg.WriteMsg(conn, ctl.loginMsg); err != nil {
|
if err = msg.WriteMsg(conn, ctl.loginMsg); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -249,10 +270,11 @@ func (ctl *Control) login() (err error) {
|
|||||||
|
|
||||||
ctl.conn = conn
|
ctl.conn = conn
|
||||||
// update runId got from server
|
// update runId got from server
|
||||||
ctl.runId = loginRespMsg.RunId
|
ctl.setRunId(loginRespMsg.RunId)
|
||||||
|
config.ClientCommonCfg.ServerUdpPort = loginRespMsg.ServerUdpPort
|
||||||
ctl.ClearLogPrefix()
|
ctl.ClearLogPrefix()
|
||||||
ctl.AddLogPrefix(loginRespMsg.RunId)
|
ctl.AddLogPrefix(loginRespMsg.RunId)
|
||||||
ctl.Info("login to server success, get run id [%s]", loginRespMsg.RunId)
|
ctl.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort)
|
||||||
|
|
||||||
// login success, so we let closedCh available again
|
// login success, so we let closedCh available again
|
||||||
ctl.closedCh = make(chan int)
|
ctl.closedCh = make(chan int)
|
||||||
@@ -261,6 +283,27 @@ func (ctl *Control) login() (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
|
||||||
|
if config.ClientCommonCfg.TcpMux {
|
||||||
|
stream, errRet := ctl.session.OpenStream()
|
||||||
|
if errRet != nil {
|
||||||
|
err = errRet
|
||||||
|
ctl.Warn("start new connection to server error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn = frpNet.WrapConn(stream)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
conn, err = frpNet.ConnectServerByHttpProxy(config.ClientCommonCfg.HttpProxy, config.ClientCommonCfg.Protocol,
|
||||||
|
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerPort))
|
||||||
|
if err != nil {
|
||||||
|
ctl.Warn("start new connection to server error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (ctl *Control) reader() {
|
func (ctl *Control) reader() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
@@ -305,6 +348,7 @@ func (ctl *Control) writer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// manager handles all channel events and do corresponding process
|
||||||
func (ctl *Control) manager() {
|
func (ctl *Control) manager() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
@@ -345,22 +389,26 @@ func (ctl *Control) manager() {
|
|||||||
ctl.Warn("[%s] start error: %s", m.ProxyName, m.Error)
|
ctl.Warn("[%s] start error: %s", m.ProxyName, m.Error)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cfg, ok := ctl.pxyCfgs[m.ProxyName]
|
cfg, ok := ctl.getProxyConf(m.ProxyName)
|
||||||
if !ok {
|
if !ok {
|
||||||
// it will never go to this branch now
|
// it will never go to this branch now
|
||||||
ctl.Warn("[%s] no proxy conf found", m.ProxyName)
|
ctl.Warn("[%s] no proxy conf found", m.ProxyName)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
oldPxy, ok := ctl.proxies[m.ProxyName]
|
|
||||||
|
oldPxy, ok := ctl.getProxy(m.ProxyName)
|
||||||
if ok {
|
if ok {
|
||||||
oldPxy.Close()
|
oldPxy.Close()
|
||||||
}
|
}
|
||||||
pxy := NewProxy(ctl, cfg)
|
pxy := NewProxy(ctl, cfg)
|
||||||
if err := pxy.Run(); err != nil {
|
if err := pxy.Run(); err != nil {
|
||||||
ctl.Warn("[%s] proxy start running error: %v", m.ProxyName, err)
|
ctl.Warn("[%s] proxy start running error: %v", m.ProxyName, err)
|
||||||
|
ctl.sendCh <- &msg.CloseProxy{
|
||||||
|
ProxyName: m.ProxyName,
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ctl.proxies[m.ProxyName] = pxy
|
ctl.addProxy(m.ProxyName, pxy)
|
||||||
ctl.Info("[%s] start proxy success", m.ProxyName)
|
ctl.Info("[%s] start proxy success", m.ProxyName)
|
||||||
case *msg.Pong:
|
case *msg.Pong:
|
||||||
ctl.lastPong = time.Now()
|
ctl.lastPong = time.Now()
|
||||||
@@ -370,26 +418,43 @@ func (ctl *Control) manager() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// control keep watching closedCh, start a new connection if previous control connection is closed
|
// controler keep watching closedCh, start a new connection if previous control connection is closed.
|
||||||
|
// If controler is notified by closedCh, reader and writer and manager will exit, then recall these functions.
|
||||||
func (ctl *Control) controler() {
|
func (ctl *Control) controler() {
|
||||||
var err error
|
var err error
|
||||||
maxDelayTime := 30 * time.Second
|
maxDelayTime := 30 * time.Second
|
||||||
delayTime := time.Second
|
delayTime := time.Second
|
||||||
|
|
||||||
checkInterval := 30 * time.Second
|
checkInterval := 10 * time.Second
|
||||||
checkProxyTicker := time.NewTicker(checkInterval)
|
checkProxyTicker := time.NewTicker(checkInterval)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-checkProxyTicker.C:
|
case <-checkProxyTicker.C:
|
||||||
// Every 30 seconds, check which proxy registered failed and reregister it to server.
|
// Every 10 seconds, check which proxy registered failed and reregister it to server.
|
||||||
|
ctl.mu.RLock()
|
||||||
for _, cfg := range ctl.pxyCfgs {
|
for _, cfg := range ctl.pxyCfgs {
|
||||||
if _, exist := ctl.proxies[cfg.GetName()]; !exist {
|
if _, exist := ctl.proxies[cfg.GetName()]; !exist {
|
||||||
ctl.Info("try to reregister proxy [%s]", cfg.GetName())
|
ctl.Info("try to register proxy [%s]", cfg.GetName())
|
||||||
var newProxyMsg msg.NewProxy
|
var newProxyMsg msg.NewProxy
|
||||||
cfg.UnMarshalToMsg(&newProxyMsg)
|
cfg.UnMarshalToMsg(&newProxyMsg)
|
||||||
ctl.sendCh <- &newProxyMsg
|
ctl.sendCh <- &newProxyMsg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, cfg := range ctl.visitorCfgs {
|
||||||
|
if _, exist := ctl.visitors[cfg.GetName()]; !exist {
|
||||||
|
ctl.Info("try to start visitor [%s]", cfg.GetName())
|
||||||
|
visitor := NewVisitor(ctl, cfg)
|
||||||
|
err = visitor.Run()
|
||||||
|
if err != nil {
|
||||||
|
visitor.Warn("start error: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ctl.visitors[cfg.GetName()] = visitor
|
||||||
|
visitor.Info("start visitor success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctl.mu.RUnlock()
|
||||||
case _, ok := <-ctl.closedCh:
|
case _, ok := <-ctl.closedCh:
|
||||||
// we won't get any variable from this channel
|
// we won't get any variable from this channel
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -400,6 +465,14 @@ func (ctl *Control) controler() {
|
|||||||
for _, pxy := range ctl.proxies {
|
for _, pxy := range ctl.proxies {
|
||||||
pxy.Close()
|
pxy.Close()
|
||||||
}
|
}
|
||||||
|
// if ctl.exit is true, just exit
|
||||||
|
ctl.mu.RLock()
|
||||||
|
exit := ctl.exit
|
||||||
|
ctl.mu.RUnlock()
|
||||||
|
if exit {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
// loop util reconnect to server success
|
// loop util reconnect to server success
|
||||||
@@ -429,11 +502,13 @@ func (ctl *Control) controler() {
|
|||||||
go ctl.reader()
|
go ctl.reader()
|
||||||
|
|
||||||
// send NewProxy message for all configured proxies
|
// send NewProxy message for all configured proxies
|
||||||
|
ctl.mu.RLock()
|
||||||
for _, cfg := range ctl.pxyCfgs {
|
for _, cfg := range ctl.pxyCfgs {
|
||||||
var newProxyMsg msg.NewProxy
|
var newProxyMsg msg.NewProxy
|
||||||
cfg.UnMarshalToMsg(&newProxyMsg)
|
cfg.UnMarshalToMsg(&newProxyMsg)
|
||||||
ctl.sendCh <- &newProxyMsg
|
ctl.sendCh <- &newProxyMsg
|
||||||
}
|
}
|
||||||
|
ctl.mu.RUnlock()
|
||||||
|
|
||||||
checkProxyTicker.Stop()
|
checkProxyTicker.Stop()
|
||||||
checkProxyTicker = time.NewTicker(checkInterval)
|
checkProxyTicker = time.NewTicker(checkInterval)
|
||||||
@@ -441,3 +516,107 @@ func (ctl *Control) controler() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) setRunId(runId string) {
|
||||||
|
ctl.mu.Lock()
|
||||||
|
defer ctl.mu.Unlock()
|
||||||
|
ctl.runId = runId
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) getRunId() string {
|
||||||
|
ctl.mu.RLock()
|
||||||
|
defer ctl.mu.RUnlock()
|
||||||
|
return ctl.runId
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) getProxy(name string) (pxy Proxy, ok bool) {
|
||||||
|
ctl.mu.RLock()
|
||||||
|
defer ctl.mu.RUnlock()
|
||||||
|
pxy, ok = ctl.proxies[name]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) addProxy(name string, pxy Proxy) {
|
||||||
|
ctl.mu.Lock()
|
||||||
|
defer ctl.mu.Unlock()
|
||||||
|
ctl.proxies[name] = pxy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) getProxyConf(name string) (conf config.ProxyConf, ok bool) {
|
||||||
|
ctl.mu.RLock()
|
||||||
|
defer ctl.mu.RUnlock()
|
||||||
|
conf, ok = ctl.pxyCfgs[name]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) reloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) {
|
||||||
|
ctl.mu.Lock()
|
||||||
|
defer ctl.mu.Unlock()
|
||||||
|
|
||||||
|
removedPxyNames := make([]string, 0)
|
||||||
|
for name, oldCfg := range ctl.pxyCfgs {
|
||||||
|
del := false
|
||||||
|
cfg, ok := pxyCfgs[name]
|
||||||
|
if !ok {
|
||||||
|
del = true
|
||||||
|
} else {
|
||||||
|
if !oldCfg.Compare(cfg) {
|
||||||
|
del = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if del {
|
||||||
|
removedPxyNames = append(removedPxyNames, name)
|
||||||
|
delete(ctl.pxyCfgs, name)
|
||||||
|
if pxy, ok := ctl.proxies[name]; ok {
|
||||||
|
pxy.Close()
|
||||||
|
}
|
||||||
|
delete(ctl.proxies, name)
|
||||||
|
ctl.sendCh <- &msg.CloseProxy{
|
||||||
|
ProxyName: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctl.Info("proxy removed: %v", removedPxyNames)
|
||||||
|
|
||||||
|
addedPxyNames := make([]string, 0)
|
||||||
|
for name, cfg := range pxyCfgs {
|
||||||
|
if _, ok := ctl.pxyCfgs[name]; !ok {
|
||||||
|
ctl.pxyCfgs[name] = cfg
|
||||||
|
addedPxyNames = append(addedPxyNames, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctl.Info("proxy added: %v", addedPxyNames)
|
||||||
|
|
||||||
|
removedVisitorName := make([]string, 0)
|
||||||
|
for name, oldVisitorCfg := range ctl.visitorCfgs {
|
||||||
|
del := false
|
||||||
|
cfg, ok := visitorCfgs[name]
|
||||||
|
if !ok {
|
||||||
|
del = true
|
||||||
|
} else {
|
||||||
|
if !oldVisitorCfg.Compare(cfg) {
|
||||||
|
del = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if del {
|
||||||
|
removedVisitorName = append(removedVisitorName, name)
|
||||||
|
delete(ctl.visitorCfgs, name)
|
||||||
|
if visitor, ok := ctl.visitors[name]; ok {
|
||||||
|
visitor.Close()
|
||||||
|
}
|
||||||
|
delete(ctl.visitors, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctl.Info("visitor removed: %v", removedVisitorName)
|
||||||
|
|
||||||
|
addedVisitorName := make([]string, 0)
|
||||||
|
for name, visitorCfg := range visitorCfgs {
|
||||||
|
if _, ok := ctl.visitorCfgs[name]; !ok {
|
||||||
|
ctl.visitorCfgs[name] = visitorCfg
|
||||||
|
addedVisitorName = append(addedVisitorName, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctl.Info("visitor added: %v", addedVisitorName)
|
||||||
|
}
|
||||||
|
|||||||
157
client/proxy.go
157
client/proxy.go
@@ -15,6 +15,7 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@@ -24,14 +25,15 @@ import (
|
|||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/models/msg"
|
"github.com/fatedier/frp/models/msg"
|
||||||
"github.com/fatedier/frp/models/plugin"
|
"github.com/fatedier/frp/models/plugin"
|
||||||
"github.com/fatedier/frp/models/proto/tcp"
|
|
||||||
"github.com/fatedier/frp/models/proto/udp"
|
"github.com/fatedier/frp/models/proto/udp"
|
||||||
"github.com/fatedier/frp/utils/errors"
|
"github.com/fatedier/frp/utils/errors"
|
||||||
|
frpIo "github.com/fatedier/frp/utils/io"
|
||||||
"github.com/fatedier/frp/utils/log"
|
"github.com/fatedier/frp/utils/log"
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
"github.com/fatedier/frp/utils/pool"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Proxy defines how to work for different proxy type.
|
// Proxy defines how to deal with work connections for different proxy type.
|
||||||
type Proxy interface {
|
type Proxy interface {
|
||||||
Run() error
|
Run() error
|
||||||
|
|
||||||
@@ -67,6 +69,16 @@ func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy) {
|
|||||||
BaseProxy: baseProxy,
|
BaseProxy: baseProxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
|
case *config.StcpProxyConf:
|
||||||
|
pxy = &StcpProxy{
|
||||||
|
BaseProxy: baseProxy,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
case *config.XtcpProxyConf:
|
||||||
|
pxy = &XtcpProxy{
|
||||||
|
BaseProxy: baseProxy,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -103,7 +115,8 @@ func (pxy *TcpProxy) Close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn) {
|
func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||||
|
[]byte(config.ClientCommonCfg.PrivilegeToken))
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP
|
// HTTP
|
||||||
@@ -131,7 +144,8 @@ func (pxy *HttpProxy) Close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn) {
|
func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn) {
|
||||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||||
|
[]byte(config.ClientCommonCfg.PrivilegeToken))
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPS
|
// HTTPS
|
||||||
@@ -159,7 +173,130 @@ func (pxy *HttpsProxy) Close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn) {
|
func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn) {
|
||||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||||
|
[]byte(config.ClientCommonCfg.PrivilegeToken))
|
||||||
|
}
|
||||||
|
|
||||||
|
// STCP
|
||||||
|
type StcpProxy struct {
|
||||||
|
BaseProxy
|
||||||
|
|
||||||
|
cfg *config.StcpProxyConf
|
||||||
|
proxyPlugin plugin.Plugin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *StcpProxy) Run() (err error) {
|
||||||
|
if pxy.cfg.Plugin != "" {
|
||||||
|
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *StcpProxy) Close() {
|
||||||
|
if pxy.proxyPlugin != nil {
|
||||||
|
pxy.proxyPlugin.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||||
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||||
|
[]byte(config.ClientCommonCfg.PrivilegeToken))
|
||||||
|
}
|
||||||
|
|
||||||
|
// XTCP
|
||||||
|
type XtcpProxy struct {
|
||||||
|
BaseProxy
|
||||||
|
|
||||||
|
cfg *config.XtcpProxyConf
|
||||||
|
proxyPlugin plugin.Plugin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XtcpProxy) Run() (err error) {
|
||||||
|
if pxy.cfg.Plugin != "" {
|
||||||
|
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XtcpProxy) Close() {
|
||||||
|
if pxy.proxyPlugin != nil {
|
||||||
|
pxy.proxyPlugin.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||||
|
defer conn.Close()
|
||||||
|
var natHoleSidMsg msg.NatHoleSid
|
||||||
|
err := msg.ReadMsgInto(conn, &natHoleSidMsg)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("xtcp read from workConn error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
natHoleClientMsg := &msg.NatHoleClient{
|
||||||
|
ProxyName: pxy.cfg.ProxyName,
|
||||||
|
Sid: natHoleSidMsg.Sid,
|
||||||
|
}
|
||||||
|
raddr, _ := net.ResolveUDPAddr("udp",
|
||||||
|
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerUdpPort))
|
||||||
|
clientConn, err := net.DialUDP("udp", nil, raddr)
|
||||||
|
defer clientConn.Close()
|
||||||
|
|
||||||
|
err = msg.WriteMsg(clientConn, natHoleClientMsg)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("send natHoleClientMsg to server error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for client address at most 5 seconds.
|
||||||
|
var natHoleRespMsg msg.NatHoleResp
|
||||||
|
clientConn.SetReadDeadline(time.Now().Add(5 * time.Second))
|
||||||
|
|
||||||
|
buf := pool.GetBuf(1024)
|
||||||
|
n, err := clientConn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("get natHoleRespMsg error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("get natHoleRespMsg error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
clientConn.SetReadDeadline(time.Time{})
|
||||||
|
clientConn.Close()
|
||||||
|
pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
|
||||||
|
|
||||||
|
// Send sid to visitor udp address.
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String())
|
||||||
|
daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.VisitorAddr)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("resolve visitor udp address error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lConn, err := net.DialUDP("udp", laddr, daddr)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("dial visitor udp address error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lConn.Write([]byte(natHoleRespMsg.Sid))
|
||||||
|
|
||||||
|
kcpConn, err := frpNet.NewKcpConnFromUdp(lConn, true, natHoleRespMsg.VisitorAddr)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("create kcp connection from udp connection error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf,
|
||||||
|
frpNet.WrapConn(kcpConn), []byte(pxy.cfg.Sk))
|
||||||
}
|
}
|
||||||
|
|
||||||
// UDP
|
// UDP
|
||||||
@@ -269,7 +406,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) {
|
baseInfo *config.BaseProxyConf, workConn frpNet.Conn, encKey []byte) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
remote io.ReadWriteCloser
|
remote io.ReadWriteCloser
|
||||||
@@ -277,14 +414,14 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
|
|||||||
)
|
)
|
||||||
remote = workConn
|
remote = workConn
|
||||||
if baseInfo.UseEncryption {
|
if baseInfo.UseEncryption {
|
||||||
remote, err = tcp.WithEncryption(remote, []byte(config.ClientCommonCfg.PrivilegeToken))
|
remote, err = frpIo.WithEncryption(remote, encKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
workConn.Error("create encryption stream error: %v", err)
|
workConn.Error("create encryption stream error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if baseInfo.UseCompression {
|
if baseInfo.UseCompression {
|
||||||
remote = tcp.WithCompression(remote)
|
remote = frpIo.WithCompression(remote)
|
||||||
}
|
}
|
||||||
|
|
||||||
if proxyPlugin != nil {
|
if proxyPlugin != nil {
|
||||||
@@ -294,7 +431,7 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
|
|||||||
workConn.Debug("handle by plugin finished")
|
workConn.Debug("handle by plugin finished")
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
localConn, err := frpNet.ConnectTcpServer(fmt.Sprintf("%s:%d", localInfo.LocalIp, localInfo.LocalPort))
|
localConn, err := frpNet.ConnectServer("tcp", fmt.Sprintf("%s:%d", localInfo.LocalIp, localInfo.LocalPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
workConn.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIp, localInfo.LocalPort, err)
|
workConn.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIp, localInfo.LocalPort, err)
|
||||||
return
|
return
|
||||||
@@ -302,7 +439,7 @@ 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())
|
||||||
tcp.Join(localConn, remote)
|
frpIo.Join(localConn, remote)
|
||||||
workConn.Debug("join connections closed")
|
workConn.Debug("join connections closed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,10 @@
|
|||||||
|
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import "github.com/fatedier/frp/models/config"
|
import (
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/utils/log"
|
||||||
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
// manager control connection with server
|
// manager control connection with server
|
||||||
@@ -23,11 +26,11 @@ type Service struct {
|
|||||||
closedCh chan int
|
closedCh chan int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(pxyCfgs map[string]config.ProxyConf) (svr *Service) {
|
func NewService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) (svr *Service) {
|
||||||
svr = &Service{
|
svr = &Service{
|
||||||
closedCh: make(chan int),
|
closedCh: make(chan int),
|
||||||
}
|
}
|
||||||
ctl := NewControl(svr, pxyCfgs)
|
ctl := NewControl(svr, pxyCfgs, visitorCfgs)
|
||||||
svr.ctl = ctl
|
svr.ctl = ctl
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -38,6 +41,18 @@ func (svr *Service) Run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.ClientCommonCfg.AdminPort != 0 {
|
||||||
|
err = svr.RunAdminServer(config.ClientCommonCfg.AdminAddr, config.ClientCommonCfg.AdminPort)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("run admin server error: %v", err)
|
||||||
|
}
|
||||||
|
log.Info("admin server listen on %s:%d", config.ClientCommonCfg.AdminAddr, config.ClientCommonCfg.AdminPort)
|
||||||
|
}
|
||||||
|
|
||||||
<-svr.closedCh
|
<-svr.closedCh
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (svr *Service) Close() error {
|
||||||
|
return svr.ctl.Close()
|
||||||
|
}
|
||||||
|
|||||||
322
client/visitor.go
Normal file
322
client/visitor.go
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/models/msg"
|
||||||
|
frpIo "github.com/fatedier/frp/utils/io"
|
||||||
|
"github.com/fatedier/frp/utils/log"
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
"github.com/fatedier/frp/utils/pool"
|
||||||
|
"github.com/fatedier/frp/utils/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Visitor is used for forward traffics from local port tot remote service.
|
||||||
|
type Visitor interface {
|
||||||
|
Run() error
|
||||||
|
Close()
|
||||||
|
log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVisitor(ctl *Control, pxyConf config.ProxyConf) (visitor Visitor) {
|
||||||
|
baseVisitor := BaseVisitor{
|
||||||
|
ctl: ctl,
|
||||||
|
Logger: log.NewPrefixLogger(pxyConf.GetName()),
|
||||||
|
}
|
||||||
|
switch cfg := pxyConf.(type) {
|
||||||
|
case *config.StcpProxyConf:
|
||||||
|
visitor = &StcpVisitor{
|
||||||
|
BaseVisitor: baseVisitor,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
case *config.XtcpProxyConf:
|
||||||
|
visitor = &XtcpVisitor{
|
||||||
|
BaseVisitor: baseVisitor,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseVisitor struct {
|
||||||
|
ctl *Control
|
||||||
|
l frpNet.Listener
|
||||||
|
closed bool
|
||||||
|
mu sync.RWMutex
|
||||||
|
log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type StcpVisitor struct {
|
||||||
|
BaseVisitor
|
||||||
|
|
||||||
|
cfg *config.StcpProxyConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *StcpVisitor) Run() (err error) {
|
||||||
|
sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, int64(sv.cfg.BindPort))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go sv.worker()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *StcpVisitor) Close() {
|
||||||
|
sv.l.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *StcpVisitor) worker() {
|
||||||
|
for {
|
||||||
|
conn, err := sv.l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
sv.Warn("stcp local listener closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go sv.handleConn(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||||
|
defer userConn.Close()
|
||||||
|
|
||||||
|
sv.Debug("get a new stcp user connection")
|
||||||
|
visitorConn, err := sv.ctl.connectServer()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer visitorConn.Close()
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
newVisitorConnMsg := &msg.NewVisitorConn{
|
||||||
|
ProxyName: sv.cfg.ServerName,
|
||||||
|
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
||||||
|
Timestamp: now,
|
||||||
|
UseEncryption: sv.cfg.UseEncryption,
|
||||||
|
UseCompression: sv.cfg.UseCompression,
|
||||||
|
}
|
||||||
|
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
||||||
|
if err != nil {
|
||||||
|
sv.Warn("send newVisitorConnMsg to server error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var newVisitorConnRespMsg msg.NewVisitorConnResp
|
||||||
|
visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
|
||||||
|
if err != nil {
|
||||||
|
sv.Warn("get newVisitorConnRespMsg error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
visitorConn.SetReadDeadline(time.Time{})
|
||||||
|
|
||||||
|
if newVisitorConnRespMsg.Error != "" {
|
||||||
|
sv.Warn("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var remote io.ReadWriteCloser
|
||||||
|
remote = visitorConn
|
||||||
|
if sv.cfg.UseEncryption {
|
||||||
|
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
|
||||||
|
if err != nil {
|
||||||
|
sv.Error("create encryption stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sv.cfg.UseCompression {
|
||||||
|
remote = frpIo.WithCompression(remote)
|
||||||
|
}
|
||||||
|
|
||||||
|
frpIo.Join(userConn, remote)
|
||||||
|
}
|
||||||
|
|
||||||
|
type XtcpVisitor struct {
|
||||||
|
BaseVisitor
|
||||||
|
|
||||||
|
cfg *config.XtcpProxyConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XtcpVisitor) Run() (err error) {
|
||||||
|
sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, int64(sv.cfg.BindPort))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go sv.worker()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XtcpVisitor) Close() {
|
||||||
|
sv.l.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XtcpVisitor) worker() {
|
||||||
|
for {
|
||||||
|
conn, err := sv.l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
sv.Warn("stcp local listener closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go sv.handleConn(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||||
|
defer userConn.Close()
|
||||||
|
|
||||||
|
sv.Debug("get a new xtcp user connection")
|
||||||
|
if config.ClientCommonCfg.ServerUdpPort == 0 {
|
||||||
|
sv.Error("xtcp is not supported by server")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
raddr, err := net.ResolveUDPAddr("udp",
|
||||||
|
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerUdpPort))
|
||||||
|
visitorConn, err := net.DialUDP("udp", nil, raddr)
|
||||||
|
defer visitorConn.Close()
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
natHoleVisitorMsg := &msg.NatHoleVisitor{
|
||||||
|
ProxyName: sv.cfg.ServerName,
|
||||||
|
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
||||||
|
Timestamp: now,
|
||||||
|
}
|
||||||
|
err = msg.WriteMsg(visitorConn, natHoleVisitorMsg)
|
||||||
|
if err != nil {
|
||||||
|
sv.Warn("send natHoleVisitorMsg to server error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for client address at most 10 seconds.
|
||||||
|
var natHoleRespMsg msg.NatHoleResp
|
||||||
|
visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
buf := pool.GetBuf(1024)
|
||||||
|
n, err := visitorConn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
sv.Warn("get natHoleRespMsg error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg)
|
||||||
|
if err != nil {
|
||||||
|
sv.Warn("get natHoleRespMsg error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
visitorConn.SetReadDeadline(time.Time{})
|
||||||
|
pool.PutBuf(buf)
|
||||||
|
|
||||||
|
sv.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
|
||||||
|
|
||||||
|
// Close visitorConn, so we can use it's local address.
|
||||||
|
visitorConn.Close()
|
||||||
|
|
||||||
|
// Send detect message.
|
||||||
|
array := strings.Split(natHoleRespMsg.ClientAddr, ":")
|
||||||
|
if len(array) <= 1 {
|
||||||
|
sv.Error("get natHoleResp client address error: %s", natHoleRespMsg.ClientAddr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
laddr, _ := net.ResolveUDPAddr("udp", visitorConn.LocalAddr().String())
|
||||||
|
/*
|
||||||
|
for i := 1000; i < 65000; i++ {
|
||||||
|
sv.sendDetectMsg(array[0], int64(i), laddr, "a")
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
port, err := strconv.ParseInt(array[1], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
sv.Error("get natHoleResp client address error: %s", natHoleRespMsg.ClientAddr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sv.sendDetectMsg(array[0], int64(port), laddr, []byte(natHoleRespMsg.Sid))
|
||||||
|
sv.Trace("send all detect msg done")
|
||||||
|
|
||||||
|
// Listen for visitorConn's address and wait for client connection.
|
||||||
|
lConn, err := net.ListenUDP("udp", laddr)
|
||||||
|
if err != nil {
|
||||||
|
sv.Error("listen on visitorConn's local adress error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lConn.SetReadDeadline(time.Now().Add(5 * time.Second))
|
||||||
|
sidBuf := pool.GetBuf(1024)
|
||||||
|
n, _, err = lConn.ReadFromUDP(sidBuf)
|
||||||
|
if err != nil {
|
||||||
|
sv.Warn("get sid from client error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lConn.SetReadDeadline(time.Time{})
|
||||||
|
if string(sidBuf[:n]) != natHoleRespMsg.Sid {
|
||||||
|
sv.Warn("incorrect sid from client")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sv.Info("nat hole connection make success, sid [%s]", string(sidBuf[:n]))
|
||||||
|
pool.PutBuf(sidBuf)
|
||||||
|
|
||||||
|
var remote io.ReadWriteCloser
|
||||||
|
remote, err = frpNet.NewKcpConnFromUdp(lConn, false, natHoleRespMsg.ClientAddr)
|
||||||
|
if err != nil {
|
||||||
|
sv.Error("create kcp connection from udp connection error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if sv.cfg.UseEncryption {
|
||||||
|
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
|
||||||
|
if err != nil {
|
||||||
|
sv.Error("create encryption stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sv.cfg.UseCompression {
|
||||||
|
remote = frpIo.WithCompression(remote)
|
||||||
|
}
|
||||||
|
|
||||||
|
frpIo.Join(userConn, remote)
|
||||||
|
sv.Debug("join connections closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XtcpVisitor) sendDetectMsg(addr string, port int64, 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
|
||||||
|
}
|
||||||
@@ -15,10 +15,17 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
docopt "github.com/docopt/docopt-go"
|
docopt "github.com/docopt/docopt-go"
|
||||||
ini "github.com/vaughan0/go-ini"
|
ini "github.com/vaughan0/go-ini"
|
||||||
@@ -37,6 +44,7 @@ var usage string = `frpc is the client of frp
|
|||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
frpc [-c config_file] [-L log_file] [--log-level=<log_level>] [--server-addr=<server_addr>]
|
frpc [-c config_file] [-L log_file] [--log-level=<log_level>] [--server-addr=<server_addr>]
|
||||||
|
frpc [-c config_file] --reload
|
||||||
frpc -h | --help
|
frpc -h | --help
|
||||||
frpc -v | --version
|
frpc -v | --version
|
||||||
|
|
||||||
@@ -45,6 +53,7 @@ Options:
|
|||||||
-L log_file set output log file, including console
|
-L log_file set output log file, including console
|
||||||
--log-level=<log_level> set log level: debug, info, warn, error
|
--log-level=<log_level> set log level: debug, info, warn, error
|
||||||
--server-addr=<server_addr> addr which frps is listening for, example: 0.0.0.0:7000
|
--server-addr=<server_addr> addr which frps is listening for, example: 0.0.0.0:7000
|
||||||
|
--reload reload configure file without program exit
|
||||||
-h --help show this screen
|
-h --help show this screen
|
||||||
-v --version show version
|
-v --version show version
|
||||||
`
|
`
|
||||||
@@ -70,6 +79,47 @@ func main() {
|
|||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
config.ClientCommonCfg.ConfigFile = confFile
|
||||||
|
|
||||||
|
// check if reload command
|
||||||
|
if args["--reload"] != nil {
|
||||||
|
if args["--reload"].(bool) {
|
||||||
|
req, err := http.NewRequest("GET", "http://"+
|
||||||
|
config.ClientCommonCfg.AdminAddr+":"+fmt.Sprintf("%d", config.ClientCommonCfg.AdminPort)+"/api/reload", nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("frps reload error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(config.ClientCommonCfg.AdminUser+":"+
|
||||||
|
config.ClientCommonCfg.AdminPwd))
|
||||||
|
|
||||||
|
req.Header.Add("Authorization", authStr)
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("frpc reload error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
} else {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("frpc reload error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
res := &client.GeneralResponse{}
|
||||||
|
err = json.Unmarshal(body, &res)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("http response error: %s\n", strings.TrimSpace(string(body)))
|
||||||
|
os.Exit(1)
|
||||||
|
} else if res.Code != 0 {
|
||||||
|
fmt.Printf("reload error: %s\n", res.Msg)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Printf("reload success\n")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if args["-L"] != nil {
|
if args["-L"] != nil {
|
||||||
if args["-L"].(string) == "console" {
|
if args["-L"].(string) == "console" {
|
||||||
@@ -106,7 +156,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pxyCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, config.ClientCommonCfg.Start)
|
pxyCfgs, visitorCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, config.ClientCommonCfg.Start)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -115,10 +165,25 @@ func main() {
|
|||||||
log.InitLog(config.ClientCommonCfg.LogWay, config.ClientCommonCfg.LogFile,
|
log.InitLog(config.ClientCommonCfg.LogWay, config.ClientCommonCfg.LogFile,
|
||||||
config.ClientCommonCfg.LogLevel, config.ClientCommonCfg.LogMaxDays)
|
config.ClientCommonCfg.LogLevel, config.ClientCommonCfg.LogMaxDays)
|
||||||
|
|
||||||
svr := client.NewService(pxyCfgs)
|
svr := client.NewService(pxyCfgs, visitorCfgs)
|
||||||
|
|
||||||
|
// Capture the exit signal if we use kcp.
|
||||||
|
if config.ClientCommonCfg.Protocol == "kcp" {
|
||||||
|
go HandleSignal(svr)
|
||||||
|
}
|
||||||
|
|
||||||
err = svr.Run()
|
err = svr.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func HandleSignal(svr *client.Service) {
|
||||||
|
ch := make(chan os.Signal)
|
||||||
|
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
<-ch
|
||||||
|
svr.Close()
|
||||||
|
time.Sleep(250 * time.Millisecond)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ server_addr = 0.0.0.0
|
|||||||
server_port = 7000
|
server_port = 7000
|
||||||
|
|
||||||
# if you want to connect frps by http proxy, you can set http_proxy here or in global environment variables
|
# if you want to connect frps by http proxy, you can set http_proxy here or in global environment variables
|
||||||
|
# it only works when protocol is tcp
|
||||||
# http_proxy = http://user:pwd@192.168.1.128:8080
|
# http_proxy = http://user:pwd@192.168.1.128:8080
|
||||||
|
|
||||||
# console or real logFile path like ./frpc.log
|
# console or real logFile path like ./frpc.log
|
||||||
@@ -19,6 +20,12 @@ log_max_days = 3
|
|||||||
# for authentication
|
# for authentication
|
||||||
privilege_token = 12345678
|
privilege_token = 12345678
|
||||||
|
|
||||||
|
# set admin address for control frpc's action by http api such as reload
|
||||||
|
admin_addr = 127.0.0.1
|
||||||
|
admin_port = 7400
|
||||||
|
admin_user = admin
|
||||||
|
admin_pwd = admin
|
||||||
|
|
||||||
# connections will be established in advance, default value is zero
|
# connections will be established in advance, default value is zero
|
||||||
pool_count = 5
|
pool_count = 5
|
||||||
|
|
||||||
@@ -32,6 +39,10 @@ user = your_name
|
|||||||
# default is true
|
# default is true
|
||||||
login_fail_exit = true
|
login_fail_exit = true
|
||||||
|
|
||||||
|
# communication protocol used to connect to server
|
||||||
|
# now it supports tcp and kcp, default is tcp
|
||||||
|
protocol = tcp
|
||||||
|
|
||||||
# proxy names you want to start divided by ','
|
# proxy names you want to start divided by ','
|
||||||
# default is empty, means all proxies
|
# default is empty, means all proxies
|
||||||
# start = ssh,dns
|
# start = ssh,dns
|
||||||
@@ -105,3 +116,46 @@ remote_port = 6004
|
|||||||
plugin = http_proxy
|
plugin = http_proxy
|
||||||
plugin_http_user = abc
|
plugin_http_user = abc
|
||||||
plugin_http_passwd = abc
|
plugin_http_passwd = abc
|
||||||
|
|
||||||
|
[secret_tcp]
|
||||||
|
# If the type is secret tcp, remote_port is useless
|
||||||
|
# Who want to connect local port should deploy another frpc with stcp proxy and role is visitor
|
||||||
|
type = stcp
|
||||||
|
# sk used for authentication for visitors
|
||||||
|
sk = abcdefg
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 22
|
||||||
|
use_encryption = false
|
||||||
|
use_compression = false
|
||||||
|
|
||||||
|
# user of frpc should be same in both stcp server and stcp visitor
|
||||||
|
[secret_tcp_visitor]
|
||||||
|
# frpc role visitor -> frps -> frpc role server
|
||||||
|
role = visitor
|
||||||
|
type = stcp
|
||||||
|
# the server name you want to visitor
|
||||||
|
server_name = secret_tcp
|
||||||
|
sk = abcdefg
|
||||||
|
# connect this address to visitor stcp server
|
||||||
|
bind_addr = 127.0.0.1
|
||||||
|
bind_port = 9000
|
||||||
|
use_encryption = false
|
||||||
|
use_compression = false
|
||||||
|
|
||||||
|
[p2p_tcp]
|
||||||
|
type = xtcp
|
||||||
|
sk = abcdefg
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 22
|
||||||
|
use_encryption = false
|
||||||
|
use_compression = false
|
||||||
|
|
||||||
|
[p2p_tcp_visitor]
|
||||||
|
role = visitor
|
||||||
|
type = xtcp
|
||||||
|
server_name = p2p_tcp
|
||||||
|
sk = abcdefg
|
||||||
|
bind_addr = 127.0.0.1
|
||||||
|
bind_port = 9001
|
||||||
|
use_encryption = false
|
||||||
|
use_compression = false
|
||||||
|
|||||||
@@ -5,11 +5,24 @@
|
|||||||
bind_addr = 0.0.0.0
|
bind_addr = 0.0.0.0
|
||||||
bind_port = 7000
|
bind_port = 7000
|
||||||
|
|
||||||
|
# udp port to help make udp hole to penetrate nat
|
||||||
|
bind_udp_port = 7001
|
||||||
|
|
||||||
|
# udp port used for kcp protocol, it can be same with 'bind_port'
|
||||||
|
# if not set, kcp is disabled in frps
|
||||||
|
kcp_bind_port = 7000
|
||||||
|
|
||||||
|
# specify which address proxy will listen for, default value is same with bind_addr
|
||||||
|
# proxy_bind_addr = 127.0.0.1
|
||||||
|
|
||||||
# if you want to support virtual host, you must set the http port for listening (optional)
|
# if you want to support virtual host, you must set the http port for listening (optional)
|
||||||
vhost_http_port = 80
|
vhost_http_port = 80
|
||||||
vhost_https_port = 443
|
vhost_https_port = 443
|
||||||
|
|
||||||
# if you want to configure or reload frps by dashboard, dashboard_port must be set
|
# set dashboard_addr and dashboard_port to view dashboard of frps
|
||||||
|
# dashboard_addr's default value is same with bind_addr
|
||||||
|
# dashboard is available only if dashboard_port is set
|
||||||
|
dashboard_addr = 0.0.0.0
|
||||||
dashboard_port = 7500
|
dashboard_port = 7500
|
||||||
|
|
||||||
# dashboard user and pwd for basic auth protect, if not set, both default value is admin
|
# dashboard user and pwd for basic auth protect, if not set, both default value is admin
|
||||||
|
|||||||
BIN
doc/pic/donate-wechatpay.png
Normal file
BIN
doc/pic/donate-wechatpay.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
75
glide.lock
generated
Normal file
75
glide.lock
generated
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
hash: 03ff8b71f63e9038c0182a4ef2a55aa9349782f4813c331e2d1f02f3dd15b4f8
|
||||||
|
updated: 2017-11-01T16:16:18.577622991+08:00
|
||||||
|
imports:
|
||||||
|
- name: github.com/armon/go-socks5
|
||||||
|
version: e75332964ef517daa070d7c38a9466a0d687e0a5
|
||||||
|
- name: github.com/davecgh/go-spew
|
||||||
|
version: 346938d642f2ec3594ed81d874461961cd0faa76
|
||||||
|
subpackages:
|
||||||
|
- spew
|
||||||
|
- name: github.com/docopt/docopt-go
|
||||||
|
version: 784ddc588536785e7299f7272f39101f7faccc3f
|
||||||
|
- name: github.com/fatedier/beego
|
||||||
|
version: 6c6a4f5bd5eb5a39f7e289b8f345b55f75e7e3e8
|
||||||
|
subpackages:
|
||||||
|
- logs
|
||||||
|
- name: github.com/fatedier/kcp-go
|
||||||
|
version: cd167d2f15f451b0f33780ce862fca97adc0331e
|
||||||
|
- name: github.com/golang/snappy
|
||||||
|
version: 5979233c5d6225d4a8e438cdd0b411888449ddab
|
||||||
|
- name: github.com/julienschmidt/httprouter
|
||||||
|
version: 8a45e95fc75cb77048068a62daed98cc22fdac7c
|
||||||
|
- name: github.com/klauspost/cpuid
|
||||||
|
version: 09cded8978dc9e80714c4d85b0322337b0a1e5e0
|
||||||
|
- name: github.com/klauspost/reedsolomon
|
||||||
|
version: dde6ad55c5e5a6379a4e82dcca32ee407346eb6d
|
||||||
|
- name: github.com/pkg/errors
|
||||||
|
version: c605e284fe17294bda444b34710735b29d1a9d90
|
||||||
|
- name: github.com/pmezard/go-difflib
|
||||||
|
version: 792786c7400a136282c1664665ae0a8db921c6c2
|
||||||
|
subpackages:
|
||||||
|
- difflib
|
||||||
|
- name: github.com/rakyll/statik
|
||||||
|
version: 274df120e9065bdd08eb1120e0375e3dc1ae8465
|
||||||
|
subpackages:
|
||||||
|
- fs
|
||||||
|
- name: github.com/stretchr/testify
|
||||||
|
version: 2402e8e7a02fc811447d11f881aa9746cdc57983
|
||||||
|
subpackages:
|
||||||
|
- assert
|
||||||
|
- name: github.com/templexxx/cpufeat
|
||||||
|
version: 3794dfbfb04749f896b521032f69383f24c3687e
|
||||||
|
- name: github.com/templexxx/reedsolomon
|
||||||
|
version: 7092926d7d05c415fabb892b1464a03f8228ab80
|
||||||
|
- name: github.com/templexxx/xor
|
||||||
|
version: 0af8e873c554da75f37f2049cdffda804533d44c
|
||||||
|
- name: github.com/tjfoc/gmsm
|
||||||
|
version: 21d76dee237dbbc8dfe1510000b9bf2733635aa1
|
||||||
|
subpackages:
|
||||||
|
- sm4
|
||||||
|
- name: github.com/vaughan0/go-ini
|
||||||
|
version: a98ad7ee00ec53921f08832bc06ecf7fd600e6a1
|
||||||
|
- name: github.com/xtaci/kcp-go
|
||||||
|
version: df437e2b8ec365a336200f9d9da53441cf72ed47
|
||||||
|
- name: github.com/xtaci/smux
|
||||||
|
version: 2de5471dfcbc029f5fe1392b83fe784127c4943e
|
||||||
|
- name: golang.org/x/crypto
|
||||||
|
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
|
||||||
|
subpackages:
|
||||||
|
- blowfish
|
||||||
|
- cast5
|
||||||
|
- pbkdf2
|
||||||
|
- salsa20
|
||||||
|
- salsa20/salsa
|
||||||
|
- tea
|
||||||
|
- twofish
|
||||||
|
- xtea
|
||||||
|
- name: golang.org/x/net
|
||||||
|
version: e4fa1c5465ad6111f206fc92186b8c83d64adbe1
|
||||||
|
subpackages:
|
||||||
|
- bpf
|
||||||
|
- context
|
||||||
|
- internal/iana
|
||||||
|
- internal/socket
|
||||||
|
- ipv4
|
||||||
|
testImports: []
|
||||||
73
glide.yaml
Normal file
73
glide.yaml
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package: github.com/fatedier/frp
|
||||||
|
import:
|
||||||
|
- package: github.com/armon/go-socks5
|
||||||
|
version: e75332964ef517daa070d7c38a9466a0d687e0a5
|
||||||
|
- package: github.com/davecgh/go-spew
|
||||||
|
version: v1.1.0
|
||||||
|
subpackages:
|
||||||
|
- spew
|
||||||
|
- package: github.com/docopt/docopt-go
|
||||||
|
version: 0.6.2
|
||||||
|
- package: github.com/fatedier/beego
|
||||||
|
version: 6c6a4f5bd5eb5a39f7e289b8f345b55f75e7e3e8
|
||||||
|
subpackages:
|
||||||
|
- logs
|
||||||
|
- package: github.com/fatedier/kcp-go
|
||||||
|
version: cd167d2f15f451b0f33780ce862fca97adc0331e
|
||||||
|
- package: github.com/golang/snappy
|
||||||
|
version: 5979233c5d6225d4a8e438cdd0b411888449ddab
|
||||||
|
- package: github.com/julienschmidt/httprouter
|
||||||
|
version: 8a45e95fc75cb77048068a62daed98cc22fdac7c
|
||||||
|
- package: github.com/klauspost/cpuid
|
||||||
|
version: v1.0
|
||||||
|
- package: github.com/klauspost/reedsolomon
|
||||||
|
version: dde6ad55c5e5a6379a4e82dcca32ee407346eb6d
|
||||||
|
- package: github.com/pkg/errors
|
||||||
|
version: c605e284fe17294bda444b34710735b29d1a9d90
|
||||||
|
- package: github.com/pmezard/go-difflib
|
||||||
|
version: v1.0.0
|
||||||
|
subpackages:
|
||||||
|
- difflib
|
||||||
|
- package: github.com/rakyll/statik
|
||||||
|
version: v0.1.0
|
||||||
|
subpackages:
|
||||||
|
- fs
|
||||||
|
- package: github.com/stretchr/testify
|
||||||
|
version: 2402e8e7a02fc811447d11f881aa9746cdc57983
|
||||||
|
subpackages:
|
||||||
|
- assert
|
||||||
|
- package: github.com/templexxx/cpufeat
|
||||||
|
version: 3794dfbfb04749f896b521032f69383f24c3687e
|
||||||
|
- package: github.com/templexxx/reedsolomon
|
||||||
|
version: 7092926d7d05c415fabb892b1464a03f8228ab80
|
||||||
|
- package: github.com/templexxx/xor
|
||||||
|
version: 0.1.2
|
||||||
|
- package: github.com/tjfoc/gmsm
|
||||||
|
version: 21d76dee237dbbc8dfe1510000b9bf2733635aa1
|
||||||
|
subpackages:
|
||||||
|
- sm4
|
||||||
|
- package: github.com/vaughan0/go-ini
|
||||||
|
version: a98ad7ee00ec53921f08832bc06ecf7fd600e6a1
|
||||||
|
- package: github.com/xtaci/kcp-go
|
||||||
|
version: v3.17
|
||||||
|
- package: github.com/xtaci/smux
|
||||||
|
version: 2de5471dfcbc029f5fe1392b83fe784127c4943e
|
||||||
|
- package: golang.org/x/crypto
|
||||||
|
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
|
||||||
|
subpackages:
|
||||||
|
- blowfish
|
||||||
|
- cast5
|
||||||
|
- pbkdf2
|
||||||
|
- salsa20
|
||||||
|
- salsa20/salsa
|
||||||
|
- tea
|
||||||
|
- twofish
|
||||||
|
- xtea
|
||||||
|
- package: golang.org/x/net
|
||||||
|
version: e4fa1c5465ad6111f206fc92186b8c83d64adbe1
|
||||||
|
subpackages:
|
||||||
|
- bpf
|
||||||
|
- context
|
||||||
|
- internal/iana
|
||||||
|
- internal/socket
|
||||||
|
- ipv4
|
||||||
@@ -30,17 +30,23 @@ type ClientCommonConf struct {
|
|||||||
ConfigFile string
|
ConfigFile string
|
||||||
ServerAddr string
|
ServerAddr string
|
||||||
ServerPort int64
|
ServerPort int64
|
||||||
|
ServerUdpPort int64 // this is specified by login response message from frps
|
||||||
HttpProxy string
|
HttpProxy string
|
||||||
LogFile string
|
LogFile string
|
||||||
LogWay string
|
LogWay string
|
||||||
LogLevel string
|
LogLevel string
|
||||||
LogMaxDays int64
|
LogMaxDays int64
|
||||||
PrivilegeToken string
|
PrivilegeToken string
|
||||||
|
AdminAddr string
|
||||||
|
AdminPort int64
|
||||||
|
AdminUser string
|
||||||
|
AdminPwd string
|
||||||
PoolCount int
|
PoolCount int
|
||||||
TcpMux bool
|
TcpMux bool
|
||||||
User string
|
User string
|
||||||
LoginFailExit bool
|
LoginFailExit bool
|
||||||
Start map[string]struct{}
|
Start map[string]struct{}
|
||||||
|
Protocol string
|
||||||
HeartBeatInterval int64
|
HeartBeatInterval int64
|
||||||
HeartBeatTimeout int64
|
HeartBeatTimeout int64
|
||||||
}
|
}
|
||||||
@@ -50,17 +56,23 @@ func GetDeaultClientCommonConf() *ClientCommonConf {
|
|||||||
ConfigFile: "./frpc.ini",
|
ConfigFile: "./frpc.ini",
|
||||||
ServerAddr: "0.0.0.0",
|
ServerAddr: "0.0.0.0",
|
||||||
ServerPort: 7000,
|
ServerPort: 7000,
|
||||||
|
ServerUdpPort: 0,
|
||||||
HttpProxy: "",
|
HttpProxy: "",
|
||||||
LogFile: "console",
|
LogFile: "console",
|
||||||
LogWay: "console",
|
LogWay: "console",
|
||||||
LogLevel: "info",
|
LogLevel: "info",
|
||||||
LogMaxDays: 3,
|
LogMaxDays: 3,
|
||||||
PrivilegeToken: "",
|
PrivilegeToken: "",
|
||||||
|
AdminAddr: "127.0.0.1",
|
||||||
|
AdminPort: 0,
|
||||||
|
AdminUser: "",
|
||||||
|
AdminPwd: "",
|
||||||
PoolCount: 1,
|
PoolCount: 1,
|
||||||
TcpMux: true,
|
TcpMux: true,
|
||||||
User: "",
|
User: "",
|
||||||
LoginFailExit: true,
|
LoginFailExit: true,
|
||||||
Start: make(map[string]struct{}),
|
Start: make(map[string]struct{}),
|
||||||
|
Protocol: "tcp",
|
||||||
HeartBeatInterval: 30,
|
HeartBeatInterval: 30,
|
||||||
HeartBeatTimeout: 90,
|
HeartBeatTimeout: 90,
|
||||||
}
|
}
|
||||||
@@ -109,7 +121,9 @@ func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
|
|||||||
|
|
||||||
tmpStr, ok = conf.Get("common", "log_max_days")
|
tmpStr, ok = conf.Get("common", "log_max_days")
|
||||||
if ok {
|
if ok {
|
||||||
cfg.LogMaxDays, _ = strconv.ParseInt(tmpStr, 10, 64)
|
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
|
||||||
|
cfg.LogMaxDays = v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpStr, ok = conf.Get("common", "privilege_token")
|
tmpStr, ok = conf.Get("common", "privilege_token")
|
||||||
@@ -117,6 +131,28 @@ func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
|
|||||||
cfg.PrivilegeToken = tmpStr
|
cfg.PrivilegeToken = tmpStr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tmpStr, ok = conf.Get("common", "admin_addr")
|
||||||
|
if ok {
|
||||||
|
cfg.AdminAddr = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpStr, ok = conf.Get("common", "admin_port")
|
||||||
|
if ok {
|
||||||
|
if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
|
||||||
|
cfg.AdminPort = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpStr, ok = conf.Get("common", "admin_user")
|
||||||
|
if ok {
|
||||||
|
cfg.AdminUser = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpStr, ok = conf.Get("common", "admin_pwd")
|
||||||
|
if ok {
|
||||||
|
cfg.AdminPwd = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
tmpStr, ok = conf.Get("common", "pool_count")
|
tmpStr, ok = conf.Get("common", "pool_count")
|
||||||
if ok {
|
if ok {
|
||||||
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||||
@@ -143,7 +179,7 @@ func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
|
|||||||
if ok {
|
if ok {
|
||||||
proxyNames := strings.Split(tmpStr, ",")
|
proxyNames := strings.Split(tmpStr, ",")
|
||||||
for _, name := range proxyNames {
|
for _, name := range proxyNames {
|
||||||
cfg.Start[name] = struct{}{}
|
cfg.Start[strings.TrimSpace(name)] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +190,15 @@ func LoadClientCommonConf(conf ini.File) (cfg *ClientCommonConf, err error) {
|
|||||||
cfg.LoginFailExit = true
|
cfg.LoginFailExit = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tmpStr, ok = conf.Get("common", "protocol")
|
||||||
|
if ok {
|
||||||
|
// Now it only support tcp and kcp.
|
||||||
|
if tmpStr != "kcp" {
|
||||||
|
tmpStr = "tcp"
|
||||||
|
}
|
||||||
|
cfg.Protocol = tmpStr
|
||||||
|
}
|
||||||
|
|
||||||
tmpStr, ok = conf.Get("common", "heartbeat_timeout")
|
tmpStr, ok = conf.Get("common", "heartbeat_timeout")
|
||||||
if ok {
|
if ok {
|
||||||
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ func init() {
|
|||||||
proxyConfTypeMap[consts.UdpProxy] = reflect.TypeOf(UdpProxyConf{})
|
proxyConfTypeMap[consts.UdpProxy] = reflect.TypeOf(UdpProxyConf{})
|
||||||
proxyConfTypeMap[consts.HttpProxy] = reflect.TypeOf(HttpProxyConf{})
|
proxyConfTypeMap[consts.HttpProxy] = reflect.TypeOf(HttpProxyConf{})
|
||||||
proxyConfTypeMap[consts.HttpsProxy] = reflect.TypeOf(HttpsProxyConf{})
|
proxyConfTypeMap[consts.HttpsProxy] = reflect.TypeOf(HttpsProxyConf{})
|
||||||
|
proxyConfTypeMap[consts.StcpProxy] = reflect.TypeOf(StcpProxyConf{})
|
||||||
|
proxyConfTypeMap[consts.XtcpProxy] = reflect.TypeOf(XtcpProxyConf{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfByType creates a empty ProxyConf object by proxyType.
|
// NewConfByType creates a empty ProxyConf object by proxyType.
|
||||||
@@ -55,6 +57,7 @@ type ProxyConf interface {
|
|||||||
LoadFromFile(name string, conf ini.Section) error
|
LoadFromFile(name string, conf ini.Section) error
|
||||||
UnMarshalToMsg(pMsg *msg.NewProxy)
|
UnMarshalToMsg(pMsg *msg.NewProxy)
|
||||||
Check() error
|
Check() error
|
||||||
|
Compare(conf ProxyConf) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxyConf(pMsg *msg.NewProxy) (cfg ProxyConf, err error) {
|
func NewProxyConf(pMsg *msg.NewProxy) (cfg ProxyConf, err error) {
|
||||||
@@ -104,6 +107,16 @@ func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf {
|
|||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cfg *BaseProxyConf) compare(cmp *BaseProxyConf) bool {
|
||||||
|
if cfg.ProxyName != cmp.ProxyName ||
|
||||||
|
cfg.ProxyType != cmp.ProxyType ||
|
||||||
|
cfg.UseEncryption != cmp.UseEncryption ||
|
||||||
|
cfg.UseCompression != cmp.UseCompression {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (cfg *BaseProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
|
func (cfg *BaseProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
|
||||||
cfg.ProxyName = pMsg.ProxyName
|
cfg.ProxyName = pMsg.ProxyName
|
||||||
cfg.ProxyType = pMsg.ProxyType
|
cfg.ProxyType = pMsg.ProxyType
|
||||||
@@ -148,8 +161,16 @@ type BindInfoConf struct {
|
|||||||
RemotePort int64 `json:"remote_port"`
|
RemotePort int64 `json:"remote_port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cfg *BindInfoConf) compare(cmp *BindInfoConf) bool {
|
||||||
|
if cfg.BindAddr != cmp.BindAddr ||
|
||||||
|
cfg.RemotePort != cmp.RemotePort {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (cfg *BindInfoConf) LoadFromMsg(pMsg *msg.NewProxy) {
|
func (cfg *BindInfoConf) LoadFromMsg(pMsg *msg.NewProxy) {
|
||||||
cfg.BindAddr = ServerCommonCfg.BindAddr
|
cfg.BindAddr = ServerCommonCfg.ProxyBindAddr
|
||||||
cfg.RemotePort = pMsg.RemotePort
|
cfg.RemotePort = pMsg.RemotePort
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,6 +208,14 @@ type DomainConf struct {
|
|||||||
SubDomain string `json:"sub_domain"`
|
SubDomain string `json:"sub_domain"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cfg *DomainConf) compare(cmp *DomainConf) bool {
|
||||||
|
if strings.Join(cfg.CustomDomains, " ") != strings.Join(cmp.CustomDomains, " ") ||
|
||||||
|
cfg.SubDomain != cmp.SubDomain {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (cfg *DomainConf) LoadFromMsg(pMsg *msg.NewProxy) {
|
func (cfg *DomainConf) LoadFromMsg(pMsg *msg.NewProxy) {
|
||||||
cfg.CustomDomains = pMsg.CustomDomains
|
cfg.CustomDomains = pMsg.CustomDomains
|
||||||
cfg.SubDomain = pMsg.SubDomain
|
cfg.SubDomain = pMsg.SubDomain
|
||||||
@@ -245,6 +274,14 @@ type LocalSvrConf struct {
|
|||||||
LocalPort int `json:"-"`
|
LocalPort int `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cfg *LocalSvrConf) compare(cmp *LocalSvrConf) bool {
|
||||||
|
if cfg.LocalIp != cmp.LocalIp ||
|
||||||
|
cfg.LocalPort != cmp.LocalPort {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (cfg *LocalSvrConf) LoadFromFile(name string, section ini.Section) (err error) {
|
func (cfg *LocalSvrConf) LoadFromFile(name string, section ini.Section) (err error) {
|
||||||
if cfg.LocalIp = section["local_ip"]; cfg.LocalIp == "" {
|
if cfg.LocalIp = section["local_ip"]; cfg.LocalIp == "" {
|
||||||
cfg.LocalIp = "127.0.0.1"
|
cfg.LocalIp = "127.0.0.1"
|
||||||
@@ -265,6 +302,20 @@ type PluginConf struct {
|
|||||||
PluginParams map[string]string `json:"-"`
|
PluginParams map[string]string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cfg *PluginConf) compare(cmp *PluginConf) bool {
|
||||||
|
if cfg.Plugin != cmp.Plugin ||
|
||||||
|
len(cfg.PluginParams) != len(cmp.PluginParams) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for k, v := range cfg.PluginParams {
|
||||||
|
value, ok := cmp.PluginParams[k]
|
||||||
|
if !ok || v != value {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (cfg *PluginConf) LoadFromFile(name string, section ini.Section) (err error) {
|
func (cfg *PluginConf) LoadFromFile(name string, section ini.Section) (err error) {
|
||||||
cfg.Plugin = section["plugin"]
|
cfg.Plugin = section["plugin"]
|
||||||
cfg.PluginParams = make(map[string]string)
|
cfg.PluginParams = make(map[string]string)
|
||||||
@@ -290,6 +341,21 @@ type TcpProxyConf struct {
|
|||||||
PluginConf
|
PluginConf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool {
|
||||||
|
cmpConf, ok := cmp.(*TcpProxyConf)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
|
||||||
|
!cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) ||
|
||||||
|
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
|
||||||
|
!cfg.PluginConf.compare(&cmpConf.PluginConf) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (cfg *TcpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
|
func (cfg *TcpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
|
||||||
cfg.BaseProxyConf.LoadFromMsg(pMsg)
|
cfg.BaseProxyConf.LoadFromMsg(pMsg)
|
||||||
cfg.BindInfoConf.LoadFromMsg(pMsg)
|
cfg.BindInfoConf.LoadFromMsg(pMsg)
|
||||||
@@ -329,6 +395,20 @@ type UdpProxyConf struct {
|
|||||||
LocalSvrConf
|
LocalSvrConf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cfg *UdpProxyConf) Compare(cmp ProxyConf) bool {
|
||||||
|
cmpConf, ok := cmp.(*UdpProxyConf)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
|
||||||
|
!cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) ||
|
||||||
|
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (cfg *UdpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
|
func (cfg *UdpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
|
||||||
cfg.BaseProxyConf.LoadFromMsg(pMsg)
|
cfg.BaseProxyConf.LoadFromMsg(pMsg)
|
||||||
cfg.BindInfoConf.LoadFromMsg(pMsg)
|
cfg.BindInfoConf.LoadFromMsg(pMsg)
|
||||||
@@ -371,6 +451,25 @@ type HttpProxyConf struct {
|
|||||||
HttpPwd string `json:"-"`
|
HttpPwd string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cfg *HttpProxyConf) Compare(cmp ProxyConf) bool {
|
||||||
|
cmpConf, ok := cmp.(*HttpProxyConf)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
|
||||||
|
!cfg.DomainConf.compare(&cmpConf.DomainConf) ||
|
||||||
|
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
|
||||||
|
!cfg.PluginConf.compare(&cmpConf.PluginConf) ||
|
||||||
|
strings.Join(cfg.Locations, " ") != strings.Join(cmpConf.Locations, " ") ||
|
||||||
|
cfg.HostHeaderRewrite != cmpConf.HostHeaderRewrite ||
|
||||||
|
cfg.HttpUser != cmpConf.HttpUser ||
|
||||||
|
cfg.HttpPwd != cmpConf.HttpPwd {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (cfg *HttpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
|
func (cfg *HttpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
|
||||||
cfg.BaseProxyConf.LoadFromMsg(pMsg)
|
cfg.BaseProxyConf.LoadFromMsg(pMsg)
|
||||||
cfg.DomainConf.LoadFromMsg(pMsg)
|
cfg.DomainConf.LoadFromMsg(pMsg)
|
||||||
@@ -388,8 +487,10 @@ func (cfg *HttpProxyConf) LoadFromFile(name string, section ini.Section) (err er
|
|||||||
if err = cfg.DomainConf.LoadFromFile(name, section); err != nil {
|
if err = cfg.DomainConf.LoadFromFile(name, section); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
|
if err = cfg.PluginConf.LoadFromFile(name, section); err != nil {
|
||||||
return
|
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -435,6 +536,21 @@ type HttpsProxyConf struct {
|
|||||||
PluginConf
|
PluginConf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cfg *HttpsProxyConf) Compare(cmp ProxyConf) bool {
|
||||||
|
cmpConf, ok := cmp.(*HttpsProxyConf)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
|
||||||
|
!cfg.DomainConf.compare(&cmpConf.DomainConf) ||
|
||||||
|
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
|
||||||
|
!cfg.PluginConf.compare(&cmpConf.PluginConf) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (cfg *HttpsProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
|
func (cfg *HttpsProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
|
||||||
cfg.BaseProxyConf.LoadFromMsg(pMsg)
|
cfg.BaseProxyConf.LoadFromMsg(pMsg)
|
||||||
cfg.DomainConf.LoadFromMsg(pMsg)
|
cfg.DomainConf.LoadFromMsg(pMsg)
|
||||||
@@ -447,8 +563,10 @@ func (cfg *HttpsProxyConf) LoadFromFile(name string, section ini.Section) (err e
|
|||||||
if err = cfg.DomainConf.LoadFromFile(name, section); err != nil {
|
if err = cfg.DomainConf.LoadFromFile(name, section); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
|
if err = cfg.PluginConf.LoadFromFile(name, section); err != nil {
|
||||||
return
|
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -466,9 +584,189 @@ func (cfg *HttpsProxyConf) Check() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// STCP
|
||||||
|
type StcpProxyConf struct {
|
||||||
|
BaseProxyConf
|
||||||
|
|
||||||
|
Role string `json:"role"`
|
||||||
|
Sk string `json:"sk"`
|
||||||
|
|
||||||
|
// used in role server
|
||||||
|
LocalSvrConf
|
||||||
|
PluginConf
|
||||||
|
|
||||||
|
// used in role visitor
|
||||||
|
ServerName string `json:"server_name"`
|
||||||
|
BindAddr string `json:"bind_addr"`
|
||||||
|
BindPort int `json:"bind_port"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *StcpProxyConf) Compare(cmp ProxyConf) bool {
|
||||||
|
cmpConf, ok := cmp.(*StcpProxyConf)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
|
||||||
|
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
|
||||||
|
!cfg.PluginConf.compare(&cmpConf.PluginConf) ||
|
||||||
|
cfg.Role != cmpConf.Role ||
|
||||||
|
cfg.Sk != cmpConf.Sk ||
|
||||||
|
cfg.ServerName != cmpConf.ServerName ||
|
||||||
|
cfg.BindAddr != cmpConf.BindAddr ||
|
||||||
|
cfg.BindPort != cmpConf.BindPort {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only for role server.
|
||||||
|
func (cfg *StcpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
|
||||||
|
cfg.BaseProxyConf.LoadFromMsg(pMsg)
|
||||||
|
cfg.Sk = pMsg.Sk
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *StcpProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
|
||||||
|
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpStr := section["role"]
|
||||||
|
if tmpStr == "server" || tmpStr == "visitor" {
|
||||||
|
cfg.Role = tmpStr
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Parse conf error: incorrect role [%s]", tmpStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Sk = section["sk"]
|
||||||
|
|
||||||
|
if tmpStr == "visitor" {
|
||||||
|
prefix := section["prefix"]
|
||||||
|
cfg.ServerName = prefix + section["server_name"]
|
||||||
|
if cfg.BindAddr = section["bind_addr"]; cfg.BindAddr == "" {
|
||||||
|
cfg.BindAddr = "127.0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok := section["bind_port"]; ok {
|
||||||
|
if cfg.BindPort, err = strconv.Atoi(tmpStr); err != nil {
|
||||||
|
return fmt.Errorf("Parse conf error: proxy [%s] bind_port error", name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Parse conf error: proxy [%s] bind_port not found", name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err = cfg.PluginConf.LoadFromFile(name, section); err != nil {
|
||||||
|
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *StcpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
|
||||||
|
cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
|
||||||
|
pMsg.Sk = cfg.Sk
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *StcpProxyConf) Check() (err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// XTCP
|
||||||
|
type XtcpProxyConf struct {
|
||||||
|
BaseProxyConf
|
||||||
|
|
||||||
|
Role string `json:"role"`
|
||||||
|
Sk string `json:"sk"`
|
||||||
|
|
||||||
|
// used in role server
|
||||||
|
LocalSvrConf
|
||||||
|
PluginConf
|
||||||
|
|
||||||
|
// used in role visitor
|
||||||
|
ServerName string `json:"server_name"`
|
||||||
|
BindAddr string `json:"bind_addr"`
|
||||||
|
BindPort int `json:"bind_port"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *XtcpProxyConf) Compare(cmp ProxyConf) bool {
|
||||||
|
cmpConf, ok := cmp.(*XtcpProxyConf)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
|
||||||
|
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
|
||||||
|
!cfg.PluginConf.compare(&cmpConf.PluginConf) ||
|
||||||
|
cfg.Role != cmpConf.Role ||
|
||||||
|
cfg.Sk != cmpConf.Sk ||
|
||||||
|
cfg.ServerName != cmpConf.ServerName ||
|
||||||
|
cfg.BindAddr != cmpConf.BindAddr ||
|
||||||
|
cfg.BindPort != cmpConf.BindPort {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only for role server.
|
||||||
|
func (cfg *XtcpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
|
||||||
|
cfg.BaseProxyConf.LoadFromMsg(pMsg)
|
||||||
|
cfg.Sk = pMsg.Sk
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *XtcpProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
|
||||||
|
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpStr := section["role"]
|
||||||
|
if tmpStr == "server" || tmpStr == "visitor" {
|
||||||
|
cfg.Role = tmpStr
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Parse conf error: incorrect role [%s]", tmpStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Sk = section["sk"]
|
||||||
|
|
||||||
|
if tmpStr == "visitor" {
|
||||||
|
prefix := section["prefix"]
|
||||||
|
cfg.ServerName = prefix + section["server_name"]
|
||||||
|
if cfg.BindAddr = section["bind_addr"]; cfg.BindAddr == "" {
|
||||||
|
cfg.BindAddr = "127.0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok := section["bind_port"]; ok {
|
||||||
|
if cfg.BindPort, err = strconv.Atoi(tmpStr); err != nil {
|
||||||
|
return fmt.Errorf("Parse conf error: proxy [%s] bind_port error", name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Parse conf error: proxy [%s] bind_port not found", name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err = cfg.PluginConf.LoadFromFile(name, section); err != nil {
|
||||||
|
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *XtcpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
|
||||||
|
cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
|
||||||
|
pMsg.Sk = cfg.Sk
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *XtcpProxyConf) Check() (err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// if len(startProxy) is 0, start all
|
// if len(startProxy) is 0, start all
|
||||||
// otherwise just start proxies in startProxy map
|
// otherwise just start proxies in startProxy map
|
||||||
func LoadProxyConfFromFile(prefix string, conf ini.File, startProxy map[string]struct{}) (proxyConfs map[string]ProxyConf, err error) {
|
func LoadProxyConfFromFile(prefix string, conf ini.File, startProxy map[string]struct{}) (
|
||||||
|
proxyConfs map[string]ProxyConf, visitorConfs map[string]ProxyConf, err error) {
|
||||||
|
|
||||||
if prefix != "" {
|
if prefix != "" {
|
||||||
prefix += "."
|
prefix += "."
|
||||||
}
|
}
|
||||||
@@ -478,14 +776,23 @@ func LoadProxyConfFromFile(prefix string, conf ini.File, startProxy map[string]s
|
|||||||
startAll = false
|
startAll = false
|
||||||
}
|
}
|
||||||
proxyConfs = make(map[string]ProxyConf)
|
proxyConfs = make(map[string]ProxyConf)
|
||||||
|
visitorConfs = make(map[string]ProxyConf)
|
||||||
for name, section := range conf {
|
for name, section := range conf {
|
||||||
_, shouldStart := startProxy[name]
|
_, shouldStart := startProxy[name]
|
||||||
if name != "common" && (startAll || shouldStart) {
|
if name != "common" && (startAll || shouldStart) {
|
||||||
|
// some proxy or visotr configure may be used this prefix
|
||||||
|
section["prefix"] = prefix
|
||||||
cfg, err := NewProxyConfFromFile(name, section)
|
cfg, err := NewProxyConfFromFile(name, section)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return proxyConfs, err
|
return proxyConfs, visitorConfs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
role := section["role"]
|
||||||
|
if role == "visitor" {
|
||||||
|
visitorConfs[prefix+name] = cfg
|
||||||
|
} else {
|
||||||
|
proxyConfs[prefix+name] = cfg
|
||||||
}
|
}
|
||||||
proxyConfs[prefix+name] = cfg
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -27,15 +27,19 @@ var ServerCommonCfg *ServerCommonConf
|
|||||||
|
|
||||||
// common config
|
// common config
|
||||||
type ServerCommonConf struct {
|
type ServerCommonConf struct {
|
||||||
ConfigFile string
|
ConfigFile string
|
||||||
BindAddr string
|
BindAddr string
|
||||||
BindPort int64
|
BindPort int64
|
||||||
|
BindUdpPort int64
|
||||||
|
KcpBindPort int64
|
||||||
|
ProxyBindAddr string
|
||||||
|
|
||||||
// If VhostHttpPort equals 0, don't listen a public port for http protocol.
|
// If VhostHttpPort equals 0, don't listen a public port for http protocol.
|
||||||
VhostHttpPort int64
|
VhostHttpPort int64
|
||||||
|
|
||||||
// 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 int64
|
VhostHttpsPort int64
|
||||||
|
DashboardAddr string
|
||||||
|
|
||||||
// if DashboardPort equals 0, dashboard is not available
|
// if DashboardPort equals 0, dashboard is not available
|
||||||
DashboardPort int64
|
DashboardPort int64
|
||||||
@@ -64,8 +68,12 @@ func GetDefaultServerCommonConf() *ServerCommonConf {
|
|||||||
ConfigFile: "./frps.ini",
|
ConfigFile: "./frps.ini",
|
||||||
BindAddr: "0.0.0.0",
|
BindAddr: "0.0.0.0",
|
||||||
BindPort: 7000,
|
BindPort: 7000,
|
||||||
|
BindUdpPort: 0,
|
||||||
|
KcpBindPort: 0,
|
||||||
|
ProxyBindAddr: "0.0.0.0",
|
||||||
VhostHttpPort: 0,
|
VhostHttpPort: 0,
|
||||||
VhostHttpsPort: 0,
|
VhostHttpsPort: 0,
|
||||||
|
DashboardAddr: "0.0.0.0",
|
||||||
DashboardPort: 0,
|
DashboardPort: 0,
|
||||||
DashboardUser: "admin",
|
DashboardUser: "admin",
|
||||||
DashboardPwd: "admin",
|
DashboardPwd: "admin",
|
||||||
@@ -107,6 +115,29 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tmpStr, ok = conf.Get("common", "bind_udp_port")
|
||||||
|
if ok {
|
||||||
|
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||||
|
if err == nil {
|
||||||
|
cfg.BindUdpPort = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpStr, ok = conf.Get("common", "kcp_bind_port")
|
||||||
|
if ok {
|
||||||
|
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||||
|
if err == nil && v > 0 {
|
||||||
|
cfg.KcpBindPort = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpStr, ok = conf.Get("common", "proxy_bind_addr")
|
||||||
|
if ok {
|
||||||
|
cfg.ProxyBindAddr = tmpStr
|
||||||
|
} else {
|
||||||
|
cfg.ProxyBindAddr = cfg.BindAddr
|
||||||
|
}
|
||||||
|
|
||||||
tmpStr, ok = conf.Get("common", "vhost_http_port")
|
tmpStr, ok = conf.Get("common", "vhost_http_port")
|
||||||
if ok {
|
if ok {
|
||||||
cfg.VhostHttpPort, err = strconv.ParseInt(tmpStr, 10, 64)
|
cfg.VhostHttpPort, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||||
@@ -129,6 +160,13 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
|
|||||||
cfg.VhostHttpsPort = 0
|
cfg.VhostHttpsPort = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tmpStr, ok = conf.Get("common", "dashboard_addr")
|
||||||
|
if ok {
|
||||||
|
cfg.DashboardAddr = tmpStr
|
||||||
|
} else {
|
||||||
|
cfg.DashboardAddr = cfg.BindAddr
|
||||||
|
}
|
||||||
|
|
||||||
tmpStr, ok = conf.Get("common", "dashboard_port")
|
tmpStr, ok = conf.Get("common", "dashboard_port")
|
||||||
if ok {
|
if ok {
|
||||||
cfg.DashboardPort, err = strconv.ParseInt(tmpStr, 10, 64)
|
cfg.DashboardPort, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||||
|
|||||||
@@ -27,4 +27,6 @@ var (
|
|||||||
UdpProxy string = "udp"
|
UdpProxy string = "udp"
|
||||||
HttpProxy string = "http"
|
HttpProxy string = "http"
|
||||||
HttpsProxy string = "https"
|
HttpsProxy string = "https"
|
||||||
|
StcpProxy string = "stcp"
|
||||||
|
XtcpProxy string = "xtcp"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,16 +20,23 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TypeLogin = 'o'
|
TypeLogin = 'o'
|
||||||
TypeLoginResp = '1'
|
TypeLoginResp = '1'
|
||||||
TypeNewProxy = 'p'
|
TypeNewProxy = 'p'
|
||||||
TypeNewProxyResp = '2'
|
TypeNewProxyResp = '2'
|
||||||
TypeNewWorkConn = 'w'
|
TypeCloseProxy = 'c'
|
||||||
TypeReqWorkConn = 'r'
|
TypeNewWorkConn = 'w'
|
||||||
TypeStartWorkConn = 's'
|
TypeReqWorkConn = 'r'
|
||||||
TypePing = 'h'
|
TypeStartWorkConn = 's'
|
||||||
TypePong = '4'
|
TypeNewVisitorConn = 'v'
|
||||||
TypeUdpPacket = 'u'
|
TypeNewVisitorConnResp = '3'
|
||||||
|
TypePing = 'h'
|
||||||
|
TypePong = '4'
|
||||||
|
TypeUdpPacket = 'u'
|
||||||
|
TypeNatHoleVisitor = 'i'
|
||||||
|
TypeNatHoleClient = 'n'
|
||||||
|
TypeNatHoleResp = 'm'
|
||||||
|
TypeNatHoleSid = '5'
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -45,12 +52,19 @@ func init() {
|
|||||||
TypeMap[TypeLoginResp] = reflect.TypeOf(LoginResp{})
|
TypeMap[TypeLoginResp] = reflect.TypeOf(LoginResp{})
|
||||||
TypeMap[TypeNewProxy] = reflect.TypeOf(NewProxy{})
|
TypeMap[TypeNewProxy] = reflect.TypeOf(NewProxy{})
|
||||||
TypeMap[TypeNewProxyResp] = reflect.TypeOf(NewProxyResp{})
|
TypeMap[TypeNewProxyResp] = reflect.TypeOf(NewProxyResp{})
|
||||||
|
TypeMap[TypeCloseProxy] = reflect.TypeOf(CloseProxy{})
|
||||||
TypeMap[TypeNewWorkConn] = reflect.TypeOf(NewWorkConn{})
|
TypeMap[TypeNewWorkConn] = reflect.TypeOf(NewWorkConn{})
|
||||||
TypeMap[TypeReqWorkConn] = reflect.TypeOf(ReqWorkConn{})
|
TypeMap[TypeReqWorkConn] = reflect.TypeOf(ReqWorkConn{})
|
||||||
TypeMap[TypeStartWorkConn] = reflect.TypeOf(StartWorkConn{})
|
TypeMap[TypeStartWorkConn] = reflect.TypeOf(StartWorkConn{})
|
||||||
|
TypeMap[TypeNewVisitorConn] = reflect.TypeOf(NewVisitorConn{})
|
||||||
|
TypeMap[TypeNewVisitorConnResp] = reflect.TypeOf(NewVisitorConnResp{})
|
||||||
TypeMap[TypePing] = reflect.TypeOf(Ping{})
|
TypeMap[TypePing] = reflect.TypeOf(Ping{})
|
||||||
TypeMap[TypePong] = reflect.TypeOf(Pong{})
|
TypeMap[TypePong] = reflect.TypeOf(Pong{})
|
||||||
TypeMap[TypeUdpPacket] = reflect.TypeOf(UdpPacket{})
|
TypeMap[TypeUdpPacket] = reflect.TypeOf(UdpPacket{})
|
||||||
|
TypeMap[TypeNatHoleVisitor] = reflect.TypeOf(NatHoleVisitor{})
|
||||||
|
TypeMap[TypeNatHoleClient] = reflect.TypeOf(NatHoleClient{})
|
||||||
|
TypeMap[TypeNatHoleResp] = reflect.TypeOf(NatHoleResp{})
|
||||||
|
TypeMap[TypeNatHoleSid] = reflect.TypeOf(NatHoleSid{})
|
||||||
|
|
||||||
for k, v := range TypeMap {
|
for k, v := range TypeMap {
|
||||||
TypeStringMap[v] = k
|
TypeStringMap[v] = k
|
||||||
@@ -76,9 +90,10 @@ type Login struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type LoginResp struct {
|
type LoginResp struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
RunId string `json:"run_id"`
|
RunId string `json:"run_id"`
|
||||||
Error string `json:"error"`
|
ServerUdpPort int64 `json:"server_udp_port"`
|
||||||
|
Error string `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// When frpc login success, send this message to frps for running a new proxy.
|
// When frpc login success, send this message to frps for running a new proxy.
|
||||||
@@ -98,6 +113,9 @@ type NewProxy struct {
|
|||||||
HostHeaderRewrite string `json:"host_header_rewrite"`
|
HostHeaderRewrite string `json:"host_header_rewrite"`
|
||||||
HttpUser string `json:"http_user"`
|
HttpUser string `json:"http_user"`
|
||||||
HttpPwd string `json:"http_pwd"`
|
HttpPwd string `json:"http_pwd"`
|
||||||
|
|
||||||
|
// stcp
|
||||||
|
Sk string `json:"sk"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NewProxyResp struct {
|
type NewProxyResp struct {
|
||||||
@@ -105,6 +123,10 @@ type NewProxyResp struct {
|
|||||||
Error string `json:"error"`
|
Error string `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CloseProxy struct {
|
||||||
|
ProxyName string `json:"proxy_name"`
|
||||||
|
}
|
||||||
|
|
||||||
type NewWorkConn struct {
|
type NewWorkConn struct {
|
||||||
RunId string `json:"run_id"`
|
RunId string `json:"run_id"`
|
||||||
}
|
}
|
||||||
@@ -116,6 +138,19 @@ type StartWorkConn struct {
|
|||||||
ProxyName string `json:"proxy_name"`
|
ProxyName string `json:"proxy_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NewVisitorConn struct {
|
||||||
|
ProxyName string `json:"proxy_name"`
|
||||||
|
SignKey string `json:"sign_key"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
UseEncryption bool `json:"use_encryption"`
|
||||||
|
UseCompression bool `json:"use_compression"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NewVisitorConnResp struct {
|
||||||
|
ProxyName string `json:"proxy_name"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
type Ping struct {
|
type Ping struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,3 +162,24 @@ type UdpPacket struct {
|
|||||||
LocalAddr *net.UDPAddr `json:"l"`
|
LocalAddr *net.UDPAddr `json:"l"`
|
||||||
RemoteAddr *net.UDPAddr `json:"r"`
|
RemoteAddr *net.UDPAddr `json:"r"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NatHoleVisitor struct {
|
||||||
|
ProxyName string `json:"proxy_name"`
|
||||||
|
SignKey string `json:"sign_key"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NatHoleClient struct {
|
||||||
|
ProxyName string `json:"proxy_name"`
|
||||||
|
Sid string `json:"sid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NatHoleResp struct {
|
||||||
|
Sid string `json:"sid"`
|
||||||
|
VisitorAddr string `json:"visitor_addr"`
|
||||||
|
ClientAddr string `json:"client_addr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NatHoleSid struct {
|
||||||
|
Sid string `json:"sid"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
package plugin
|
package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -23,8 +24,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/fatedier/frp/models/proto/tcp"
|
|
||||||
"github.com/fatedier/frp/utils/errors"
|
"github.com/fatedier/frp/utils/errors"
|
||||||
|
frpIo "github.com/fatedier/frp/utils/io"
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -106,14 +107,26 @@ func (hp *HttpProxy) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hp *HttpProxy) Handle(conn io.ReadWriteCloser) {
|
func (hp *HttpProxy) Handle(conn io.ReadWriteCloser) {
|
||||||
var wrapConn net.Conn
|
var wrapConn frpNet.Conn
|
||||||
if realConn, ok := conn.(net.Conn); ok {
|
if realConn, ok := conn.(frpNet.Conn); ok {
|
||||||
wrapConn = realConn
|
wrapConn = realConn
|
||||||
} else {
|
} else {
|
||||||
wrapConn = frpNet.WrapReadWriteCloserToConn(conn)
|
wrapConn = frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
hp.l.PutConn(wrapConn)
|
sc, rd := frpNet.NewShareConn(wrapConn)
|
||||||
|
request, err := http.ReadRequest(bufio.NewReader(rd))
|
||||||
|
if err != nil {
|
||||||
|
wrapConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.Method == http.MethodConnect {
|
||||||
|
hp.handleConnectReq(request, frpIo.WrapReadWriteCloser(rd, wrapConn, nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hp.l.PutConn(sc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,13 +137,15 @@ func (hp *HttpProxy) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hp *HttpProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (hp *HttpProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
if ok := hp.Auth(rw, req); !ok {
|
if ok := hp.Auth(req); !ok {
|
||||||
rw.Header().Set("Proxy-Authenticate", "Basic")
|
rw.Header().Set("Proxy-Authenticate", "Basic")
|
||||||
rw.WriteHeader(http.StatusProxyAuthRequired)
|
rw.WriteHeader(http.StatusProxyAuthRequired)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Method == "CONNECT" {
|
if req.Method == http.MethodConnect {
|
||||||
|
// deprecated
|
||||||
|
// Connect request is handled in Handle function.
|
||||||
hp.ConnectHandler(rw, req)
|
hp.ConnectHandler(rw, req)
|
||||||
} else {
|
} else {
|
||||||
hp.HttpHandler(rw, req)
|
hp.HttpHandler(rw, req)
|
||||||
@@ -156,6 +171,9 @@ func (hp *HttpProxy) HttpHandler(rw http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deprecated
|
||||||
|
// Hijack needs to SetReadDeadline on the Conn of the request, but if we use stream compression here,
|
||||||
|
// we may always get i/o timeout error.
|
||||||
func (hp *HttpProxy) ConnectHandler(rw http.ResponseWriter, req *http.Request) {
|
func (hp *HttpProxy) ConnectHandler(rw http.ResponseWriter, req *http.Request) {
|
||||||
hj, ok := rw.(http.Hijacker)
|
hj, ok := rw.(http.Hijacker)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -175,12 +193,12 @@ func (hp *HttpProxy) ConnectHandler(rw http.ResponseWriter, req *http.Request) {
|
|||||||
client.Close()
|
client.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
client.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
|
client.Write([]byte("HTTP/1.1 200 OK\r\n\r\n"))
|
||||||
|
|
||||||
go tcp.Join(remote, client)
|
go frpIo.Join(remote, client)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hp *HttpProxy) Auth(rw http.ResponseWriter, req *http.Request) bool {
|
func (hp *HttpProxy) Auth(req *http.Request) bool {
|
||||||
if hp.AuthUser == "" && hp.AuthPasswd == "" {
|
if hp.AuthUser == "" && hp.AuthPasswd == "" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -206,6 +224,30 @@ func (hp *HttpProxy) Auth(rw http.ResponseWriter, req *http.Request) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (hp *HttpProxy) handleConnectReq(req *http.Request, rwc io.ReadWriteCloser) {
|
||||||
|
defer rwc.Close()
|
||||||
|
if ok := hp.Auth(req); !ok {
|
||||||
|
res := getBadResponse()
|
||||||
|
res.Write(rwc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
remote, err := net.Dial("tcp", req.URL.Host)
|
||||||
|
if err != nil {
|
||||||
|
res := &http.Response{
|
||||||
|
StatusCode: 400,
|
||||||
|
Proto: "HTTP/1.1",
|
||||||
|
ProtoMajor: 1,
|
||||||
|
ProtoMinor: 1,
|
||||||
|
}
|
||||||
|
res.Write(rwc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rwc.Write([]byte("HTTP/1.1 200 OK\r\n\r\n"))
|
||||||
|
|
||||||
|
frpIo.Join(remote, rwc)
|
||||||
|
}
|
||||||
|
|
||||||
func copyHeaders(dst, src http.Header) {
|
func copyHeaders(dst, src http.Header) {
|
||||||
for key, values := range src {
|
for key, values := range src {
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
@@ -225,3 +267,17 @@ func removeProxyHeaders(req *http.Request) {
|
|||||||
req.Header.Del("Transfer-Encoding")
|
req.Header.Del("Transfer-Encoding")
|
||||||
req.Header.Del("Upgrade")
|
req.Header.Del("Upgrade")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getBadResponse() *http.Response {
|
||||||
|
header := make(map[string][]string)
|
||||||
|
header["Proxy-Authenticate"] = []string{"Basic"}
|
||||||
|
res := &http.Response{
|
||||||
|
Status: "407 Not authorized",
|
||||||
|
StatusCode: 407,
|
||||||
|
Proto: "HTTP/1.1",
|
||||||
|
ProtoMajor: 1,
|
||||||
|
ProtoMinor: 1,
|
||||||
|
Header: header,
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|||||||
65
models/plugin/socks5.go
Normal file
65
models/plugin/socks5.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
|
||||||
|
gosocks5 "github.com/armon/go-socks5"
|
||||||
|
)
|
||||||
|
|
||||||
|
const PluginSocks5 = "socks5"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(PluginSocks5, NewSocks5Plugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Socks5Plugin struct {
|
||||||
|
Server *gosocks5.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSocks5Plugin(params map[string]string) (p Plugin, err error) {
|
||||||
|
sp := &Socks5Plugin{}
|
||||||
|
sp.Server, err = gosocks5.New(&gosocks5.Config{
|
||||||
|
Logger: log.New(ioutil.Discard, "", log.LstdFlags),
|
||||||
|
})
|
||||||
|
p = sp
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser) {
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
var wrapConn frpNet.Conn
|
||||||
|
if realConn, ok := conn.(frpNet.Conn); ok {
|
||||||
|
wrapConn = realConn
|
||||||
|
} else {
|
||||||
|
wrapConn = frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
sp.Server.ServeConn(wrapConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *Socks5Plugin) Name() string {
|
||||||
|
return PluginSocks5
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *Socks5Plugin) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/fatedier/frp/models/proto/tcp"
|
frpIo "github.com/fatedier/frp/utils/io"
|
||||||
)
|
)
|
||||||
|
|
||||||
const PluginUnixDomainSocket = "unix_domain_socket"
|
const PluginUnixDomainSocket = "unix_domain_socket"
|
||||||
@@ -57,7 +57,7 @@ func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tcp.Join(localConn, conn)
|
frpIo.Join(localConn, conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uds *UnixDomainSocketPlugin) Name() string {
|
func (uds *UnixDomainSocketPlugin) Name() string {
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
// Copyright 2016 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 tcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Join two io.ReadWriteCloser and do some operations.
|
|
||||||
func Join(c1 io.ReadWriteCloser, c2 io.ReadWriteCloser) (inCount int64, outCount int64) {
|
|
||||||
var wait sync.WaitGroup
|
|
||||||
pipe := func(to io.ReadWriteCloser, from io.ReadWriteCloser, count *int64) {
|
|
||||||
defer to.Close()
|
|
||||||
defer from.Close()
|
|
||||||
defer wait.Done()
|
|
||||||
|
|
||||||
*count, _ = io.Copy(to, from)
|
|
||||||
}
|
|
||||||
|
|
||||||
wait.Add(2)
|
|
||||||
go pipe(c1, c2, &inCount)
|
|
||||||
go pipe(c2, c1, &outCount)
|
|
||||||
wait.Wait()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,67 +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 tcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestJoin(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
var (
|
|
||||||
n int
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
text1 := "A document that gives tips for writing clear, idiomatic Go code. A must read for any new Go programmer. It augments the tour and the language specification, both of which should be read first."
|
|
||||||
text2 := "A document that specifies the conditions under which reads of a variable in one goroutine can be guaranteed to observe values produced by writes to the same variable in a different goroutine."
|
|
||||||
|
|
||||||
// Forward bytes directly.
|
|
||||||
pr, pw := io.Pipe()
|
|
||||||
pr2, pw2 := io.Pipe()
|
|
||||||
pr3, pw3 := io.Pipe()
|
|
||||||
pr4, pw4 := io.Pipe()
|
|
||||||
|
|
||||||
conn1 := WrapReadWriteCloser(pr, pw2)
|
|
||||||
conn2 := WrapReadWriteCloser(pr2, pw)
|
|
||||||
conn3 := WrapReadWriteCloser(pr3, pw4)
|
|
||||||
conn4 := WrapReadWriteCloser(pr4, pw3)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
Join(conn2, conn3)
|
|
||||||
}()
|
|
||||||
|
|
||||||
buf1 := make([]byte, 1024)
|
|
||||||
buf2 := make([]byte, 1024)
|
|
||||||
|
|
||||||
conn1.Write([]byte(text1))
|
|
||||||
conn4.Write([]byte(text2))
|
|
||||||
|
|
||||||
n, err = conn4.Read(buf1)
|
|
||||||
assert.NoError(err)
|
|
||||||
assert.Equal(text1, string(buf1[:n]))
|
|
||||||
|
|
||||||
n, err = conn1.Read(buf2)
|
|
||||||
assert.NoError(err)
|
|
||||||
assert.Equal(text2, string(buf2[:n]))
|
|
||||||
|
|
||||||
conn1.Close()
|
|
||||||
conn2.Close()
|
|
||||||
conn3.Close()
|
|
||||||
conn4.Close()
|
|
||||||
}
|
|
||||||
@@ -50,7 +50,7 @@ type Control struct {
|
|||||||
workConnCh chan net.Conn
|
workConnCh chan net.Conn
|
||||||
|
|
||||||
// proxies in one client
|
// proxies in one client
|
||||||
proxies []Proxy
|
proxies map[string]Proxy
|
||||||
|
|
||||||
// pool count
|
// pool count
|
||||||
poolCount int
|
poolCount int
|
||||||
@@ -82,7 +82,7 @@ func NewControl(svr *Service, ctlConn net.Conn, loginMsg *msg.Login) *Control {
|
|||||||
sendCh: make(chan msg.Message, 10),
|
sendCh: make(chan msg.Message, 10),
|
||||||
readCh: make(chan msg.Message, 10),
|
readCh: make(chan msg.Message, 10),
|
||||||
workConnCh: make(chan net.Conn, loginMsg.PoolCount+10),
|
workConnCh: make(chan net.Conn, loginMsg.PoolCount+10),
|
||||||
proxies: make([]Proxy, 0),
|
proxies: make(map[string]Proxy),
|
||||||
poolCount: loginMsg.PoolCount,
|
poolCount: loginMsg.PoolCount,
|
||||||
lastPing: time.Now(),
|
lastPing: time.Now(),
|
||||||
runId: loginMsg.RunId,
|
runId: loginMsg.RunId,
|
||||||
@@ -97,9 +97,10 @@ func NewControl(svr *Service, ctlConn net.Conn, loginMsg *msg.Login) *Control {
|
|||||||
// Start send a login success message to client and start working.
|
// Start send a login success message to client and start working.
|
||||||
func (ctl *Control) Start() {
|
func (ctl *Control) Start() {
|
||||||
loginRespMsg := &msg.LoginResp{
|
loginRespMsg := &msg.LoginResp{
|
||||||
Version: version.Full(),
|
Version: version.Full(),
|
||||||
RunId: ctl.runId,
|
RunId: ctl.runId,
|
||||||
Error: "",
|
ServerUdpPort: config.ServerCommonCfg.BindUdpPort,
|
||||||
|
Error: "",
|
||||||
}
|
}
|
||||||
msg.WriteMsg(ctl.conn, loginRespMsg)
|
msg.WriteMsg(ctl.conn, loginRespMsg)
|
||||||
|
|
||||||
@@ -265,6 +266,8 @@ func (ctl *Control) stoper() {
|
|||||||
workConn.Close()
|
workConn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctl.mu.Lock()
|
||||||
|
defer ctl.mu.Unlock()
|
||||||
for _, pxy := range ctl.proxies {
|
for _, pxy := range ctl.proxies {
|
||||||
pxy.Close()
|
pxy.Close()
|
||||||
ctl.svr.DelProxy(pxy.GetName())
|
ctl.svr.DelProxy(pxy.GetName())
|
||||||
@@ -317,6 +320,9 @@ func (ctl *Control) manager() {
|
|||||||
StatsNewProxy(m.ProxyName, m.ProxyType)
|
StatsNewProxy(m.ProxyName, m.ProxyType)
|
||||||
}
|
}
|
||||||
ctl.sendCh <- resp
|
ctl.sendCh <- resp
|
||||||
|
case *msg.CloseProxy:
|
||||||
|
ctl.CloseProxy(m)
|
||||||
|
ctl.conn.Info("close proxy [%s] success", m.ProxyName)
|
||||||
case *msg.Ping:
|
case *msg.Ping:
|
||||||
ctl.lastPing = time.Now()
|
ctl.lastPing = time.Now()
|
||||||
ctl.conn.Debug("receive heartbeat")
|
ctl.conn.Debug("receive heartbeat")
|
||||||
@@ -355,6 +361,25 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ctl.proxies = append(ctl.proxies, pxy)
|
|
||||||
|
ctl.mu.Lock()
|
||||||
|
ctl.proxies[pxy.GetName()] = pxy
|
||||||
|
ctl.mu.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
|
||||||
|
ctl.mu.Lock()
|
||||||
|
defer ctl.mu.Unlock()
|
||||||
|
|
||||||
|
pxy, ok := ctl.proxies[closeMsg.ProxyName]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pxy.Close()
|
||||||
|
ctl.svr.DelProxy(pxy.GetName())
|
||||||
|
delete(ctl.proxies, closeMsg.ProxyName)
|
||||||
|
StatsCloseProxy(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,16 +15,14 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"compress/gzip"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/assets"
|
"github.com/fatedier/frp/assets"
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
)
|
)
|
||||||
@@ -38,20 +36,24 @@ func RunDashboardServer(addr string, port int64) (err error) {
|
|||||||
// url router
|
// url router
|
||||||
router := httprouter.New()
|
router := httprouter.New()
|
||||||
|
|
||||||
|
user, passwd := config.ServerCommonCfg.DashboardUser, config.ServerCommonCfg.DashboardPwd
|
||||||
|
|
||||||
// api, see dashboard_api.go
|
// api, see dashboard_api.go
|
||||||
router.GET("/api/serverinfo", httprouterBasicAuth(apiServerInfo))
|
router.GET("/api/serverinfo", frpNet.HttprouterBasicAuth(apiServerInfo, user, passwd))
|
||||||
router.GET("/api/proxy/tcp", httprouterBasicAuth(apiProxyTcp))
|
router.GET("/api/proxy/tcp", frpNet.HttprouterBasicAuth(apiProxyTcp, user, passwd))
|
||||||
router.GET("/api/proxy/udp", httprouterBasicAuth(apiProxyUdp))
|
router.GET("/api/proxy/udp", frpNet.HttprouterBasicAuth(apiProxyUdp, user, passwd))
|
||||||
router.GET("/api/proxy/http", httprouterBasicAuth(apiProxyHttp))
|
router.GET("/api/proxy/http", frpNet.HttprouterBasicAuth(apiProxyHttp, user, passwd))
|
||||||
router.GET("/api/proxy/https", httprouterBasicAuth(apiProxyHttps))
|
router.GET("/api/proxy/https", frpNet.HttprouterBasicAuth(apiProxyHttps, user, passwd))
|
||||||
router.GET("/api/proxy/traffic/:name", httprouterBasicAuth(apiProxyTraffic))
|
router.GET("/api/proxy/traffic/:name", frpNet.HttprouterBasicAuth(apiProxyTraffic, user, passwd))
|
||||||
|
|
||||||
// view
|
// view
|
||||||
router.Handler("GET", "/favicon.ico", http.FileServer(assets.FileSystem))
|
router.Handler("GET", "/favicon.ico", http.FileServer(assets.FileSystem))
|
||||||
router.Handler("GET", "/static/*filepath", MakeGzipHandler(basicAuthWraper(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))))
|
router.Handler("GET", "/static/*filepath", frpNet.MakeHttpGzipHandler(
|
||||||
router.HandlerFunc("GET", "/", basicAuth(func(w http.ResponseWriter, r *http.Request) {
|
frpNet.NewHttpBasicAuthWraper(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)), user, passwd)))
|
||||||
|
|
||||||
|
router.HandlerFunc("GET", "/", frpNet.HttpBasicAuth(func(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||||
}))
|
}, user, passwd))
|
||||||
|
|
||||||
address := fmt.Sprintf("%s:%d", addr, port)
|
address := fmt.Sprintf("%s:%d", addr, port)
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
@@ -71,91 +73,3 @@ func RunDashboardServer(addr string, port int64) (err error) {
|
|||||||
go server.Serve(ln)
|
go server.Serve(ln)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func use(h http.HandlerFunc, middleware ...func(http.HandlerFunc) http.HandlerFunc) http.HandlerFunc {
|
|
||||||
for _, m := range middleware {
|
|
||||||
h = m(h)
|
|
||||||
}
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
type AuthWraper struct {
|
|
||||||
h http.Handler
|
|
||||||
user string
|
|
||||||
passwd string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (aw *AuthWraper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
user, passwd, hasAuth := r.BasicAuth()
|
|
||||||
if (aw.user == "" && aw.passwd == "") || (hasAuth && user == aw.user && passwd == aw.passwd) {
|
|
||||||
aw.h.ServeHTTP(w, r)
|
|
||||||
} else {
|
|
||||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
|
||||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func basicAuthWraper(h http.Handler) http.Handler {
|
|
||||||
return &AuthWraper{
|
|
||||||
h: h,
|
|
||||||
user: config.ServerCommonCfg.DashboardUser,
|
|
||||||
passwd: config.ServerCommonCfg.DashboardPwd,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func basicAuth(h http.HandlerFunc) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
user, passwd, hasAuth := r.BasicAuth()
|
|
||||||
if (config.ServerCommonCfg.DashboardUser == "" && config.ServerCommonCfg.DashboardPwd == "") ||
|
|
||||||
(hasAuth && user == config.ServerCommonCfg.DashboardUser && passwd == config.ServerCommonCfg.DashboardPwd) {
|
|
||||||
h.ServeHTTP(w, r)
|
|
||||||
} else {
|
|
||||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
|
||||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func httprouterBasicAuth(h httprouter.Handle) httprouter.Handle {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|
||||||
user, passwd, hasAuth := r.BasicAuth()
|
|
||||||
if (config.ServerCommonCfg.DashboardUser == "" && config.ServerCommonCfg.DashboardPwd == "") ||
|
|
||||||
(hasAuth && user == config.ServerCommonCfg.DashboardUser && passwd == config.ServerCommonCfg.DashboardPwd) {
|
|
||||||
h(w, r, ps)
|
|
||||||
} else {
|
|
||||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
|
||||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type GzipWraper struct {
|
|
||||||
h http.Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gw *GzipWraper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
|
||||||
gw.h.ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Encoding", "gzip")
|
|
||||||
gz := gzip.NewWriter(w)
|
|
||||||
defer gz.Close()
|
|
||||||
gzr := gzipResponseWriter{Writer: gz, ResponseWriter: w}
|
|
||||||
gw.h.ServeHTTP(gzr, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func MakeGzipHandler(h http.Handler) http.Handler {
|
|
||||||
return &GzipWraper{
|
|
||||||
h: h,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type gzipResponseWriter struct {
|
|
||||||
io.Writer
|
|
||||||
http.ResponseWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w gzipResponseWriter) Write(b []byte) (int, error) {
|
|
||||||
return w.Writer.Write(b)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -16,7 +16,12 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
frpIo "github.com/fatedier/frp/utils/io"
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
"github.com/fatedier/frp/utils/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ControlManager struct {
|
type ControlManager struct {
|
||||||
@@ -87,3 +92,72 @@ func (pm *ProxyManager) GetByName(name string) (pxy Proxy, ok bool) {
|
|||||||
pxy, ok = pm.pxys[name]
|
pxy, ok = pm.pxys[name]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Manager for visitor listeners.
|
||||||
|
type VisitorManager struct {
|
||||||
|
visitorListeners map[string]*frpNet.CustomListener
|
||||||
|
skMap map[string]string
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVisitorManager() *VisitorManager {
|
||||||
|
return &VisitorManager{
|
||||||
|
visitorListeners: make(map[string]*frpNet.CustomListener),
|
||||||
|
skMap: make(map[string]string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vm *VisitorManager) Listen(name string, sk string) (l *frpNet.CustomListener, err error) {
|
||||||
|
vm.mu.Lock()
|
||||||
|
defer vm.mu.Unlock()
|
||||||
|
|
||||||
|
if _, ok := vm.visitorListeners[name]; ok {
|
||||||
|
err = fmt.Errorf("custom listener for [%s] is repeated", name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l = frpNet.NewCustomListener()
|
||||||
|
vm.visitorListeners[name] = l
|
||||||
|
vm.skMap[name] = sk
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vm *VisitorManager) NewConn(name string, conn frpNet.Conn, timestamp int64, signKey string,
|
||||||
|
useEncryption bool, useCompression bool) (err error) {
|
||||||
|
|
||||||
|
vm.mu.RLock()
|
||||||
|
defer vm.mu.RUnlock()
|
||||||
|
|
||||||
|
if l, ok := vm.visitorListeners[name]; ok {
|
||||||
|
var sk string
|
||||||
|
if sk = vm.skMap[name]; util.GetAuthKey(sk, timestamp) != signKey {
|
||||||
|
err = fmt.Errorf("visitor connection of [%s] auth failed", name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var rwc io.ReadWriteCloser = conn
|
||||||
|
if useEncryption {
|
||||||
|
if rwc, err = frpIo.WithEncryption(rwc, []byte(sk)); err != nil {
|
||||||
|
err = fmt.Errorf("create encryption connection failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if useCompression {
|
||||||
|
rwc = frpIo.WithCompression(rwc)
|
||||||
|
}
|
||||||
|
err = l.PutConn(frpNet.WrapReadWriteCloserToConn(rwc, conn))
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("custom listener for [%s] doesn't exist", name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vm *VisitorManager) CloseListener(name string) {
|
||||||
|
vm.mu.Lock()
|
||||||
|
defer vm.mu.Unlock()
|
||||||
|
|
||||||
|
delete(vm.visitorListeners, name)
|
||||||
|
delete(vm.skMap, name)
|
||||||
|
}
|
||||||
|
|||||||
182
server/nathole.go
Normal file
182
server/nathole.go
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/msg"
|
||||||
|
"github.com/fatedier/frp/utils/errors"
|
||||||
|
"github.com/fatedier/frp/utils/log"
|
||||||
|
"github.com/fatedier/frp/utils/pool"
|
||||||
|
"github.com/fatedier/frp/utils/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Timeout seconds.
|
||||||
|
var NatHoleTimeout int64 = 10
|
||||||
|
|
||||||
|
type NatHoleController struct {
|
||||||
|
listener *net.UDPConn
|
||||||
|
|
||||||
|
clientCfgs map[string]*NatHoleClientCfg
|
||||||
|
sessions map[string]*NatHoleSession
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNatHoleController(udpBindAddr string) (nc *NatHoleController, err error) {
|
||||||
|
addr, err := net.ResolveUDPAddr("udp", udpBindAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lconn, err := net.ListenUDP("udp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nc = &NatHoleController{
|
||||||
|
listener: lconn,
|
||||||
|
clientCfgs: make(map[string]*NatHoleClientCfg),
|
||||||
|
sessions: make(map[string]*NatHoleSession),
|
||||||
|
}
|
||||||
|
return nc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *NatHoleController) ListenClient(name string, sk string) (sidCh chan string) {
|
||||||
|
clientCfg := &NatHoleClientCfg{
|
||||||
|
Name: name,
|
||||||
|
Sk: sk,
|
||||||
|
SidCh: make(chan string),
|
||||||
|
}
|
||||||
|
nc.mu.Lock()
|
||||||
|
nc.clientCfgs[name] = clientCfg
|
||||||
|
nc.mu.Unlock()
|
||||||
|
return clientCfg.SidCh
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *NatHoleController) CloseClient(name string) {
|
||||||
|
nc.mu.Lock()
|
||||||
|
defer nc.mu.Unlock()
|
||||||
|
delete(nc.clientCfgs, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *NatHoleController) Run() {
|
||||||
|
for {
|
||||||
|
buf := pool.GetBuf(1024)
|
||||||
|
n, raddr, err := nc.listener.ReadFromUDP(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Trace("nat hole listener read from udp error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rd := bytes.NewReader(buf[:n])
|
||||||
|
rawMsg, err := msg.ReadMsg(rd)
|
||||||
|
if err != nil {
|
||||||
|
log.Trace("read nat hole message error: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch m := rawMsg.(type) {
|
||||||
|
case *msg.NatHoleVisitor:
|
||||||
|
go nc.HandleVisitor(m, raddr)
|
||||||
|
case *msg.NatHoleClient:
|
||||||
|
go nc.HandleClient(m, raddr)
|
||||||
|
default:
|
||||||
|
log.Trace("error nat hole message type")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pool.PutBuf(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *NatHoleController) GenSid() string {
|
||||||
|
t := time.Now().Unix()
|
||||||
|
id, _ := util.RandId()
|
||||||
|
return fmt.Sprintf("%d%s", t, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *NatHoleController) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDPAddr) {
|
||||||
|
sid := nc.GenSid()
|
||||||
|
session := &NatHoleSession{
|
||||||
|
Sid: sid,
|
||||||
|
VisitorAddr: raddr,
|
||||||
|
NotifyCh: make(chan struct{}, 0),
|
||||||
|
}
|
||||||
|
nc.mu.Lock()
|
||||||
|
clientCfg, ok := nc.clientCfgs[m.ProxyName]
|
||||||
|
if !ok || m.SignKey != util.GetAuthKey(clientCfg.Sk, m.Timestamp) {
|
||||||
|
nc.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nc.sessions[sid] = session
|
||||||
|
nc.mu.Unlock()
|
||||||
|
log.Trace("handle visitor message, sid [%s]", sid)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
nc.mu.Lock()
|
||||||
|
delete(nc.sessions, sid)
|
||||||
|
nc.mu.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
err := errors.PanicToError(func() {
|
||||||
|
clientCfg.SidCh <- sid
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait client connections.
|
||||||
|
select {
|
||||||
|
case <-session.NotifyCh:
|
||||||
|
resp := nc.GenNatHoleResponse(raddr, session)
|
||||||
|
log.Trace("send nat hole response to visitor")
|
||||||
|
nc.listener.WriteToUDP(resp, raddr)
|
||||||
|
case <-time.After(time.Duration(NatHoleTimeout) * time.Second):
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *NatHoleController) HandleClient(m *msg.NatHoleClient, raddr *net.UDPAddr) {
|
||||||
|
nc.mu.RLock()
|
||||||
|
session, ok := nc.sessions[m.Sid]
|
||||||
|
nc.mu.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Trace("handle client message, sid [%s]", session.Sid)
|
||||||
|
session.ClientAddr = raddr
|
||||||
|
session.NotifyCh <- struct{}{}
|
||||||
|
|
||||||
|
resp := nc.GenNatHoleResponse(raddr, session)
|
||||||
|
log.Trace("send nat hole response to client")
|
||||||
|
nc.listener.WriteToUDP(resp, raddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *NatHoleController) GenNatHoleResponse(raddr *net.UDPAddr, session *NatHoleSession) []byte {
|
||||||
|
m := &msg.NatHoleResp{
|
||||||
|
Sid: session.Sid,
|
||||||
|
VisitorAddr: session.VisitorAddr.String(),
|
||||||
|
ClientAddr: session.ClientAddr.String(),
|
||||||
|
}
|
||||||
|
b := bytes.NewBuffer(nil)
|
||||||
|
err := msg.WriteMsg(b, m)
|
||||||
|
if err != nil {
|
||||||
|
return []byte("")
|
||||||
|
}
|
||||||
|
return b.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
type NatHoleSession struct {
|
||||||
|
Sid string
|
||||||
|
VisitorAddr *net.UDPAddr
|
||||||
|
ClientAddr *net.UDPAddr
|
||||||
|
|
||||||
|
NotifyCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type NatHoleClientCfg struct {
|
||||||
|
Name string
|
||||||
|
Sk string
|
||||||
|
SidCh chan string
|
||||||
|
}
|
||||||
153
server/proxy.go
153
server/proxy.go
@@ -24,9 +24,9 @@ import (
|
|||||||
|
|
||||||
"github.com/fatedier/frp/models/config"
|
"github.com/fatedier/frp/models/config"
|
||||||
"github.com/fatedier/frp/models/msg"
|
"github.com/fatedier/frp/models/msg"
|
||||||
"github.com/fatedier/frp/models/proto/tcp"
|
|
||||||
"github.com/fatedier/frp/models/proto/udp"
|
"github.com/fatedier/frp/models/proto/udp"
|
||||||
"github.com/fatedier/frp/utils/errors"
|
"github.com/fatedier/frp/utils/errors"
|
||||||
|
frpIo "github.com/fatedier/frp/utils/io"
|
||||||
"github.com/fatedier/frp/utils/log"
|
"github.com/fatedier/frp/utils/log"
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
"github.com/fatedier/frp/utils/vhost"
|
"github.com/fatedier/frp/utils/vhost"
|
||||||
@@ -143,6 +143,16 @@ func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy, err error) {
|
|||||||
BaseProxy: basePxy,
|
BaseProxy: basePxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
|
case *config.StcpProxyConf:
|
||||||
|
pxy = &StcpProxy{
|
||||||
|
BaseProxy: basePxy,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
case *config.XtcpProxyConf:
|
||||||
|
pxy = &XtcpProxy{
|
||||||
|
BaseProxy: basePxy,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return pxy, fmt.Errorf("proxy type not support")
|
return pxy, fmt.Errorf("proxy type not support")
|
||||||
}
|
}
|
||||||
@@ -156,7 +166,7 @@ type TcpProxy struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *TcpProxy) Run() error {
|
func (pxy *TcpProxy) Run() error {
|
||||||
listener, err := frpNet.ListenTcp(config.ServerCommonCfg.BindAddr, pxy.cfg.RemotePort)
|
listener, err := frpNet.ListenTcp(config.ServerCommonCfg.ProxyBindAddr, pxy.cfg.RemotePort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -179,13 +189,16 @@ func (pxy *TcpProxy) Close() {
|
|||||||
type HttpProxy struct {
|
type HttpProxy struct {
|
||||||
BaseProxy
|
BaseProxy
|
||||||
cfg *config.HttpProxyConf
|
cfg *config.HttpProxyConf
|
||||||
|
|
||||||
|
closeFuncs []func()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *HttpProxy) Run() (err error) {
|
func (pxy *HttpProxy) Run() (err error) {
|
||||||
routeConfig := &vhost.VhostRouteConfig{
|
routeConfig := vhost.VhostRouteConfig{
|
||||||
RewriteHost: pxy.cfg.HostHeaderRewrite,
|
RewriteHost: pxy.cfg.HostHeaderRewrite,
|
||||||
Username: pxy.cfg.HttpUser,
|
Username: pxy.cfg.HttpUser,
|
||||||
Password: pxy.cfg.HttpPwd,
|
Password: pxy.cfg.HttpPwd,
|
||||||
|
CreateConnFn: pxy.GetRealConn,
|
||||||
}
|
}
|
||||||
|
|
||||||
locations := pxy.cfg.Locations
|
locations := pxy.cfg.Locations
|
||||||
@@ -196,13 +209,16 @@ func (pxy *HttpProxy) Run() (err error) {
|
|||||||
routeConfig.Domain = domain
|
routeConfig.Domain = domain
|
||||||
for _, location := range locations {
|
for _, location := range locations {
|
||||||
routeConfig.Location = location
|
routeConfig.Location = location
|
||||||
l, err := pxy.ctl.svr.VhostHttpMuxer.Listen(routeConfig)
|
err := pxy.ctl.svr.httpReverseProxy.Register(routeConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
l.AddLogPrefix(pxy.name)
|
tmpDomain := routeConfig.Domain
|
||||||
|
tmpLocation := routeConfig.Location
|
||||||
|
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
||||||
|
pxy.ctl.svr.httpReverseProxy.UnRegister(tmpDomain, tmpLocation)
|
||||||
|
})
|
||||||
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location)
|
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location)
|
||||||
pxy.listeners = append(pxy.listeners, l)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,17 +226,18 @@ func (pxy *HttpProxy) Run() (err error) {
|
|||||||
routeConfig.Domain = pxy.cfg.SubDomain + "." + config.ServerCommonCfg.SubDomainHost
|
routeConfig.Domain = pxy.cfg.SubDomain + "." + config.ServerCommonCfg.SubDomainHost
|
||||||
for _, location := range locations {
|
for _, location := range locations {
|
||||||
routeConfig.Location = location
|
routeConfig.Location = location
|
||||||
l, err := pxy.ctl.svr.VhostHttpMuxer.Listen(routeConfig)
|
err := pxy.ctl.svr.httpReverseProxy.Register(routeConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
l.AddLogPrefix(pxy.name)
|
tmpDomain := routeConfig.Domain
|
||||||
|
tmpLocation := routeConfig.Location
|
||||||
|
pxy.closeFuncs = append(pxy.closeFuncs, func() {
|
||||||
|
pxy.ctl.svr.httpReverseProxy.UnRegister(tmpDomain, tmpLocation)
|
||||||
|
})
|
||||||
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location)
|
pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location)
|
||||||
pxy.listeners = append(pxy.listeners, l)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pxy.startListenHandler(pxy, HandleUserTcpConnection)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,8 +245,33 @@ func (pxy *HttpProxy) GetConf() config.ProxyConf {
|
|||||||
return pxy.cfg
|
return pxy.cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pxy *HttpProxy) GetRealConn() (workConn frpNet.Conn, err error) {
|
||||||
|
tmpConn, errRet := pxy.GetWorkConnFromPool()
|
||||||
|
if errRet != nil {
|
||||||
|
err = errRet
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var rwc io.ReadWriteCloser = tmpConn
|
||||||
|
if pxy.cfg.UseEncryption {
|
||||||
|
rwc, err = frpIo.WithEncryption(rwc, []byte(config.ServerCommonCfg.PrivilegeToken))
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("create encryption stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pxy.cfg.UseCompression {
|
||||||
|
rwc = frpIo.WithCompression(rwc)
|
||||||
|
}
|
||||||
|
workConn = frpNet.WrapReadWriteCloserToConn(rwc, tmpConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (pxy *HttpProxy) Close() {
|
func (pxy *HttpProxy) Close() {
|
||||||
pxy.BaseProxy.Close()
|
pxy.BaseProxy.Close()
|
||||||
|
for _, closeFn := range pxy.closeFuncs {
|
||||||
|
closeFn()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type HttpsProxy struct {
|
type HttpsProxy struct {
|
||||||
@@ -274,6 +316,81 @@ func (pxy *HttpsProxy) Close() {
|
|||||||
pxy.BaseProxy.Close()
|
pxy.BaseProxy.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StcpProxy struct {
|
||||||
|
BaseProxy
|
||||||
|
cfg *config.StcpProxyConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *StcpProxy) Run() error {
|
||||||
|
listener, err := pxy.ctl.svr.visitorManager.Listen(pxy.GetName(), pxy.cfg.Sk)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
listener.AddLogPrefix(pxy.name)
|
||||||
|
pxy.listeners = append(pxy.listeners, listener)
|
||||||
|
pxy.Info("stcp proxy custom listen success")
|
||||||
|
|
||||||
|
pxy.startListenHandler(pxy, HandleUserTcpConnection)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *StcpProxy) GetConf() config.ProxyConf {
|
||||||
|
return pxy.cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *StcpProxy) Close() {
|
||||||
|
pxy.BaseProxy.Close()
|
||||||
|
pxy.ctl.svr.visitorManager.CloseListener(pxy.GetName())
|
||||||
|
}
|
||||||
|
|
||||||
|
type XtcpProxy struct {
|
||||||
|
BaseProxy
|
||||||
|
cfg *config.XtcpProxyConf
|
||||||
|
|
||||||
|
closeCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XtcpProxy) Run() error {
|
||||||
|
if pxy.ctl.svr.natHoleController == nil {
|
||||||
|
pxy.Error("udp port for xtcp is not specified.")
|
||||||
|
return fmt.Errorf("xtcp is not supported in frps")
|
||||||
|
}
|
||||||
|
sidCh := pxy.ctl.svr.natHoleController.ListenClient(pxy.GetName(), pxy.cfg.Sk)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-pxy.closeCh:
|
||||||
|
break
|
||||||
|
case sid := <-sidCh:
|
||||||
|
workConn, err := pxy.GetWorkConnFromPool()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m := &msg.NatHoleSid{
|
||||||
|
Sid: sid,
|
||||||
|
}
|
||||||
|
err = msg.WriteMsg(workConn, m)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Warn("write nat hole sid package error, %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XtcpProxy) GetConf() config.ProxyConf {
|
||||||
|
return pxy.cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XtcpProxy) Close() {
|
||||||
|
pxy.BaseProxy.Close()
|
||||||
|
pxy.ctl.svr.natHoleController.CloseClient(pxy.GetName())
|
||||||
|
errors.PanicToError(func() {
|
||||||
|
close(pxy.closeCh)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
type UdpProxy struct {
|
type UdpProxy struct {
|
||||||
BaseProxy
|
BaseProxy
|
||||||
cfg *config.UdpProxyConf
|
cfg *config.UdpProxyConf
|
||||||
@@ -298,7 +415,7 @@ type UdpProxy struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *UdpProxy) Run() (err error) {
|
func (pxy *UdpProxy) Run() (err error) {
|
||||||
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", config.ServerCommonCfg.BindAddr, pxy.cfg.RemotePort))
|
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", config.ServerCommonCfg.ProxyBindAddr, pxy.cfg.RemotePort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -461,20 +578,20 @@ func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn) {
|
|||||||
var local io.ReadWriteCloser = workConn
|
var local io.ReadWriteCloser = workConn
|
||||||
cfg := pxy.GetConf().GetBaseInfo()
|
cfg := pxy.GetConf().GetBaseInfo()
|
||||||
if cfg.UseEncryption {
|
if cfg.UseEncryption {
|
||||||
local, err = tcp.WithEncryption(local, []byte(config.ServerCommonCfg.PrivilegeToken))
|
local, err = frpIo.WithEncryption(local, []byte(config.ServerCommonCfg.PrivilegeToken))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pxy.Error("create encryption stream error: %v", err)
|
pxy.Error("create encryption stream error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cfg.UseCompression {
|
if cfg.UseCompression {
|
||||||
local = tcp.WithCompression(local)
|
local = frpIo.WithCompression(local)
|
||||||
}
|
}
|
||||||
pxy.Debug("join connections, workConn(l[%s] r[%s]) userConn(l[%s] r[%s])", workConn.LocalAddr().String(),
|
pxy.Debug("join connections, workConn(l[%s] r[%s]) userConn(l[%s] r[%s])", workConn.LocalAddr().String(),
|
||||||
workConn.RemoteAddr().String(), userConn.LocalAddr().String(), userConn.RemoteAddr().String())
|
workConn.RemoteAddr().String(), userConn.LocalAddr().String(), userConn.RemoteAddr().String())
|
||||||
|
|
||||||
StatsOpenConnection(pxy.GetName())
|
StatsOpenConnection(pxy.GetName())
|
||||||
inCount, outCount := tcp.Join(local, userConn)
|
inCount, outCount := frpIo.Join(local, userConn)
|
||||||
StatsCloseConnection(pxy.GetName())
|
StatsCloseConnection(pxy.GetName())
|
||||||
StatsAddTrafficIn(pxy.GetName(), inCount)
|
StatsAddTrafficIn(pxy.GetName(), inCount)
|
||||||
StatsAddTrafficOut(pxy.GetName(), outCount)
|
StatsAddTrafficOut(pxy.GetName(), outCount)
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/assets"
|
"github.com/fatedier/frp/assets"
|
||||||
@@ -41,58 +43,84 @@ type Service struct {
|
|||||||
// Accept connections from client.
|
// Accept connections from client.
|
||||||
listener frpNet.Listener
|
listener frpNet.Listener
|
||||||
|
|
||||||
// For http proxies, route requests to different clients by hostname and other infomation.
|
// Accept connections using kcp.
|
||||||
VhostHttpMuxer *vhost.HttpMuxer
|
kcpListener frpNet.Listener
|
||||||
|
|
||||||
// For https proxies, route requests to different clients by hostname and other infomation.
|
// For https proxies, route requests to different clients by hostname and other infomation.
|
||||||
VhostHttpsMuxer *vhost.HttpsMuxer
|
VhostHttpsMuxer *vhost.HttpsMuxer
|
||||||
|
|
||||||
|
httpReverseProxy *vhost.HttpReverseProxy
|
||||||
|
|
||||||
// Manage all controllers.
|
// Manage all controllers.
|
||||||
ctlManager *ControlManager
|
ctlManager *ControlManager
|
||||||
|
|
||||||
// Manage all proxies.
|
// Manage all proxies.
|
||||||
pxyManager *ProxyManager
|
pxyManager *ProxyManager
|
||||||
|
|
||||||
|
// Manage all visitor listeners.
|
||||||
|
visitorManager *VisitorManager
|
||||||
|
|
||||||
|
// Controller for nat hole connections.
|
||||||
|
natHoleController *NatHoleController
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService() (svr *Service, err error) {
|
func NewService() (svr *Service, err error) {
|
||||||
svr = &Service{
|
svr = &Service{
|
||||||
ctlManager: NewControlManager(),
|
ctlManager: NewControlManager(),
|
||||||
pxyManager: NewProxyManager(),
|
pxyManager: NewProxyManager(),
|
||||||
|
visitorManager: NewVisitorManager(),
|
||||||
}
|
}
|
||||||
|
cfg := config.ServerCommonCfg
|
||||||
|
|
||||||
// Init assets.
|
// Init assets.
|
||||||
err = assets.Load(config.ServerCommonCfg.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)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen for accepting connections from client.
|
// Listen for accepting connections from client.
|
||||||
svr.listener, err = frpNet.ListenTcp(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.BindPort)
|
svr.listener, err = frpNet.ListenTcp(cfg.BindAddr, cfg.BindPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("Create server listener error, %v", err)
|
err = fmt.Errorf("Create server listener error, %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
log.Info("frps tcp listen on %s:%d", cfg.BindAddr, cfg.BindPort)
|
||||||
|
|
||||||
|
// Listen for accepting connections from client using kcp protocol.
|
||||||
|
if cfg.KcpBindPort > 0 {
|
||||||
|
svr.kcpListener, err = frpNet.ListenKcp(cfg.BindAddr, cfg.KcpBindPort)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Listen on kcp address udp [%s:%d] error: %v", cfg.BindAddr, cfg.KcpBindPort, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Info("frps kcp listen on udp %s:%d", cfg.BindAddr, cfg.KcpBindPort)
|
||||||
|
}
|
||||||
|
|
||||||
// Create http vhost muxer.
|
// Create http vhost muxer.
|
||||||
if config.ServerCommonCfg.VhostHttpPort != 0 {
|
if cfg.VhostHttpPort > 0 {
|
||||||
var l frpNet.Listener
|
rp := vhost.NewHttpReverseProxy()
|
||||||
l, err = frpNet.ListenTcp(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.VhostHttpPort)
|
svr.httpReverseProxy = rp
|
||||||
|
|
||||||
|
address := fmt.Sprintf("%s:%d", cfg.ProxyBindAddr, cfg.VhostHttpPort)
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: address,
|
||||||
|
Handler: rp,
|
||||||
|
}
|
||||||
|
var l net.Listener
|
||||||
|
l, err = net.Listen("tcp", address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("Create vhost http listener error, %v", err)
|
err = fmt.Errorf("Create vhost http listener error, %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
svr.VhostHttpMuxer, err = vhost.NewHttpMuxer(l, 30*time.Second)
|
go server.Serve(l)
|
||||||
if err != nil {
|
log.Info("http service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpPort)
|
||||||
err = fmt.Errorf("Create vhost httpMuxer error, %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create https vhost muxer.
|
// Create https vhost muxer.
|
||||||
if config.ServerCommonCfg.VhostHttpsPort != 0 {
|
if cfg.VhostHttpsPort > 0 {
|
||||||
var l frpNet.Listener
|
var l frpNet.Listener
|
||||||
l, err = frpNet.ListenTcp(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.VhostHttpsPort)
|
l, err = frpNet.ListenTcp(cfg.ProxyBindAddr, cfg.VhostHttpsPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("Create vhost https listener error, %v", err)
|
err = fmt.Errorf("Create vhost https listener error, %v", err)
|
||||||
return
|
return
|
||||||
@@ -102,24 +130,49 @@ func NewService() (svr *Service, err error) {
|
|||||||
err = fmt.Errorf("Create vhost httpsMuxer error, %v", err)
|
err = fmt.Errorf("Create vhost httpsMuxer error, %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
log.Info("https service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create nat hole controller.
|
||||||
|
if cfg.BindUdpPort > 0 {
|
||||||
|
var nc *NatHoleController
|
||||||
|
addr := fmt.Sprintf("%s:%d", cfg.BindAddr, cfg.BindUdpPort)
|
||||||
|
nc, err = NewNatHoleController(addr)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Create nat hole controller error, %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
svr.natHoleController = nc
|
||||||
|
log.Info("nat hole udp service listen on %s:%d", cfg.BindAddr, cfg.BindUdpPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create dashboard web server.
|
// Create dashboard web server.
|
||||||
if config.ServerCommonCfg.DashboardPort != 0 {
|
if cfg.DashboardPort > 0 {
|
||||||
err = RunDashboardServer(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.DashboardPort)
|
err = RunDashboardServer(cfg.DashboardAddr, cfg.DashboardPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("Create dashboard web server error, %v", err)
|
err = fmt.Errorf("Create dashboard web server error, %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Info("Dashboard listen on %s:%d", config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.DashboardPort)
|
log.Info("Dashboard listen on %s:%d", cfg.DashboardAddr, cfg.DashboardPort)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svr *Service) Run() {
|
func (svr *Service) Run() {
|
||||||
|
if svr.natHoleController != nil {
|
||||||
|
go svr.natHoleController.Run()
|
||||||
|
}
|
||||||
|
if config.ServerCommonCfg.KcpBindPort > 0 {
|
||||||
|
go svr.HandleListener(svr.kcpListener)
|
||||||
|
}
|
||||||
|
svr.HandleListener(svr.listener)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svr *Service) HandleListener(l frpNet.Listener) {
|
||||||
// Listen for incoming connections from client.
|
// Listen for incoming connections from client.
|
||||||
for {
|
for {
|
||||||
c, err := svr.listener.Accept()
|
c, err := l.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Listener for incoming connections from client closed")
|
log.Warn("Listener for incoming connections from client closed")
|
||||||
return
|
return
|
||||||
@@ -131,7 +184,7 @@ func (svr *Service) Run() {
|
|||||||
var rawMsg msg.Message
|
var rawMsg msg.Message
|
||||||
conn.SetReadDeadline(time.Now().Add(connReadTimeout))
|
conn.SetReadDeadline(time.Now().Add(connReadTimeout))
|
||||||
if rawMsg, err = msg.ReadMsg(conn); err != nil {
|
if rawMsg, err = msg.ReadMsg(conn); err != nil {
|
||||||
log.Warn("Failed to read message: %v", err)
|
log.Trace("Failed to read message: %v", err)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -152,6 +205,20 @@ func (svr *Service) Run() {
|
|||||||
}
|
}
|
||||||
case *msg.NewWorkConn:
|
case *msg.NewWorkConn:
|
||||||
svr.RegisterWorkConn(conn, m)
|
svr.RegisterWorkConn(conn, m)
|
||||||
|
case *msg.NewVisitorConn:
|
||||||
|
if err = svr.RegisterVisitorConn(conn, m); err != nil {
|
||||||
|
conn.Warn("%v", err)
|
||||||
|
msg.WriteMsg(conn, &msg.NewVisitorConnResp{
|
||||||
|
ProxyName: m.ProxyName,
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
conn.Close()
|
||||||
|
} else {
|
||||||
|
msg.WriteMsg(conn, &msg.NewVisitorConnResp{
|
||||||
|
ProxyName: m.ProxyName,
|
||||||
|
Error: "",
|
||||||
|
})
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
log.Warn("Error message type for the new connection [%s]", conn.RemoteAddr().String())
|
log.Warn("Error message type for the new connection [%s]", conn.RemoteAddr().String())
|
||||||
conn.Close()
|
conn.Close()
|
||||||
@@ -238,9 +305,13 @@ func (svr *Service) RegisterWorkConn(workConn frpNet.Conn, newMsg *msg.NewWorkCo
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (svr *Service) RegisterVisitorConn(visitorConn frpNet.Conn, newMsg *msg.NewVisitorConn) error {
|
||||||
|
return svr.visitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey,
|
||||||
|
newMsg.UseEncryption, newMsg.UseCompression)
|
||||||
|
}
|
||||||
|
|
||||||
func (svr *Service) RegisterProxy(name string, pxy Proxy) error {
|
func (svr *Service) RegisterProxy(name string, pxy Proxy) error {
|
||||||
err := svr.pxyManager.Add(name, pxy)
|
return svr.pxyManager.Add(name, pxy)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svr *Service) DelProxy(name string) {
|
func (svr *Service) DelProxy(name string) {
|
||||||
|
|||||||
@@ -12,38 +12,74 @@
|
|||||||
// 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 tcp
|
package io
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"sync"
|
||||||
"github.com/golang/snappy"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/utils/crypto"
|
"github.com/fatedier/frp/utils/crypto"
|
||||||
|
"github.com/fatedier/frp/utils/pool"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Join two io.ReadWriteCloser and do some operations.
|
||||||
|
func Join(c1 io.ReadWriteCloser, c2 io.ReadWriteCloser) (inCount int64, outCount int64) {
|
||||||
|
var wait sync.WaitGroup
|
||||||
|
pipe := func(to io.ReadWriteCloser, from io.ReadWriteCloser, count *int64) {
|
||||||
|
defer to.Close()
|
||||||
|
defer from.Close()
|
||||||
|
defer wait.Done()
|
||||||
|
|
||||||
|
buf := pool.GetBuf(16 * 1024)
|
||||||
|
defer pool.PutBuf(buf)
|
||||||
|
*count, _ = io.CopyBuffer(to, from, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
wait.Add(2)
|
||||||
|
go pipe(c1, c2, &inCount)
|
||||||
|
go pipe(c2, c1, &outCount)
|
||||||
|
wait.Wait()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func WithEncryption(rwc io.ReadWriteCloser, key []byte) (io.ReadWriteCloser, error) {
|
func WithEncryption(rwc io.ReadWriteCloser, key []byte) (io.ReadWriteCloser, error) {
|
||||||
w, err := crypto.NewWriter(rwc, key)
|
w, err := crypto.NewWriter(rwc, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return WrapReadWriteCloser(crypto.NewReader(rwc, key), w), nil
|
return WrapReadWriteCloser(crypto.NewReader(rwc, key), w, func() error {
|
||||||
|
return rwc.Close()
|
||||||
|
}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithCompression(rwc io.ReadWriteCloser) io.ReadWriteCloser {
|
func WithCompression(rwc io.ReadWriteCloser) io.ReadWriteCloser {
|
||||||
return WrapReadWriteCloser(snappy.NewReader(rwc), snappy.NewWriter(rwc))
|
sr := pool.GetSnappyReader(rwc)
|
||||||
}
|
sw := pool.GetSnappyWriter(rwc)
|
||||||
|
return WrapReadWriteCloser(sr, sw, func() error {
|
||||||
func WrapReadWriteCloser(r io.Reader, w io.Writer) io.ReadWriteCloser {
|
err := rwc.Close()
|
||||||
return &ReadWriteCloser{
|
pool.PutSnappyReader(sr)
|
||||||
r: r,
|
pool.PutSnappyWriter(sw)
|
||||||
w: w,
|
return err
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReadWriteCloser struct {
|
type ReadWriteCloser struct {
|
||||||
r io.Reader
|
r io.Reader
|
||||||
w io.Writer
|
w io.Writer
|
||||||
|
closeFn func() error
|
||||||
|
|
||||||
|
closed bool
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// closeFn will be called only once
|
||||||
|
func WrapReadWriteCloser(r io.Reader, w io.Writer, closeFn func() error) io.ReadWriteCloser {
|
||||||
|
return &ReadWriteCloser{
|
||||||
|
r: r,
|
||||||
|
w: w,
|
||||||
|
closeFn: closeFn,
|
||||||
|
closed: false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rwc *ReadWriteCloser) Read(p []byte) (n int, err error) {
|
func (rwc *ReadWriteCloser) Read(p []byte) (n int, err error) {
|
||||||
@@ -55,6 +91,14 @@ func (rwc *ReadWriteCloser) Write(p []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rwc *ReadWriteCloser) Close() (errRet error) {
|
func (rwc *ReadWriteCloser) Close() (errRet error) {
|
||||||
|
rwc.mu.Lock()
|
||||||
|
if rwc.closed {
|
||||||
|
rwc.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rwc.closed = true
|
||||||
|
rwc.mu.Unlock()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if rc, ok := rwc.r.(io.Closer); ok {
|
if rc, ok := rwc.r.(io.Closer); ok {
|
||||||
err = rc.Close()
|
err = rc.Close()
|
||||||
@@ -69,5 +113,12 @@ func (rwc *ReadWriteCloser) Close() (errRet error) {
|
|||||||
errRet = err
|
errRet = err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rwc.closeFn != nil {
|
||||||
|
err = rwc.closeFn()
|
||||||
|
if err != nil {
|
||||||
|
errRet = err
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
// 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 tcp
|
package io
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
@@ -21,6 +21,51 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestJoin(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
var (
|
||||||
|
n int
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
text1 := "A document that gives tips for writing clear, idiomatic Go code. A must read for any new Go programmer. It augments the tour and the language specification, both of which should be read first."
|
||||||
|
text2 := "A document that specifies the conditions under which reads of a variable in one goroutine can be guaranteed to observe values produced by writes to the same variable in a different goroutine."
|
||||||
|
|
||||||
|
// Forward bytes directly.
|
||||||
|
pr, pw := io.Pipe()
|
||||||
|
pr2, pw2 := io.Pipe()
|
||||||
|
pr3, pw3 := io.Pipe()
|
||||||
|
pr4, pw4 := io.Pipe()
|
||||||
|
|
||||||
|
conn1 := WrapReadWriteCloser(pr, pw2, nil)
|
||||||
|
conn2 := WrapReadWriteCloser(pr2, pw, nil)
|
||||||
|
conn3 := WrapReadWriteCloser(pr3, pw4, nil)
|
||||||
|
conn4 := WrapReadWriteCloser(pr4, pw3, nil)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
Join(conn2, conn3)
|
||||||
|
}()
|
||||||
|
|
||||||
|
buf1 := make([]byte, 1024)
|
||||||
|
buf2 := make([]byte, 1024)
|
||||||
|
|
||||||
|
conn1.Write([]byte(text1))
|
||||||
|
conn4.Write([]byte(text2))
|
||||||
|
|
||||||
|
n, err = conn4.Read(buf1)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(text1, string(buf1[:n]))
|
||||||
|
|
||||||
|
n, err = conn1.Read(buf2)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(text2, string(buf2[:n]))
|
||||||
|
|
||||||
|
conn1.Close()
|
||||||
|
conn2.Close()
|
||||||
|
conn3.Close()
|
||||||
|
conn4.Close()
|
||||||
|
}
|
||||||
|
|
||||||
func TestWithCompression(t *testing.T) {
|
func TestWithCompression(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
@@ -28,8 +73,8 @@ func TestWithCompression(t *testing.T) {
|
|||||||
pr, pw := io.Pipe()
|
pr, pw := io.Pipe()
|
||||||
pr2, pw2 := io.Pipe()
|
pr2, pw2 := io.Pipe()
|
||||||
|
|
||||||
conn1 := WrapReadWriteCloser(pr, pw2)
|
conn1 := WrapReadWriteCloser(pr, pw2, nil)
|
||||||
conn2 := WrapReadWriteCloser(pr2, pw)
|
conn2 := WrapReadWriteCloser(pr2, pw, nil)
|
||||||
|
|
||||||
compressionStream1 := WithCompression(conn1)
|
compressionStream1 := WithCompression(conn1)
|
||||||
compressionStream2 := WithCompression(conn2)
|
compressionStream2 := WithCompression(conn2)
|
||||||
@@ -71,12 +116,12 @@ func TestWithEncryption(t *testing.T) {
|
|||||||
pr5, pw5 := io.Pipe()
|
pr5, pw5 := io.Pipe()
|
||||||
pr6, pw6 := io.Pipe()
|
pr6, pw6 := io.Pipe()
|
||||||
|
|
||||||
conn1 := WrapReadWriteCloser(pr, pw2)
|
conn1 := WrapReadWriteCloser(pr, pw2, nil)
|
||||||
conn2 := WrapReadWriteCloser(pr2, pw)
|
conn2 := WrapReadWriteCloser(pr2, pw, nil)
|
||||||
conn3 := WrapReadWriteCloser(pr3, pw4)
|
conn3 := WrapReadWriteCloser(pr3, pw4, nil)
|
||||||
conn4 := WrapReadWriteCloser(pr4, pw3)
|
conn4 := WrapReadWriteCloser(pr4, pw3, nil)
|
||||||
conn5 := WrapReadWriteCloser(pr5, pw6)
|
conn5 := WrapReadWriteCloser(pr5, pw6, nil)
|
||||||
conn6 := WrapReadWriteCloser(pr6, pw5)
|
conn6 := WrapReadWriteCloser(pr6, pw5, nil)
|
||||||
|
|
||||||
encryptStream1, err := WithEncryption(conn3, []byte(key))
|
encryptStream1, err := WithEncryption(conn3, []byte(key))
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
@@ -88,6 +88,7 @@ func Trace(format string, v ...interface{}) {
|
|||||||
// Logger
|
// Logger
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
AddLogPrefix(string)
|
AddLogPrefix(string)
|
||||||
|
GetPrefixStr() string
|
||||||
GetAllPrefix() []string
|
GetAllPrefix() []string
|
||||||
ClearLogPrefix()
|
ClearLogPrefix()
|
||||||
Error(string, ...interface{})
|
Error(string, ...interface{})
|
||||||
@@ -119,6 +120,10 @@ func (pl *PrefixLogger) AddLogPrefix(prefix string) {
|
|||||||
pl.allPrefix = append(pl.allPrefix, prefix)
|
pl.allPrefix = append(pl.allPrefix, prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pl *PrefixLogger) GetPrefixStr() string {
|
||||||
|
return pl.prefix
|
||||||
|
}
|
||||||
|
|
||||||
func (pl *PrefixLogger) GetAllPrefix() []string {
|
func (pl *PrefixLogger) GetAllPrefix() []string {
|
||||||
return pl.allPrefix
|
return pl.allPrefix
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,11 +15,17 @@
|
|||||||
package net
|
package net
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/frp/utils/log"
|
"github.com/fatedier/frp/utils/log"
|
||||||
|
|
||||||
|
kcp "github.com/xtaci/kcp-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Conn is the interface of connections used in frp.
|
// Conn is the interface of connections used in frp.
|
||||||
@@ -43,37 +49,128 @@ func WrapConn(c net.Conn) Conn {
|
|||||||
type WrapReadWriteCloserConn struct {
|
type WrapReadWriteCloserConn struct {
|
||||||
io.ReadWriteCloser
|
io.ReadWriteCloser
|
||||||
log.Logger
|
log.Logger
|
||||||
|
|
||||||
|
underConn net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func WrapReadWriteCloserToConn(rwc io.ReadWriteCloser, underConn net.Conn) Conn {
|
||||||
|
return &WrapReadWriteCloserConn{
|
||||||
|
ReadWriteCloser: rwc,
|
||||||
|
Logger: log.NewPrefixLogger(""),
|
||||||
|
underConn: underConn,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *WrapReadWriteCloserConn) LocalAddr() net.Addr {
|
func (conn *WrapReadWriteCloserConn) LocalAddr() net.Addr {
|
||||||
|
if conn.underConn != nil {
|
||||||
|
return conn.underConn.LocalAddr()
|
||||||
|
}
|
||||||
return (*net.TCPAddr)(nil)
|
return (*net.TCPAddr)(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *WrapReadWriteCloserConn) RemoteAddr() net.Addr {
|
func (conn *WrapReadWriteCloserConn) RemoteAddr() net.Addr {
|
||||||
|
if conn.underConn != nil {
|
||||||
|
return conn.underConn.RemoteAddr()
|
||||||
|
}
|
||||||
return (*net.TCPAddr)(nil)
|
return (*net.TCPAddr)(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *WrapReadWriteCloserConn) SetDeadline(t time.Time) error {
|
func (conn *WrapReadWriteCloserConn) SetDeadline(t time.Time) error {
|
||||||
return nil
|
if conn.underConn != nil {
|
||||||
|
return conn.underConn.SetDeadline(t)
|
||||||
|
}
|
||||||
|
return &net.OpError{Op: "set", Net: "wrap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *WrapReadWriteCloserConn) SetReadDeadline(t time.Time) error {
|
func (conn *WrapReadWriteCloserConn) SetReadDeadline(t time.Time) error {
|
||||||
return nil
|
if conn.underConn != nil {
|
||||||
|
return conn.underConn.SetReadDeadline(t)
|
||||||
|
}
|
||||||
|
return &net.OpError{Op: "set", Net: "wrap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *WrapReadWriteCloserConn) SetWriteDeadline(t time.Time) error {
|
func (conn *WrapReadWriteCloserConn) SetWriteDeadline(t time.Time) error {
|
||||||
return nil
|
if conn.underConn != nil {
|
||||||
|
return conn.underConn.SetWriteDeadline(t)
|
||||||
|
}
|
||||||
|
return &net.OpError{Op: "set", Net: "wrap", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WrapReadWriteCloserToConn(rwc io.ReadWriteCloser) Conn {
|
func ConnectServer(protocol string, addr string) (c Conn, err error) {
|
||||||
return &WrapReadWriteCloserConn{
|
switch protocol {
|
||||||
ReadWriteCloser: rwc,
|
case "tcp":
|
||||||
Logger: log.NewPrefixLogger(""),
|
return ConnectTcpServer(addr)
|
||||||
|
case "kcp":
|
||||||
|
kcpConn, errRet := kcp.DialWithOptions(addr, nil, 10, 3)
|
||||||
|
if errRet != nil {
|
||||||
|
err = errRet
|
||||||
|
return
|
||||||
|
}
|
||||||
|
kcpConn.SetStreamMode(true)
|
||||||
|
kcpConn.SetWriteDelay(true)
|
||||||
|
kcpConn.SetNoDelay(1, 20, 2, 1)
|
||||||
|
kcpConn.SetWindowSize(128, 512)
|
||||||
|
kcpConn.SetMtu(1350)
|
||||||
|
kcpConn.SetACKNoDelay(false)
|
||||||
|
kcpConn.SetReadBuffer(4194304)
|
||||||
|
kcpConn.SetWriteBuffer(4194304)
|
||||||
|
c = WrapConn(kcpConn)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupport protocol: %s", protocol)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Listener interface {
|
func ConnectServerByHttpProxy(httpProxy string, protocol string, addr string) (c Conn, err error) {
|
||||||
Accept() (Conn, error)
|
switch protocol {
|
||||||
Close() error
|
case "tcp":
|
||||||
log.Logger
|
return ConnectTcpServerByHttpProxy(httpProxy, addr)
|
||||||
|
case "kcp":
|
||||||
|
// http proxy is not supported for kcp
|
||||||
|
return ConnectServer(protocol, addr)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupport protocol: %s", protocol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SharedConn struct {
|
||||||
|
Conn
|
||||||
|
sync.Mutex
|
||||||
|
buf *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// the bytes you read in io.Reader, will be reserved in SharedConn
|
||||||
|
func NewShareConn(conn Conn) (*SharedConn, io.Reader) {
|
||||||
|
sc := &SharedConn{
|
||||||
|
Conn: conn,
|
||||||
|
buf: bytes.NewBuffer(make([]byte, 0, 1024)),
|
||||||
|
}
|
||||||
|
return sc, io.TeeReader(conn, sc.buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *SharedConn) Read(p []byte) (n int, err error) {
|
||||||
|
sc.Lock()
|
||||||
|
if sc.buf == nil {
|
||||||
|
sc.Unlock()
|
||||||
|
return sc.Conn.Read(p)
|
||||||
|
}
|
||||||
|
sc.Unlock()
|
||||||
|
n, err = sc.buf.Read(p)
|
||||||
|
|
||||||
|
if err == io.EOF {
|
||||||
|
sc.Lock()
|
||||||
|
sc.buf = nil
|
||||||
|
sc.Unlock()
|
||||||
|
var n2 int
|
||||||
|
n2, err = sc.Conn.Read(p[n:])
|
||||||
|
|
||||||
|
n += n2
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *SharedConn) WriteBuff(buffer []byte) (err error) {
|
||||||
|
sc.buf.Reset()
|
||||||
|
_, err = sc.buf.Write(buffer)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
105
utils/net/http.go
Normal file
105
utils/net/http.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/gzip"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HttpAuthWraper struct {
|
||||||
|
h http.Handler
|
||||||
|
user string
|
||||||
|
passwd string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHttpBasicAuthWraper(h http.Handler, user, passwd string) http.Handler {
|
||||||
|
return &HttpAuthWraper{
|
||||||
|
h: h,
|
||||||
|
user: user,
|
||||||
|
passwd: passwd,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aw *HttpAuthWraper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
user, passwd, hasAuth := r.BasicAuth()
|
||||||
|
if (aw.user == "" && aw.passwd == "") || (hasAuth && user == aw.user && passwd == aw.passwd) {
|
||||||
|
aw.h.ServeHTTP(w, r)
|
||||||
|
} else {
|
||||||
|
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||||
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HttpBasicAuth(h http.HandlerFunc, user, passwd string) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
reqUser, reqPasswd, hasAuth := r.BasicAuth()
|
||||||
|
if (user == "" && passwd == "") ||
|
||||||
|
(hasAuth && reqUser == user && reqPasswd == passwd) {
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
} else {
|
||||||
|
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||||
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HttprouterBasicAuth(h httprouter.Handle, user, passwd string) httprouter.Handle {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
reqUser, reqPasswd, hasAuth := r.BasicAuth()
|
||||||
|
if (user == "" && passwd == "") ||
|
||||||
|
(hasAuth && reqUser == user && reqPasswd == passwd) {
|
||||||
|
h(w, r, ps)
|
||||||
|
} else {
|
||||||
|
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||||
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type HttpGzipWraper struct {
|
||||||
|
h http.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gw *HttpGzipWraper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||||
|
gw.h.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Encoding", "gzip")
|
||||||
|
gz := gzip.NewWriter(w)
|
||||||
|
defer gz.Close()
|
||||||
|
gzr := gzipResponseWriter{Writer: gz, ResponseWriter: w}
|
||||||
|
gw.h.ServeHTTP(gzr, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeHttpGzipHandler(h http.Handler) http.Handler {
|
||||||
|
return &HttpGzipWraper{
|
||||||
|
h: h,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type gzipResponseWriter struct {
|
||||||
|
io.Writer
|
||||||
|
http.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w gzipResponseWriter) Write(b []byte) (int, error) {
|
||||||
|
return w.Writer.Write(b)
|
||||||
|
}
|
||||||
101
utils/net/kcp.go
Normal file
101
utils/net/kcp.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/utils/log"
|
||||||
|
|
||||||
|
kcp "github.com/fatedier/kcp-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KcpListener struct {
|
||||||
|
net.Addr
|
||||||
|
listener net.Listener
|
||||||
|
accept chan Conn
|
||||||
|
closeFlag bool
|
||||||
|
log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListenKcp(bindAddr string, bindPort int64) (l *KcpListener, err error) {
|
||||||
|
listener, err := kcp.ListenWithOptions(fmt.Sprintf("%s:%d", bindAddr, bindPort), nil, 10, 3)
|
||||||
|
if err != nil {
|
||||||
|
return l, err
|
||||||
|
}
|
||||||
|
listener.SetReadBuffer(4194304)
|
||||||
|
listener.SetWriteBuffer(4194304)
|
||||||
|
|
||||||
|
l = &KcpListener{
|
||||||
|
Addr: listener.Addr(),
|
||||||
|
listener: listener,
|
||||||
|
accept: make(chan Conn),
|
||||||
|
closeFlag: false,
|
||||||
|
Logger: log.NewPrefixLogger(""),
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
conn, err := listener.AcceptKCP()
|
||||||
|
if err != nil {
|
||||||
|
if l.closeFlag {
|
||||||
|
close(l.accept)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
conn.SetStreamMode(true)
|
||||||
|
conn.SetWriteDelay(true)
|
||||||
|
conn.SetNoDelay(1, 20, 2, 1)
|
||||||
|
conn.SetMtu(1350)
|
||||||
|
conn.SetWindowSize(1024, 1024)
|
||||||
|
conn.SetACKNoDelay(false)
|
||||||
|
|
||||||
|
l.accept <- WrapConn(conn)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return l, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *KcpListener) Accept() (Conn, error) {
|
||||||
|
conn, ok := <-l.accept
|
||||||
|
if !ok {
|
||||||
|
return conn, fmt.Errorf("channel for kcp listener closed")
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *KcpListener) Close() error {
|
||||||
|
if !l.closeFlag {
|
||||||
|
l.closeFlag = true
|
||||||
|
l.listener.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKcpConnFromUdp(conn *net.UDPConn, connected bool, raddr string) (net.Conn, error) {
|
||||||
|
kcpConn, err := kcp.NewConnEx(1, connected, raddr, nil, 10, 3, conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
kcpConn.SetStreamMode(true)
|
||||||
|
kcpConn.SetWriteDelay(true)
|
||||||
|
kcpConn.SetNoDelay(1, 20, 2, 1)
|
||||||
|
kcpConn.SetMtu(1350)
|
||||||
|
kcpConn.SetWindowSize(1024, 1024)
|
||||||
|
kcpConn.SetACKNoDelay(false)
|
||||||
|
return kcpConn, nil
|
||||||
|
}
|
||||||
99
utils/net/listener.go
Normal file
99
utils/net/listener.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/utils/errors"
|
||||||
|
"github.com/fatedier/frp/utils/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Listener interface {
|
||||||
|
Accept() (Conn, error)
|
||||||
|
Close() error
|
||||||
|
log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogListener struct {
|
||||||
|
l net.Listener
|
||||||
|
net.Listener
|
||||||
|
log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func WrapLogListener(l net.Listener) Listener {
|
||||||
|
return &LogListener{
|
||||||
|
l: l,
|
||||||
|
Listener: l,
|
||||||
|
Logger: log.NewPrefixLogger(""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logL *LogListener) Accept() (Conn, error) {
|
||||||
|
c, err := logL.l.Accept()
|
||||||
|
return WrapConn(c), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom listener
|
||||||
|
type CustomListener struct {
|
||||||
|
conns chan Conn
|
||||||
|
closed bool
|
||||||
|
mu sync.Mutex
|
||||||
|
|
||||||
|
log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCustomListener() *CustomListener {
|
||||||
|
return &CustomListener{
|
||||||
|
conns: make(chan Conn, 64),
|
||||||
|
Logger: log.NewPrefixLogger(""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *CustomListener) Accept() (Conn, error) {
|
||||||
|
conn, ok := <-l.conns
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("listener closed")
|
||||||
|
}
|
||||||
|
conn.AddLogPrefix(l.GetPrefixStr())
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *CustomListener) PutConn(conn Conn) error {
|
||||||
|
err := errors.PanicToError(func() {
|
||||||
|
select {
|
||||||
|
case l.conns <- conn:
|
||||||
|
default:
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *CustomListener) Close() error {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
if !l.closed {
|
||||||
|
close(l.conns)
|
||||||
|
l.closed = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *CustomListener) Addr() net.Addr {
|
||||||
|
return (*net.TCPAddr)(nil)
|
||||||
|
}
|
||||||
@@ -17,15 +17,18 @@ package pool
|
|||||||
import "sync"
|
import "sync"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
bufPool5k sync.Pool
|
bufPool16k sync.Pool
|
||||||
bufPool2k sync.Pool
|
bufPool5k sync.Pool
|
||||||
bufPool1k sync.Pool
|
bufPool2k sync.Pool
|
||||||
bufPool sync.Pool
|
bufPool1k sync.Pool
|
||||||
|
bufPool sync.Pool
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetBuf(size int) []byte {
|
func GetBuf(size int) []byte {
|
||||||
var x interface{}
|
var x interface{}
|
||||||
if size >= 5*1024 {
|
if size >= 16*1024 {
|
||||||
|
x = bufPool16k.Get()
|
||||||
|
} else if size >= 5*1024 {
|
||||||
x = bufPool5k.Get()
|
x = bufPool5k.Get()
|
||||||
} else if size >= 2*1024 {
|
} else if size >= 2*1024 {
|
||||||
x = bufPool2k.Get()
|
x = bufPool2k.Get()
|
||||||
@@ -46,7 +49,9 @@ func GetBuf(size int) []byte {
|
|||||||
|
|
||||||
func PutBuf(buf []byte) {
|
func PutBuf(buf []byte) {
|
||||||
size := cap(buf)
|
size := cap(buf)
|
||||||
if size >= 5*1024 {
|
if size >= 16*1024 {
|
||||||
|
bufPool16k.Put(buf)
|
||||||
|
} else if size >= 5*1024 {
|
||||||
bufPool5k.Put(buf)
|
bufPool5k.Put(buf)
|
||||||
} else if size >= 2*1024 {
|
} else if size >= 2*1024 {
|
||||||
bufPool2k.Put(buf)
|
bufPool2k.Put(buf)
|
||||||
|
|||||||
57
utils/pool/snappy.go
Normal file
57
utils/pool/snappy.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// 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 pool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/golang/snappy"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
snappyReaderPool sync.Pool
|
||||||
|
snappyWriterPool sync.Pool
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetSnappyReader(r io.Reader) *snappy.Reader {
|
||||||
|
var x interface{}
|
||||||
|
x = snappyReaderPool.Get()
|
||||||
|
if x == nil {
|
||||||
|
return snappy.NewReader(r)
|
||||||
|
}
|
||||||
|
sr := x.(*snappy.Reader)
|
||||||
|
sr.Reset(r)
|
||||||
|
return sr
|
||||||
|
}
|
||||||
|
|
||||||
|
func PutSnappyReader(sr *snappy.Reader) {
|
||||||
|
snappyReaderPool.Put(sr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSnappyWriter(w io.Writer) *snappy.Writer {
|
||||||
|
var x interface{}
|
||||||
|
x = snappyWriterPool.Get()
|
||||||
|
if x == nil {
|
||||||
|
return snappy.NewWriter(w)
|
||||||
|
}
|
||||||
|
sw := x.(*snappy.Writer)
|
||||||
|
sw.Reset(w)
|
||||||
|
return sw
|
||||||
|
}
|
||||||
|
|
||||||
|
func PutSnappyWriter(sw *snappy.Writer) {
|
||||||
|
snappyWriterPool.Put(sw)
|
||||||
|
}
|
||||||
@@ -19,37 +19,31 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.11.0"
|
var version string = "0.14.1"
|
||||||
|
|
||||||
func Full() string {
|
func Full() string {
|
||||||
return version
|
return version
|
||||||
}
|
}
|
||||||
|
|
||||||
func Proto(v string) int64 {
|
func getSubVersion(v string, position int) int64 {
|
||||||
arr := strings.Split(v, ".")
|
arr := strings.Split(v, ".")
|
||||||
if len(arr) < 3 {
|
if len(arr) < 3 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
res, _ := strconv.ParseInt(arr[0], 10, 64)
|
res, _ := strconv.ParseInt(arr[position], 10, 64)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Proto(v string) int64 {
|
||||||
|
return getSubVersion(v, 0)
|
||||||
|
}
|
||||||
|
|
||||||
func Major(v string) int64 {
|
func Major(v string) int64 {
|
||||||
arr := strings.Split(v, ".")
|
return getSubVersion(v, 1)
|
||||||
if len(arr) < 3 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
res, _ := strconv.ParseInt(arr[1], 10, 64)
|
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Minor(v string) int64 {
|
func Minor(v string) int64 {
|
||||||
arr := strings.Split(v, ".")
|
return getSubVersion(v, 2)
|
||||||
if len(arr) < 3 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
res, _ := strconv.ParseInt(arr[2], 10, 64)
|
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// add every case there if server will not accept client's protocol and return false
|
// add every case there if server will not accept client's protocol and return false
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ type HttpMuxer struct {
|
|||||||
|
|
||||||
func GetHttpRequestInfo(c frpNet.Conn) (_ frpNet.Conn, _ map[string]string, err error) {
|
func GetHttpRequestInfo(c frpNet.Conn) (_ frpNet.Conn, _ map[string]string, err error) {
|
||||||
reqInfoMap := make(map[string]string, 0)
|
reqInfoMap := make(map[string]string, 0)
|
||||||
sc, rd := newShareConn(c)
|
sc, rd := frpNet.NewShareConn(c)
|
||||||
|
|
||||||
request, err := http.ReadRequest(bufio.NewReader(rd))
|
request, err := http.ReadRequest(bufio.NewReader(rd))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -57,30 +57,35 @@ func GetHttpRequestInfo(c frpNet.Conn) (_ frpNet.Conn, _ map[string]string, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewHttpMuxer(listener frpNet.Listener, timeout time.Duration) (*HttpMuxer, error) {
|
func NewHttpMuxer(listener frpNet.Listener, timeout time.Duration) (*HttpMuxer, error) {
|
||||||
mux, err := NewVhostMuxer(listener, GetHttpRequestInfo, HttpAuthFunc, HttpHostNameRewrite, timeout)
|
mux, err := NewVhostMuxer(listener, GetHttpRequestInfo, HttpAuthFunc, ModifyHttpRequest, timeout)
|
||||||
return &HttpMuxer{mux}, err
|
return &HttpMuxer{mux}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func HttpHostNameRewrite(c frpNet.Conn, rewriteHost string) (_ frpNet.Conn, err error) {
|
func ModifyHttpRequest(c frpNet.Conn, rewriteHost string) (_ frpNet.Conn, err error) {
|
||||||
sc, rd := newShareConn(c)
|
sc, rd := frpNet.NewShareConn(c)
|
||||||
var buff []byte
|
var buff []byte
|
||||||
if buff, err = hostNameRewrite(rd, rewriteHost); err != nil {
|
remoteIP := strings.Split(c.RemoteAddr().String(), ":")[0]
|
||||||
|
if buff, err = hostNameRewrite(rd, rewriteHost, remoteIP); err != nil {
|
||||||
return sc, err
|
return sc, err
|
||||||
}
|
}
|
||||||
err = sc.WriteBuff(buff)
|
err = sc.WriteBuff(buff)
|
||||||
return sc, err
|
return sc, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func hostNameRewrite(request io.Reader, rewriteHost string) (_ []byte, err error) {
|
func hostNameRewrite(request io.Reader, rewriteHost string, remoteIP string) (_ []byte, err error) {
|
||||||
buf := pool.GetBuf(1024)
|
buf := pool.GetBuf(1024)
|
||||||
defer pool.PutBuf(buf)
|
defer pool.PutBuf(buf)
|
||||||
|
|
||||||
request.Read(buf)
|
var n int
|
||||||
retBuffer, err := parseRequest(buf, rewriteHost)
|
n, err = request.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
retBuffer, err := parseRequest(buf[:n], rewriteHost, remoteIP)
|
||||||
return retBuffer, err
|
return retBuffer, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseRequest(org []byte, rewriteHost string) (ret []byte, err error) {
|
func parseRequest(org []byte, rewriteHost string, remoteIP string) (ret []byte, err error) {
|
||||||
tp := bytes.NewBuffer(org)
|
tp := bytes.NewBuffer(org)
|
||||||
// First line: GET /index.html HTTP/1.0
|
// First line: GET /index.html HTTP/1.0
|
||||||
var b []byte
|
var b []byte
|
||||||
@@ -106,10 +111,19 @@ func parseRequest(org []byte, rewriteHost string) (ret []byte, err error) {
|
|||||||
// GET /index.html HTTP/1.1
|
// GET /index.html HTTP/1.1
|
||||||
// Host: www.google.com
|
// Host: www.google.com
|
||||||
if req.URL.Host == "" {
|
if req.URL.Host == "" {
|
||||||
changedBuf, err := changeHostName(tp, rewriteHost)
|
var changedBuf []byte
|
||||||
|
if rewriteHost != "" {
|
||||||
|
changedBuf, err = changeHostName(tp, rewriteHost)
|
||||||
|
}
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
buf.Write(b)
|
buf.Write(b)
|
||||||
buf.Write(changedBuf)
|
buf.WriteString(fmt.Sprintf("X-Forwarded-For: %s\r\n", remoteIP))
|
||||||
|
buf.WriteString(fmt.Sprintf("X-Real-IP: %s\r\n", remoteIP))
|
||||||
|
if len(changedBuf) == 0 {
|
||||||
|
tp.WriteTo(buf)
|
||||||
|
} else {
|
||||||
|
buf.Write(changedBuf)
|
||||||
|
}
|
||||||
return buf.Bytes(), err
|
return buf.Bytes(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,18 +131,21 @@ func parseRequest(org []byte, rewriteHost string) (ret []byte, err error) {
|
|||||||
// GET http://www.google.com/index.html HTTP/1.1
|
// GET http://www.google.com/index.html HTTP/1.1
|
||||||
// Host: doesntmatter
|
// Host: doesntmatter
|
||||||
// In this case, any Host line is ignored.
|
// In this case, any Host line is ignored.
|
||||||
hostPort := strings.Split(req.URL.Host, ":")
|
if rewriteHost != "" {
|
||||||
if len(hostPort) == 1 {
|
hostPort := strings.Split(req.URL.Host, ":")
|
||||||
req.URL.Host = rewriteHost
|
if len(hostPort) == 1 {
|
||||||
} else if len(hostPort) == 2 {
|
req.URL.Host = rewriteHost
|
||||||
req.URL.Host = fmt.Sprintf("%s:%s", rewriteHost, hostPort[1])
|
} else if len(hostPort) == 2 {
|
||||||
|
req.URL.Host = fmt.Sprintf("%s:%s", rewriteHost, hostPort[1])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
firstLine := req.Method + " " + req.URL.String() + " " + req.Proto
|
firstLine := req.Method + " " + req.URL.String() + " " + req.Proto
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
buf.WriteString(firstLine)
|
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)
|
tp.WriteTo(buf)
|
||||||
return buf.Bytes(), err
|
return buf.Bytes(), err
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseRequestLine parses "GET /foo HTTP/1.1" into its three parts.
|
// parseRequestLine parses "GET /foo HTTP/1.1" into its three parts.
|
||||||
@@ -162,9 +179,9 @@ func changeHostName(buff *bytes.Buffer, rewriteHost string) (_ []byte, err error
|
|||||||
var hostHeader string
|
var hostHeader string
|
||||||
portPos := bytes.IndexByte(kv[j+1:], ':')
|
portPos := bytes.IndexByte(kv[j+1:], ':')
|
||||||
if portPos == -1 {
|
if portPos == -1 {
|
||||||
hostHeader = fmt.Sprintf("Host: %s\n", rewriteHost)
|
hostHeader = fmt.Sprintf("Host: %s\r\n", rewriteHost)
|
||||||
} else {
|
} else {
|
||||||
hostHeader = fmt.Sprintf("Host: %s:%s\n", rewriteHost, kv[portPos+1:])
|
hostHeader = fmt.Sprintf("Host: %s:%s\r\n", rewriteHost, kv[j+portPos+2:])
|
||||||
}
|
}
|
||||||
retBuf.WriteString(hostHeader)
|
retBuf.WriteString(hostHeader)
|
||||||
peek = peek[i+1:]
|
peek = peek[i+1:]
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ func readHandshake(rd io.Reader) (host string, err error) {
|
|||||||
|
|
||||||
func GetHttpsHostname(c frpNet.Conn) (sc frpNet.Conn, _ map[string]string, err error) {
|
func GetHttpsHostname(c frpNet.Conn) (sc frpNet.Conn, _ map[string]string, err error) {
|
||||||
reqInfoMap := make(map[string]string, 0)
|
reqInfoMap := make(map[string]string, 0)
|
||||||
sc, rd := newShareConn(c)
|
sc, rd := frpNet.NewShareConn(c)
|
||||||
host, err := readHandshake(rd)
|
host, err := readHandshake(rd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sc, reqInfoMap, err
|
return sc, reqInfoMap, err
|
||||||
|
|||||||
186
utils/vhost/newhttp.go
Normal file
186
utils/vhost/newhttp.go
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package vhost
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
frpLog "github.com/fatedier/frp/utils/log"
|
||||||
|
"github.com/fatedier/frp/utils/pool"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
responseHeaderTimeout = time.Duration(30) * time.Second
|
||||||
|
|
||||||
|
ErrRouterConfigConflict = errors.New("router config conflict")
|
||||||
|
ErrNoDomain = errors.New("no such domain")
|
||||||
|
)
|
||||||
|
|
||||||
|
func getHostFromAddr(addr string) (host string) {
|
||||||
|
strs := strings.Split(addr, ":")
|
||||||
|
if len(strs) > 1 {
|
||||||
|
host = strs[0]
|
||||||
|
} else {
|
||||||
|
host = addr
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type HttpReverseProxy struct {
|
||||||
|
proxy *ReverseProxy
|
||||||
|
tr *http.Transport
|
||||||
|
|
||||||
|
vhostRouter *VhostRouters
|
||||||
|
|
||||||
|
cfgMu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHttpReverseProxy() *HttpReverseProxy {
|
||||||
|
rp := &HttpReverseProxy{
|
||||||
|
vhostRouter: NewVhostRouters(),
|
||||||
|
}
|
||||||
|
proxy := &ReverseProxy{
|
||||||
|
Director: func(req *http.Request) {
|
||||||
|
req.URL.Scheme = "http"
|
||||||
|
url := req.Context().Value("url").(string)
|
||||||
|
host := getHostFromAddr(req.Context().Value("host").(string))
|
||||||
|
host = rp.GetRealHost(host, url)
|
||||||
|
if host != "" {
|
||||||
|
req.Host = host
|
||||||
|
}
|
||||||
|
req.URL.Host = req.Host
|
||||||
|
},
|
||||||
|
Transport: &http.Transport{
|
||||||
|
ResponseHeaderTimeout: responseHeaderTimeout,
|
||||||
|
DisableKeepAlives: true,
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
url := ctx.Value("url").(string)
|
||||||
|
host := getHostFromAddr(ctx.Value("host").(string))
|
||||||
|
return rp.CreateConnection(host, url)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
BufferPool: newWrapPool(),
|
||||||
|
ErrorLog: log.New(newWrapLogger(), "", 0),
|
||||||
|
}
|
||||||
|
rp.proxy = proxy
|
||||||
|
return rp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rp *HttpReverseProxy) Register(routeCfg VhostRouteConfig) error {
|
||||||
|
rp.cfgMu.Lock()
|
||||||
|
defer rp.cfgMu.Unlock()
|
||||||
|
_, ok := rp.vhostRouter.Exist(routeCfg.Domain, routeCfg.Location)
|
||||||
|
if ok {
|
||||||
|
return ErrRouterConfigConflict
|
||||||
|
} else {
|
||||||
|
rp.vhostRouter.Add(routeCfg.Domain, routeCfg.Location, &routeCfg)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rp *HttpReverseProxy) UnRegister(domain string, location string) {
|
||||||
|
rp.cfgMu.Lock()
|
||||||
|
defer rp.cfgMu.Unlock()
|
||||||
|
rp.vhostRouter.Del(domain, location)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rp *HttpReverseProxy) GetRealHost(domain string, location string) (host string) {
|
||||||
|
vr, ok := rp.getVhost(domain, location)
|
||||||
|
if ok {
|
||||||
|
host = vr.payload.(*VhostRouteConfig).RewriteHost
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rp *HttpReverseProxy) CreateConnection(domain string, location string) (net.Conn, error) {
|
||||||
|
vr, ok := rp.getVhost(domain, location)
|
||||||
|
if ok {
|
||||||
|
fn := vr.payload.(*VhostRouteConfig).CreateConnFn
|
||||||
|
if fn != nil {
|
||||||
|
return fn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, ErrNoDomain
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rp *HttpReverseProxy) CheckAuth(domain, location, user, passwd string) bool {
|
||||||
|
vr, ok := rp.getVhost(domain, location)
|
||||||
|
if ok {
|
||||||
|
checkUser := vr.payload.(*VhostRouteConfig).Username
|
||||||
|
checkPasswd := vr.payload.(*VhostRouteConfig).Password
|
||||||
|
if (checkUser != "" || checkPasswd != "") && (checkUser != user || checkPasswd != passwd) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rp *HttpReverseProxy) getVhost(domain string, location string) (vr *VhostRouter, ok bool) {
|
||||||
|
rp.cfgMu.RLock()
|
||||||
|
defer rp.cfgMu.RUnlock()
|
||||||
|
|
||||||
|
// first we check the full hostname
|
||||||
|
// if not exist, then check the wildcard_domain such as *.example.com
|
||||||
|
vr, ok = rp.vhostRouter.Get(domain, location)
|
||||||
|
if ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
domainSplit := strings.Split(domain, ".")
|
||||||
|
if len(domainSplit) < 3 {
|
||||||
|
return vr, false
|
||||||
|
}
|
||||||
|
domainSplit[0] = "*"
|
||||||
|
domain = strings.Join(domainSplit, ".")
|
||||||
|
vr, ok = rp.vhostRouter.Get(domain, location)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rp *HttpReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
domain := getHostFromAddr(req.Host)
|
||||||
|
location := req.URL.Path
|
||||||
|
user, passwd, _ := req.BasicAuth()
|
||||||
|
if !rp.CheckAuth(domain, location, user, passwd) {
|
||||||
|
rw.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||||
|
http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rp.proxy.ServeHTTP(rw, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
type wrapPool struct{}
|
||||||
|
|
||||||
|
func newWrapPool() *wrapPool { return &wrapPool{} }
|
||||||
|
|
||||||
|
func (p *wrapPool) Get() []byte { return pool.GetBuf(32 * 1024) }
|
||||||
|
|
||||||
|
func (p *wrapPool) Put(buf []byte) { pool.PutBuf(buf) }
|
||||||
|
|
||||||
|
type wrapLogger struct{}
|
||||||
|
|
||||||
|
func newWrapLogger() *wrapLogger { return &wrapLogger{} }
|
||||||
|
|
||||||
|
func (l *wrapLogger) Write(p []byte) (n int, err error) {
|
||||||
|
frpLog.Warn("%s", string(bytes.TrimRight(p, "\n")))
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
63
utils/vhost/resource.go
Normal file
63
utils/vhost/resource.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
// 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 (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/utils/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
NotFound = `<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Not Found</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
width: 35em;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-family: Tahoma, Verdana, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>The page you visit not found.</h1>
|
||||||
|
<p>Sorry, the page you are looking for is currently unavailable.<br/>
|
||||||
|
Please try again later.</p>
|
||||||
|
<p>The server is powered by <a href="https://github.com/fatedier/frp">frp</a>.</p>
|
||||||
|
<p><em>Faithfully yours, frp.</em></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
func notFoundResponse() *http.Response {
|
||||||
|
header := make(http.Header)
|
||||||
|
header.Set("server", "frp/"+version.Full())
|
||||||
|
header.Set("Content-Type", "text/html")
|
||||||
|
res := &http.Response{
|
||||||
|
Status: "Not Found",
|
||||||
|
StatusCode: 404,
|
||||||
|
Proto: "HTTP/1.0",
|
||||||
|
ProtoMajor: 1,
|
||||||
|
ProtoMinor: 0,
|
||||||
|
Header: header,
|
||||||
|
Body: ioutil.NopCloser(strings.NewReader(NotFound)),
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
370
utils/vhost/reverseproxy.go
Normal file
370
utils/vhost/reverseproxy.go
Normal file
@@ -0,0 +1,370 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// HTTP reverse proxy handler
|
||||||
|
|
||||||
|
package vhost
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// onExitFlushLoop is a callback set by tests to detect the state of the
|
||||||
|
// flushLoop() goroutine.
|
||||||
|
var onExitFlushLoop func()
|
||||||
|
|
||||||
|
// ReverseProxy is an HTTP Handler that takes an incoming request and
|
||||||
|
// sends it to another server, proxying the response back to the
|
||||||
|
// client.
|
||||||
|
type ReverseProxy struct {
|
||||||
|
// Director must be a function which modifies
|
||||||
|
// the request into a new request to be sent
|
||||||
|
// using Transport. Its response is then copied
|
||||||
|
// back to the original client unmodified.
|
||||||
|
// Director must not access the provided Request
|
||||||
|
// after returning.
|
||||||
|
Director func(*http.Request)
|
||||||
|
|
||||||
|
// The transport used to perform proxy requests.
|
||||||
|
// If nil, http.DefaultTransport is used.
|
||||||
|
Transport http.RoundTripper
|
||||||
|
|
||||||
|
// FlushInterval specifies the flush interval
|
||||||
|
// to flush to the client while copying the
|
||||||
|
// response body.
|
||||||
|
// If zero, no periodic flushing is done.
|
||||||
|
FlushInterval time.Duration
|
||||||
|
|
||||||
|
// ErrorLog specifies an optional logger for errors
|
||||||
|
// that occur when attempting to proxy the request.
|
||||||
|
// If nil, logging goes to os.Stderr via the log package's
|
||||||
|
// standard logger.
|
||||||
|
ErrorLog *log.Logger
|
||||||
|
|
||||||
|
// BufferPool optionally specifies a buffer pool to
|
||||||
|
// get byte slices for use by io.CopyBuffer when
|
||||||
|
// copying HTTP response bodies.
|
||||||
|
BufferPool BufferPool
|
||||||
|
|
||||||
|
// ModifyResponse is an optional function that
|
||||||
|
// modifies the Response from the backend.
|
||||||
|
// If it returns an error, the proxy returns a StatusBadGateway error.
|
||||||
|
ModifyResponse func(*http.Response) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// A BufferPool is an interface for getting and returning temporary
|
||||||
|
// byte slices for use by io.CopyBuffer.
|
||||||
|
type BufferPool interface {
|
||||||
|
Get() []byte
|
||||||
|
Put([]byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
func singleJoiningSlash(a, b string) string {
|
||||||
|
aslash := strings.HasSuffix(a, "/")
|
||||||
|
bslash := strings.HasPrefix(b, "/")
|
||||||
|
switch {
|
||||||
|
case aslash && bslash:
|
||||||
|
return a + b[1:]
|
||||||
|
case !aslash && !bslash:
|
||||||
|
return a + "/" + b
|
||||||
|
}
|
||||||
|
return a + b
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSingleHostReverseProxy returns a new ReverseProxy that routes
|
||||||
|
// URLs to the scheme, host, and base path provided in target. If the
|
||||||
|
// target's path is "/base" and the incoming request was for "/dir",
|
||||||
|
// the target request will be for /base/dir.
|
||||||
|
// NewSingleHostReverseProxy does not rewrite the Host header.
|
||||||
|
// To rewrite Host headers, use ReverseProxy directly with a custom
|
||||||
|
// Director policy.
|
||||||
|
func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy {
|
||||||
|
targetQuery := target.RawQuery
|
||||||
|
director := func(req *http.Request) {
|
||||||
|
req.URL.Scheme = target.Scheme
|
||||||
|
req.URL.Host = target.Host
|
||||||
|
req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
|
||||||
|
if targetQuery == "" || req.URL.RawQuery == "" {
|
||||||
|
req.URL.RawQuery = targetQuery + req.URL.RawQuery
|
||||||
|
} else {
|
||||||
|
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
|
||||||
|
}
|
||||||
|
if _, ok := req.Header["User-Agent"]; !ok {
|
||||||
|
// explicitly disable User-Agent so it's not set to default value
|
||||||
|
req.Header.Set("User-Agent", "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &ReverseProxy{Director: director}
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyHeader(dst, src http.Header) {
|
||||||
|
for k, vv := range src {
|
||||||
|
for _, v := range vv {
|
||||||
|
dst.Add(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloneHeader(h http.Header) http.Header {
|
||||||
|
h2 := make(http.Header, len(h))
|
||||||
|
for k, vv := range h {
|
||||||
|
vv2 := make([]string, len(vv))
|
||||||
|
copy(vv2, vv)
|
||||||
|
h2[k] = vv2
|
||||||
|
}
|
||||||
|
return h2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hop-by-hop headers. These are removed when sent to the backend.
|
||||||
|
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
|
||||||
|
var hopHeaders = []string{
|
||||||
|
"Connection",
|
||||||
|
"Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google
|
||||||
|
"Keep-Alive",
|
||||||
|
"Proxy-Authenticate",
|
||||||
|
"Proxy-Authorization",
|
||||||
|
"Te", // canonicalized version of "TE"
|
||||||
|
"Trailer", // not Trailers per URL above; http://www.rfc-editor.org/errata_search.php?eid=4522
|
||||||
|
"Transfer-Encoding",
|
||||||
|
"Upgrade",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
transport := p.Transport
|
||||||
|
if transport == nil {
|
||||||
|
transport = http.DefaultTransport
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := req.Context()
|
||||||
|
if cn, ok := rw.(http.CloseNotifier); ok {
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
ctx, cancel = context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
notifyChan := cn.CloseNotify()
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-notifyChan:
|
||||||
|
cancel()
|
||||||
|
case <-ctx.Done():
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
outreq := req.WithContext(ctx) // includes shallow copies of maps, but okay
|
||||||
|
if req.ContentLength == 0 {
|
||||||
|
outreq.Body = nil // Issue 16036: nil Body for http.Transport retries
|
||||||
|
}
|
||||||
|
|
||||||
|
outreq.Header = cloneHeader(req.Header)
|
||||||
|
|
||||||
|
// Modify for frp
|
||||||
|
outreq = outreq.WithContext(context.WithValue(outreq.Context(), "url", req.URL.Path))
|
||||||
|
outreq = outreq.WithContext(context.WithValue(outreq.Context(), "host", req.Host))
|
||||||
|
|
||||||
|
p.Director(outreq)
|
||||||
|
outreq.Close = false
|
||||||
|
|
||||||
|
// Remove hop-by-hop headers listed in the "Connection" header.
|
||||||
|
// See RFC 2616, section 14.10.
|
||||||
|
if c := outreq.Header.Get("Connection"); c != "" {
|
||||||
|
for _, f := range strings.Split(c, ",") {
|
||||||
|
if f = strings.TrimSpace(f); f != "" {
|
||||||
|
outreq.Header.Del(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove hop-by-hop headers to the backend. Especially
|
||||||
|
// important is "Connection" because we want a persistent
|
||||||
|
// connection, regardless of what the client sent to us.
|
||||||
|
for _, h := range hopHeaders {
|
||||||
|
if outreq.Header.Get(h) != "" {
|
||||||
|
outreq.Header.Del(h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||||
|
// If we aren't the first proxy retain prior
|
||||||
|
// X-Forwarded-For information as a comma+space
|
||||||
|
// separated list and fold multiple headers into one.
|
||||||
|
if prior, ok := outreq.Header["X-Forwarded-For"]; ok {
|
||||||
|
clientIP = strings.Join(prior, ", ") + ", " + clientIP
|
||||||
|
}
|
||||||
|
outreq.Header.Set("X-Forwarded-For", clientIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := transport.RoundTrip(outreq)
|
||||||
|
if err != nil {
|
||||||
|
p.logf("http: proxy error: %v", err)
|
||||||
|
rw.WriteHeader(http.StatusNotFound)
|
||||||
|
rw.Write([]byte(NotFound))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove hop-by-hop headers listed in the
|
||||||
|
// "Connection" header of the response.
|
||||||
|
if c := res.Header.Get("Connection"); c != "" {
|
||||||
|
for _, f := range strings.Split(c, ",") {
|
||||||
|
if f = strings.TrimSpace(f); f != "" {
|
||||||
|
res.Header.Del(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, h := range hopHeaders {
|
||||||
|
res.Header.Del(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.ModifyResponse != nil {
|
||||||
|
if err := p.ModifyResponse(res); err != nil {
|
||||||
|
p.logf("http: proxy error: %v", err)
|
||||||
|
rw.WriteHeader(http.StatusBadGateway)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copyHeader(rw.Header(), res.Header)
|
||||||
|
|
||||||
|
// The "Trailer" header isn't included in the Transport's response,
|
||||||
|
// at least for *http.Transport. Build it up from Trailer.
|
||||||
|
announcedTrailers := len(res.Trailer)
|
||||||
|
if announcedTrailers > 0 {
|
||||||
|
trailerKeys := make([]string, 0, len(res.Trailer))
|
||||||
|
for k := range res.Trailer {
|
||||||
|
trailerKeys = append(trailerKeys, k)
|
||||||
|
}
|
||||||
|
rw.Header().Add("Trailer", strings.Join(trailerKeys, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.WriteHeader(res.StatusCode)
|
||||||
|
if len(res.Trailer) > 0 {
|
||||||
|
// Force chunking if we saw a response trailer.
|
||||||
|
// This prevents net/http from calculating the length for short
|
||||||
|
// bodies and adding a Content-Length.
|
||||||
|
if fl, ok := rw.(http.Flusher); ok {
|
||||||
|
fl.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.copyResponse(rw, res.Body)
|
||||||
|
res.Body.Close() // close now, instead of defer, to populate res.Trailer
|
||||||
|
|
||||||
|
if len(res.Trailer) == announcedTrailers {
|
||||||
|
copyHeader(rw.Header(), res.Trailer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, vv := range res.Trailer {
|
||||||
|
k = http.TrailerPrefix + k
|
||||||
|
for _, v := range vv {
|
||||||
|
rw.Header().Add(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) {
|
||||||
|
if p.FlushInterval != 0 {
|
||||||
|
if wf, ok := dst.(writeFlusher); ok {
|
||||||
|
mlw := &maxLatencyWriter{
|
||||||
|
dst: wf,
|
||||||
|
latency: p.FlushInterval,
|
||||||
|
done: make(chan bool),
|
||||||
|
}
|
||||||
|
go mlw.flushLoop()
|
||||||
|
defer mlw.stop()
|
||||||
|
dst = mlw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf []byte
|
||||||
|
if p.BufferPool != nil {
|
||||||
|
buf = p.BufferPool.Get()
|
||||||
|
}
|
||||||
|
p.copyBuffer(dst, src, buf)
|
||||||
|
if p.BufferPool != nil {
|
||||||
|
p.BufferPool.Put(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) {
|
||||||
|
if len(buf) == 0 {
|
||||||
|
buf = make([]byte, 32*1024)
|
||||||
|
}
|
||||||
|
var written int64
|
||||||
|
for {
|
||||||
|
nr, rerr := src.Read(buf)
|
||||||
|
if rerr != nil && rerr != io.EOF && rerr != context.Canceled {
|
||||||
|
p.logf("httputil: ReverseProxy read error during body copy: %v", rerr)
|
||||||
|
}
|
||||||
|
if nr > 0 {
|
||||||
|
nw, werr := dst.Write(buf[:nr])
|
||||||
|
if nw > 0 {
|
||||||
|
written += int64(nw)
|
||||||
|
}
|
||||||
|
if werr != nil {
|
||||||
|
return written, werr
|
||||||
|
}
|
||||||
|
if nr != nw {
|
||||||
|
return written, io.ErrShortWrite
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rerr != nil {
|
||||||
|
return written, rerr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ReverseProxy) logf(format string, args ...interface{}) {
|
||||||
|
if p.ErrorLog != nil {
|
||||||
|
p.ErrorLog.Printf(format, args...)
|
||||||
|
} else {
|
||||||
|
log.Printf(format, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type writeFlusher interface {
|
||||||
|
io.Writer
|
||||||
|
http.Flusher
|
||||||
|
}
|
||||||
|
|
||||||
|
type maxLatencyWriter struct {
|
||||||
|
dst writeFlusher
|
||||||
|
latency time.Duration
|
||||||
|
|
||||||
|
mu sync.Mutex // protects Write + Flush
|
||||||
|
done chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *maxLatencyWriter) Write(p []byte) (int, error) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
return m.dst.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *maxLatencyWriter) flushLoop() {
|
||||||
|
t := time.NewTicker(m.latency)
|
||||||
|
defer t.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-m.done:
|
||||||
|
if onExitFlushLoop != nil {
|
||||||
|
onExitFlushLoop()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case <-t.C:
|
||||||
|
m.mu.Lock()
|
||||||
|
m.dst.Flush()
|
||||||
|
m.mu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *maxLatencyWriter) stop() { m.done <- true }
|
||||||
@@ -14,7 +14,8 @@ type VhostRouters struct {
|
|||||||
type VhostRouter struct {
|
type VhostRouter struct {
|
||||||
domain string
|
domain string
|
||||||
location string
|
location string
|
||||||
listener *Listener
|
|
||||||
|
payload interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVhostRouters() *VhostRouters {
|
func NewVhostRouters() *VhostRouters {
|
||||||
@@ -23,7 +24,7 @@ func NewVhostRouters() *VhostRouters {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *VhostRouters) Add(domain, location string, l *Listener) {
|
func (r *VhostRouters) Add(domain, location string, payload interface{}) {
|
||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
defer r.mutex.Unlock()
|
defer r.mutex.Unlock()
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@ func (r *VhostRouters) Add(domain, location string, l *Listener) {
|
|||||||
vr := &VhostRouter{
|
vr := &VhostRouter{
|
||||||
domain: domain,
|
domain: domain,
|
||||||
location: location,
|
location: location,
|
||||||
listener: l,
|
payload: payload,
|
||||||
}
|
}
|
||||||
vrs = append(vrs, vr)
|
vrs = append(vrs, vr)
|
||||||
|
|
||||||
|
|||||||
@@ -13,13 +13,12 @@
|
|||||||
package vhost
|
package vhost
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/utils/errors"
|
||||||
"github.com/fatedier/frp/utils/log"
|
"github.com/fatedier/frp/utils/log"
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
)
|
)
|
||||||
@@ -51,12 +50,16 @@ func NewVhostMuxer(listener frpNet.Listener, vhostFunc muxFunc, authFunc httpAut
|
|||||||
return mux, nil
|
return mux, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreateConnFunc func() (frpNet.Conn, error)
|
||||||
|
|
||||||
type VhostRouteConfig struct {
|
type VhostRouteConfig struct {
|
||||||
Domain string
|
Domain string
|
||||||
Location string
|
Location string
|
||||||
RewriteHost string
|
RewriteHost string
|
||||||
Username string
|
Username string
|
||||||
Password string
|
Password string
|
||||||
|
|
||||||
|
CreateConnFn CreateConnFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// listen for a new domain name, if rewriteHost is not empty and rewriteFunc is not nil
|
// listen for a new domain name, if rewriteHost is not empty and rewriteFunc is not nil
|
||||||
@@ -92,7 +95,7 @@ func (v *VhostMuxer) getListener(name, path string) (l *Listener, exist bool) {
|
|||||||
// if not exist, then check the wildcard_domain such as *.example.com
|
// if not exist, then check the wildcard_domain such as *.example.com
|
||||||
vr, found := v.registryRouter.Get(name, path)
|
vr, found := v.registryRouter.Get(name, path)
|
||||||
if found {
|
if found {
|
||||||
return vr.listener, true
|
return vr.payload.(*Listener), true
|
||||||
}
|
}
|
||||||
|
|
||||||
domainSplit := strings.Split(name, ".")
|
domainSplit := strings.Split(name, ".")
|
||||||
@@ -107,7 +110,7 @@ func (v *VhostMuxer) getListener(name, path string) (l *Listener, exist bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return vr.listener, true
|
return vr.payload.(*Listener), true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VhostMuxer) run() {
|
func (v *VhostMuxer) run() {
|
||||||
@@ -128,7 +131,7 @@ func (v *VhostMuxer) handle(c frpNet.Conn) {
|
|||||||
|
|
||||||
sConn, reqInfoMap, err := v.vhostFunc(c)
|
sConn, reqInfoMap, err := v.vhostFunc(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("get hostname from http/https request error: %v", err)
|
log.Warn("get hostname from http/https request error: %v", err)
|
||||||
c.Close()
|
c.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -137,17 +140,19 @@ func (v *VhostMuxer) handle(c frpNet.Conn) {
|
|||||||
path := strings.ToLower(reqInfoMap["Path"])
|
path := strings.ToLower(reqInfoMap["Path"])
|
||||||
l, ok := v.getListener(name, path)
|
l, ok := v.getListener(name, path)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
res := notFoundResponse()
|
||||||
|
res.Write(c)
|
||||||
log.Debug("http request for host [%s] path [%s] not found", name, path)
|
log.Debug("http request for host [%s] path [%s] not found", name, path)
|
||||||
c.Close()
|
c.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// if authFunc is exist and userName/password is set
|
// if authFunc is exist and userName/password is set
|
||||||
// verify user access
|
// then verify user access
|
||||||
if l.mux.authFunc != nil && l.userName != "" && l.passWord != "" {
|
if l.mux.authFunc != nil && l.userName != "" && l.passWord != "" {
|
||||||
bAccess, err := l.mux.authFunc(c, l.userName, l.passWord, reqInfoMap["Authorization"])
|
bAccess, err := l.mux.authFunc(c, l.userName, l.passWord, reqInfoMap["Authorization"])
|
||||||
if bAccess == false || err != nil {
|
if bAccess == false || err != nil {
|
||||||
l.Debug("check Authorization failed")
|
l.Debug("check http Authorization failed")
|
||||||
res := noAuthResponse()
|
res := noAuthResponse()
|
||||||
res.Write(c)
|
res.Write(c)
|
||||||
c.Close()
|
c.Close()
|
||||||
@@ -162,7 +167,12 @@ func (v *VhostMuxer) handle(c frpNet.Conn) {
|
|||||||
c = sConn
|
c = sConn
|
||||||
|
|
||||||
l.Debug("get new http request host [%s] path [%s]", name, path)
|
l.Debug("get new http request host [%s] path [%s]", name, path)
|
||||||
l.accept <- c
|
err = errors.PanicToError(func() {
|
||||||
|
l.accept <- c
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
l.Warn("listener is already closed, ignore this request")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Listener struct {
|
type Listener struct {
|
||||||
@@ -182,9 +192,10 @@ func (l *Listener) Accept() (frpNet.Conn, error) {
|
|||||||
return nil, fmt.Errorf("Listener closed")
|
return nil, fmt.Errorf("Listener closed")
|
||||||
}
|
}
|
||||||
|
|
||||||
// if rewriteFunc is exist and rewriteHost is set
|
// if rewriteFunc is exist
|
||||||
// rewrite http requests with a modified host header
|
// rewrite http requests with a modified host header
|
||||||
if l.mux.rewriteFunc != nil && l.rewriteHost != "" {
|
// if l.rewriteHost is empty, nothing to do
|
||||||
|
if l.mux.rewriteFunc != nil {
|
||||||
sConn, err := l.mux.rewriteFunc(conn, l.rewriteHost)
|
sConn, err := l.mux.rewriteFunc(conn, l.rewriteHost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Warn("host header rewrite failed: %v", err)
|
l.Warn("host header rewrite failed: %v", err)
|
||||||
@@ -209,45 +220,3 @@ func (l *Listener) Close() error {
|
|||||||
func (l *Listener) Name() string {
|
func (l *Listener) Name() string {
|
||||||
return l.name
|
return l.name
|
||||||
}
|
}
|
||||||
|
|
||||||
type sharedConn struct {
|
|
||||||
frpNet.Conn
|
|
||||||
sync.Mutex
|
|
||||||
buff *bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
// the bytes you read in io.Reader, will be reserved in sharedConn
|
|
||||||
func newShareConn(conn frpNet.Conn) (*sharedConn, io.Reader) {
|
|
||||||
sc := &sharedConn{
|
|
||||||
Conn: conn,
|
|
||||||
buff: bytes.NewBuffer(make([]byte, 0, 1024)),
|
|
||||||
}
|
|
||||||
return sc, io.TeeReader(conn, sc.buff)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sc *sharedConn) Read(p []byte) (n int, err error) {
|
|
||||||
sc.Lock()
|
|
||||||
if sc.buff == nil {
|
|
||||||
sc.Unlock()
|
|
||||||
return sc.Conn.Read(p)
|
|
||||||
}
|
|
||||||
sc.Unlock()
|
|
||||||
n, err = sc.buff.Read(p)
|
|
||||||
|
|
||||||
if err == io.EOF {
|
|
||||||
sc.Lock()
|
|
||||||
sc.buff = nil
|
|
||||||
sc.Unlock()
|
|
||||||
var n2 int
|
|
||||||
n2, err = sc.Conn.Read(p[n:])
|
|
||||||
|
|
||||||
n += n2
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sc *sharedConn) WriteBuff(buffer []byte) (err error) {
|
|
||||||
sc.buff.Reset()
|
|
||||||
_, err = sc.buff.Write(buffer)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|||||||
22
vendor/github.com/armon/go-socks5/.gitignore
generated
vendored
Normal file
22
vendor/github.com/armon/go-socks5/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
4
vendor/github.com/armon/go-socks5/.travis.yml
generated
vendored
Normal file
4
vendor/github.com/armon/go-socks5/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.1
|
||||||
|
- tip
|
||||||
20
vendor/github.com/armon/go-socks5/LICENSE
generated
vendored
Normal file
20
vendor/github.com/armon/go-socks5/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Armon Dadgar
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
45
vendor/github.com/armon/go-socks5/README.md
generated
vendored
Normal file
45
vendor/github.com/armon/go-socks5/README.md
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
go-socks5 [](https://travis-ci.org/armon/go-socks5)
|
||||||
|
=========
|
||||||
|
|
||||||
|
Provides the `socks5` package that implements a [SOCKS5 server](http://en.wikipedia.org/wiki/SOCKS).
|
||||||
|
SOCKS (Secure Sockets) is used to route traffic between a client and server through
|
||||||
|
an intermediate proxy layer. This can be used to bypass firewalls or NATs.
|
||||||
|
|
||||||
|
Feature
|
||||||
|
=======
|
||||||
|
|
||||||
|
The package has the following features:
|
||||||
|
* "No Auth" mode
|
||||||
|
* User/Password authentication
|
||||||
|
* Support for the CONNECT command
|
||||||
|
* Rules to do granular filtering of commands
|
||||||
|
* Custom DNS resolution
|
||||||
|
* Unit tests
|
||||||
|
|
||||||
|
TODO
|
||||||
|
====
|
||||||
|
|
||||||
|
The package still needs the following:
|
||||||
|
* Support for the BIND command
|
||||||
|
* Support for the ASSOCIATE command
|
||||||
|
|
||||||
|
|
||||||
|
Example
|
||||||
|
=======
|
||||||
|
|
||||||
|
Below is a simple example of usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Create a SOCKS5 server
|
||||||
|
conf := &socks5.Config{}
|
||||||
|
server, err := socks5.New(conf)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create SOCKS5 proxy on localhost port 8000
|
||||||
|
if err := server.ListenAndServe("tcp", "127.0.0.1:8000"); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
151
vendor/github.com/armon/go-socks5/auth.go
generated
vendored
Normal file
151
vendor/github.com/armon/go-socks5/auth.go
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
NoAuth = uint8(0)
|
||||||
|
noAcceptable = uint8(255)
|
||||||
|
UserPassAuth = uint8(2)
|
||||||
|
userAuthVersion = uint8(1)
|
||||||
|
authSuccess = uint8(0)
|
||||||
|
authFailure = uint8(1)
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
UserAuthFailed = fmt.Errorf("User authentication failed")
|
||||||
|
NoSupportedAuth = fmt.Errorf("No supported authentication mechanism")
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Request encapsulates authentication state provided
|
||||||
|
// during negotiation
|
||||||
|
type AuthContext struct {
|
||||||
|
// Provided auth method
|
||||||
|
Method uint8
|
||||||
|
// Payload provided during negotiation.
|
||||||
|
// Keys depend on the used auth method.
|
||||||
|
// For UserPassauth contains Username
|
||||||
|
Payload map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Authenticator interface {
|
||||||
|
Authenticate(reader io.Reader, writer io.Writer) (*AuthContext, error)
|
||||||
|
GetCode() uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoAuthAuthenticator is used to handle the "No Authentication" mode
|
||||||
|
type NoAuthAuthenticator struct{}
|
||||||
|
|
||||||
|
func (a NoAuthAuthenticator) GetCode() uint8 {
|
||||||
|
return NoAuth
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a NoAuthAuthenticator) Authenticate(reader io.Reader, writer io.Writer) (*AuthContext, error) {
|
||||||
|
_, err := writer.Write([]byte{socks5Version, NoAuth})
|
||||||
|
return &AuthContext{NoAuth, nil}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserPassAuthenticator is used to handle username/password based
|
||||||
|
// authentication
|
||||||
|
type UserPassAuthenticator struct {
|
||||||
|
Credentials CredentialStore
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a UserPassAuthenticator) GetCode() uint8 {
|
||||||
|
return UserPassAuth
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a UserPassAuthenticator) Authenticate(reader io.Reader, writer io.Writer) (*AuthContext, error) {
|
||||||
|
// Tell the client to use user/pass auth
|
||||||
|
if _, err := writer.Write([]byte{socks5Version, UserPassAuth}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the version and username length
|
||||||
|
header := []byte{0, 0}
|
||||||
|
if _, err := io.ReadAtLeast(reader, header, 2); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we are compatible
|
||||||
|
if header[0] != userAuthVersion {
|
||||||
|
return nil, fmt.Errorf("Unsupported auth version: %v", header[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the user name
|
||||||
|
userLen := int(header[1])
|
||||||
|
user := make([]byte, userLen)
|
||||||
|
if _, err := io.ReadAtLeast(reader, user, userLen); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the password length
|
||||||
|
if _, err := reader.Read(header[:1]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the password
|
||||||
|
passLen := int(header[0])
|
||||||
|
pass := make([]byte, passLen)
|
||||||
|
if _, err := io.ReadAtLeast(reader, pass, passLen); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the password
|
||||||
|
if a.Credentials.Valid(string(user), string(pass)) {
|
||||||
|
if _, err := writer.Write([]byte{userAuthVersion, authSuccess}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, err := writer.Write([]byte{userAuthVersion, authFailure}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, UserAuthFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done
|
||||||
|
return &AuthContext{UserPassAuth, map[string]string{"Username": string(user)}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// authenticate is used to handle connection authentication
|
||||||
|
func (s *Server) authenticate(conn io.Writer, bufConn io.Reader) (*AuthContext, error) {
|
||||||
|
// Get the methods
|
||||||
|
methods, err := readMethods(bufConn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to get auth methods: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select a usable method
|
||||||
|
for _, method := range methods {
|
||||||
|
cator, found := s.authMethods[method]
|
||||||
|
if found {
|
||||||
|
return cator.Authenticate(bufConn, conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No usable method found
|
||||||
|
return nil, noAcceptableAuth(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// noAcceptableAuth is used to handle when we have no eligible
|
||||||
|
// authentication mechanism
|
||||||
|
func noAcceptableAuth(conn io.Writer) error {
|
||||||
|
conn.Write([]byte{socks5Version, noAcceptable})
|
||||||
|
return NoSupportedAuth
|
||||||
|
}
|
||||||
|
|
||||||
|
// readMethods is used to read the number of methods
|
||||||
|
// and proceeding auth methods
|
||||||
|
func readMethods(r io.Reader) ([]byte, error) {
|
||||||
|
header := []byte{0}
|
||||||
|
if _, err := r.Read(header); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
numMethods := int(header[0])
|
||||||
|
methods := make([]byte, numMethods)
|
||||||
|
_, err := io.ReadAtLeast(r, methods, numMethods)
|
||||||
|
return methods, err
|
||||||
|
}
|
||||||
119
vendor/github.com/armon/go-socks5/auth_test.go
generated
vendored
Normal file
119
vendor/github.com/armon/go-socks5/auth_test.go
generated
vendored
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNoAuth(t *testing.T) {
|
||||||
|
req := bytes.NewBuffer(nil)
|
||||||
|
req.Write([]byte{1, NoAuth})
|
||||||
|
var resp bytes.Buffer
|
||||||
|
|
||||||
|
s, _ := New(&Config{})
|
||||||
|
ctx, err := s.authenticate(&resp, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.Method != NoAuth {
|
||||||
|
t.Fatal("Invalid Context Method")
|
||||||
|
}
|
||||||
|
|
||||||
|
out := resp.Bytes()
|
||||||
|
if !bytes.Equal(out, []byte{socks5Version, NoAuth}) {
|
||||||
|
t.Fatalf("bad: %v", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPasswordAuth_Valid(t *testing.T) {
|
||||||
|
req := bytes.NewBuffer(nil)
|
||||||
|
req.Write([]byte{2, NoAuth, UserPassAuth})
|
||||||
|
req.Write([]byte{1, 3, 'f', 'o', 'o', 3, 'b', 'a', 'r'})
|
||||||
|
var resp bytes.Buffer
|
||||||
|
|
||||||
|
cred := StaticCredentials{
|
||||||
|
"foo": "bar",
|
||||||
|
}
|
||||||
|
|
||||||
|
cator := UserPassAuthenticator{Credentials: cred}
|
||||||
|
|
||||||
|
s, _ := New(&Config{AuthMethods: []Authenticator{cator}})
|
||||||
|
|
||||||
|
ctx, err := s.authenticate(&resp, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.Method != UserPassAuth {
|
||||||
|
t.Fatal("Invalid Context Method")
|
||||||
|
}
|
||||||
|
|
||||||
|
val, ok := ctx.Payload["Username"]
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("Missing key Username in auth context's payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
if val != "foo" {
|
||||||
|
t.Fatal("Invalid Username in auth context's payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
out := resp.Bytes()
|
||||||
|
if !bytes.Equal(out, []byte{socks5Version, UserPassAuth, 1, authSuccess}) {
|
||||||
|
t.Fatalf("bad: %v", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPasswordAuth_Invalid(t *testing.T) {
|
||||||
|
req := bytes.NewBuffer(nil)
|
||||||
|
req.Write([]byte{2, NoAuth, UserPassAuth})
|
||||||
|
req.Write([]byte{1, 3, 'f', 'o', 'o', 3, 'b', 'a', 'z'})
|
||||||
|
var resp bytes.Buffer
|
||||||
|
|
||||||
|
cred := StaticCredentials{
|
||||||
|
"foo": "bar",
|
||||||
|
}
|
||||||
|
cator := UserPassAuthenticator{Credentials: cred}
|
||||||
|
s, _ := New(&Config{AuthMethods: []Authenticator{cator}})
|
||||||
|
|
||||||
|
ctx, err := s.authenticate(&resp, req)
|
||||||
|
if err != UserAuthFailed {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx != nil {
|
||||||
|
t.Fatal("Invalid Context Method")
|
||||||
|
}
|
||||||
|
|
||||||
|
out := resp.Bytes()
|
||||||
|
if !bytes.Equal(out, []byte{socks5Version, UserPassAuth, 1, authFailure}) {
|
||||||
|
t.Fatalf("bad: %v", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoSupportedAuth(t *testing.T) {
|
||||||
|
req := bytes.NewBuffer(nil)
|
||||||
|
req.Write([]byte{1, NoAuth})
|
||||||
|
var resp bytes.Buffer
|
||||||
|
|
||||||
|
cred := StaticCredentials{
|
||||||
|
"foo": "bar",
|
||||||
|
}
|
||||||
|
cator := UserPassAuthenticator{Credentials: cred}
|
||||||
|
|
||||||
|
s, _ := New(&Config{AuthMethods: []Authenticator{cator}})
|
||||||
|
|
||||||
|
ctx, err := s.authenticate(&resp, req)
|
||||||
|
if err != NoSupportedAuth {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx != nil {
|
||||||
|
t.Fatal("Invalid Context Method")
|
||||||
|
}
|
||||||
|
|
||||||
|
out := resp.Bytes()
|
||||||
|
if !bytes.Equal(out, []byte{socks5Version, noAcceptable}) {
|
||||||
|
t.Fatalf("bad: %v", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
17
vendor/github.com/armon/go-socks5/credentials.go
generated
vendored
Normal file
17
vendor/github.com/armon/go-socks5/credentials.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package socks5
|
||||||
|
|
||||||
|
// CredentialStore is used to support user/pass authentication
|
||||||
|
type CredentialStore interface {
|
||||||
|
Valid(user, password string) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// StaticCredentials enables using a map directly as a credential store
|
||||||
|
type StaticCredentials map[string]string
|
||||||
|
|
||||||
|
func (s StaticCredentials) Valid(user, password string) bool {
|
||||||
|
pass, ok := s[user]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return password == pass
|
||||||
|
}
|
||||||
24
vendor/github.com/armon/go-socks5/credentials_test.go
generated
vendored
Normal file
24
vendor/github.com/armon/go-socks5/credentials_test.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStaticCredentials(t *testing.T) {
|
||||||
|
creds := StaticCredentials{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
if !creds.Valid("foo", "bar") {
|
||||||
|
t.Fatalf("expect valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !creds.Valid("baz", "") {
|
||||||
|
t.Fatalf("expect valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
if creds.Valid("foo", "") {
|
||||||
|
t.Fatalf("expect invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
364
vendor/github.com/armon/go-socks5/request.go
generated
vendored
Normal file
364
vendor/github.com/armon/go-socks5/request.go
generated
vendored
Normal file
@@ -0,0 +1,364 @@
|
|||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ConnectCommand = uint8(1)
|
||||||
|
BindCommand = uint8(2)
|
||||||
|
AssociateCommand = uint8(3)
|
||||||
|
ipv4Address = uint8(1)
|
||||||
|
fqdnAddress = uint8(3)
|
||||||
|
ipv6Address = uint8(4)
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
successReply uint8 = iota
|
||||||
|
serverFailure
|
||||||
|
ruleFailure
|
||||||
|
networkUnreachable
|
||||||
|
hostUnreachable
|
||||||
|
connectionRefused
|
||||||
|
ttlExpired
|
||||||
|
commandNotSupported
|
||||||
|
addrTypeNotSupported
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
unrecognizedAddrType = fmt.Errorf("Unrecognized address type")
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddressRewriter is used to rewrite a destination transparently
|
||||||
|
type AddressRewriter interface {
|
||||||
|
Rewrite(ctx context.Context, request *Request) (context.Context, *AddrSpec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddrSpec is used to return the target AddrSpec
|
||||||
|
// which may be specified as IPv4, IPv6, or a FQDN
|
||||||
|
type AddrSpec struct {
|
||||||
|
FQDN string
|
||||||
|
IP net.IP
|
||||||
|
Port int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AddrSpec) String() string {
|
||||||
|
if a.FQDN != "" {
|
||||||
|
return fmt.Sprintf("%s (%s):%d", a.FQDN, a.IP, a.Port)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s:%d", a.IP, a.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address returns a string suitable to dial; prefer returning IP-based
|
||||||
|
// address, fallback to FQDN
|
||||||
|
func (a AddrSpec) Address() string {
|
||||||
|
if 0 != len(a.IP) {
|
||||||
|
return net.JoinHostPort(a.IP.String(), strconv.Itoa(a.Port))
|
||||||
|
}
|
||||||
|
return net.JoinHostPort(a.FQDN, strconv.Itoa(a.Port))
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Request represents request received by a server
|
||||||
|
type Request struct {
|
||||||
|
// Protocol version
|
||||||
|
Version uint8
|
||||||
|
// Requested command
|
||||||
|
Command uint8
|
||||||
|
// AuthContext provided during negotiation
|
||||||
|
AuthContext *AuthContext
|
||||||
|
// AddrSpec of the the network that sent the request
|
||||||
|
RemoteAddr *AddrSpec
|
||||||
|
// AddrSpec of the desired destination
|
||||||
|
DestAddr *AddrSpec
|
||||||
|
// AddrSpec of the actual destination (might be affected by rewrite)
|
||||||
|
realDestAddr *AddrSpec
|
||||||
|
bufConn io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
type conn interface {
|
||||||
|
Write([]byte) (int, error)
|
||||||
|
RemoteAddr() net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRequest creates a new Request from the tcp connection
|
||||||
|
func NewRequest(bufConn io.Reader) (*Request, error) {
|
||||||
|
// Read the version byte
|
||||||
|
header := []byte{0, 0, 0}
|
||||||
|
if _, err := io.ReadAtLeast(bufConn, header, 3); err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to get command version: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we are compatible
|
||||||
|
if header[0] != socks5Version {
|
||||||
|
return nil, fmt.Errorf("Unsupported command version: %v", header[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read in the destination address
|
||||||
|
dest, err := readAddrSpec(bufConn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
request := &Request{
|
||||||
|
Version: socks5Version,
|
||||||
|
Command: header[1],
|
||||||
|
DestAddr: dest,
|
||||||
|
bufConn: bufConn,
|
||||||
|
}
|
||||||
|
|
||||||
|
return request, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleRequest is used for request processing after authentication
|
||||||
|
func (s *Server) handleRequest(req *Request, conn conn) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Resolve the address if we have a FQDN
|
||||||
|
dest := req.DestAddr
|
||||||
|
if dest.FQDN != "" {
|
||||||
|
ctx_, addr, err := s.config.Resolver.Resolve(ctx, dest.FQDN)
|
||||||
|
if err != nil {
|
||||||
|
if err := sendReply(conn, hostUnreachable, nil); err != nil {
|
||||||
|
return fmt.Errorf("Failed to send reply: %v", err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Failed to resolve destination '%v': %v", dest.FQDN, err)
|
||||||
|
}
|
||||||
|
ctx = ctx_
|
||||||
|
dest.IP = addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply any address rewrites
|
||||||
|
req.realDestAddr = req.DestAddr
|
||||||
|
if s.config.Rewriter != nil {
|
||||||
|
ctx, req.realDestAddr = s.config.Rewriter.Rewrite(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch on the command
|
||||||
|
switch req.Command {
|
||||||
|
case ConnectCommand:
|
||||||
|
return s.handleConnect(ctx, conn, req)
|
||||||
|
case BindCommand:
|
||||||
|
return s.handleBind(ctx, conn, req)
|
||||||
|
case AssociateCommand:
|
||||||
|
return s.handleAssociate(ctx, conn, req)
|
||||||
|
default:
|
||||||
|
if err := sendReply(conn, commandNotSupported, nil); err != nil {
|
||||||
|
return fmt.Errorf("Failed to send reply: %v", err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Unsupported command: %v", req.Command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleConnect is used to handle a connect command
|
||||||
|
func (s *Server) handleConnect(ctx context.Context, conn conn, req *Request) error {
|
||||||
|
// Check if this is allowed
|
||||||
|
if ctx_, ok := s.config.Rules.Allow(ctx, req); !ok {
|
||||||
|
if err := sendReply(conn, ruleFailure, nil); err != nil {
|
||||||
|
return fmt.Errorf("Failed to send reply: %v", err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Connect to %v blocked by rules", req.DestAddr)
|
||||||
|
} else {
|
||||||
|
ctx = ctx_
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to connect
|
||||||
|
dial := s.config.Dial
|
||||||
|
if dial == nil {
|
||||||
|
dial = func(ctx context.Context, net_, addr string) (net.Conn, error) {
|
||||||
|
return net.Dial(net_, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
target, err := dial(ctx, "tcp", req.realDestAddr.Address())
|
||||||
|
if err != nil {
|
||||||
|
msg := err.Error()
|
||||||
|
resp := hostUnreachable
|
||||||
|
if strings.Contains(msg, "refused") {
|
||||||
|
resp = connectionRefused
|
||||||
|
} else if strings.Contains(msg, "network is unreachable") {
|
||||||
|
resp = networkUnreachable
|
||||||
|
}
|
||||||
|
if err := sendReply(conn, resp, nil); err != nil {
|
||||||
|
return fmt.Errorf("Failed to send reply: %v", err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Connect to %v failed: %v", req.DestAddr, err)
|
||||||
|
}
|
||||||
|
defer target.Close()
|
||||||
|
|
||||||
|
// Send success
|
||||||
|
local := target.LocalAddr().(*net.TCPAddr)
|
||||||
|
bind := AddrSpec{IP: local.IP, Port: local.Port}
|
||||||
|
if err := sendReply(conn, successReply, &bind); err != nil {
|
||||||
|
return fmt.Errorf("Failed to send reply: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start proxying
|
||||||
|
errCh := make(chan error, 2)
|
||||||
|
go proxy(target, req.bufConn, errCh)
|
||||||
|
go proxy(conn, target, errCh)
|
||||||
|
|
||||||
|
// Wait
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
e := <-errCh
|
||||||
|
if e != nil {
|
||||||
|
// return from this function closes target (and conn).
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleBind is used to handle a connect command
|
||||||
|
func (s *Server) handleBind(ctx context.Context, conn conn, req *Request) error {
|
||||||
|
// Check if this is allowed
|
||||||
|
if ctx_, ok := s.config.Rules.Allow(ctx, req); !ok {
|
||||||
|
if err := sendReply(conn, ruleFailure, nil); err != nil {
|
||||||
|
return fmt.Errorf("Failed to send reply: %v", err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Bind to %v blocked by rules", req.DestAddr)
|
||||||
|
} else {
|
||||||
|
ctx = ctx_
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Support bind
|
||||||
|
if err := sendReply(conn, commandNotSupported, nil); err != nil {
|
||||||
|
return fmt.Errorf("Failed to send reply: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleAssociate is used to handle a connect command
|
||||||
|
func (s *Server) handleAssociate(ctx context.Context, conn conn, req *Request) error {
|
||||||
|
// Check if this is allowed
|
||||||
|
if ctx_, ok := s.config.Rules.Allow(ctx, req); !ok {
|
||||||
|
if err := sendReply(conn, ruleFailure, nil); err != nil {
|
||||||
|
return fmt.Errorf("Failed to send reply: %v", err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Associate to %v blocked by rules", req.DestAddr)
|
||||||
|
} else {
|
||||||
|
ctx = ctx_
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Support associate
|
||||||
|
if err := sendReply(conn, commandNotSupported, nil); err != nil {
|
||||||
|
return fmt.Errorf("Failed to send reply: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readAddrSpec is used to read AddrSpec.
|
||||||
|
// Expects an address type byte, follwed by the address and port
|
||||||
|
func readAddrSpec(r io.Reader) (*AddrSpec, error) {
|
||||||
|
d := &AddrSpec{}
|
||||||
|
|
||||||
|
// Get the address type
|
||||||
|
addrType := []byte{0}
|
||||||
|
if _, err := r.Read(addrType); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle on a per type basis
|
||||||
|
switch addrType[0] {
|
||||||
|
case ipv4Address:
|
||||||
|
addr := make([]byte, 4)
|
||||||
|
if _, err := io.ReadAtLeast(r, addr, len(addr)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
d.IP = net.IP(addr)
|
||||||
|
|
||||||
|
case ipv6Address:
|
||||||
|
addr := make([]byte, 16)
|
||||||
|
if _, err := io.ReadAtLeast(r, addr, len(addr)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
d.IP = net.IP(addr)
|
||||||
|
|
||||||
|
case fqdnAddress:
|
||||||
|
if _, err := r.Read(addrType); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
addrLen := int(addrType[0])
|
||||||
|
fqdn := make([]byte, addrLen)
|
||||||
|
if _, err := io.ReadAtLeast(r, fqdn, addrLen); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
d.FQDN = string(fqdn)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, unrecognizedAddrType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the port
|
||||||
|
port := []byte{0, 0}
|
||||||
|
if _, err := io.ReadAtLeast(r, port, 2); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
d.Port = (int(port[0]) << 8) | int(port[1])
|
||||||
|
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendReply is used to send a reply message
|
||||||
|
func sendReply(w io.Writer, resp uint8, addr *AddrSpec) error {
|
||||||
|
// Format the address
|
||||||
|
var addrType uint8
|
||||||
|
var addrBody []byte
|
||||||
|
var addrPort uint16
|
||||||
|
switch {
|
||||||
|
case addr == nil:
|
||||||
|
addrType = ipv4Address
|
||||||
|
addrBody = []byte{0, 0, 0, 0}
|
||||||
|
addrPort = 0
|
||||||
|
|
||||||
|
case addr.FQDN != "":
|
||||||
|
addrType = fqdnAddress
|
||||||
|
addrBody = append([]byte{byte(len(addr.FQDN))}, addr.FQDN...)
|
||||||
|
addrPort = uint16(addr.Port)
|
||||||
|
|
||||||
|
case addr.IP.To4() != nil:
|
||||||
|
addrType = ipv4Address
|
||||||
|
addrBody = []byte(addr.IP.To4())
|
||||||
|
addrPort = uint16(addr.Port)
|
||||||
|
|
||||||
|
case addr.IP.To16() != nil:
|
||||||
|
addrType = ipv6Address
|
||||||
|
addrBody = []byte(addr.IP.To16())
|
||||||
|
addrPort = uint16(addr.Port)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Failed to format address: %v", addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the message
|
||||||
|
msg := make([]byte, 6+len(addrBody))
|
||||||
|
msg[0] = socks5Version
|
||||||
|
msg[1] = resp
|
||||||
|
msg[2] = 0 // Reserved
|
||||||
|
msg[3] = addrType
|
||||||
|
copy(msg[4:], addrBody)
|
||||||
|
msg[4+len(addrBody)] = byte(addrPort >> 8)
|
||||||
|
msg[4+len(addrBody)+1] = byte(addrPort & 0xff)
|
||||||
|
|
||||||
|
// Send the message
|
||||||
|
_, err := w.Write(msg)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type closeWriter interface {
|
||||||
|
CloseWrite() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// proxy is used to suffle data from src to destination, and sends errors
|
||||||
|
// down a dedicated channel
|
||||||
|
func proxy(dst io.Writer, src io.Reader, errCh chan error) {
|
||||||
|
_, err := io.Copy(dst, src)
|
||||||
|
if tcpConn, ok := dst.(closeWriter); ok {
|
||||||
|
tcpConn.CloseWrite()
|
||||||
|
}
|
||||||
|
errCh <- err
|
||||||
|
}
|
||||||
169
vendor/github.com/armon/go-socks5/request_test.go
generated
vendored
Normal file
169
vendor/github.com/armon/go-socks5/request_test.go
generated
vendored
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MockConn struct {
|
||||||
|
buf bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockConn) Write(b []byte) (int, error) {
|
||||||
|
return m.buf.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockConn) RemoteAddr() net.Addr {
|
||||||
|
return &net.TCPAddr{IP: []byte{127, 0, 0, 1}, Port: 65432}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequest_Connect(t *testing.T) {
|
||||||
|
// Create a local listener
|
||||||
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
conn, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
buf := make([]byte, 4)
|
||||||
|
if _, err := io.ReadAtLeast(conn, buf, 4); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(buf, []byte("ping")) {
|
||||||
|
t.Fatalf("bad: %v", buf)
|
||||||
|
}
|
||||||
|
conn.Write([]byte("pong"))
|
||||||
|
}()
|
||||||
|
lAddr := l.Addr().(*net.TCPAddr)
|
||||||
|
|
||||||
|
// Make server
|
||||||
|
s := &Server{config: &Config{
|
||||||
|
Rules: PermitAll(),
|
||||||
|
Resolver: DNSResolver{},
|
||||||
|
Logger: log.New(os.Stdout, "", log.LstdFlags),
|
||||||
|
}}
|
||||||
|
|
||||||
|
// Create the connect request
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
buf.Write([]byte{5, 1, 0, 1, 127, 0, 0, 1})
|
||||||
|
|
||||||
|
port := []byte{0, 0}
|
||||||
|
binary.BigEndian.PutUint16(port, uint16(lAddr.Port))
|
||||||
|
buf.Write(port)
|
||||||
|
|
||||||
|
// Send a ping
|
||||||
|
buf.Write([]byte("ping"))
|
||||||
|
|
||||||
|
// Handle the request
|
||||||
|
resp := &MockConn{}
|
||||||
|
req, err := NewRequest(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.handleRequest(req, resp); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify response
|
||||||
|
out := resp.buf.Bytes()
|
||||||
|
expected := []byte{
|
||||||
|
5,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
127, 0, 0, 1,
|
||||||
|
0, 0,
|
||||||
|
'p', 'o', 'n', 'g',
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore the port for both
|
||||||
|
out[8] = 0
|
||||||
|
out[9] = 0
|
||||||
|
|
||||||
|
if !bytes.Equal(out, expected) {
|
||||||
|
t.Fatalf("bad: %v %v", out, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequest_Connect_RuleFail(t *testing.T) {
|
||||||
|
// Create a local listener
|
||||||
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
conn, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
buf := make([]byte, 4)
|
||||||
|
if _, err := io.ReadAtLeast(conn, buf, 4); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(buf, []byte("ping")) {
|
||||||
|
t.Fatalf("bad: %v", buf)
|
||||||
|
}
|
||||||
|
conn.Write([]byte("pong"))
|
||||||
|
}()
|
||||||
|
lAddr := l.Addr().(*net.TCPAddr)
|
||||||
|
|
||||||
|
// Make server
|
||||||
|
s := &Server{config: &Config{
|
||||||
|
Rules: PermitNone(),
|
||||||
|
Resolver: DNSResolver{},
|
||||||
|
Logger: log.New(os.Stdout, "", log.LstdFlags),
|
||||||
|
}}
|
||||||
|
|
||||||
|
// Create the connect request
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
buf.Write([]byte{5, 1, 0, 1, 127, 0, 0, 1})
|
||||||
|
|
||||||
|
port := []byte{0, 0}
|
||||||
|
binary.BigEndian.PutUint16(port, uint16(lAddr.Port))
|
||||||
|
buf.Write(port)
|
||||||
|
|
||||||
|
// Send a ping
|
||||||
|
buf.Write([]byte("ping"))
|
||||||
|
|
||||||
|
// Handle the request
|
||||||
|
resp := &MockConn{}
|
||||||
|
req, err := NewRequest(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.handleRequest(req, resp); !strings.Contains(err.Error(), "blocked by rules") {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify response
|
||||||
|
out := resp.buf.Bytes()
|
||||||
|
expected := []byte{
|
||||||
|
5,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0, 0, 0, 0,
|
||||||
|
0, 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(out, expected) {
|
||||||
|
t.Fatalf("bad: %v %v", out, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
23
vendor/github.com/armon/go-socks5/resolver.go
generated
vendored
Normal file
23
vendor/github.com/armon/go-socks5/resolver.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NameResolver is used to implement custom name resolution
|
||||||
|
type NameResolver interface {
|
||||||
|
Resolve(ctx context.Context, name string) (context.Context, net.IP, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNSResolver uses the system DNS to resolve host names
|
||||||
|
type DNSResolver struct{}
|
||||||
|
|
||||||
|
func (d DNSResolver) Resolve(ctx context.Context, name string) (context.Context, net.IP, error) {
|
||||||
|
addr, err := net.ResolveIPAddr("ip", name)
|
||||||
|
if err != nil {
|
||||||
|
return ctx, nil, err
|
||||||
|
}
|
||||||
|
return ctx, addr.IP, err
|
||||||
|
}
|
||||||
21
vendor/github.com/armon/go-socks5/resolver_test.go
generated
vendored
Normal file
21
vendor/github.com/armon/go-socks5/resolver_test.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDNSResolver(t *testing.T) {
|
||||||
|
d := DNSResolver{}
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
_, addr, err := d.Resolve(ctx, "localhost")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !addr.IsLoopback() {
|
||||||
|
t.Fatalf("expected loopback")
|
||||||
|
}
|
||||||
|
}
|
||||||
41
vendor/github.com/armon/go-socks5/ruleset.go
generated
vendored
Normal file
41
vendor/github.com/armon/go-socks5/ruleset.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RuleSet is used to provide custom rules to allow or prohibit actions
|
||||||
|
type RuleSet interface {
|
||||||
|
Allow(ctx context.Context, req *Request) (context.Context, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PermitAll returns a RuleSet which allows all types of connections
|
||||||
|
func PermitAll() RuleSet {
|
||||||
|
return &PermitCommand{true, true, true}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PermitNone returns a RuleSet which disallows all types of connections
|
||||||
|
func PermitNone() RuleSet {
|
||||||
|
return &PermitCommand{false, false, false}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PermitCommand is an implementation of the RuleSet which
|
||||||
|
// enables filtering supported commands
|
||||||
|
type PermitCommand struct {
|
||||||
|
EnableConnect bool
|
||||||
|
EnableBind bool
|
||||||
|
EnableAssociate bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PermitCommand) Allow(ctx context.Context, req *Request) (context.Context, bool) {
|
||||||
|
switch req.Command {
|
||||||
|
case ConnectCommand:
|
||||||
|
return ctx, p.EnableConnect
|
||||||
|
case BindCommand:
|
||||||
|
return ctx, p.EnableBind
|
||||||
|
case AssociateCommand:
|
||||||
|
return ctx, p.EnableAssociate
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx, false
|
||||||
|
}
|
||||||
24
vendor/github.com/armon/go-socks5/ruleset_test.go
generated
vendored
Normal file
24
vendor/github.com/armon/go-socks5/ruleset_test.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPermitCommand(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
r := &PermitCommand{true, false, false}
|
||||||
|
|
||||||
|
if _, ok := r.Allow(ctx, &Request{Command: ConnectCommand}); !ok {
|
||||||
|
t.Fatalf("expect connect")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := r.Allow(ctx, &Request{Command: BindCommand}); ok {
|
||||||
|
t.Fatalf("do not expect bind")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := r.Allow(ctx, &Request{Command: AssociateCommand}); ok {
|
||||||
|
t.Fatalf("do not expect associate")
|
||||||
|
}
|
||||||
|
}
|
||||||
169
vendor/github.com/armon/go-socks5/socks5.go
generated
vendored
Normal file
169
vendor/github.com/armon/go-socks5/socks5.go
generated
vendored
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
socks5Version = uint8(5)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config is used to setup and configure a Server
|
||||||
|
type Config struct {
|
||||||
|
// AuthMethods can be provided to implement custom authentication
|
||||||
|
// By default, "auth-less" mode is enabled.
|
||||||
|
// For password-based auth use UserPassAuthenticator.
|
||||||
|
AuthMethods []Authenticator
|
||||||
|
|
||||||
|
// If provided, username/password authentication is enabled,
|
||||||
|
// by appending a UserPassAuthenticator to AuthMethods. If not provided,
|
||||||
|
// and AUthMethods is nil, then "auth-less" mode is enabled.
|
||||||
|
Credentials CredentialStore
|
||||||
|
|
||||||
|
// Resolver can be provided to do custom name resolution.
|
||||||
|
// Defaults to DNSResolver if not provided.
|
||||||
|
Resolver NameResolver
|
||||||
|
|
||||||
|
// Rules is provided to enable custom logic around permitting
|
||||||
|
// various commands. If not provided, PermitAll is used.
|
||||||
|
Rules RuleSet
|
||||||
|
|
||||||
|
// Rewriter can be used to transparently rewrite addresses.
|
||||||
|
// This is invoked before the RuleSet is invoked.
|
||||||
|
// Defaults to NoRewrite.
|
||||||
|
Rewriter AddressRewriter
|
||||||
|
|
||||||
|
// BindIP is used for bind or udp associate
|
||||||
|
BindIP net.IP
|
||||||
|
|
||||||
|
// Logger can be used to provide a custom log target.
|
||||||
|
// Defaults to stdout.
|
||||||
|
Logger *log.Logger
|
||||||
|
|
||||||
|
// Optional function for dialing out
|
||||||
|
Dial func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server is reponsible for accepting connections and handling
|
||||||
|
// the details of the SOCKS5 protocol
|
||||||
|
type Server struct {
|
||||||
|
config *Config
|
||||||
|
authMethods map[uint8]Authenticator
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new Server and potentially returns an error
|
||||||
|
func New(conf *Config) (*Server, error) {
|
||||||
|
// Ensure we have at least one authentication method enabled
|
||||||
|
if len(conf.AuthMethods) == 0 {
|
||||||
|
if conf.Credentials != nil {
|
||||||
|
conf.AuthMethods = []Authenticator{&UserPassAuthenticator{conf.Credentials}}
|
||||||
|
} else {
|
||||||
|
conf.AuthMethods = []Authenticator{&NoAuthAuthenticator{}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we have a DNS resolver
|
||||||
|
if conf.Resolver == nil {
|
||||||
|
conf.Resolver = DNSResolver{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we have a rule set
|
||||||
|
if conf.Rules == nil {
|
||||||
|
conf.Rules = PermitAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we have a log target
|
||||||
|
if conf.Logger == nil {
|
||||||
|
conf.Logger = log.New(os.Stdout, "", log.LstdFlags)
|
||||||
|
}
|
||||||
|
|
||||||
|
server := &Server{
|
||||||
|
config: conf,
|
||||||
|
}
|
||||||
|
|
||||||
|
server.authMethods = make(map[uint8]Authenticator)
|
||||||
|
|
||||||
|
for _, a := range conf.AuthMethods {
|
||||||
|
server.authMethods[a.GetCode()] = a
|
||||||
|
}
|
||||||
|
|
||||||
|
return server, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServe is used to create a listener and serve on it
|
||||||
|
func (s *Server) ListenAndServe(network, addr string) error {
|
||||||
|
l, err := net.Listen(network, addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.Serve(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve is used to serve connections from a listener
|
||||||
|
func (s *Server) Serve(l net.Listener) error {
|
||||||
|
for {
|
||||||
|
conn, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go s.ServeConn(conn)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeConn is used to serve a single connection.
|
||||||
|
func (s *Server) ServeConn(conn net.Conn) error {
|
||||||
|
defer conn.Close()
|
||||||
|
bufConn := bufio.NewReader(conn)
|
||||||
|
|
||||||
|
// Read the version byte
|
||||||
|
version := []byte{0}
|
||||||
|
if _, err := bufConn.Read(version); err != nil {
|
||||||
|
s.config.Logger.Printf("[ERR] socks: Failed to get version byte: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we are compatible
|
||||||
|
if version[0] != socks5Version {
|
||||||
|
err := fmt.Errorf("Unsupported SOCKS version: %v", version)
|
||||||
|
s.config.Logger.Printf("[ERR] socks: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate the connection
|
||||||
|
authContext, err := s.authenticate(conn, bufConn)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Failed to authenticate: %v", err)
|
||||||
|
s.config.Logger.Printf("[ERR] socks: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
request, err := NewRequest(bufConn)
|
||||||
|
if err != nil {
|
||||||
|
if err == unrecognizedAddrType {
|
||||||
|
if err := sendReply(conn, addrTypeNotSupported, nil); err != nil {
|
||||||
|
return fmt.Errorf("Failed to send reply: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Failed to read destination address: %v", err)
|
||||||
|
}
|
||||||
|
request.AuthContext = authContext
|
||||||
|
if client, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
|
||||||
|
request.RemoteAddr = &AddrSpec{IP: client.IP, Port: client.Port}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the client request
|
||||||
|
if err := s.handleRequest(request, conn); err != nil {
|
||||||
|
err = fmt.Errorf("Failed to handle request: %v", err)
|
||||||
|
s.config.Logger.Printf("[ERR] socks: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
110
vendor/github.com/armon/go-socks5/socks5_test.go
generated
vendored
Normal file
110
vendor/github.com/armon/go-socks5/socks5_test.go
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSOCKS5_Connect(t *testing.T) {
|
||||||
|
// Create a local listener
|
||||||
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
conn, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
buf := make([]byte, 4)
|
||||||
|
if _, err := io.ReadAtLeast(conn, buf, 4); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(buf, []byte("ping")) {
|
||||||
|
t.Fatalf("bad: %v", buf)
|
||||||
|
}
|
||||||
|
conn.Write([]byte("pong"))
|
||||||
|
}()
|
||||||
|
lAddr := l.Addr().(*net.TCPAddr)
|
||||||
|
|
||||||
|
// Create a socks server
|
||||||
|
creds := StaticCredentials{
|
||||||
|
"foo": "bar",
|
||||||
|
}
|
||||||
|
cator := UserPassAuthenticator{Credentials: creds}
|
||||||
|
conf := &Config{
|
||||||
|
AuthMethods: []Authenticator{cator},
|
||||||
|
Logger: log.New(os.Stdout, "", log.LstdFlags),
|
||||||
|
}
|
||||||
|
serv, err := New(conf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start listening
|
||||||
|
go func() {
|
||||||
|
if err := serv.ListenAndServe("tcp", "127.0.0.1:12365"); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
|
||||||
|
// Get a local conn
|
||||||
|
conn, err := net.Dial("tcp", "127.0.0.1:12365")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect, auth and connec to local
|
||||||
|
req := bytes.NewBuffer(nil)
|
||||||
|
req.Write([]byte{5})
|
||||||
|
req.Write([]byte{2, NoAuth, UserPassAuth})
|
||||||
|
req.Write([]byte{1, 3, 'f', 'o', 'o', 3, 'b', 'a', 'r'})
|
||||||
|
req.Write([]byte{5, 1, 0, 1, 127, 0, 0, 1})
|
||||||
|
|
||||||
|
port := []byte{0, 0}
|
||||||
|
binary.BigEndian.PutUint16(port, uint16(lAddr.Port))
|
||||||
|
req.Write(port)
|
||||||
|
|
||||||
|
// Send a ping
|
||||||
|
req.Write([]byte("ping"))
|
||||||
|
|
||||||
|
// Send all the bytes
|
||||||
|
conn.Write(req.Bytes())
|
||||||
|
|
||||||
|
// Verify response
|
||||||
|
expected := []byte{
|
||||||
|
socks5Version, UserPassAuth,
|
||||||
|
1, authSuccess,
|
||||||
|
5,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
127, 0, 0, 1,
|
||||||
|
0, 0,
|
||||||
|
'p', 'o', 'n', 'g',
|
||||||
|
}
|
||||||
|
out := make([]byte, len(expected))
|
||||||
|
|
||||||
|
conn.SetDeadline(time.Now().Add(time.Second))
|
||||||
|
if _, err := io.ReadAtLeast(conn, out, len(out)); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore the port
|
||||||
|
out[12] = 0
|
||||||
|
out[13] = 0
|
||||||
|
|
||||||
|
if !bytes.Equal(out, expected) {
|
||||||
|
t.Fatalf("bad: %v", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
22
vendor/github.com/davecgh/go-spew/.gitignore
generated
vendored
Normal file
22
vendor/github.com/davecgh/go-spew/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
14
vendor/github.com/davecgh/go-spew/.travis.yml
generated
vendored
Normal file
14
vendor/github.com/davecgh/go-spew/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.5.4
|
||||||
|
- 1.6.3
|
||||||
|
- 1.7
|
||||||
|
install:
|
||||||
|
- go get -v golang.org/x/tools/cmd/cover
|
||||||
|
script:
|
||||||
|
- go test -v -tags=safe ./spew
|
||||||
|
- go test -v -tags=testcgo ./spew -covermode=count -coverprofile=profile.cov
|
||||||
|
after_success:
|
||||||
|
- go get -v github.com/mattn/goveralls
|
||||||
|
- export PATH=$PATH:$HOME/gopath/bin
|
||||||
|
- goveralls -coverprofile=profile.cov -service=travis-ci
|
||||||
205
vendor/github.com/davecgh/go-spew/README.md
generated
vendored
Normal file
205
vendor/github.com/davecgh/go-spew/README.md
generated
vendored
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
go-spew
|
||||||
|
=======
|
||||||
|
|
||||||
|
[]
|
||||||
|
(https://travis-ci.org/davecgh/go-spew) [![ISC License]
|
||||||
|
(http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) [![Coverage Status]
|
||||||
|
(https://img.shields.io/coveralls/davecgh/go-spew.svg)]
|
||||||
|
(https://coveralls.io/r/davecgh/go-spew?branch=master)
|
||||||
|
|
||||||
|
|
||||||
|
Go-spew implements a deep pretty printer for Go data structures to aid in
|
||||||
|
debugging. A comprehensive suite of tests with 100% test coverage is provided
|
||||||
|
to ensure proper functionality. See `test_coverage.txt` for the gocov coverage
|
||||||
|
report. Go-spew is licensed under the liberal ISC license, so it may be used in
|
||||||
|
open source or commercial projects.
|
||||||
|
|
||||||
|
If you're interested in reading about how this package came to life and some
|
||||||
|
of the challenges involved in providing a deep pretty printer, there is a blog
|
||||||
|
post about it
|
||||||
|
[here](https://web.archive.org/web/20160304013555/https://blog.cyphertite.com/go-spew-a-journey-into-dumping-go-data-structures/).
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
[]
|
||||||
|
(http://godoc.org/github.com/davecgh/go-spew/spew)
|
||||||
|
|
||||||
|
Full `go doc` style documentation for the project can be viewed online without
|
||||||
|
installing this package by using the excellent GoDoc site here:
|
||||||
|
http://godoc.org/github.com/davecgh/go-spew/spew
|
||||||
|
|
||||||
|
You can also view the documentation locally once the package is installed with
|
||||||
|
the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to
|
||||||
|
http://localhost:6060/pkg/github.com/davecgh/go-spew/spew
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ go get -u github.com/davecgh/go-spew/spew
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
Add this import line to the file you're working in:
|
||||||
|
|
||||||
|
```Go
|
||||||
|
import "github.com/davecgh/go-spew/spew"
|
||||||
|
```
|
||||||
|
|
||||||
|
To dump a variable with full newlines, indentation, type, and pointer
|
||||||
|
information use Dump, Fdump, or Sdump:
|
||||||
|
|
||||||
|
```Go
|
||||||
|
spew.Dump(myVar1, myVar2, ...)
|
||||||
|
spew.Fdump(someWriter, myVar1, myVar2, ...)
|
||||||
|
str := spew.Sdump(myVar1, myVar2, ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, if you would prefer to use format strings with a compacted inline
|
||||||
|
printing style, use the convenience wrappers Printf, Fprintf, etc with %v (most
|
||||||
|
compact), %+v (adds pointer addresses), %#v (adds types), or %#+v (adds types
|
||||||
|
and pointer addresses):
|
||||||
|
|
||||||
|
```Go
|
||||||
|
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugging a Web Application Example
|
||||||
|
|
||||||
|
Here is an example of how you can use `spew.Sdump()` to help debug a web application. Please be sure to wrap your output using the `html.EscapeString()` function for safety reasons. You should also only use this debugging technique in a development environment, never in production.
|
||||||
|
|
||||||
|
```Go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "text/html")
|
||||||
|
fmt.Fprintf(w, "Hi there, %s!", r.URL.Path[1:])
|
||||||
|
fmt.Fprintf(w, "<!--\n" + html.EscapeString(spew.Sdump(w)) + "\n-->")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", handler)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sample Dump Output
|
||||||
|
|
||||||
|
```
|
||||||
|
(main.Foo) {
|
||||||
|
unexportedField: (*main.Bar)(0xf84002e210)({
|
||||||
|
flag: (main.Flag) flagTwo,
|
||||||
|
data: (uintptr) <nil>
|
||||||
|
}),
|
||||||
|
ExportedField: (map[interface {}]interface {}) {
|
||||||
|
(string) "one": (bool) true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
([]uint8) {
|
||||||
|
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||||
|
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||||
|
00000020 31 32 |12|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sample Formatter Output
|
||||||
|
|
||||||
|
Double pointer to a uint8:
|
||||||
|
```
|
||||||
|
%v: <**>5
|
||||||
|
%+v: <**>(0xf8400420d0->0xf8400420c8)5
|
||||||
|
%#v: (**uint8)5
|
||||||
|
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
|
||||||
|
```
|
||||||
|
|
||||||
|
Pointer to circular struct with a uint8 field and a pointer to itself:
|
||||||
|
```
|
||||||
|
%v: <*>{1 <*><shown>}
|
||||||
|
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
|
||||||
|
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
|
||||||
|
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Options
|
||||||
|
|
||||||
|
Configuration of spew is handled by fields in the ConfigState type. For
|
||||||
|
convenience, all of the top-level functions use a global state available via the
|
||||||
|
spew.Config global.
|
||||||
|
|
||||||
|
It is also possible to create a ConfigState instance that provides methods
|
||||||
|
equivalent to the top-level functions. This allows concurrent configuration
|
||||||
|
options. See the ConfigState documentation for more details.
|
||||||
|
|
||||||
|
```
|
||||||
|
* Indent
|
||||||
|
String to use for each indentation level for Dump functions.
|
||||||
|
It is a single space by default. A popular alternative is "\t".
|
||||||
|
|
||||||
|
* MaxDepth
|
||||||
|
Maximum number of levels to descend into nested data structures.
|
||||||
|
There is no limit by default.
|
||||||
|
|
||||||
|
* DisableMethods
|
||||||
|
Disables invocation of error and Stringer interface methods.
|
||||||
|
Method invocation is enabled by default.
|
||||||
|
|
||||||
|
* DisablePointerMethods
|
||||||
|
Disables invocation of error and Stringer interface methods on types
|
||||||
|
which only accept pointer receivers from non-pointer variables. This option
|
||||||
|
relies on access to the unsafe package, so it will not have any effect when
|
||||||
|
running in environments without access to the unsafe package such as Google
|
||||||
|
App Engine or with the "safe" build tag specified.
|
||||||
|
Pointer method invocation is enabled by default.
|
||||||
|
|
||||||
|
* DisablePointerAddresses
|
||||||
|
DisablePointerAddresses specifies whether to disable the printing of
|
||||||
|
pointer addresses. This is useful when diffing data structures in tests.
|
||||||
|
|
||||||
|
* DisableCapacities
|
||||||
|
DisableCapacities specifies whether to disable the printing of capacities
|
||||||
|
for arrays, slices, maps and channels. This is useful when diffing data
|
||||||
|
structures in tests.
|
||||||
|
|
||||||
|
* ContinueOnMethod
|
||||||
|
Enables recursion into types after invoking error and Stringer interface
|
||||||
|
methods. Recursion after method invocation is disabled by default.
|
||||||
|
|
||||||
|
* SortKeys
|
||||||
|
Specifies map keys should be sorted before being printed. Use
|
||||||
|
this to have a more deterministic, diffable output. Note that
|
||||||
|
only native types (bool, int, uint, floats, uintptr and string)
|
||||||
|
and types which implement error or Stringer interfaces are supported,
|
||||||
|
with other types sorted according to the reflect.Value.String() output
|
||||||
|
which guarantees display stability. Natural map order is used by
|
||||||
|
default.
|
||||||
|
|
||||||
|
* SpewKeys
|
||||||
|
SpewKeys specifies that, as a last resort attempt, map keys should be
|
||||||
|
spewed to strings and sorted by those strings. This is only considered
|
||||||
|
if SortKeys is true.
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Unsafe Package Dependency
|
||||||
|
|
||||||
|
This package relies on the unsafe package to perform some of the more advanced
|
||||||
|
features, however it also supports a "limited" mode which allows it to work in
|
||||||
|
environments where the unsafe package is not available. By default, it will
|
||||||
|
operate in this mode on Google App Engine and when compiled with GopherJS. The
|
||||||
|
"safe" build tag may also be specified to force the package to build without
|
||||||
|
using the unsafe package.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Go-spew is licensed under the [copyfree](http://copyfree.org) ISC License.
|
||||||
22
vendor/github.com/davecgh/go-spew/cov_report.sh
generated
vendored
Normal file
22
vendor/github.com/davecgh/go-spew/cov_report.sh
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# This script uses gocov to generate a test coverage report.
|
||||||
|
# The gocov tool my be obtained with the following command:
|
||||||
|
# go get github.com/axw/gocov/gocov
|
||||||
|
#
|
||||||
|
# It will be installed to $GOPATH/bin, so ensure that location is in your $PATH.
|
||||||
|
|
||||||
|
# Check for gocov.
|
||||||
|
if ! type gocov >/dev/null 2>&1; then
|
||||||
|
echo >&2 "This script requires the gocov tool."
|
||||||
|
echo >&2 "You may obtain it with the following command:"
|
||||||
|
echo >&2 "go get github.com/axw/gocov/gocov"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Only run the cgo tests if gcc is installed.
|
||||||
|
if type gcc >/dev/null 2>&1; then
|
||||||
|
(cd spew && gocov test -tags testcgo | gocov report)
|
||||||
|
else
|
||||||
|
(cd spew && gocov test | gocov report)
|
||||||
|
fi
|
||||||
298
vendor/github.com/davecgh/go-spew/spew/common_test.go
generated
vendored
Normal file
298
vendor/github.com/davecgh/go-spew/spew/common_test.go
generated
vendored
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// custom type to test Stinger interface on non-pointer receiver.
|
||||||
|
type stringer string
|
||||||
|
|
||||||
|
// String implements the Stringer interface for testing invocation of custom
|
||||||
|
// stringers on types with non-pointer receivers.
|
||||||
|
func (s stringer) String() string {
|
||||||
|
return "stringer " + string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// custom type to test Stinger interface on pointer receiver.
|
||||||
|
type pstringer string
|
||||||
|
|
||||||
|
// String implements the Stringer interface for testing invocation of custom
|
||||||
|
// stringers on types with only pointer receivers.
|
||||||
|
func (s *pstringer) String() string {
|
||||||
|
return "stringer " + string(*s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// xref1 and xref2 are cross referencing structs for testing circular reference
|
||||||
|
// detection.
|
||||||
|
type xref1 struct {
|
||||||
|
ps2 *xref2
|
||||||
|
}
|
||||||
|
type xref2 struct {
|
||||||
|
ps1 *xref1
|
||||||
|
}
|
||||||
|
|
||||||
|
// indirCir1, indirCir2, and indirCir3 are used to generate an indirect circular
|
||||||
|
// reference for testing detection.
|
||||||
|
type indirCir1 struct {
|
||||||
|
ps2 *indirCir2
|
||||||
|
}
|
||||||
|
type indirCir2 struct {
|
||||||
|
ps3 *indirCir3
|
||||||
|
}
|
||||||
|
type indirCir3 struct {
|
||||||
|
ps1 *indirCir1
|
||||||
|
}
|
||||||
|
|
||||||
|
// embed is used to test embedded structures.
|
||||||
|
type embed struct {
|
||||||
|
a string
|
||||||
|
}
|
||||||
|
|
||||||
|
// embedwrap is used to test embedded structures.
|
||||||
|
type embedwrap struct {
|
||||||
|
*embed
|
||||||
|
e *embed
|
||||||
|
}
|
||||||
|
|
||||||
|
// panicer is used to intentionally cause a panic for testing spew properly
|
||||||
|
// handles them
|
||||||
|
type panicer int
|
||||||
|
|
||||||
|
func (p panicer) String() string {
|
||||||
|
panic("test panic")
|
||||||
|
}
|
||||||
|
|
||||||
|
// customError is used to test custom error interface invocation.
|
||||||
|
type customError int
|
||||||
|
|
||||||
|
func (e customError) Error() string {
|
||||||
|
return fmt.Sprintf("error: %d", int(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringizeWants converts a slice of wanted test output into a format suitable
|
||||||
|
// for a test error message.
|
||||||
|
func stringizeWants(wants []string) string {
|
||||||
|
s := ""
|
||||||
|
for i, want := range wants {
|
||||||
|
if i > 0 {
|
||||||
|
s += fmt.Sprintf("want%d: %s", i+1, want)
|
||||||
|
} else {
|
||||||
|
s += "want: " + want
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// testFailed returns whether or not a test failed by checking if the result
|
||||||
|
// of the test is in the slice of wanted strings.
|
||||||
|
func testFailed(result string, wants []string) bool {
|
||||||
|
for _, want := range wants {
|
||||||
|
if result == want {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortableStruct struct {
|
||||||
|
x int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss sortableStruct) String() string {
|
||||||
|
return fmt.Sprintf("ss.%d", ss.x)
|
||||||
|
}
|
||||||
|
|
||||||
|
type unsortableStruct struct {
|
||||||
|
x int
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortTestCase struct {
|
||||||
|
input []reflect.Value
|
||||||
|
expected []reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func helpTestSortValues(tests []sortTestCase, cs *spew.ConfigState, t *testing.T) {
|
||||||
|
getInterfaces := func(values []reflect.Value) []interface{} {
|
||||||
|
interfaces := []interface{}{}
|
||||||
|
for _, v := range values {
|
||||||
|
interfaces = append(interfaces, v.Interface())
|
||||||
|
}
|
||||||
|
return interfaces
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
spew.SortValues(test.input, cs)
|
||||||
|
// reflect.DeepEqual cannot really make sense of reflect.Value,
|
||||||
|
// probably because of all the pointer tricks. For instance,
|
||||||
|
// v(2.0) != v(2.0) on a 32-bits system. Turn them into interface{}
|
||||||
|
// instead.
|
||||||
|
input := getInterfaces(test.input)
|
||||||
|
expected := getInterfaces(test.expected)
|
||||||
|
if !reflect.DeepEqual(input, expected) {
|
||||||
|
t.Errorf("Sort mismatch:\n %v != %v", input, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSortValues ensures the sort functionality for relect.Value based sorting
|
||||||
|
// works as intended.
|
||||||
|
func TestSortValues(t *testing.T) {
|
||||||
|
v := reflect.ValueOf
|
||||||
|
|
||||||
|
a := v("a")
|
||||||
|
b := v("b")
|
||||||
|
c := v("c")
|
||||||
|
embedA := v(embed{"a"})
|
||||||
|
embedB := v(embed{"b"})
|
||||||
|
embedC := v(embed{"c"})
|
||||||
|
tests := []sortTestCase{
|
||||||
|
// No values.
|
||||||
|
{
|
||||||
|
[]reflect.Value{},
|
||||||
|
[]reflect.Value{},
|
||||||
|
},
|
||||||
|
// Bools.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(false), v(true), v(false)},
|
||||||
|
[]reflect.Value{v(false), v(false), v(true)},
|
||||||
|
},
|
||||||
|
// Ints.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(2), v(1), v(3)},
|
||||||
|
[]reflect.Value{v(1), v(2), v(3)},
|
||||||
|
},
|
||||||
|
// Uints.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(uint8(2)), v(uint8(1)), v(uint8(3))},
|
||||||
|
[]reflect.Value{v(uint8(1)), v(uint8(2)), v(uint8(3))},
|
||||||
|
},
|
||||||
|
// Floats.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(2.0), v(1.0), v(3.0)},
|
||||||
|
[]reflect.Value{v(1.0), v(2.0), v(3.0)},
|
||||||
|
},
|
||||||
|
// Strings.
|
||||||
|
{
|
||||||
|
[]reflect.Value{b, a, c},
|
||||||
|
[]reflect.Value{a, b, c},
|
||||||
|
},
|
||||||
|
// Array
|
||||||
|
{
|
||||||
|
[]reflect.Value{v([3]int{3, 2, 1}), v([3]int{1, 3, 2}), v([3]int{1, 2, 3})},
|
||||||
|
[]reflect.Value{v([3]int{1, 2, 3}), v([3]int{1, 3, 2}), v([3]int{3, 2, 1})},
|
||||||
|
},
|
||||||
|
// Uintptrs.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(uintptr(2)), v(uintptr(1)), v(uintptr(3))},
|
||||||
|
[]reflect.Value{v(uintptr(1)), v(uintptr(2)), v(uintptr(3))},
|
||||||
|
},
|
||||||
|
// SortableStructs.
|
||||||
|
{
|
||||||
|
// Note: not sorted - DisableMethods is set.
|
||||||
|
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
||||||
|
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
||||||
|
},
|
||||||
|
// UnsortableStructs.
|
||||||
|
{
|
||||||
|
// Note: not sorted - SpewKeys is false.
|
||||||
|
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||||
|
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||||
|
},
|
||||||
|
// Invalid.
|
||||||
|
{
|
||||||
|
[]reflect.Value{embedB, embedA, embedC},
|
||||||
|
[]reflect.Value{embedB, embedA, embedC},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cs := spew.ConfigState{DisableMethods: true, SpewKeys: false}
|
||||||
|
helpTestSortValues(tests, &cs, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSortValuesWithMethods ensures the sort functionality for relect.Value
|
||||||
|
// based sorting works as intended when using string methods.
|
||||||
|
func TestSortValuesWithMethods(t *testing.T) {
|
||||||
|
v := reflect.ValueOf
|
||||||
|
|
||||||
|
a := v("a")
|
||||||
|
b := v("b")
|
||||||
|
c := v("c")
|
||||||
|
tests := []sortTestCase{
|
||||||
|
// Ints.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(2), v(1), v(3)},
|
||||||
|
[]reflect.Value{v(1), v(2), v(3)},
|
||||||
|
},
|
||||||
|
// Strings.
|
||||||
|
{
|
||||||
|
[]reflect.Value{b, a, c},
|
||||||
|
[]reflect.Value{a, b, c},
|
||||||
|
},
|
||||||
|
// SortableStructs.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
||||||
|
[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})},
|
||||||
|
},
|
||||||
|
// UnsortableStructs.
|
||||||
|
{
|
||||||
|
// Note: not sorted - SpewKeys is false.
|
||||||
|
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||||
|
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cs := spew.ConfigState{DisableMethods: false, SpewKeys: false}
|
||||||
|
helpTestSortValues(tests, &cs, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSortValuesWithSpew ensures the sort functionality for relect.Value
|
||||||
|
// based sorting works as intended when using spew to stringify keys.
|
||||||
|
func TestSortValuesWithSpew(t *testing.T) {
|
||||||
|
v := reflect.ValueOf
|
||||||
|
|
||||||
|
a := v("a")
|
||||||
|
b := v("b")
|
||||||
|
c := v("c")
|
||||||
|
tests := []sortTestCase{
|
||||||
|
// Ints.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(2), v(1), v(3)},
|
||||||
|
[]reflect.Value{v(1), v(2), v(3)},
|
||||||
|
},
|
||||||
|
// Strings.
|
||||||
|
{
|
||||||
|
[]reflect.Value{b, a, c},
|
||||||
|
[]reflect.Value{a, b, c},
|
||||||
|
},
|
||||||
|
// SortableStructs.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
||||||
|
[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})},
|
||||||
|
},
|
||||||
|
// UnsortableStructs.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||||
|
[]reflect.Value{v(unsortableStruct{1}), v(unsortableStruct{2}), v(unsortableStruct{3})},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cs := spew.ConfigState{DisableMethods: true, SpewKeys: true}
|
||||||
|
helpTestSortValues(tests, &cs, t)
|
||||||
|
}
|
||||||
1042
vendor/github.com/davecgh/go-spew/spew/dump_test.go
generated
vendored
Normal file
1042
vendor/github.com/davecgh/go-spew/spew/dump_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
99
vendor/github.com/davecgh/go-spew/spew/dumpcgo_test.go
generated
vendored
Normal file
99
vendor/github.com/davecgh/go-spew/spew/dumpcgo_test.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
// Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when both cgo is supported and "-tags testcgo" is added to the go test
|
||||||
|
// command line. This means the cgo tests are only added (and hence run) when
|
||||||
|
// specifially requested. This configuration is used because spew itself
|
||||||
|
// does not require cgo to run even though it does handle certain cgo types
|
||||||
|
// specially. Rather than forcing all clients to require cgo and an external
|
||||||
|
// C compiler just to run the tests, this scheme makes them optional.
|
||||||
|
// +build cgo,testcgo
|
||||||
|
|
||||||
|
package spew_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew/testdata"
|
||||||
|
)
|
||||||
|
|
||||||
|
func addCgoDumpTests() {
|
||||||
|
// C char pointer.
|
||||||
|
v := testdata.GetCgoCharPointer()
|
||||||
|
nv := testdata.GetCgoNullCharPointer()
|
||||||
|
pv := &v
|
||||||
|
vcAddr := fmt.Sprintf("%p", v)
|
||||||
|
vAddr := fmt.Sprintf("%p", pv)
|
||||||
|
pvAddr := fmt.Sprintf("%p", &pv)
|
||||||
|
vt := "*testdata._Ctype_char"
|
||||||
|
vs := "116"
|
||||||
|
addDumpTest(v, "("+vt+")("+vcAddr+")("+vs+")\n")
|
||||||
|
addDumpTest(pv, "(*"+vt+")("+vAddr+"->"+vcAddr+")("+vs+")\n")
|
||||||
|
addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+"->"+vcAddr+")("+vs+")\n")
|
||||||
|
addDumpTest(nv, "("+vt+")(<nil>)\n")
|
||||||
|
|
||||||
|
// C char array.
|
||||||
|
v2, v2l, v2c := testdata.GetCgoCharArray()
|
||||||
|
v2Len := fmt.Sprintf("%d", v2l)
|
||||||
|
v2Cap := fmt.Sprintf("%d", v2c)
|
||||||
|
v2t := "[6]testdata._Ctype_char"
|
||||||
|
v2s := "(len=" + v2Len + " cap=" + v2Cap + ") " +
|
||||||
|
"{\n 00000000 74 65 73 74 32 00 " +
|
||||||
|
" |test2.|\n}"
|
||||||
|
addDumpTest(v2, "("+v2t+") "+v2s+"\n")
|
||||||
|
|
||||||
|
// C unsigned char array.
|
||||||
|
v3, v3l, v3c := testdata.GetCgoUnsignedCharArray()
|
||||||
|
v3Len := fmt.Sprintf("%d", v3l)
|
||||||
|
v3Cap := fmt.Sprintf("%d", v3c)
|
||||||
|
v3t := "[6]testdata._Ctype_unsignedchar"
|
||||||
|
v3t2 := "[6]testdata._Ctype_uchar"
|
||||||
|
v3s := "(len=" + v3Len + " cap=" + v3Cap + ") " +
|
||||||
|
"{\n 00000000 74 65 73 74 33 00 " +
|
||||||
|
" |test3.|\n}"
|
||||||
|
addDumpTest(v3, "("+v3t+") "+v3s+"\n", "("+v3t2+") "+v3s+"\n")
|
||||||
|
|
||||||
|
// C signed char array.
|
||||||
|
v4, v4l, v4c := testdata.GetCgoSignedCharArray()
|
||||||
|
v4Len := fmt.Sprintf("%d", v4l)
|
||||||
|
v4Cap := fmt.Sprintf("%d", v4c)
|
||||||
|
v4t := "[6]testdata._Ctype_schar"
|
||||||
|
v4t2 := "testdata._Ctype_schar"
|
||||||
|
v4s := "(len=" + v4Len + " cap=" + v4Cap + ") " +
|
||||||
|
"{\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 101,\n (" + v4t2 +
|
||||||
|
") 115,\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 52,\n (" + v4t2 +
|
||||||
|
") 0\n}"
|
||||||
|
addDumpTest(v4, "("+v4t+") "+v4s+"\n")
|
||||||
|
|
||||||
|
// C uint8_t array.
|
||||||
|
v5, v5l, v5c := testdata.GetCgoUint8tArray()
|
||||||
|
v5Len := fmt.Sprintf("%d", v5l)
|
||||||
|
v5Cap := fmt.Sprintf("%d", v5c)
|
||||||
|
v5t := "[6]testdata._Ctype_uint8_t"
|
||||||
|
v5s := "(len=" + v5Len + " cap=" + v5Cap + ") " +
|
||||||
|
"{\n 00000000 74 65 73 74 35 00 " +
|
||||||
|
" |test5.|\n}"
|
||||||
|
addDumpTest(v5, "("+v5t+") "+v5s+"\n")
|
||||||
|
|
||||||
|
// C typedefed unsigned char array.
|
||||||
|
v6, v6l, v6c := testdata.GetCgoTypdefedUnsignedCharArray()
|
||||||
|
v6Len := fmt.Sprintf("%d", v6l)
|
||||||
|
v6Cap := fmt.Sprintf("%d", v6c)
|
||||||
|
v6t := "[6]testdata._Ctype_custom_uchar_t"
|
||||||
|
v6s := "(len=" + v6Len + " cap=" + v6Cap + ") " +
|
||||||
|
"{\n 00000000 74 65 73 74 36 00 " +
|
||||||
|
" |test6.|\n}"
|
||||||
|
addDumpTest(v6, "("+v6t+") "+v6s+"\n")
|
||||||
|
}
|
||||||
26
vendor/github.com/davecgh/go-spew/spew/dumpnocgo_test.go
generated
vendored
Normal file
26
vendor/github.com/davecgh/go-spew/spew/dumpnocgo_test.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) 2013 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when either cgo is not supported or "-tags testcgo" is not added to the go
|
||||||
|
// test command line. This file intentionally does not setup any cgo tests in
|
||||||
|
// this scenario.
|
||||||
|
// +build !cgo !testcgo
|
||||||
|
|
||||||
|
package spew_test
|
||||||
|
|
||||||
|
func addCgoDumpTests() {
|
||||||
|
// Don't add any tests for cgo since this file is only compiled when
|
||||||
|
// there should not be any cgo tests.
|
||||||
|
}
|
||||||
226
vendor/github.com/davecgh/go-spew/spew/example_test.go
generated
vendored
Normal file
226
vendor/github.com/davecgh/go-spew/spew/example_test.go
generated
vendored
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Flag int
|
||||||
|
|
||||||
|
const (
|
||||||
|
flagOne Flag = iota
|
||||||
|
flagTwo
|
||||||
|
)
|
||||||
|
|
||||||
|
var flagStrings = map[Flag]string{
|
||||||
|
flagOne: "flagOne",
|
||||||
|
flagTwo: "flagTwo",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Flag) String() string {
|
||||||
|
if s, ok := flagStrings[f]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Unknown flag (%d)", int(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bar struct {
|
||||||
|
data uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type Foo struct {
|
||||||
|
unexportedField Bar
|
||||||
|
ExportedField map[interface{}]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example demonstrates how to use Dump to dump variables to stdout.
|
||||||
|
func ExampleDump() {
|
||||||
|
// The following package level declarations are assumed for this example:
|
||||||
|
/*
|
||||||
|
type Flag int
|
||||||
|
|
||||||
|
const (
|
||||||
|
flagOne Flag = iota
|
||||||
|
flagTwo
|
||||||
|
)
|
||||||
|
|
||||||
|
var flagStrings = map[Flag]string{
|
||||||
|
flagOne: "flagOne",
|
||||||
|
flagTwo: "flagTwo",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Flag) String() string {
|
||||||
|
if s, ok := flagStrings[f]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Unknown flag (%d)", int(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bar struct {
|
||||||
|
data uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type Foo struct {
|
||||||
|
unexportedField Bar
|
||||||
|
ExportedField map[interface{}]interface{}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Setup some sample data structures for the example.
|
||||||
|
bar := Bar{uintptr(0)}
|
||||||
|
s1 := Foo{bar, map[interface{}]interface{}{"one": true}}
|
||||||
|
f := Flag(5)
|
||||||
|
b := []byte{
|
||||||
|
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
|
||||||
|
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
|
||||||
|
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
|
||||||
|
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
|
||||||
|
0x31, 0x32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dump!
|
||||||
|
spew.Dump(s1, f, b)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// (spew_test.Foo) {
|
||||||
|
// unexportedField: (spew_test.Bar) {
|
||||||
|
// data: (uintptr) <nil>
|
||||||
|
// },
|
||||||
|
// ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||||
|
// (string) (len=3) "one": (bool) true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// (spew_test.Flag) Unknown flag (5)
|
||||||
|
// ([]uint8) (len=34 cap=34) {
|
||||||
|
// 00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||||
|
// 00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||||
|
// 00000020 31 32 |12|
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example demonstrates how to use Printf to display a variable with a
|
||||||
|
// format string and inline formatting.
|
||||||
|
func ExamplePrintf() {
|
||||||
|
// Create a double pointer to a uint 8.
|
||||||
|
ui8 := uint8(5)
|
||||||
|
pui8 := &ui8
|
||||||
|
ppui8 := &pui8
|
||||||
|
|
||||||
|
// Create a circular data type.
|
||||||
|
type circular struct {
|
||||||
|
ui8 uint8
|
||||||
|
c *circular
|
||||||
|
}
|
||||||
|
c := circular{ui8: 1}
|
||||||
|
c.c = &c
|
||||||
|
|
||||||
|
// Print!
|
||||||
|
spew.Printf("ppui8: %v\n", ppui8)
|
||||||
|
spew.Printf("circular: %v\n", c)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// ppui8: <**>5
|
||||||
|
// circular: {1 <*>{1 <*><shown>}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example demonstrates how to use a ConfigState.
|
||||||
|
func ExampleConfigState() {
|
||||||
|
// Modify the indent level of the ConfigState only. The global
|
||||||
|
// configuration is not modified.
|
||||||
|
scs := spew.ConfigState{Indent: "\t"}
|
||||||
|
|
||||||
|
// Output using the ConfigState instance.
|
||||||
|
v := map[string]int{"one": 1}
|
||||||
|
scs.Printf("v: %v\n", v)
|
||||||
|
scs.Dump(v)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// v: map[one:1]
|
||||||
|
// (map[string]int) (len=1) {
|
||||||
|
// (string) (len=3) "one": (int) 1
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example demonstrates how to use ConfigState.Dump to dump variables to
|
||||||
|
// stdout
|
||||||
|
func ExampleConfigState_Dump() {
|
||||||
|
// See the top-level Dump example for details on the types used in this
|
||||||
|
// example.
|
||||||
|
|
||||||
|
// Create two ConfigState instances with different indentation.
|
||||||
|
scs := spew.ConfigState{Indent: "\t"}
|
||||||
|
scs2 := spew.ConfigState{Indent: " "}
|
||||||
|
|
||||||
|
// Setup some sample data structures for the example.
|
||||||
|
bar := Bar{uintptr(0)}
|
||||||
|
s1 := Foo{bar, map[interface{}]interface{}{"one": true}}
|
||||||
|
|
||||||
|
// Dump using the ConfigState instances.
|
||||||
|
scs.Dump(s1)
|
||||||
|
scs2.Dump(s1)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// (spew_test.Foo) {
|
||||||
|
// unexportedField: (spew_test.Bar) {
|
||||||
|
// data: (uintptr) <nil>
|
||||||
|
// },
|
||||||
|
// ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||||
|
// (string) (len=3) "one": (bool) true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// (spew_test.Foo) {
|
||||||
|
// unexportedField: (spew_test.Bar) {
|
||||||
|
// data: (uintptr) <nil>
|
||||||
|
// },
|
||||||
|
// ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||||
|
// (string) (len=3) "one": (bool) true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example demonstrates how to use ConfigState.Printf to display a variable
|
||||||
|
// with a format string and inline formatting.
|
||||||
|
func ExampleConfigState_Printf() {
|
||||||
|
// See the top-level Dump example for details on the types used in this
|
||||||
|
// example.
|
||||||
|
|
||||||
|
// Create two ConfigState instances and modify the method handling of the
|
||||||
|
// first ConfigState only.
|
||||||
|
scs := spew.NewDefaultConfig()
|
||||||
|
scs2 := spew.NewDefaultConfig()
|
||||||
|
scs.DisableMethods = true
|
||||||
|
|
||||||
|
// Alternatively
|
||||||
|
// scs := spew.ConfigState{Indent: " ", DisableMethods: true}
|
||||||
|
// scs2 := spew.ConfigState{Indent: " "}
|
||||||
|
|
||||||
|
// This is of type Flag which implements a Stringer and has raw value 1.
|
||||||
|
f := flagTwo
|
||||||
|
|
||||||
|
// Dump using the ConfigState instances.
|
||||||
|
scs.Printf("f: %v\n", f)
|
||||||
|
scs2.Printf("f: %v\n", f)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// f: 1
|
||||||
|
// f: flagTwo
|
||||||
|
}
|
||||||
1558
vendor/github.com/davecgh/go-spew/spew/format_test.go
generated
vendored
Normal file
1558
vendor/github.com/davecgh/go-spew/spew/format_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
87
vendor/github.com/davecgh/go-spew/spew/internal_test.go
generated
vendored
Normal file
87
vendor/github.com/davecgh/go-spew/spew/internal_test.go
generated
vendored
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test file is part of the spew package rather than than the spew_test
|
||||||
|
package because it needs access to internals to properly test certain cases
|
||||||
|
which are not possible via the public interface since they should never happen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// dummyFmtState implements a fake fmt.State to use for testing invalid
|
||||||
|
// reflect.Value handling. This is necessary because the fmt package catches
|
||||||
|
// invalid values before invoking the formatter on them.
|
||||||
|
type dummyFmtState struct {
|
||||||
|
bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dfs *dummyFmtState) Flag(f int) bool {
|
||||||
|
if f == int('+') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dfs *dummyFmtState) Precision() (int, bool) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dfs *dummyFmtState) Width() (int, bool) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestInvalidReflectValue ensures the dump and formatter code handles an
|
||||||
|
// invalid reflect value properly. This needs access to internal state since it
|
||||||
|
// should never happen in real code and therefore can't be tested via the public
|
||||||
|
// API.
|
||||||
|
func TestInvalidReflectValue(t *testing.T) {
|
||||||
|
i := 1
|
||||||
|
|
||||||
|
// Dump invalid reflect value.
|
||||||
|
v := new(reflect.Value)
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
d := dumpState{w: buf, cs: &Config}
|
||||||
|
d.dump(*v)
|
||||||
|
s := buf.String()
|
||||||
|
want := "<invalid>"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("InvalidReflectValue #%d\n got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
|
// Formatter invalid reflect value.
|
||||||
|
buf2 := new(dummyFmtState)
|
||||||
|
f := formatState{value: *v, cs: &Config, fs: buf2}
|
||||||
|
f.format(*v)
|
||||||
|
s = buf2.String()
|
||||||
|
want = "<invalid>"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("InvalidReflectValue #%d got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SortValues makes the internal sortValues function available to the test
|
||||||
|
// package.
|
||||||
|
func SortValues(values []reflect.Value, cs *ConfigState) {
|
||||||
|
sortValues(values, cs)
|
||||||
|
}
|
||||||
102
vendor/github.com/davecgh/go-spew/spew/internalunsafe_test.go
generated
vendored
Normal file
102
vendor/github.com/davecgh/go-spew/spew/internalunsafe_test.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
// Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
||||||
|
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
||||||
|
// tag is deprecated and thus should not be used.
|
||||||
|
// +build !js,!appengine,!safe,!disableunsafe
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test file is part of the spew package rather than than the spew_test
|
||||||
|
package because it needs access to internals to properly test certain cases
|
||||||
|
which are not possible via the public interface since they should never happen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// changeKind uses unsafe to intentionally change the kind of a reflect.Value to
|
||||||
|
// the maximum kind value which does not exist. This is needed to test the
|
||||||
|
// fallback code which punts to the standard fmt library for new types that
|
||||||
|
// might get added to the language.
|
||||||
|
func changeKind(v *reflect.Value, readOnly bool) {
|
||||||
|
rvf := (*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + offsetFlag))
|
||||||
|
*rvf = *rvf | ((1<<flagKindWidth - 1) << flagKindShift)
|
||||||
|
if readOnly {
|
||||||
|
*rvf |= flagRO
|
||||||
|
} else {
|
||||||
|
*rvf &= ^uintptr(flagRO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAddedReflectValue tests functionaly of the dump and formatter code which
|
||||||
|
// falls back to the standard fmt library for new types that might get added to
|
||||||
|
// the language.
|
||||||
|
func TestAddedReflectValue(t *testing.T) {
|
||||||
|
i := 1
|
||||||
|
|
||||||
|
// Dump using a reflect.Value that is exported.
|
||||||
|
v := reflect.ValueOf(int8(5))
|
||||||
|
changeKind(&v, false)
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
d := dumpState{w: buf, cs: &Config}
|
||||||
|
d.dump(v)
|
||||||
|
s := buf.String()
|
||||||
|
want := "(int8) 5"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
|
// Dump using a reflect.Value that is not exported.
|
||||||
|
changeKind(&v, true)
|
||||||
|
buf.Reset()
|
||||||
|
d.dump(v)
|
||||||
|
s = buf.String()
|
||||||
|
want = "(int8) <int8 Value>"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
|
// Formatter using a reflect.Value that is exported.
|
||||||
|
changeKind(&v, false)
|
||||||
|
buf2 := new(dummyFmtState)
|
||||||
|
f := formatState{value: v, cs: &Config, fs: buf2}
|
||||||
|
f.format(v)
|
||||||
|
s = buf2.String()
|
||||||
|
want = "5"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
|
// Formatter using a reflect.Value that is not exported.
|
||||||
|
changeKind(&v, true)
|
||||||
|
buf2.Reset()
|
||||||
|
f = formatState{value: v, cs: &Config, fs: buf2}
|
||||||
|
f.format(v)
|
||||||
|
s = buf2.String()
|
||||||
|
want = "<int8 Value>"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
320
vendor/github.com/davecgh/go-spew/spew/spew_test.go
generated
vendored
Normal file
320
vendor/github.com/davecgh/go-spew/spew/spew_test.go
generated
vendored
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// spewFunc is used to identify which public function of the spew package or
|
||||||
|
// ConfigState a test applies to.
|
||||||
|
type spewFunc int
|
||||||
|
|
||||||
|
const (
|
||||||
|
fCSFdump spewFunc = iota
|
||||||
|
fCSFprint
|
||||||
|
fCSFprintf
|
||||||
|
fCSFprintln
|
||||||
|
fCSPrint
|
||||||
|
fCSPrintln
|
||||||
|
fCSSdump
|
||||||
|
fCSSprint
|
||||||
|
fCSSprintf
|
||||||
|
fCSSprintln
|
||||||
|
fCSErrorf
|
||||||
|
fCSNewFormatter
|
||||||
|
fErrorf
|
||||||
|
fFprint
|
||||||
|
fFprintln
|
||||||
|
fPrint
|
||||||
|
fPrintln
|
||||||
|
fSdump
|
||||||
|
fSprint
|
||||||
|
fSprintf
|
||||||
|
fSprintln
|
||||||
|
)
|
||||||
|
|
||||||
|
// Map of spewFunc values to names for pretty printing.
|
||||||
|
var spewFuncStrings = map[spewFunc]string{
|
||||||
|
fCSFdump: "ConfigState.Fdump",
|
||||||
|
fCSFprint: "ConfigState.Fprint",
|
||||||
|
fCSFprintf: "ConfigState.Fprintf",
|
||||||
|
fCSFprintln: "ConfigState.Fprintln",
|
||||||
|
fCSSdump: "ConfigState.Sdump",
|
||||||
|
fCSPrint: "ConfigState.Print",
|
||||||
|
fCSPrintln: "ConfigState.Println",
|
||||||
|
fCSSprint: "ConfigState.Sprint",
|
||||||
|
fCSSprintf: "ConfigState.Sprintf",
|
||||||
|
fCSSprintln: "ConfigState.Sprintln",
|
||||||
|
fCSErrorf: "ConfigState.Errorf",
|
||||||
|
fCSNewFormatter: "ConfigState.NewFormatter",
|
||||||
|
fErrorf: "spew.Errorf",
|
||||||
|
fFprint: "spew.Fprint",
|
||||||
|
fFprintln: "spew.Fprintln",
|
||||||
|
fPrint: "spew.Print",
|
||||||
|
fPrintln: "spew.Println",
|
||||||
|
fSdump: "spew.Sdump",
|
||||||
|
fSprint: "spew.Sprint",
|
||||||
|
fSprintf: "spew.Sprintf",
|
||||||
|
fSprintln: "spew.Sprintln",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f spewFunc) String() string {
|
||||||
|
if s, ok := spewFuncStrings[f]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Unknown spewFunc (%d)", int(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// spewTest is used to describe a test to be performed against the public
|
||||||
|
// functions of the spew package or ConfigState.
|
||||||
|
type spewTest struct {
|
||||||
|
cs *spew.ConfigState
|
||||||
|
f spewFunc
|
||||||
|
format string
|
||||||
|
in interface{}
|
||||||
|
want string
|
||||||
|
}
|
||||||
|
|
||||||
|
// spewTests houses the tests to be performed against the public functions of
|
||||||
|
// the spew package and ConfigState.
|
||||||
|
//
|
||||||
|
// These tests are only intended to ensure the public functions are exercised
|
||||||
|
// and are intentionally not exhaustive of types. The exhaustive type
|
||||||
|
// tests are handled in the dump and format tests.
|
||||||
|
var spewTests []spewTest
|
||||||
|
|
||||||
|
// redirStdout is a helper function to return the standard output from f as a
|
||||||
|
// byte slice.
|
||||||
|
func redirStdout(f func()) ([]byte, error) {
|
||||||
|
tempFile, err := ioutil.TempFile("", "ss-test")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fileName := tempFile.Name()
|
||||||
|
defer os.Remove(fileName) // Ignore error
|
||||||
|
|
||||||
|
origStdout := os.Stdout
|
||||||
|
os.Stdout = tempFile
|
||||||
|
f()
|
||||||
|
os.Stdout = origStdout
|
||||||
|
tempFile.Close()
|
||||||
|
|
||||||
|
return ioutil.ReadFile(fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initSpewTests() {
|
||||||
|
// Config states with various settings.
|
||||||
|
scsDefault := spew.NewDefaultConfig()
|
||||||
|
scsNoMethods := &spew.ConfigState{Indent: " ", DisableMethods: true}
|
||||||
|
scsNoPmethods := &spew.ConfigState{Indent: " ", DisablePointerMethods: true}
|
||||||
|
scsMaxDepth := &spew.ConfigState{Indent: " ", MaxDepth: 1}
|
||||||
|
scsContinue := &spew.ConfigState{Indent: " ", ContinueOnMethod: true}
|
||||||
|
scsNoPtrAddr := &spew.ConfigState{DisablePointerAddresses: true}
|
||||||
|
scsNoCap := &spew.ConfigState{DisableCapacities: true}
|
||||||
|
|
||||||
|
// Variables for tests on types which implement Stringer interface with and
|
||||||
|
// without a pointer receiver.
|
||||||
|
ts := stringer("test")
|
||||||
|
tps := pstringer("test")
|
||||||
|
|
||||||
|
type ptrTester struct {
|
||||||
|
s *struct{}
|
||||||
|
}
|
||||||
|
tptr := &ptrTester{s: &struct{}{}}
|
||||||
|
|
||||||
|
// depthTester is used to test max depth handling for structs, array, slices
|
||||||
|
// and maps.
|
||||||
|
type depthTester struct {
|
||||||
|
ic indirCir1
|
||||||
|
arr [1]string
|
||||||
|
slice []string
|
||||||
|
m map[string]int
|
||||||
|
}
|
||||||
|
dt := depthTester{indirCir1{nil}, [1]string{"arr"}, []string{"slice"},
|
||||||
|
map[string]int{"one": 1}}
|
||||||
|
|
||||||
|
// Variable for tests on types which implement error interface.
|
||||||
|
te := customError(10)
|
||||||
|
|
||||||
|
spewTests = []spewTest{
|
||||||
|
{scsDefault, fCSFdump, "", int8(127), "(int8) 127\n"},
|
||||||
|
{scsDefault, fCSFprint, "", int16(32767), "32767"},
|
||||||
|
{scsDefault, fCSFprintf, "%v", int32(2147483647), "2147483647"},
|
||||||
|
{scsDefault, fCSFprintln, "", int(2147483647), "2147483647\n"},
|
||||||
|
{scsDefault, fCSPrint, "", int64(9223372036854775807), "9223372036854775807"},
|
||||||
|
{scsDefault, fCSPrintln, "", uint8(255), "255\n"},
|
||||||
|
{scsDefault, fCSSdump, "", uint8(64), "(uint8) 64\n"},
|
||||||
|
{scsDefault, fCSSprint, "", complex(1, 2), "(1+2i)"},
|
||||||
|
{scsDefault, fCSSprintf, "%v", complex(float32(3), 4), "(3+4i)"},
|
||||||
|
{scsDefault, fCSSprintln, "", complex(float64(5), 6), "(5+6i)\n"},
|
||||||
|
{scsDefault, fCSErrorf, "%#v", uint16(65535), "(uint16)65535"},
|
||||||
|
{scsDefault, fCSNewFormatter, "%v", uint32(4294967295), "4294967295"},
|
||||||
|
{scsDefault, fErrorf, "%v", uint64(18446744073709551615), "18446744073709551615"},
|
||||||
|
{scsDefault, fFprint, "", float32(3.14), "3.14"},
|
||||||
|
{scsDefault, fFprintln, "", float64(6.28), "6.28\n"},
|
||||||
|
{scsDefault, fPrint, "", true, "true"},
|
||||||
|
{scsDefault, fPrintln, "", false, "false\n"},
|
||||||
|
{scsDefault, fSdump, "", complex(-10, -20), "(complex128) (-10-20i)\n"},
|
||||||
|
{scsDefault, fSprint, "", complex(-1, -2), "(-1-2i)"},
|
||||||
|
{scsDefault, fSprintf, "%v", complex(float32(-3), -4), "(-3-4i)"},
|
||||||
|
{scsDefault, fSprintln, "", complex(float64(-5), -6), "(-5-6i)\n"},
|
||||||
|
{scsNoMethods, fCSFprint, "", ts, "test"},
|
||||||
|
{scsNoMethods, fCSFprint, "", &ts, "<*>test"},
|
||||||
|
{scsNoMethods, fCSFprint, "", tps, "test"},
|
||||||
|
{scsNoMethods, fCSFprint, "", &tps, "<*>test"},
|
||||||
|
{scsNoPmethods, fCSFprint, "", ts, "stringer test"},
|
||||||
|
{scsNoPmethods, fCSFprint, "", &ts, "<*>stringer test"},
|
||||||
|
{scsNoPmethods, fCSFprint, "", tps, "test"},
|
||||||
|
{scsNoPmethods, fCSFprint, "", &tps, "<*>stringer test"},
|
||||||
|
{scsMaxDepth, fCSFprint, "", dt, "{{<max>} [<max>] [<max>] map[<max>]}"},
|
||||||
|
{scsMaxDepth, fCSFdump, "", dt, "(spew_test.depthTester) {\n" +
|
||||||
|
" ic: (spew_test.indirCir1) {\n <max depth reached>\n },\n" +
|
||||||
|
" arr: ([1]string) (len=1 cap=1) {\n <max depth reached>\n },\n" +
|
||||||
|
" slice: ([]string) (len=1 cap=1) {\n <max depth reached>\n },\n" +
|
||||||
|
" m: (map[string]int) (len=1) {\n <max depth reached>\n }\n}\n"},
|
||||||
|
{scsContinue, fCSFprint, "", ts, "(stringer test) test"},
|
||||||
|
{scsContinue, fCSFdump, "", ts, "(spew_test.stringer) " +
|
||||||
|
"(len=4) (stringer test) \"test\"\n"},
|
||||||
|
{scsContinue, fCSFprint, "", te, "(error: 10) 10"},
|
||||||
|
{scsContinue, fCSFdump, "", te, "(spew_test.customError) " +
|
||||||
|
"(error: 10) 10\n"},
|
||||||
|
{scsNoPtrAddr, fCSFprint, "", tptr, "<*>{<*>{}}"},
|
||||||
|
{scsNoPtrAddr, fCSSdump, "", tptr, "(*spew_test.ptrTester)({\ns: (*struct {})({\n})\n})\n"},
|
||||||
|
{scsNoCap, fCSSdump, "", make([]string, 0, 10), "([]string) {\n}\n"},
|
||||||
|
{scsNoCap, fCSSdump, "", make([]string, 1, 10), "([]string) (len=1) {\n(string) \"\"\n}\n"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSpew executes all of the tests described by spewTests.
|
||||||
|
func TestSpew(t *testing.T) {
|
||||||
|
initSpewTests()
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(spewTests))
|
||||||
|
for i, test := range spewTests {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
switch test.f {
|
||||||
|
case fCSFdump:
|
||||||
|
test.cs.Fdump(buf, test.in)
|
||||||
|
|
||||||
|
case fCSFprint:
|
||||||
|
test.cs.Fprint(buf, test.in)
|
||||||
|
|
||||||
|
case fCSFprintf:
|
||||||
|
test.cs.Fprintf(buf, test.format, test.in)
|
||||||
|
|
||||||
|
case fCSFprintln:
|
||||||
|
test.cs.Fprintln(buf, test.in)
|
||||||
|
|
||||||
|
case fCSPrint:
|
||||||
|
b, err := redirStdout(func() { test.cs.Print(test.in) })
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v #%d %v", test.f, i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.Write(b)
|
||||||
|
|
||||||
|
case fCSPrintln:
|
||||||
|
b, err := redirStdout(func() { test.cs.Println(test.in) })
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v #%d %v", test.f, i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.Write(b)
|
||||||
|
|
||||||
|
case fCSSdump:
|
||||||
|
str := test.cs.Sdump(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fCSSprint:
|
||||||
|
str := test.cs.Sprint(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fCSSprintf:
|
||||||
|
str := test.cs.Sprintf(test.format, test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fCSSprintln:
|
||||||
|
str := test.cs.Sprintln(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fCSErrorf:
|
||||||
|
err := test.cs.Errorf(test.format, test.in)
|
||||||
|
buf.WriteString(err.Error())
|
||||||
|
|
||||||
|
case fCSNewFormatter:
|
||||||
|
fmt.Fprintf(buf, test.format, test.cs.NewFormatter(test.in))
|
||||||
|
|
||||||
|
case fErrorf:
|
||||||
|
err := spew.Errorf(test.format, test.in)
|
||||||
|
buf.WriteString(err.Error())
|
||||||
|
|
||||||
|
case fFprint:
|
||||||
|
spew.Fprint(buf, test.in)
|
||||||
|
|
||||||
|
case fFprintln:
|
||||||
|
spew.Fprintln(buf, test.in)
|
||||||
|
|
||||||
|
case fPrint:
|
||||||
|
b, err := redirStdout(func() { spew.Print(test.in) })
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v #%d %v", test.f, i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.Write(b)
|
||||||
|
|
||||||
|
case fPrintln:
|
||||||
|
b, err := redirStdout(func() { spew.Println(test.in) })
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v #%d %v", test.f, i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.Write(b)
|
||||||
|
|
||||||
|
case fSdump:
|
||||||
|
str := spew.Sdump(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fSprint:
|
||||||
|
str := spew.Sprint(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fSprintf:
|
||||||
|
str := spew.Sprintf(test.format, test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fSprintln:
|
||||||
|
str := spew.Sprintln(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
default:
|
||||||
|
t.Errorf("%v #%d unrecognized function", test.f, i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s := buf.String()
|
||||||
|
if test.want != s {
|
||||||
|
t.Errorf("ConfigState #%d\n got: %s want: %s", i, s, test.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
82
vendor/github.com/davecgh/go-spew/spew/testdata/dumpcgo.go
generated
vendored
Normal file
82
vendor/github.com/davecgh/go-spew/spew/testdata/dumpcgo.go
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
// Copyright (c) 2013 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when both cgo is supported and "-tags testcgo" is added to the go test
|
||||||
|
// command line. This code should really only be in the dumpcgo_test.go file,
|
||||||
|
// but unfortunately Go will not allow cgo in test files, so this is a
|
||||||
|
// workaround to allow cgo types to be tested. This configuration is used
|
||||||
|
// because spew itself does not require cgo to run even though it does handle
|
||||||
|
// certain cgo types specially. Rather than forcing all clients to require cgo
|
||||||
|
// and an external C compiler just to run the tests, this scheme makes them
|
||||||
|
// optional.
|
||||||
|
// +build cgo,testcgo
|
||||||
|
|
||||||
|
package testdata
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <stdint.h>
|
||||||
|
typedef unsigned char custom_uchar_t;
|
||||||
|
|
||||||
|
char *ncp = 0;
|
||||||
|
char *cp = "test";
|
||||||
|
char ca[6] = {'t', 'e', 's', 't', '2', '\0'};
|
||||||
|
unsigned char uca[6] = {'t', 'e', 's', 't', '3', '\0'};
|
||||||
|
signed char sca[6] = {'t', 'e', 's', 't', '4', '\0'};
|
||||||
|
uint8_t ui8ta[6] = {'t', 'e', 's', 't', '5', '\0'};
|
||||||
|
custom_uchar_t tuca[6] = {'t', 'e', 's', 't', '6', '\0'};
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
// GetCgoNullCharPointer returns a null char pointer via cgo. This is only
|
||||||
|
// used for tests.
|
||||||
|
func GetCgoNullCharPointer() interface{} {
|
||||||
|
return C.ncp
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoCharPointer returns a char pointer via cgo. This is only used for
|
||||||
|
// tests.
|
||||||
|
func GetCgoCharPointer() interface{} {
|
||||||
|
return C.cp
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoCharArray returns a char array via cgo and the array's len and cap.
|
||||||
|
// This is only used for tests.
|
||||||
|
func GetCgoCharArray() (interface{}, int, int) {
|
||||||
|
return C.ca, len(C.ca), cap(C.ca)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoUnsignedCharArray returns an unsigned char array via cgo and the
|
||||||
|
// array's len and cap. This is only used for tests.
|
||||||
|
func GetCgoUnsignedCharArray() (interface{}, int, int) {
|
||||||
|
return C.uca, len(C.uca), cap(C.uca)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoSignedCharArray returns a signed char array via cgo and the array's len
|
||||||
|
// and cap. This is only used for tests.
|
||||||
|
func GetCgoSignedCharArray() (interface{}, int, int) {
|
||||||
|
return C.sca, len(C.sca), cap(C.sca)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoUint8tArray returns a uint8_t array via cgo and the array's len and
|
||||||
|
// cap. This is only used for tests.
|
||||||
|
func GetCgoUint8tArray() (interface{}, int, int) {
|
||||||
|
return C.ui8ta, len(C.ui8ta), cap(C.ui8ta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoTypdefedUnsignedCharArray returns a typedefed unsigned char array via
|
||||||
|
// cgo and the array's len and cap. This is only used for tests.
|
||||||
|
func GetCgoTypdefedUnsignedCharArray() (interface{}, int, int) {
|
||||||
|
return C.tuca, len(C.tuca), cap(C.tuca)
|
||||||
|
}
|
||||||
61
vendor/github.com/davecgh/go-spew/test_coverage.txt
generated
vendored
Normal file
61
vendor/github.com/davecgh/go-spew/test_coverage.txt
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go dumpState.dump 100.00% (88/88)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go formatState.format 100.00% (82/82)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go formatState.formatPtr 100.00% (52/52)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go dumpState.dumpPtr 100.00% (44/44)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go dumpState.dumpSlice 100.00% (39/39)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go handleMethods 100.00% (30/30)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go printHexPtr 100.00% (18/18)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go unsafeReflectValue 100.00% (13/13)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go formatState.constructOrigFormat 100.00% (12/12)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go fdump 100.00% (11/11)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go formatState.Format 100.00% (11/11)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go init 100.00% (10/10)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go printComplex 100.00% (9/9)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go valuesSorter.Less 100.00% (8/8)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go formatState.buildDefaultFormat 100.00% (7/7)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go formatState.unpackValue 100.00% (5/5)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go dumpState.indent 100.00% (4/4)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go catchPanic 100.00% (4/4)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.convertArgs 100.00% (4/4)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go convertArgs 100.00% (4/4)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go newFormatter 100.00% (3/3)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go Sdump 100.00% (3/3)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go printBool 100.00% (3/3)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go sortValues 100.00% (3/3)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Sdump 100.00% (3/3)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go dumpState.unpackValue 100.00% (3/3)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Printf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Println 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Sprint 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Sprintf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Sprintln 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go printFloat 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go NewDefaultConfig 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go printInt 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go printUint 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go valuesSorter.Len 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go valuesSorter.Swap 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Errorf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Fprint 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Fprintf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Fprintln 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Print 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Printf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Println 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Sprint 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Sprintf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Sprintln 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.NewFormatter 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Fdump 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Dump 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go Fdump 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go Dump 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Fprintln 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go NewFormatter 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Errorf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Fprint 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Fprintf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Print 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew ------------------------------- 100.00% (505/505)
|
||||||
|
|
||||||
1536
vendor/github.com/docopt/docopt-go/docopt_test.go
generated
vendored
Normal file
1536
vendor/github.com/docopt/docopt-go/docopt_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
37
vendor/github.com/docopt/docopt-go/example_test.go
generated
vendored
Normal file
37
vendor/github.com/docopt/docopt-go/example_test.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package docopt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleParse() {
|
||||||
|
usage := `Usage:
|
||||||
|
config_example tcp [<host>] [--force] [--timeout=<seconds>]
|
||||||
|
config_example serial <port> [--baud=<rate>] [--timeout=<seconds>]
|
||||||
|
config_example -h | --help | --version`
|
||||||
|
// parse the command line `comfig_example tcp 127.0.0.1 --force`
|
||||||
|
argv := []string{"tcp", "127.0.0.1", "--force"}
|
||||||
|
arguments, _ := Parse(usage, argv, true, "0.1.1rc", false)
|
||||||
|
// sort the keys of the arguments map
|
||||||
|
var keys []string
|
||||||
|
for k := range arguments {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
// print the argument keys and values
|
||||||
|
for _, k := range keys {
|
||||||
|
fmt.Printf("%9s %v\n", k, arguments[k])
|
||||||
|
}
|
||||||
|
// output:
|
||||||
|
// --baud <nil>
|
||||||
|
// --force true
|
||||||
|
// --help false
|
||||||
|
// --timeout <nil>
|
||||||
|
// --version false
|
||||||
|
// -h false
|
||||||
|
// <host> 127.0.0.1
|
||||||
|
// <port> <nil>
|
||||||
|
// serial false
|
||||||
|
// tcp true
|
||||||
|
}
|
||||||
29
vendor/github.com/docopt/docopt-go/examples/arguments/arguments_example.go
generated
vendored
Normal file
29
vendor/github.com/docopt/docopt-go/examples/arguments/arguments_example.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `Usage: arguments_example [-vqrh] [FILE] ...
|
||||||
|
arguments_example (--left | --right) CORRECTION FILE
|
||||||
|
|
||||||
|
Process FILE and optionally apply correction to either left-hand side or
|
||||||
|
right-hand side.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
FILE optional input file
|
||||||
|
CORRECTION correction angle, needs FILE, --left or --right to be present
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h --help
|
||||||
|
-v verbose mode
|
||||||
|
-q quiet mode
|
||||||
|
-r make report
|
||||||
|
--left use left-hand side
|
||||||
|
--right use right-hand side`
|
||||||
|
|
||||||
|
arguments, _ := docopt.Parse(usage, nil, true, "", false)
|
||||||
|
fmt.Println(arguments)
|
||||||
|
}
|
||||||
26
vendor/github.com/docopt/docopt-go/examples/calculator/calculator_example.go
generated
vendored
Normal file
26
vendor/github.com/docopt/docopt-go/examples/calculator/calculator_example.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `Not a serious example.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
calculator_example <value> ( ( + | - | * | / ) <value> )...
|
||||||
|
calculator_example <function> <value> [( , <value> )]...
|
||||||
|
calculator_example (-h | --help)
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
calculator_example 1 + 2 + 3 + 4 + 5
|
||||||
|
calculator_example 1 + 2 '*' 3 / 4 - 5 # note quotes around '*'
|
||||||
|
calculator_example sum 10 , 20 , 30 , 40
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help
|
||||||
|
`
|
||||||
|
arguments, _ := docopt.Parse(usage, nil, true, "", false)
|
||||||
|
fmt.Println(arguments)
|
||||||
|
}
|
||||||
76
vendor/github.com/docopt/docopt-go/examples/config_file/config_file_example.go
generated
vendored
Normal file
76
vendor/github.com/docopt/docopt-go/examples/config_file/config_file_example.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func loadJSONConfig() map[string]interface{} {
|
||||||
|
var result map[string]interface{}
|
||||||
|
jsonData := []byte(`{"--force": true, "--timeout": "10", "--baud": "9600"}`)
|
||||||
|
json.Unmarshal(jsonData, &result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadIniConfig() map[string]interface{} {
|
||||||
|
iniData := `
|
||||||
|
[default-arguments]
|
||||||
|
--force
|
||||||
|
--baud=19200
|
||||||
|
<host>=localhost`
|
||||||
|
// trivial ini parser
|
||||||
|
// default value for an item is bool: true (for --force)
|
||||||
|
// otherwise the value is a string
|
||||||
|
iniParsed := make(map[string]map[string]interface{})
|
||||||
|
var section string
|
||||||
|
for _, line := range strings.Split(iniData, "\n") {
|
||||||
|
if strings.HasPrefix(line, "[") {
|
||||||
|
section = line
|
||||||
|
iniParsed[section] = make(map[string]interface{})
|
||||||
|
} else if section != "" {
|
||||||
|
kv := strings.SplitN(line, "=", 2)
|
||||||
|
if len(kv) == 1 {
|
||||||
|
iniParsed[section][kv[0]] = true
|
||||||
|
} else if len(kv) == 2 {
|
||||||
|
iniParsed[section][kv[0]] = kv[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return iniParsed["[default-arguments]"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// merge combines two maps.
|
||||||
|
// truthiness takes priority over falsiness
|
||||||
|
// mapA takes priority over mapB
|
||||||
|
func merge(mapA, mapB map[string]interface{}) map[string]interface{} {
|
||||||
|
result := make(map[string]interface{})
|
||||||
|
for k, v := range mapA {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range mapB {
|
||||||
|
if _, ok := result[k]; !ok || result[k] == nil || result[k] == false {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `Usage:
|
||||||
|
config_file_example tcp [<host>] [--force] [--timeout=<seconds>]
|
||||||
|
config_file_example serial <port> [--baud=<rate>] [--timeout=<seconds>]
|
||||||
|
config_file_example -h | --help | --version`
|
||||||
|
|
||||||
|
jsonConfig := loadJSONConfig()
|
||||||
|
iniConfig := loadIniConfig()
|
||||||
|
arguments, _ := docopt.Parse(usage, nil, true, "0.1.1rc", false)
|
||||||
|
|
||||||
|
// Arguments take priority over INI, INI takes priority over JSON
|
||||||
|
result := merge(arguments, merge(iniConfig, jsonConfig))
|
||||||
|
|
||||||
|
fmt.Println("JSON config: ", jsonConfig)
|
||||||
|
fmt.Println("INI config: ", iniConfig)
|
||||||
|
fmt.Println("Result: ", result)
|
||||||
|
}
|
||||||
22
vendor/github.com/docopt/docopt-go/examples/counted/counted_example.go
generated
vendored
Normal file
22
vendor/github.com/docopt/docopt-go/examples/counted/counted_example.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `Usage: counted_example --help
|
||||||
|
counted_example -v...
|
||||||
|
counted_example go [go]
|
||||||
|
counted_example (--path=<path>)...
|
||||||
|
counted_example <file> <file>
|
||||||
|
|
||||||
|
Try: counted_example -vvvvvvvvvv
|
||||||
|
counted_example go go
|
||||||
|
counted_example --path ./here --path ./there
|
||||||
|
counted_example this.txt that.txt`
|
||||||
|
|
||||||
|
arguments, _ := docopt.Parse(usage, nil, true, "", false)
|
||||||
|
fmt.Println(arguments)
|
||||||
|
}
|
||||||
38
vendor/github.com/docopt/docopt-go/examples/git/branch/git_branch.go
generated
vendored
Normal file
38
vendor/github.com/docopt/docopt-go/examples/git/branch/git_branch.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `usage: git branch [options] [-r | -a] [--merged=<commit> | --no-merged=<commit>]
|
||||||
|
git branch [options] [-l] [-f] <branchname> [<start-point>]
|
||||||
|
git branch [options] [-r] (-d | -D) <branchname>
|
||||||
|
git branch [options] (-m | -M) [<oldbranch>] <newbranch>
|
||||||
|
|
||||||
|
Generic options:
|
||||||
|
-h, --help
|
||||||
|
-v, --verbose show hash and subject, give twice for upstream branch
|
||||||
|
-t, --track set up tracking mode (see git-pull(1))
|
||||||
|
--set-upstream change upstream info
|
||||||
|
--color=<when> use colored output
|
||||||
|
-r act on remote-tracking branches
|
||||||
|
--contains=<commit> print only branches that contain the commit
|
||||||
|
--abbrev=<n> use <n> digits to display SHA-1s
|
||||||
|
|
||||||
|
Specific git-branch actions:
|
||||||
|
-a list both remote-tracking and local branches
|
||||||
|
-d delete fully merged branch
|
||||||
|
-D delete branch (even if not merged)
|
||||||
|
-m move/rename a branch and its reflog
|
||||||
|
-M move/rename a branch, even if target exists
|
||||||
|
-l create the branch's reflog
|
||||||
|
-f, --force force creation (when already exists)
|
||||||
|
--no-merged=<commit> print only not merged branches
|
||||||
|
--merged=<commit> print only merged branches
|
||||||
|
`
|
||||||
|
|
||||||
|
args, _ := docopt.Parse(usage, nil, true, "", false)
|
||||||
|
fmt.Println(args)
|
||||||
|
}
|
||||||
30
vendor/github.com/docopt/docopt-go/examples/git/checkout/git_checkout.go
generated
vendored
Normal file
30
vendor/github.com/docopt/docopt-go/examples/git/checkout/git_checkout.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `usage: git checkout [options] <branch>
|
||||||
|
git checkout [options] <branch> -- <file>...
|
||||||
|
|
||||||
|
options:
|
||||||
|
-q, --quiet suppress progress reporting
|
||||||
|
-b <branch> create and checkout a new branch
|
||||||
|
-B <branch> create/reset and checkout a branch
|
||||||
|
-l create reflog for new branch
|
||||||
|
-t, --track set upstream info for new branch
|
||||||
|
--orphan <new branch>
|
||||||
|
new unparented branch
|
||||||
|
-2, --ours checkout our version for unmerged files
|
||||||
|
-3, --theirs checkout their version for unmerged files
|
||||||
|
-f, --force force checkout (throw away local modifications)
|
||||||
|
-m, --merge perform a 3-way merge with the new branch
|
||||||
|
--conflict <style> conflict style (merge or diff3)
|
||||||
|
-p, --patch select hunks interactively
|
||||||
|
`
|
||||||
|
|
||||||
|
args, _ := docopt.Parse(usage, nil, true, "", false)
|
||||||
|
fmt.Println(args)
|
||||||
|
}
|
||||||
37
vendor/github.com/docopt/docopt-go/examples/git/clone/git_clone.go
generated
vendored
Normal file
37
vendor/github.com/docopt/docopt-go/examples/git/clone/git_clone.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `usage: git clone [options] [--] <repo> [<dir>]
|
||||||
|
|
||||||
|
options:
|
||||||
|
-v, --verbose be more verbose
|
||||||
|
-q, --quiet be more quiet
|
||||||
|
--progress force progress reporting
|
||||||
|
-n, --no-checkout don't create a checkout
|
||||||
|
--bare create a bare repository
|
||||||
|
--mirror create a mirror repository (implies bare)
|
||||||
|
-l, --local to clone from a local repository
|
||||||
|
--no-hardlinks don't use local hardlinks, always copy
|
||||||
|
-s, --shared setup as shared repository
|
||||||
|
--recursive initialize submodules in the clone
|
||||||
|
--recurse-submodules initialize submodules in the clone
|
||||||
|
--template <template-directory>
|
||||||
|
directory from which templates will be used
|
||||||
|
--reference <repo> reference repository
|
||||||
|
-o, --origin <branch>
|
||||||
|
use <branch> instead of 'origin' to track upstream
|
||||||
|
-b, --branch <branch>
|
||||||
|
checkout <branch> instead of the remote's HEAD
|
||||||
|
-u, --upload-pack <path>
|
||||||
|
path to git-upload-pack on the remote
|
||||||
|
--depth <depth> create a shallow clone of that depth
|
||||||
|
`
|
||||||
|
|
||||||
|
args, _ := docopt.Parse(usage, nil, true, "", false)
|
||||||
|
fmt.Println(args)
|
||||||
|
}
|
||||||
108
vendor/github.com/docopt/docopt-go/examples/git/git.go
generated
vendored
Normal file
108
vendor/github.com/docopt/docopt-go/examples/git/git.go
generated
vendored
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `usage: git [--version] [--exec-path=<path>] [--html-path]
|
||||||
|
[-p|--paginate|--no-pager] [--no-replace-objects]
|
||||||
|
[--bare] [--git-dir=<path>] [--work-tree=<path>]
|
||||||
|
[-c <name>=<value>] [--help]
|
||||||
|
<command> [<args>...]
|
||||||
|
|
||||||
|
options:
|
||||||
|
-c <name=value>
|
||||||
|
-h, --help
|
||||||
|
-p, --paginate
|
||||||
|
|
||||||
|
The most commonly used git commands are:
|
||||||
|
add Add file contents to the index
|
||||||
|
branch List, create, or delete branches
|
||||||
|
checkout Checkout a branch or paths to the working tree
|
||||||
|
clone Clone a repository into a new directory
|
||||||
|
commit Record changes to the repository
|
||||||
|
push Update remote refs along with associated objects
|
||||||
|
remote Manage set of tracked repositories
|
||||||
|
|
||||||
|
See 'git help <command>' for more information on a specific command.
|
||||||
|
`
|
||||||
|
args, _ := docopt.Parse(usage, nil, true, "git version 1.7.4.4", true)
|
||||||
|
|
||||||
|
fmt.Println("global arguments:")
|
||||||
|
fmt.Println(args)
|
||||||
|
|
||||||
|
fmt.Println("command arguments:")
|
||||||
|
cmd := args["<command>"].(string)
|
||||||
|
cmdArgs := args["<args>"].([]string)
|
||||||
|
|
||||||
|
err := runCommand(cmd, cmdArgs)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func goRun(scriptName string, args []string) (err error) {
|
||||||
|
cmdArgs := make([]string, 2)
|
||||||
|
cmdArgs[0] = "run"
|
||||||
|
cmdArgs[1] = scriptName
|
||||||
|
cmdArgs = append(cmdArgs, args...)
|
||||||
|
osCmd := exec.Command("go", cmdArgs...)
|
||||||
|
var out []byte
|
||||||
|
out, err = osCmd.Output()
|
||||||
|
fmt.Println(string(out))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func runCommand(cmd string, args []string) (err error) {
|
||||||
|
argv := make([]string, 1)
|
||||||
|
argv[0] = cmd
|
||||||
|
argv = append(argv, args...)
|
||||||
|
switch cmd {
|
||||||
|
case "add":
|
||||||
|
// subcommand is a function call
|
||||||
|
return cmdAdd(argv)
|
||||||
|
case "branch":
|
||||||
|
// subcommand is a script
|
||||||
|
return goRun("branch/git_branch.go", argv)
|
||||||
|
case "checkout", "clone", "commit", "push", "remote":
|
||||||
|
// subcommand is a script
|
||||||
|
scriptName := fmt.Sprintf("%s/git_%s.go", cmd, cmd)
|
||||||
|
return goRun(scriptName, argv)
|
||||||
|
case "help", "":
|
||||||
|
return goRun("git.go", []string{"git_add.go", "--help"})
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("%s is not a git command. See 'git help'", cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdAdd(argv []string) (err error) {
|
||||||
|
usage := `usage: git add [options] [--] [<filepattern>...]
|
||||||
|
|
||||||
|
options:
|
||||||
|
-h, --help
|
||||||
|
-n, --dry-run dry run
|
||||||
|
-v, --verbose be verbose
|
||||||
|
-i, --interactive interactive picking
|
||||||
|
-p, --patch select hunks interactively
|
||||||
|
-e, --edit edit current diff and apply
|
||||||
|
-f, --force allow adding otherwise ignored files
|
||||||
|
-u, --update update tracked files
|
||||||
|
-N, --intent-to-add record only the fact that the path will be added later
|
||||||
|
-A, --all add all, noticing removal of tracked files
|
||||||
|
--refresh don't add, only refresh the index
|
||||||
|
--ignore-errors just skip files which cannot be added because of errors
|
||||||
|
--ignore-missing check if - even missing - files are ignored in dry run
|
||||||
|
`
|
||||||
|
|
||||||
|
args, _ := docopt.Parse(usage, nil, true, "", false)
|
||||||
|
fmt.Println(args)
|
||||||
|
return
|
||||||
|
}
|
||||||
34
vendor/github.com/docopt/docopt-go/examples/git/push/git_push.go
generated
vendored
Normal file
34
vendor/github.com/docopt/docopt-go/examples/git/push/git_push.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `usage: git push [options] [<repository> [<refspec>...]]
|
||||||
|
|
||||||
|
options:
|
||||||
|
-h, --help
|
||||||
|
-v, --verbose be more verbose
|
||||||
|
-q, --quiet be more quiet
|
||||||
|
--repo <repository> repository
|
||||||
|
--all push all refs
|
||||||
|
--mirror mirror all refs
|
||||||
|
--delete delete refs
|
||||||
|
--tags push tags (can't be used with --all or --mirror)
|
||||||
|
-n, --dry-run dry run
|
||||||
|
--porcelain machine-readable output
|
||||||
|
-f, --force force updates
|
||||||
|
--thin use thin pack
|
||||||
|
--receive-pack <receive-pack>
|
||||||
|
receive pack program
|
||||||
|
--exec <receive-pack>
|
||||||
|
receive pack program
|
||||||
|
-u, --set-upstream set upstream for git pull/status
|
||||||
|
--progress force progress reporting
|
||||||
|
`
|
||||||
|
|
||||||
|
args, _ := docopt.Parse(usage, nil, true, "", false)
|
||||||
|
fmt.Println(args)
|
||||||
|
}
|
||||||
28
vendor/github.com/docopt/docopt-go/examples/git/remote/git_remote.go
generated
vendored
Normal file
28
vendor/github.com/docopt/docopt-go/examples/git/remote/git_remote.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `usage: git remote [-v | --verbose]
|
||||||
|
git remote add [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>
|
||||||
|
git remote rename <old> <new>
|
||||||
|
git remote rm <name>
|
||||||
|
git remote set-head <name> (-a | -d | <branch>)
|
||||||
|
git remote [-v | --verbose] show [-n] <name>
|
||||||
|
git remote prune [-n | --dry-run] <name>
|
||||||
|
git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]
|
||||||
|
git remote set-branches <name> [--add] <branch>...
|
||||||
|
git remote set-url <name> <newurl> [<oldurl>]
|
||||||
|
git remote set-url --add <name> <newurl>
|
||||||
|
git remote set-url --delete <name> <url>
|
||||||
|
|
||||||
|
options:
|
||||||
|
-v, --verbose be verbose; must be placed before a subcommand
|
||||||
|
`
|
||||||
|
|
||||||
|
args, _ := docopt.Parse(usage, nil, true, "", false)
|
||||||
|
fmt.Println(args)
|
||||||
|
}
|
||||||
28
vendor/github.com/docopt/docopt-go/examples/naval_fate/naval_fate.go
generated
vendored
Normal file
28
vendor/github.com/docopt/docopt-go/examples/naval_fate/naval_fate.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `Naval Fate.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
naval_fate ship new <name>...
|
||||||
|
naval_fate ship <name> move <x> <y> [--speed=<kn>]
|
||||||
|
naval_fate ship shoot <x> <y>
|
||||||
|
naval_fate mine (set|remove) <x> <y> [--moored|--drifting]
|
||||||
|
naval_fate -h | --help
|
||||||
|
naval_fate --version
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h --help Show this screen.
|
||||||
|
--version Show version.
|
||||||
|
--speed=<kn> Speed in knots [default: 10].
|
||||||
|
--moored Moored (anchored) mine.
|
||||||
|
--drifting Drifting mine.`
|
||||||
|
|
||||||
|
arguments, _ := docopt.Parse(usage, nil, true, "Naval Fate 2.0", false)
|
||||||
|
fmt.Println(arguments)
|
||||||
|
}
|
||||||
19
vendor/github.com/docopt/docopt-go/examples/odd_even/odd_even_example.go
generated
vendored
Normal file
19
vendor/github.com/docopt/docopt-go/examples/odd_even/odd_even_example.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `Usage: odd_even_example [-h | --help] (ODD EVEN)...
|
||||||
|
|
||||||
|
Example, try:
|
||||||
|
odd_even_example 1 2 3 4
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help`
|
||||||
|
|
||||||
|
arguments, _ := docopt.Parse(usage, nil, true, "", false)
|
||||||
|
fmt.Println(arguments)
|
||||||
|
}
|
||||||
43
vendor/github.com/docopt/docopt-go/examples/options/options_example.go
generated
vendored
Normal file
43
vendor/github.com/docopt/docopt-go/examples/options/options_example.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `Example of program with many options using docopt.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
options_example [-hvqrf NAME] [--exclude=PATTERNS]
|
||||||
|
[--select=ERRORS | --ignore=ERRORS] [--show-source]
|
||||||
|
[--statistics] [--count] [--benchmark] PATH...
|
||||||
|
options_example (--doctest | --testsuite=DIR)
|
||||||
|
options_example --version
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
PATH destination path
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h --help show this help message and exit
|
||||||
|
--version show version and exit
|
||||||
|
-v --verbose print status messages
|
||||||
|
-q --quiet report only file names
|
||||||
|
-r --repeat show all occurrences of the same error
|
||||||
|
--exclude=PATTERNS exclude files or directories which match these comma
|
||||||
|
separated patterns [default: .svn,CVS,.bzr,.hg,.git]
|
||||||
|
-f NAME --file=NAME when parsing directories, only check filenames matching
|
||||||
|
these comma separated patterns [default: *.go]
|
||||||
|
--select=ERRORS select errors and warnings (e.g. E,W6)
|
||||||
|
--ignore=ERRORS skip errors and warnings (e.g. E4,W)
|
||||||
|
--show-source show source code for each error
|
||||||
|
--statistics count errors and warnings
|
||||||
|
--count print total number of errors and warnings to standard
|
||||||
|
error and set exit code to 1 if total is not null
|
||||||
|
--benchmark measure processing speed
|
||||||
|
--testsuite=DIR run regression tests from dir
|
||||||
|
--doctest run doctest on myself`
|
||||||
|
|
||||||
|
arguments, _ := docopt.Parse(usage, nil, true, "1.0.0rc2", false)
|
||||||
|
fmt.Println(arguments)
|
||||||
|
}
|
||||||
24
vendor/github.com/docopt/docopt-go/examples/options_shortcut/options_shortcut_example.go
generated
vendored
Normal file
24
vendor/github.com/docopt/docopt-go/examples/options_shortcut/options_shortcut_example.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `Example of program which uses [options] shortcut in pattern.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
options_shortcut_example [options] <port>
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h --help show this help message and exit
|
||||||
|
--version show version and exit
|
||||||
|
-n, --number N use N as a number
|
||||||
|
-t, --timeout TIMEOUT set timeout TIMEOUT seconds
|
||||||
|
--apply apply changes to database
|
||||||
|
-q operate in quiet mode`
|
||||||
|
|
||||||
|
arguments, _ := docopt.Parse(usage, nil, true, "1.0.0rc2", false)
|
||||||
|
fmt.Println(arguments)
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user