mirror of https://github.com/fatedier/frp
commit
8fb99ef7a9
|
@ -2,7 +2,7 @@ export PATH := $(GOPATH)/bin:$(PATH)
|
|||
export GO111MODULE=on
|
||||
LDFLAGS := -s -w
|
||||
|
||||
os-archs=darwin:amd64 darwin:arm64 freebsd:386 freebsd:amd64 linux:386 linux:amd64 linux:arm linux:arm64 windows:386 windows:amd64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat linux:riscv64
|
||||
os-archs=darwin:amd64 darwin:arm64 freebsd:386 freebsd:amd64 linux:386 linux:amd64 linux:arm linux:arm64 windows:386 windows:amd64 windows:arm64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat linux:riscv64
|
||||
|
||||
all: build
|
||||
|
||||
|
@ -23,3 +23,5 @@ app:
|
|||
@mv ./release/frps_windows_386 ./release/frps_windows_386.exe
|
||||
@mv ./release/frpc_windows_amd64 ./release/frpc_windows_amd64.exe
|
||||
@mv ./release/frps_windows_amd64 ./release/frps_windows_amd64.exe
|
||||
@mv ./release/frpc_windows_arm64 ./release/frpc_windows_arm64.exe
|
||||
@mv ./release/frps_windows_arm64 ./release/frps_windows_arm64.exe
|
||||
|
|
94
README.md
94
README.md
|
@ -8,10 +8,13 @@
|
|||
|
||||
<h3 align="center">Gold Sponsors</h3>
|
||||
<!--gold sponsors start-->
|
||||
|
||||
<p align="center">
|
||||
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||
<img width="300px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
||||
<img width="350px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
||||
</a>
|
||||
<a> </a>
|
||||
<a href="https://asocks.com/c/vDu6Dk" target="_blank">
|
||||
<img width="350px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_asocks.jpg">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
@ -19,9 +22,9 @@
|
|||
|
||||
## What is frp?
|
||||
|
||||
frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the Internet. As of now, it supports **TCP** and **UDP**, as well as **HTTP** and **HTTPS** protocols, where requests can be forwarded to internal services by domain name.
|
||||
frp is a fast reverse proxy that allows you to expose a local server located behind a NAT or firewall to the Internet. It currently supports **TCP** and **UDP**, as well as **HTTP** and **HTTPS** protocols, enabling requests to be forwarded to internal services via domain name.
|
||||
|
||||
frp also has a P2P connect mode.
|
||||
frp also offers a P2P connect mode.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
|
@ -30,12 +33,12 @@ frp also has a P2P connect mode.
|
|||
* [Development Status](#development-status)
|
||||
* [Architecture](#architecture)
|
||||
* [Example Usage](#example-usage)
|
||||
* [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)
|
||||
* [Forward DNS query request](#forward-dns-query-request)
|
||||
* [Forward Unix domain socket](#forward-unix-domain-socket)
|
||||
* [Access your computer in a LAN network via SSH](#access-your-computer-in-a-lan-network-via-ssh)
|
||||
* [Accessing Internal Web Services with Custom Domains in LAN](#accessing-internal-web-services-with-custom-domains-in-lan)
|
||||
* [Forward DNS query requests](#forward-dns-query-requests)
|
||||
* [Forward Unix Domain Socket](#forward-unix-domain-socket)
|
||||
* [Expose a simple HTTP file server](#expose-a-simple-http-file-server)
|
||||
* [Enable HTTPS for local HTTP(S) service](#enable-https-for-local-https-service)
|
||||
* [Enable HTTPS for a local HTTP(S) service](#enable-https-for-a-local-https-service)
|
||||
* [Expose your service privately](#expose-your-service-privately)
|
||||
* [P2P Mode](#p2p-mode)
|
||||
* [Features](#features)
|
||||
|
@ -86,11 +89,11 @@ frp also has a P2P connect mode.
|
|||
|
||||
## Development Status
|
||||
|
||||
frp is under development. Try the latest release version in the `master` branch, or use the `dev` branch for the version in development.
|
||||
frp is currently under development. You can try the latest release version in the `master` branch, or use the `dev` branch to access the version currently in development.
|
||||
|
||||
We are working on v2 version and trying to do some code refactor and improvements. It won't be compatible with v1.
|
||||
We are currently working on version 2 and attempting to perform some code refactoring and improvements. However, please note that it will not be compatible with version 1.
|
||||
|
||||
We will switch v0 to v1 at the right time and only accept bug fixes and improvements instead of big feature requirements.
|
||||
We will transition from version 0 to version 1 at the appropriate time and will only accept bug fixes and improvements, rather than big feature requests.
|
||||
|
||||
## Architecture
|
||||
|
||||
|
@ -98,15 +101,15 @@ We will switch v0 to v1 at the right time and only accept bug fixes and improvem
|
|||
|
||||
## Example Usage
|
||||
|
||||
Firstly, download the latest programs from [Release](https://github.com/fatedier/frp/releases) page according to your operating system and architecture.
|
||||
To begin, download the latest program for your operating system and architecture from the [Release](https://github.com/fatedier/frp/releases) page.
|
||||
|
||||
Put `frps` and `frps.ini` onto your server A with public IP.
|
||||
Next, place the `frps` binary and `frps.ini` configuration file on Server A, which has a public IP address.
|
||||
|
||||
Put `frpc` and `frpc.ini` onto your server B in LAN (that can't be connected from public Internet).
|
||||
Finally, place the `frpc` binary and `frpc.ini` configuration file on Server B, which is located on a LAN that cannot be directly accessed from the public internet.
|
||||
|
||||
### Access your computer in LAN by SSH
|
||||
### Access your computer in a LAN network via SSH
|
||||
|
||||
1. Modify `frps.ini` on server A and set the `bind_port` to be connected to frp clients:
|
||||
1. Modify `frps.ini` on server A by setting the `bind_port` for frp clients to connect to:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
|
@ -118,7 +121,7 @@ Put `frpc` and `frpc.ini` onto your server B in LAN (that can't be connected fro
|
|||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
3. On server B, modify `frpc.ini` to put in your `frps` server public IP as `server_addr` field:
|
||||
3. Modify `frpc.ini` on server B and set the `server_addr` field to the public IP address of your frps server:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
|
@ -133,23 +136,23 @@ Put `frpc` and `frpc.ini` onto your server B in LAN (that can't be connected fro
|
|||
remote_port = 6000
|
||||
```
|
||||
|
||||
Note that `local_port` (listened on client) and `remote_port` (exposed on server) are for traffic goes in/out the frp system, whereas `server_port` is used between frps.
|
||||
Note that the `local_port` (listened on the client) and `remote_port` (exposed on the server) are used for traffic going in and out of the frp system, while the `server_port` is used for communication between frps and frpc.
|
||||
|
||||
4. Start `frpc` on server B:
|
||||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. From another machine, SSH to server B via server A like this (assuming that username is `test`):
|
||||
5. To access server B from another machine through server A via SSH (assuming the username is `test`), use the following command:
|
||||
|
||||
`ssh -oPort=6000 test@x.x.x.x`
|
||||
|
||||
### Visit your web service in LAN by custom domains
|
||||
### Accessing Internal Web Services with Custom Domains in LAN
|
||||
|
||||
Sometimes we want to expose a local web service behind a NAT network to others for testing with your own domain name and unfortunately we can't resolve a domain name to a local IP.
|
||||
Sometimes we need to expose a local web service behind a NAT network to others for testing purposes with our own domain name.
|
||||
|
||||
However, we can expose an HTTP(S) service using frp.
|
||||
Unfortunately, we cannot resolve a domain name to a local IP. However, we can use frp to expose an HTTP(S) service.
|
||||
|
||||
1. Modify `frps.ini`, set the vhost HTTP port to 8080:
|
||||
1. Modify `frps.ini` and set the HTTP port for vhost to 8080:
|
||||
|
||||
```ini
|
||||
# frps.ini
|
||||
|
@ -162,7 +165,7 @@ However, we can expose an HTTP(S) service using frp.
|
|||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
3. Modify `frpc.ini` and set `server_addr` to the IP address of the remote frps server. The `local_port` is the port of your web service:
|
||||
3. Modify `frpc.ini` and set `server_addr` to the IP address of the remote frps server. Specify the `local_port` of your web service:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
|
@ -180,11 +183,11 @@ However, we can expose an HTTP(S) service using frp.
|
|||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. Resolve A record of `www.example.com` to the public IP of the remote frps server or CNAME record to your origin domain.
|
||||
5. Map the A record of `www.example.com` to either the public IP of the remote frps server or a CNAME record pointing to your original domain.
|
||||
|
||||
6. Now visit your local web service using url `http://www.example.com:8080`.
|
||||
6. Visit your local web service using url `http://www.example.com:8080`.
|
||||
|
||||
### Forward DNS query request
|
||||
### Forward DNS query requests
|
||||
|
||||
1. Modify `frps.ini`:
|
||||
|
||||
|
@ -198,7 +201,7 @@ However, we can expose an HTTP(S) service using frp.
|
|||
|
||||
`./frps -c ./frps.ini`
|
||||
|
||||
3. Modify `frpc.ini` and set `server_addr` to the IP address of the remote frps server, forward DNS query request to Google Public DNS server `8.8.8.8:53`:
|
||||
3. Modify `frpc.ini` and set `server_addr` to the IP address of the remote frps server. Forward DNS query requests to the Google Public DNS server `8.8.8.8:53`:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
|
@ -217,17 +220,17 @@ However, we can expose an HTTP(S) service using frp.
|
|||
|
||||
`./frpc -c ./frpc.ini`
|
||||
|
||||
5. Test DNS resolution using `dig` command:
|
||||
5. Test DNS resolution using the `dig` command:
|
||||
|
||||
`dig @x.x.x.x -p 6000 www.google.com`
|
||||
|
||||
### Forward Unix domain socket
|
||||
### Forward Unix Domain Socket
|
||||
|
||||
Expose a Unix domain socket (e.g. the Docker daemon socket) as TCP.
|
||||
|
||||
Configure `frps` same as above.
|
||||
Configure `frps` as above.
|
||||
|
||||
1. Start `frpc` with configuration:
|
||||
1. Start `frpc` with the following configuration:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
|
@ -242,17 +245,17 @@ Configure `frps` same as above.
|
|||
plugin_unix_path = /var/run/docker.sock
|
||||
```
|
||||
|
||||
2. Test: Get Docker version using `curl`:
|
||||
2. Test the configuration by getting the docker version using `curl`:
|
||||
|
||||
`curl http://x.x.x.x:6000/version`
|
||||
|
||||
### Expose a simple HTTP file server
|
||||
|
||||
Browser your files stored in the LAN, from public Internet.
|
||||
Expose a simple HTTP file server to access files stored in the LAN from the public Internet.
|
||||
|
||||
Configure `frps` same as above.
|
||||
Configure `frps` as described above, then:
|
||||
|
||||
1. Start `frpc` with configuration:
|
||||
1. Start `frpc` with the following configuration:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
|
@ -270,13 +273,13 @@ Configure `frps` same as above.
|
|||
plugin_http_passwd = abc
|
||||
```
|
||||
|
||||
2. Visit `http://x.x.x.x:6000/static/` from your browser and specify correct user and password to view files in `/tmp/files` on the `frpc` machine.
|
||||
2. Visit `http://x.x.x.x:6000/static/` from your browser and specify correct username and password to view files in `/tmp/files` on the `frpc` machine.
|
||||
|
||||
### Enable HTTPS for local HTTP(S) service
|
||||
### Enable HTTPS for a local HTTP(S) service
|
||||
|
||||
You may substitute `https2https` for the plugin, and point the `plugin_local_addr` to a HTTPS endpoint.
|
||||
|
||||
1. Start `frpc` with configuration:
|
||||
1. Start `frpc` with the following configuration:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
|
@ -300,7 +303,7 @@ You may substitute `https2https` for the plugin, and point the `plugin_local_add
|
|||
|
||||
### Expose your service privately
|
||||
|
||||
Some services will be at risk if exposed directly to the public network. With **STCP** (secret TCP) mode, a preshared key is needed to access the service from another client.
|
||||
To mitigate risks associated with exposing certain services directly to the public network, STCP (Secret TCP) mode requires a preshared key to be used for access to the service from other clients.
|
||||
|
||||
Configure `frps` same as above.
|
||||
|
||||
|
@ -342,9 +345,9 @@ Configure `frps` same as above.
|
|||
|
||||
### P2P Mode
|
||||
|
||||
**xtcp** is designed for transmitting large amounts of data directly between clients. A frps server is still needed, as P2P here only refers the actual data transmission.
|
||||
**xtcp** is designed to transmit large amounts of data directly between clients. A frps server is still needed, as P2P here only refers to the actual data transmission.
|
||||
|
||||
Note it can't penetrate all types of NAT devices. You might want to fallback to **stcp** if **xtcp** doesn't work.
|
||||
Note that it may not work with all types of NAT devices. You might want to fallback to stcp if xtcp doesn't work.
|
||||
|
||||
1. In `frps.ini` configure a UDP port for xtcp:
|
||||
|
||||
|
@ -353,7 +356,7 @@ Note it can't penetrate all types of NAT devices. You might want to fallback to
|
|||
bind_udp_port = 7001
|
||||
```
|
||||
|
||||
2. Start `frpc` on machine B, expose the SSH port. Note that `remote_port` field is removed:
|
||||
2. Start `frpc` on machine B, and expose the SSH port. Note that the `remote_port` field is removed:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
|
@ -368,7 +371,7 @@ Note it can't penetrate all types of NAT devices. You might want to fallback to
|
|||
local_port = 22
|
||||
```
|
||||
|
||||
3. Start another `frpc` (typically on another machine C) with the config to connect to SSH using P2P mode:
|
||||
3. Start another `frpc` (typically on another machine C) with the configuration to connect to SSH using P2P mode:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
|
@ -713,7 +716,6 @@ type = tcp
|
|||
local_port = 22
|
||||
remote_port = 6000
|
||||
bandwidth_limit = 1MB
|
||||
bandwidth_limit_mode = client
|
||||
```
|
||||
|
||||
Set `bandwidth_limit` in each proxy's configure to enable this feature. Supported units are `MB` and `KB`.
|
||||
|
|
|
@ -9,13 +9,15 @@ frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP
|
|||
|
||||
<h3 align="center">Gold Sponsors</h3>
|
||||
<!--gold sponsors start-->
|
||||
|
||||
<p align="center">
|
||||
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||
<img width="300px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
||||
<img width="350px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
||||
</a>
|
||||
<a> </a>
|
||||
<a href="https://asocks.com/c/vDu6Dk" target="_blank">
|
||||
<img width="350px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_asocks.jpg">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<!--gold sponsors end-->
|
||||
|
||||
## 为什么使用 frp ?
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
### New
|
||||
|
||||
* Added config `bandwidth_limit_mode` in frpc, default value is `client` which is current behavior. Optional value is `server`, to enable bandwidth limit in server. The major aim is to let server plugin has the ability to modify bandwidth limit for each proxy.
|
||||
* The `httpconnect` type in `tcpmux` now supports authentication through the parameters `http_user` and `http_pwd`.
|
||||
|
||||
### Improve
|
||||
### Improved
|
||||
|
||||
* `dns_server` supports ipv6.
|
||||
* frpc supports graceful shutdown for protocol `quic`.
|
||||
* The web framework has been upgraded to vue3 + element-plus, and the dashboard has added some information display and supports dark mode.
|
||||
* The e2e testing has been switched to ginkgo v2.
|
||||
|
|
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1 +1,16 @@
|
|||
<!doctype html> <html lang=en> <head> <meta charset=utf-8> <title>frp client admin UI</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?5d5774096cf5c1b4d5af"></script><script type="text/javascript" src="vendor.js?dc42700731a508d39009"></script></body> </html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>frp client admin UI</title>
|
||||
<script type="module" crossorigin src="./index-7dd223da.js"></script>
|
||||
<link rel="stylesheet" href="./index-aa3c7267.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
!function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,c,u){for(var i,a,f,l=0,s=[];l<t.length;l++)a=t[l],o[a]&&s.push(o[a][0]),o[a]=0;for(i in c)Object.prototype.hasOwnProperty.call(c,i)&&(e[i]=c[i]);for(r&&r(t,c,u);s.length;)s.shift()();if(u)for(l=0;l<u.length;l++)f=n(n.s=u[l]);return f};var t={},o={1:0};n.e=function(e){function r(){i.onerror=i.onload=null,clearTimeout(a);var n=o[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}var t=o[e];if(0===t)return new Promise(function(e){e()});if(t)return t[2];var c=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=c;var u=document.getElementsByTagName("head")[0],i=document.createElement("script");i.type="text/javascript",i.charset="utf-8",i.async=!0,i.timeout=12e4,n.nc&&i.setAttribute("nonce",n.nc),i.src=n.p+""+e+".js?"+{0:"dc42700731a508d39009"}[e];var a=setTimeout(r,12e4);return i.onerror=i.onload=r,u.appendChild(i),c},n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n.oe=function(e){throw console.error(e),e}}([]);
|
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1 +1,16 @@
|
|||
<!doctype html> <html lang=en> <head> <meta charset=utf-8> <title>frps dashboard</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?5d154ba4c6b342d8c0c3"></script><script type="text/javascript" src="vendor.js?ddbd1f69fb6e67be4b78"></script></body> </html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>frps dashboard</title>
|
||||
<script type="module" crossorigin src="./index-b8250b3f.js"></script>
|
||||
<link rel="stylesheet" href="./index-7b4711f8.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
!function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,u,c){for(var i,a,f,l=0,s=[];l<t.length;l++)a=t[l],o[a]&&s.push(o[a][0]),o[a]=0;for(i in u)Object.prototype.hasOwnProperty.call(u,i)&&(e[i]=u[i]);for(r&&r(t,u,c);s.length;)s.shift()();if(c)for(l=0;l<c.length;l++)f=n(n.s=c[l]);return f};var t={},o={1:0};n.e=function(e){function r(){i.onerror=i.onload=null,clearTimeout(a);var n=o[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}var t=o[e];if(0===t)return new Promise(function(e){e()});if(t)return t[2];var u=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=u;var c=document.getElementsByTagName("head")[0],i=document.createElement("script");i.type="text/javascript",i.charset="utf-8",i.async=!0,i.timeout=12e4,n.nc&&i.setAttribute("nonce",n.nc),i.src=n.p+""+e+".js?"+{0:"ddbd1f69fb6e67be4b78"}[e];var a=setTimeout(r,12e4);return i.onerror=i.onload=r,c.appendChild(i),u},n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n.oe=function(e){throw console.error(e),e}}([]);
|
File diff suppressed because one or more lines are too long
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/fatedier/frp/client/proxy"
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
)
|
||||
|
||||
type GeneralResponse struct {
|
||||
|
@ -70,15 +71,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
|||
log.Info("success reload conf")
|
||||
}
|
||||
|
||||
type StatusResp struct {
|
||||
TCP []ProxyStatusResp `json:"tcp"`
|
||||
UDP []ProxyStatusResp `json:"udp"`
|
||||
HTTP []ProxyStatusResp `json:"http"`
|
||||
HTTPS []ProxyStatusResp `json:"https"`
|
||||
STCP []ProxyStatusResp `json:"stcp"`
|
||||
XTCP []ProxyStatusResp `json:"xtcp"`
|
||||
SUDP []ProxyStatusResp `json:"sudp"`
|
||||
}
|
||||
type StatusResp map[string][]ProxyStatusResp
|
||||
|
||||
type ProxyStatusResp struct {
|
||||
Name string `json:"name"`
|
||||
|
@ -90,12 +83,6 @@ type ProxyStatusResp struct {
|
|||
RemoteAddr string `json:"remote_addr"`
|
||||
}
|
||||
|
||||
type ByProxyStatusResp []ProxyStatusResp
|
||||
|
||||
func (a ByProxyStatusResp) Len() int { return len(a) }
|
||||
func (a ByProxyStatusResp) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByProxyStatusResp) Less(i, j int) bool { return strings.Compare(a[i].Name, a[j].Name) < 0 }
|
||||
|
||||
func NewProxyStatusResp(status *proxy.WorkingStatus, serverAddr string) ProxyStatusResp {
|
||||
psr := ProxyStatusResp{
|
||||
Name: status.Name,
|
||||
|
@ -103,53 +90,17 @@ func NewProxyStatusResp(status *proxy.WorkingStatus, serverAddr string) ProxySta
|
|||
Status: status.Phase,
|
||||
Err: status.Err,
|
||||
}
|
||||
switch cfg := status.Cfg.(type) {
|
||||
case *config.TCPProxyConf:
|
||||
if cfg.LocalPort != 0 {
|
||||
psr.LocalAddr = net.JoinHostPort(cfg.LocalIP, strconv.Itoa(cfg.LocalPort))
|
||||
}
|
||||
psr.Plugin = cfg.Plugin
|
||||
if status.Err != "" {
|
||||
psr.RemoteAddr = net.JoinHostPort(serverAddr, strconv.Itoa(cfg.RemotePort))
|
||||
} else {
|
||||
psr.RemoteAddr = serverAddr + status.RemoteAddr
|
||||
}
|
||||
case *config.UDPProxyConf:
|
||||
if cfg.LocalPort != 0 {
|
||||
psr.LocalAddr = net.JoinHostPort(cfg.LocalIP, strconv.Itoa(cfg.LocalPort))
|
||||
}
|
||||
if status.Err != "" {
|
||||
psr.RemoteAddr = net.JoinHostPort(serverAddr, strconv.Itoa(cfg.RemotePort))
|
||||
} else {
|
||||
psr.RemoteAddr = serverAddr + status.RemoteAddr
|
||||
}
|
||||
case *config.HTTPProxyConf:
|
||||
if cfg.LocalPort != 0 {
|
||||
psr.LocalAddr = net.JoinHostPort(cfg.LocalIP, strconv.Itoa(cfg.LocalPort))
|
||||
}
|
||||
psr.Plugin = cfg.Plugin
|
||||
baseCfg := status.Cfg.GetBaseInfo()
|
||||
if baseCfg.LocalPort != 0 {
|
||||
psr.LocalAddr = net.JoinHostPort(baseCfg.LocalIP, strconv.Itoa(baseCfg.LocalPort))
|
||||
}
|
||||
psr.Plugin = baseCfg.Plugin
|
||||
|
||||
if status.Err == "" {
|
||||
psr.RemoteAddr = status.RemoteAddr
|
||||
case *config.HTTPSProxyConf:
|
||||
if cfg.LocalPort != 0 {
|
||||
psr.LocalAddr = net.JoinHostPort(cfg.LocalIP, strconv.Itoa(cfg.LocalPort))
|
||||
if util.InSlice(status.Type, []string{"tcp", "udp"}) {
|
||||
psr.RemoteAddr = serverAddr + psr.RemoteAddr
|
||||
}
|
||||
psr.Plugin = cfg.Plugin
|
||||
psr.RemoteAddr = status.RemoteAddr
|
||||
case *config.STCPProxyConf:
|
||||
if cfg.LocalPort != 0 {
|
||||
psr.LocalAddr = net.JoinHostPort(cfg.LocalIP, strconv.Itoa(cfg.LocalPort))
|
||||
}
|
||||
psr.Plugin = cfg.Plugin
|
||||
case *config.XTCPProxyConf:
|
||||
if cfg.LocalPort != 0 {
|
||||
psr.LocalAddr = net.JoinHostPort(cfg.LocalIP, strconv.Itoa(cfg.LocalPort))
|
||||
}
|
||||
psr.Plugin = cfg.Plugin
|
||||
case *config.SUDPProxyConf:
|
||||
if cfg.LocalPort != 0 {
|
||||
psr.LocalAddr = net.JoinHostPort(cfg.LocalIP, strconv.Itoa(cfg.LocalPort))
|
||||
}
|
||||
psr.Plugin = cfg.Plugin
|
||||
}
|
||||
return psr
|
||||
}
|
||||
|
@ -158,15 +109,8 @@ func NewProxyStatusResp(status *proxy.WorkingStatus, serverAddr string) ProxySta
|
|||
func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
buf []byte
|
||||
res StatusResp
|
||||
res StatusResp = make(map[string][]ProxyStatusResp)
|
||||
)
|
||||
res.TCP = make([]ProxyStatusResp, 0)
|
||||
res.UDP = make([]ProxyStatusResp, 0)
|
||||
res.HTTP = make([]ProxyStatusResp, 0)
|
||||
res.HTTPS = make([]ProxyStatusResp, 0)
|
||||
res.STCP = make([]ProxyStatusResp, 0)
|
||||
res.XTCP = make([]ProxyStatusResp, 0)
|
||||
res.SUDP = make([]ProxyStatusResp, 0)
|
||||
|
||||
log.Info("Http request [/api/status]")
|
||||
defer func() {
|
||||
|
@ -177,30 +121,17 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
ps := svr.ctl.pm.GetAllProxyStatus()
|
||||
for _, status := range ps {
|
||||
switch status.Type {
|
||||
case "tcp":
|
||||
res.TCP = append(res.TCP, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
case "udp":
|
||||
res.UDP = append(res.UDP, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
case "http":
|
||||
res.HTTP = append(res.HTTP, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
case "https":
|
||||
res.HTTPS = append(res.HTTPS, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
case "stcp":
|
||||
res.STCP = append(res.STCP, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
case "xtcp":
|
||||
res.XTCP = append(res.XTCP, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
case "sudp":
|
||||
res.SUDP = append(res.SUDP, NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
}
|
||||
res[status.Type] = append(res[status.Type], NewProxyStatusResp(status, svr.cfg.ServerAddr))
|
||||
}
|
||||
|
||||
for _, arrs := range res {
|
||||
if len(arrs) <= 1 {
|
||||
continue
|
||||
}
|
||||
sort.Slice(arrs, func(i, j int) bool {
|
||||
return strings.Compare(arrs[i].Name, arrs[j].Name) < 0
|
||||
})
|
||||
}
|
||||
sort.Sort(ByProxyStatusResp(res.TCP))
|
||||
sort.Sort(ByProxyStatusResp(res.UDP))
|
||||
sort.Sort(ByProxyStatusResp(res.HTTP))
|
||||
sort.Sort(ByProxyStatusResp(res.HTTPS))
|
||||
sort.Sort(ByProxyStatusResp(res.STCP))
|
||||
sort.Sort(ByProxyStatusResp(res.XTCP))
|
||||
sort.Sort(ByProxyStatusResp(res.SUDP))
|
||||
}
|
||||
|
||||
// GET api/config
|
||||
|
|
|
@ -81,67 +81,27 @@ func status(clientCfg config.ClientCommonConf) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res := &client.StatusResp{}
|
||||
res := make(client.StatusResp)
|
||||
err = json.Unmarshal(body, &res)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
||||
}
|
||||
|
||||
fmt.Println("Proxy Status...")
|
||||
if len(res.TCP) > 0 {
|
||||
fmt.Println("TCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.TCP {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
types := []string{"tcp", "udp", "tcpmux", "http", "https", "stcp", "sudp", "xtcp"}
|
||||
for _, pxyType := range types {
|
||||
arrs := res[pxyType]
|
||||
if len(arrs) == 0 {
|
||||
continue
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.UDP) > 0 {
|
||||
fmt.Println("UDP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.UDP {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.HTTP) > 0 {
|
||||
fmt.Println("HTTP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.HTTP {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.HTTPS) > 0 {
|
||||
fmt.Println("HTTPS")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.HTTPS {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.STCP) > 0 {
|
||||
fmt.Println("STCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.STCP {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.XTCP) > 0 {
|
||||
fmt.Println("XTCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.XTCP {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
|
||||
fmt.Println(strings.ToUpper(pxyType))
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range arrs {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
29
go.mod
29
go.mod
|
@ -13,20 +13,20 @@ require (
|
|||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/hashicorp/yamux v0.1.1
|
||||
github.com/onsi/ginkgo v1.16.4
|
||||
github.com/onsi/gomega v1.20.2
|
||||
github.com/onsi/ginkgo/v2 v2.8.3
|
||||
github.com/onsi/gomega v1.27.0
|
||||
github.com/pires/go-proxyproto v0.6.2
|
||||
github.com/prometheus/client_golang v1.13.0
|
||||
github.com/quic-go/quic-go v0.32.0
|
||||
github.com/rodaine/table v1.0.1
|
||||
github.com/spf13/cobra v1.1.3
|
||||
github.com/stretchr/testify v1.7.0
|
||||
golang.org/x/net v0.4.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
golang.org/x/net v0.7.0
|
||||
golang.org/x/oauth2 v0.3.0
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
k8s.io/apimachinery v0.25.0
|
||||
k8s.io/client-go v0.25.0
|
||||
k8s.io/apimachinery v0.26.1
|
||||
k8s.io/client-go v0.26.1
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -34,22 +34,20 @@ require (
|
|||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-playground/locales v0.14.0 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.3 // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.6 // indirect
|
||||
github.com/klauspost/reedsolomon v1.9.15 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
|
@ -64,14 +62,13 @@ require (
|
|||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||
golang.org/x/crypto v0.4.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
|
||||
golang.org/x/mod v0.6.0 // indirect
|
||||
golang.org/x/sys v0.3.0 // indirect
|
||||
golang.org/x/text v0.5.0 // indirect
|
||||
golang.org/x/tools v0.2.0 // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
|
||||
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect
|
||||
)
|
||||
|
|
76
go.sum
76
go.sum
|
@ -127,8 +127,6 @@ github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible h1:
|
|||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
|
@ -141,7 +139,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
|
|||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
|
||||
|
@ -205,8 +204,9 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
|
@ -272,7 +272,6 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
|
|||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
||||
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
|
@ -330,20 +329,11 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
|
|||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
|
||||
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.20.2 h1:8uQq0zMgLEfa0vRrrBgaJF2gyW9Da9BmfGV+OyUzfkY=
|
||||
github.com/onsi/gomega v1.20.2/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc=
|
||||
github.com/onsi/ginkgo/v2 v2.8.3 h1:RpbK1G8nWPNaCVFBWsOGnEQQGgASi6b8fxcWBvDYjxQ=
|
||||
github.com/onsi/ginkgo/v2 v2.8.3/go.mod h1:6OaUA8BCi0aZfmzYT/q9AacwTzDpNbxILUT+TlBq6MY=
|
||||
github.com/onsi/gomega v1.27.0 h1:QLidEla4bXUuZVFa4KX6JHCsuGgbi85LC/pCHrt/O08=
|
||||
github.com/onsi/gomega v1.27.0/go.mod h1:i189pavgK95OSIipFBa74gC2V4qrQuvjuyGEr3GmbXA=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
|
||||
|
@ -414,7 +404,6 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9
|
|||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
|
||||
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
|
||||
|
@ -425,13 +414,16 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
|||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=
|
||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
||||
|
@ -507,11 +499,10 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
|
||||
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
|
||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -537,7 +528,6 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/
|
|||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
|
@ -562,8 +552,8 @@ golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su
|
|||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
|
||||
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -602,7 +592,6 @@ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -615,10 +604,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -642,7 +628,6 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -675,8 +660,8 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -688,8 +673,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
@ -742,7 +727,6 @@ golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82u
|
|||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
|
@ -750,8 +734,8 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
|
||||
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -939,15 +923,12 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
|
|||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
@ -967,13 +948,12 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
|||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/apimachinery v0.25.0 h1:MlP0r6+3XbkUG2itd6vp3oxbtdQLQI94fD5gCS+gnoU=
|
||||
k8s.io/apimachinery v0.25.0/go.mod h1:qMx9eAk0sZQGsXGu86fab8tZdffHbwUfsvzqKn4mfB0=
|
||||
k8s.io/client-go v0.25.0 h1:CVWIaCETLMBNiTUta3d5nzRbXvY5Hy9Dpl+VvREpu5E=
|
||||
k8s.io/client-go v0.25.0/go.mod h1:lxykvypVfKilxhTklov0wz1FoaUZ8X4EwbhS6rpRfN8=
|
||||
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
|
||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4=
|
||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ=
|
||||
k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74=
|
||||
k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU=
|
||||
k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE=
|
||||
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs=
|
||||
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
|
|
@ -5,7 +5,7 @@ ROOT=$(unset CDPATH && cd $(dirname "${BASH_SOURCE[0]}")/.. && pwd)
|
|||
which ginkgo &> /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ginkgo not found, try to install..."
|
||||
go install github.com/onsi/ginkgo/ginkgo@v1.16.5
|
||||
go install github.com/onsi/ginkgo/v2/ginkgo@v2.8.3
|
||||
fi
|
||||
|
||||
debug=false
|
||||
|
@ -17,4 +17,4 @@ if [ x${LOG_LEVEL} != x"" ]; then
|
|||
logLevel=${LOG_LEVEL}
|
||||
fi
|
||||
|
||||
ginkgo -nodes=8 -slowSpecThreshold=20 ${ROOT}/test/e2e -- -frpc-path=${ROOT}/bin/frpc -frps-path=${ROOT}/bin/frps -log-level=${logLevel} -debug=${debug}
|
||||
ginkgo -nodes=8 --poll-progress-after=20s ${ROOT}/test/e2e -- -frpc-path=${ROOT}/bin/frpc -frps-path=${ROOT}/bin/frps -log-level=${logLevel} -debug=${debug}
|
||||
|
|
|
@ -187,6 +187,8 @@ type TCPProxyConf struct {
|
|||
type TCPMuxProxyConf struct {
|
||||
BaseProxyConf `ini:",extends"`
|
||||
DomainConf `ini:",extends"`
|
||||
HTTPUser string `ini:"http_user" json:"http_user,omitempty"`
|
||||
HTTPPwd string `ini:"http_pwd" json:"http_pwd,omitempty"`
|
||||
RouteByHTTPUser string `ini:"route_by_http_user" json:"route_by_http_user"`
|
||||
|
||||
Multiplexer string `ini:"multiplexer"`
|
||||
|
@ -607,7 +609,10 @@ func (cfg *TCPMuxProxyConf) Compare(cmp ProxyConf) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
if cfg.Multiplexer != cmpConf.Multiplexer || cfg.RouteByHTTPUser != cmpConf.RouteByHTTPUser {
|
||||
if cfg.Multiplexer != cmpConf.Multiplexer ||
|
||||
cfg.HTTPUser != cmpConf.HTTPUser ||
|
||||
cfg.HTTPPwd != cmpConf.HTTPPwd ||
|
||||
cfg.RouteByHTTPUser != cmpConf.RouteByHTTPUser {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -632,6 +637,8 @@ func (cfg *TCPMuxProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
|
|||
cfg.CustomDomains = pMsg.CustomDomains
|
||||
cfg.SubDomain = pMsg.SubDomain
|
||||
cfg.Multiplexer = pMsg.Multiplexer
|
||||
cfg.HTTPUser = pMsg.HTTPUser
|
||||
cfg.HTTPPwd = pMsg.HTTPPwd
|
||||
cfg.RouteByHTTPUser = pMsg.RouteByHTTPUser
|
||||
}
|
||||
|
||||
|
@ -642,6 +649,8 @@ func (cfg *TCPMuxProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
|||
pMsg.CustomDomains = cfg.CustomDomains
|
||||
pMsg.SubDomain = cfg.SubDomain
|
||||
pMsg.Multiplexer = cfg.Multiplexer
|
||||
pMsg.HTTPUser = cfg.HTTPUser
|
||||
pMsg.HTTPPwd = cfg.HTTPPwd
|
||||
pMsg.RouteByHTTPUser = cfg.RouteByHTTPUser
|
||||
}
|
||||
|
||||
|
|
|
@ -154,6 +154,8 @@ type ServerCommonConf struct {
|
|||
// If the length of this value is 0, all ports are allowed. By default,
|
||||
// this value is an empty set.
|
||||
AllowPorts map[int]struct{} `ini:"-" json:"-"`
|
||||
// Original string.
|
||||
AllowPortsStr string `ini:"-" json:"-"`
|
||||
// MaxPoolCount specifies the maximum pool size for each proxy. By default,
|
||||
// this value is 5.
|
||||
MaxPoolCount int64 `ini:"max_pool_count" json:"max_pool_count"`
|
||||
|
@ -259,6 +261,7 @@ func UnmarshalServerConfFromIni(source interface{}) (ServerCommonConf, error) {
|
|||
for _, port := range allowPorts {
|
||||
common.AllowPorts[int(port)] = struct{}{}
|
||||
}
|
||||
common.AllowPortsStr = allowPortStr
|
||||
}
|
||||
|
||||
// plugin.xxx
|
||||
|
|
|
@ -134,6 +134,7 @@ func Test_LoadServerCommonConf(t *testing.T) {
|
|||
12: {},
|
||||
99: {},
|
||||
},
|
||||
AllowPortsStr: "10-12,99",
|
||||
MaxPoolCount: 59,
|
||||
MaxPortsPerClient: 9,
|
||||
TLSOnly: true,
|
||||
|
|
|
@ -31,18 +31,21 @@ import (
|
|||
type HTTPConnectTCPMuxer struct {
|
||||
*vhost.Muxer
|
||||
|
||||
passthrough bool
|
||||
authRequired bool // Not supported until we really need this.
|
||||
// If passthrough is set to true, the CONNECT request will be forwarded to the backend service.
|
||||
// Otherwise, it will return an OK response to the client and forward the remaining content to the backend service.
|
||||
passthrough bool
|
||||
}
|
||||
|
||||
func NewHTTPConnectTCPMuxer(listener net.Listener, passthrough bool, timeout time.Duration) (*HTTPConnectTCPMuxer, error) {
|
||||
ret := &HTTPConnectTCPMuxer{passthrough: passthrough, authRequired: false}
|
||||
mux, err := vhost.NewMuxer(listener, ret.getHostFromHTTPConnect, nil, ret.sendConnectResponse, nil, timeout)
|
||||
ret := &HTTPConnectTCPMuxer{passthrough: passthrough}
|
||||
mux, err := vhost.NewMuxer(listener, ret.getHostFromHTTPConnect, timeout)
|
||||
mux.SetCheckAuthFunc(ret.auth).
|
||||
SetSuccessHookFunc(ret.sendConnectResponse)
|
||||
ret.Muxer = mux
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (muxer *HTTPConnectTCPMuxer) readHTTPConnectRequest(rd io.Reader) (host string, httpUser string, err error) {
|
||||
func (muxer *HTTPConnectTCPMuxer) readHTTPConnectRequest(rd io.Reader) (host, httpUser, httpPwd string, err error) {
|
||||
bufioReader := bufio.NewReader(rd)
|
||||
|
||||
req, err := http.ReadRequest(bufioReader)
|
||||
|
@ -58,7 +61,7 @@ func (muxer *HTTPConnectTCPMuxer) readHTTPConnectRequest(rd io.Reader) (host str
|
|||
host, _ = util.CanonicalHost(req.Host)
|
||||
proxyAuth := req.Header.Get("Proxy-Authorization")
|
||||
if proxyAuth != "" {
|
||||
httpUser, _, _ = util.ParseBasicAuth(proxyAuth)
|
||||
httpUser, httpPwd, _ = util.ParseBasicAuth(proxyAuth)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -74,11 +77,26 @@ func (muxer *HTTPConnectTCPMuxer) sendConnectResponse(c net.Conn, reqInfo map[st
|
|||
return res.Write(c)
|
||||
}
|
||||
|
||||
func (muxer *HTTPConnectTCPMuxer) auth(c net.Conn, username, password string, reqInfo map[string]string) (bool, error) {
|
||||
reqUsername := reqInfo["HTTPUser"]
|
||||
reqPassword := reqInfo["HTTPPwd"]
|
||||
if username == reqUsername && password == reqPassword {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
resp := util.ProxyUnauthorizedResponse()
|
||||
if resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
_ = resp.Write(c)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (muxer *HTTPConnectTCPMuxer) getHostFromHTTPConnect(c net.Conn) (net.Conn, map[string]string, error) {
|
||||
reqInfoMap := make(map[string]string, 0)
|
||||
sc, rd := gnet.NewSharedConn(c)
|
||||
|
||||
host, httpUser, err := muxer.readHTTPConnectRequest(rd)
|
||||
host, httpUser, httpPwd, err := muxer.readHTTPConnectRequest(rd)
|
||||
if err != nil {
|
||||
return nil, reqInfoMap, err
|
||||
}
|
||||
|
@ -86,18 +104,11 @@ func (muxer *HTTPConnectTCPMuxer) getHostFromHTTPConnect(c net.Conn) (net.Conn,
|
|||
reqInfoMap["Host"] = host
|
||||
reqInfoMap["Scheme"] = "tcp"
|
||||
reqInfoMap["HTTPUser"] = httpUser
|
||||
reqInfoMap["HTTPPwd"] = httpPwd
|
||||
|
||||
outConn := c
|
||||
if muxer.passthrough {
|
||||
outConn = sc
|
||||
if muxer.authRequired && httpUser == "" {
|
||||
resp := util.ProxyUnauthorizedResponse()
|
||||
if resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
_ = resp.Write(c)
|
||||
outConn = c
|
||||
}
|
||||
}
|
||||
return outConn, reqInfoMap, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package util
|
||||
|
||||
func InSlice[T comparable](v T, s []T) bool {
|
||||
for _, vv := range s {
|
||||
if v == vv {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func InSliceAny[T any](v T, s []T, equalFn func(a, b T) bool) bool {
|
||||
for _, vv := range s {
|
||||
if equalFn(v, vv) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func InSliceAnyFunc[T any](equalFn func(a, b T) bool) func(v T, s []T) bool {
|
||||
return func(v T, s []T) bool {
|
||||
return InSliceAny(v, s, equalFn)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestInSlice(t *testing.T) {
|
||||
require := require.New(t)
|
||||
require.True(InSlice(1, []int{1, 2, 3}))
|
||||
require.False(InSlice(0, []int{1, 2, 3}))
|
||||
require.True(InSlice("foo", []string{"foo", "bar"}))
|
||||
require.False(InSlice("not exist", []string{"foo", "bar"}))
|
||||
}
|
||||
|
||||
type testStructA struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
func TestInSliceAny(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
a := testStructA{Name: "foo", Age: 20}
|
||||
b := testStructA{Name: "foo", Age: 30}
|
||||
c := testStructA{Name: "bar", Age: 20}
|
||||
|
||||
equalFn := func(o, p testStructA) bool {
|
||||
return o.Name == p.Name
|
||||
}
|
||||
require.True(InSliceAny(a, []testStructA{b, c}, equalFn))
|
||||
require.False(InSliceAny(c, []testStructA{a, b}, equalFn))
|
||||
}
|
||||
|
||||
func TestInSliceAnyFunc(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
a := testStructA{Name: "foo", Age: 20}
|
||||
b := testStructA{Name: "foo", Age: 30}
|
||||
c := testStructA{Name: "bar", Age: 20}
|
||||
|
||||
equalFn := func(o, p testStructA) bool {
|
||||
return o.Name == p.Name
|
||||
}
|
||||
testStructAInSlice := InSliceAnyFunc(equalFn)
|
||||
require.True(testStructAInSlice(a, []testStructA{b, c}))
|
||||
require.False(testStructAInSlice(c, []testStructA{a, b}))
|
||||
}
|
|
@ -19,7 +19,7 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
var version = "0.47.0"
|
||||
var version = "0.48.0"
|
||||
|
||||
func Full() string {
|
||||
return version
|
||||
|
|
|
@ -28,7 +28,10 @@ type HTTPSMuxer struct {
|
|||
}
|
||||
|
||||
func NewHTTPSMuxer(listener net.Listener, timeout time.Duration) (*HTTPSMuxer, error) {
|
||||
mux, err := NewMuxer(listener, GetHTTPSHostname, nil, nil, nil, timeout)
|
||||
mux, err := NewMuxer(listener, GetHTTPSHostname, timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &HTTPSMuxer{mux}, err
|
||||
}
|
||||
|
||||
|
|
|
@ -85,17 +85,3 @@ func notFoundResponse() *http.Response {
|
|||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func noAuthResponse() *http.Response {
|
||||
header := make(map[string][]string)
|
||||
header["WWW-Authenticate"] = []string{`Basic realm="Restricted"`}
|
||||
res := &http.Response{
|
||||
Status: "401 Not authorized",
|
||||
StatusCode: 401,
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: header,
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
|
|
@ -43,43 +43,55 @@ type RequestRouteInfo struct {
|
|||
|
||||
type (
|
||||
muxFunc func(net.Conn) (net.Conn, map[string]string, error)
|
||||
httpAuthFunc func(net.Conn, string, string, string) (bool, error)
|
||||
authFunc func(conn net.Conn, username, password string, reqInfoMap map[string]string) (bool, error)
|
||||
hostRewriteFunc func(net.Conn, string) (net.Conn, error)
|
||||
successFunc func(net.Conn, map[string]string) error
|
||||
successHookFunc func(net.Conn, map[string]string) error
|
||||
)
|
||||
|
||||
// Muxer is only used for https and tcpmux proxy.
|
||||
// Muxer is a functional component used for https and tcpmux proxies.
|
||||
// It accepts connections and extracts vhost information from the beginning of the connection data.
|
||||
// It then routes the connection to its appropriate listener.
|
||||
type Muxer struct {
|
||||
listener net.Listener
|
||||
timeout time.Duration
|
||||
listener net.Listener
|
||||
timeout time.Duration
|
||||
|
||||
vhostFunc muxFunc
|
||||
authFunc httpAuthFunc
|
||||
successFunc successFunc
|
||||
rewriteFunc hostRewriteFunc
|
||||
checkAuth authFunc
|
||||
successHook successHookFunc
|
||||
rewriteHost hostRewriteFunc
|
||||
registryRouter *Routers
|
||||
}
|
||||
|
||||
func NewMuxer(
|
||||
listener net.Listener,
|
||||
vhostFunc muxFunc,
|
||||
authFunc httpAuthFunc,
|
||||
successFunc successFunc,
|
||||
rewriteFunc hostRewriteFunc,
|
||||
timeout time.Duration,
|
||||
) (mux *Muxer, err error) {
|
||||
mux = &Muxer{
|
||||
listener: listener,
|
||||
timeout: timeout,
|
||||
vhostFunc: vhostFunc,
|
||||
authFunc: authFunc,
|
||||
successFunc: successFunc,
|
||||
rewriteFunc: rewriteFunc,
|
||||
registryRouter: NewRouters(),
|
||||
}
|
||||
go mux.run()
|
||||
return mux, nil
|
||||
}
|
||||
|
||||
func (v *Muxer) SetCheckAuthFunc(f authFunc) *Muxer {
|
||||
v.checkAuth = f
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *Muxer) SetSuccessHookFunc(f successHookFunc) *Muxer {
|
||||
v.successHook = f
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *Muxer) SetRewriteHostFunc(f hostRewriteFunc) *Muxer {
|
||||
v.rewriteHost = f
|
||||
return v
|
||||
}
|
||||
|
||||
type ChooseEndpointFunc func() (string, error)
|
||||
|
||||
type CreateConnFunc func(remoteAddr string) (net.Conn, error)
|
||||
|
@ -101,7 +113,7 @@ type RouteConfig struct {
|
|||
CreateConnByEndpointFn CreateConnByEndpointFunc
|
||||
}
|
||||
|
||||
// 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 rewriteHost func is not nil,
|
||||
// then rewrite the host header to rewriteHost
|
||||
func (v *Muxer) Listen(ctx context.Context, cfg *RouteConfig) (l *Listener, err error) {
|
||||
l = &Listener{
|
||||
|
@ -109,8 +121,8 @@ func (v *Muxer) Listen(ctx context.Context, cfg *RouteConfig) (l *Listener, err
|
|||
location: cfg.Location,
|
||||
routeByHTTPUser: cfg.RouteByHTTPUser,
|
||||
rewriteHost: cfg.RewriteHost,
|
||||
userName: cfg.Username,
|
||||
passWord: cfg.Password,
|
||||
username: cfg.Username,
|
||||
password: cfg.Password,
|
||||
mux: v,
|
||||
accept: make(chan net.Conn),
|
||||
ctx: ctx,
|
||||
|
@ -205,25 +217,20 @@ func (v *Muxer) handle(c net.Conn) {
|
|||
}
|
||||
|
||||
xl := xlog.FromContextSafe(l.ctx)
|
||||
if v.successFunc != nil {
|
||||
if err := v.successFunc(c, reqInfoMap); err != nil {
|
||||
if v.successHook != nil {
|
||||
if err := v.successHook(c, reqInfoMap); err != nil {
|
||||
xl.Info("success func failure on vhost connection: %v", err)
|
||||
_ = c.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// if authFunc is exist and username/password is set
|
||||
// if checkAuth func is exist and username/password is set
|
||||
// then verify user access
|
||||
if l.mux.authFunc != nil && l.userName != "" && l.passWord != "" {
|
||||
bAccess, err := l.mux.authFunc(c, l.userName, l.passWord, reqInfoMap["Authorization"])
|
||||
if !bAccess || err != nil {
|
||||
xl.Debug("check http Authorization failed")
|
||||
res := noAuthResponse()
|
||||
if res.Body != nil {
|
||||
defer res.Body.Close()
|
||||
}
|
||||
_ = res.Write(c)
|
||||
if l.mux.checkAuth != nil && l.username != "" {
|
||||
ok, err := l.mux.checkAuth(c, l.username, l.password, reqInfoMap)
|
||||
if !ok || err != nil {
|
||||
xl.Debug("auth failed for user: %s", l.username)
|
||||
_ = c.Close()
|
||||
return
|
||||
}
|
||||
|
@ -249,8 +256,8 @@ type Listener struct {
|
|||
location string
|
||||
routeByHTTPUser string
|
||||
rewriteHost string
|
||||
userName string
|
||||
passWord string
|
||||
username string
|
||||
password string
|
||||
mux *Muxer // for closing Muxer
|
||||
accept chan net.Conn
|
||||
ctx context.Context
|
||||
|
@ -263,11 +270,11 @@ func (l *Listener) Accept() (net.Conn, error) {
|
|||
return nil, fmt.Errorf("Listener closed")
|
||||
}
|
||||
|
||||
// if rewriteFunc is exist
|
||||
// if rewriteHost func is exist
|
||||
// rewrite http requests with a modified host header
|
||||
// if l.rewriteHost is empty, nothing to do
|
||||
if l.mux.rewriteFunc != nil {
|
||||
sConn, err := l.mux.rewriteFunc(conn, l.rewriteHost)
|
||||
if l.mux.rewriteHost != nil {
|
||||
sConn, err := l.mux.rewriteHost(conn, l.rewriteHost)
|
||||
if err != nil {
|
||||
xl.Warn("host header rewrite failed: %v", err)
|
||||
return nil, fmt.Errorf("host header rewrite failed")
|
||||
|
|
|
@ -514,7 +514,7 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err
|
|||
|
||||
// NewProxy will return a interface Proxy.
|
||||
// In fact it create different proxies by different proxy type, we just call run() here.
|
||||
pxy, err := proxy.NewProxy(ctl.ctx, userInfo, ctl.rc, ctl.poolCount, ctl.GetWorkConn, pxyConf, ctl.serverCfg)
|
||||
pxy, err := proxy.NewProxy(ctl.ctx, userInfo, ctl.rc, ctl.poolCount, ctl.GetWorkConn, pxyConf, ctl.serverCfg, ctl.loginMsg)
|
||||
if err != nil {
|
||||
return remoteAddr, err
|
||||
}
|
||||
|
|
|
@ -33,16 +33,20 @@ type GeneralResponse struct {
|
|||
}
|
||||
|
||||
type serverInfoResp struct {
|
||||
Version string `json:"version"`
|
||||
BindPort int `json:"bind_port"`
|
||||
BindUDPPort int `json:"bind_udp_port"`
|
||||
VhostHTTPPort int `json:"vhost_http_port"`
|
||||
VhostHTTPSPort int `json:"vhost_https_port"`
|
||||
KCPBindPort int `json:"kcp_bind_port"`
|
||||
SubdomainHost string `json:"subdomain_host"`
|
||||
MaxPoolCount int64 `json:"max_pool_count"`
|
||||
MaxPortsPerClient int64 `json:"max_ports_per_client"`
|
||||
HeartBeatTimeout int64 `json:"heart_beat_timeout"`
|
||||
Version string `json:"version"`
|
||||
BindPort int `json:"bind_port"`
|
||||
BindUDPPort int `json:"bind_udp_port"`
|
||||
VhostHTTPPort int `json:"vhost_http_port"`
|
||||
VhostHTTPSPort int `json:"vhost_https_port"`
|
||||
TCPMuxHTTPConnectPort int `json:"tcpmux_httpconnect_port"`
|
||||
KCPBindPort int `json:"kcp_bind_port"`
|
||||
QUICBindPort int `json:"quic_bind_port"`
|
||||
SubdomainHost string `json:"subdomain_host"`
|
||||
MaxPoolCount int64 `json:"max_pool_count"`
|
||||
MaxPortsPerClient int64 `json:"max_ports_per_client"`
|
||||
HeartBeatTimeout int64 `json:"heart_beat_timeout"`
|
||||
AllowPortsStr string `json:"allow_ports_str,omitempty"`
|
||||
TLSOnly bool `json:"tls_only,omitempty"`
|
||||
|
||||
TotalTrafficIn int64 `json:"total_traffic_in"`
|
||||
TotalTrafficOut int64 `json:"total_traffic_out"`
|
||||
|
@ -70,16 +74,20 @@ func (svr *Service) APIServerInfo(w http.ResponseWriter, r *http.Request) {
|
|||
log.Info("Http request: [%s]", r.URL.Path)
|
||||
serverStats := mem.StatsCollector.GetServer()
|
||||
svrResp := serverInfoResp{
|
||||
Version: version.Full(),
|
||||
BindPort: svr.cfg.BindPort,
|
||||
BindUDPPort: svr.cfg.BindUDPPort,
|
||||
VhostHTTPPort: svr.cfg.VhostHTTPPort,
|
||||
VhostHTTPSPort: svr.cfg.VhostHTTPSPort,
|
||||
KCPBindPort: svr.cfg.KCPBindPort,
|
||||
SubdomainHost: svr.cfg.SubDomainHost,
|
||||
MaxPoolCount: svr.cfg.MaxPoolCount,
|
||||
MaxPortsPerClient: svr.cfg.MaxPortsPerClient,
|
||||
HeartBeatTimeout: svr.cfg.HeartbeatTimeout,
|
||||
Version: version.Full(),
|
||||
BindPort: svr.cfg.BindPort,
|
||||
BindUDPPort: svr.cfg.BindUDPPort,
|
||||
VhostHTTPPort: svr.cfg.VhostHTTPPort,
|
||||
VhostHTTPSPort: svr.cfg.VhostHTTPSPort,
|
||||
TCPMuxHTTPConnectPort: svr.cfg.TCPMuxHTTPConnectPort,
|
||||
KCPBindPort: svr.cfg.KCPBindPort,
|
||||
QUICBindPort: svr.cfg.QUICBindPort,
|
||||
SubdomainHost: svr.cfg.SubDomainHost,
|
||||
MaxPoolCount: svr.cfg.MaxPoolCount,
|
||||
MaxPortsPerClient: svr.cfg.MaxPortsPerClient,
|
||||
HeartBeatTimeout: svr.cfg.HeartbeatTimeout,
|
||||
AllowPortsStr: svr.cfg.AllowPortsStr,
|
||||
TLSOnly: svr.cfg.TLSOnly,
|
||||
|
||||
TotalTrafficIn: serverStats.TotalTrafficIn,
|
||||
TotalTrafficOut: serverStats.TotalTrafficOut,
|
||||
|
@ -157,6 +165,7 @@ func getConfByType(proxyType string) interface{} {
|
|||
type ProxyStatsInfo struct {
|
||||
Name string `json:"name"`
|
||||
Conf interface{} `json:"conf"`
|
||||
ClientVersion string `json:"client_version,omitempty"`
|
||||
TodayTrafficIn int64 `json:"today_traffic_in"`
|
||||
TodayTrafficOut int64 `json:"today_traffic_out"`
|
||||
CurConns int64 `json:"cur_conns"`
|
||||
|
@ -208,6 +217,9 @@ func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxySt
|
|||
continue
|
||||
}
|
||||
proxyInfo.Status = consts.Online
|
||||
if pxy.GetLoginMsg() != nil {
|
||||
proxyInfo.ClientVersion = pxy.GetLoginMsg().Version
|
||||
}
|
||||
} else {
|
||||
proxyInfo.Status = consts.Offline
|
||||
}
|
||||
|
|
|
@ -81,6 +81,8 @@ type TCPMuxGroup struct {
|
|||
groupKey string
|
||||
domain string
|
||||
routeByHTTPUser string
|
||||
username string
|
||||
password string
|
||||
|
||||
acceptCh chan net.Conn
|
||||
tcpMuxLn net.Listener
|
||||
|
@ -120,6 +122,8 @@ func (tmg *TCPMuxGroup) HTTPConnectListen(
|
|||
tmg.groupKey = groupKey
|
||||
tmg.domain = routeConfig.Domain
|
||||
tmg.routeByHTTPUser = routeConfig.RouteByHTTPUser
|
||||
tmg.username = routeConfig.Username
|
||||
tmg.password = routeConfig.Password
|
||||
tmg.tcpMuxLn = tcpMuxLn
|
||||
tmg.lns = append(tmg.lns, ln)
|
||||
if tmg.acceptCh == nil {
|
||||
|
@ -128,7 +132,10 @@ func (tmg *TCPMuxGroup) HTTPConnectListen(
|
|||
go tmg.worker()
|
||||
} else {
|
||||
// route config in the same group must be equal
|
||||
if tmg.group != group || tmg.domain != routeConfig.Domain || tmg.routeByHTTPUser != routeConfig.RouteByHTTPUser {
|
||||
if tmg.group != group || tmg.domain != routeConfig.Domain ||
|
||||
tmg.routeByHTTPUser != routeConfig.RouteByHTTPUser ||
|
||||
tmg.username != routeConfig.Username ||
|
||||
tmg.password != routeConfig.Password {
|
||||
return nil, ErrGroupParamsInvalid
|
||||
}
|
||||
if tmg.groupKey != groupKey {
|
||||
|
|
|
@ -48,6 +48,7 @@ type Proxy interface {
|
|||
GetResourceController() *controller.ResourceController
|
||||
GetUserInfo() plugin.UserInfo
|
||||
GetLimiter() *rate.Limiter
|
||||
GetLoginMsg() *msg.Login
|
||||
Close()
|
||||
}
|
||||
|
||||
|
@ -61,6 +62,7 @@ type BaseProxy struct {
|
|||
serverCfg config.ServerCommonConf
|
||||
limiter *rate.Limiter
|
||||
userInfo plugin.UserInfo
|
||||
loginMsg *msg.Login
|
||||
|
||||
mu sync.RWMutex
|
||||
xl *xlog.Logger
|
||||
|
@ -87,6 +89,10 @@ func (pxy *BaseProxy) GetUserInfo() plugin.UserInfo {
|
|||
return pxy.userInfo
|
||||
}
|
||||
|
||||
func (pxy *BaseProxy) GetLoginMsg() *msg.Login {
|
||||
return pxy.loginMsg
|
||||
}
|
||||
|
||||
func (pxy *BaseProxy) Close() {
|
||||
xl := xlog.FromContextSafe(pxy.ctx)
|
||||
xl.Info("proxy closing")
|
||||
|
@ -188,7 +194,7 @@ func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, net.Conn,
|
|||
}
|
||||
|
||||
func NewProxy(ctx context.Context, userInfo plugin.UserInfo, rc *controller.ResourceController, poolCount int,
|
||||
getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf, serverCfg config.ServerCommonConf,
|
||||
getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf, serverCfg config.ServerCommonConf, loginMsg *msg.Login,
|
||||
) (pxy Proxy, err error) {
|
||||
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(pxyConf.GetBaseInfo().ProxyName)
|
||||
|
||||
|
@ -209,6 +215,7 @@ func NewProxy(ctx context.Context, userInfo plugin.UserInfo, rc *controller.Reso
|
|||
xl: xl,
|
||||
ctx: xlog.NewContext(ctx, xl),
|
||||
userInfo: userInfo,
|
||||
loginMsg: loginMsg,
|
||||
}
|
||||
switch cfg := pxyConf.(type) {
|
||||
case *config.TCPProxyConf:
|
||||
|
|
|
@ -32,12 +32,16 @@ type TCPMuxProxy struct {
|
|||
cfg *config.TCPMuxProxyConf
|
||||
}
|
||||
|
||||
func (pxy *TCPMuxProxy) httpConnectListen(domain, routeByHTTPUser string, addrs []string) ([]string, error) {
|
||||
func (pxy *TCPMuxProxy) httpConnectListen(
|
||||
domain, routeByHTTPUser, httpUser, httpPwd string, addrs []string) ([]string, error,
|
||||
) {
|
||||
var l net.Listener
|
||||
var err error
|
||||
routeConfig := &vhost.RouteConfig{
|
||||
Domain: domain,
|
||||
RouteByHTTPUser: routeByHTTPUser,
|
||||
Username: httpUser,
|
||||
Password: httpPwd,
|
||||
}
|
||||
if pxy.cfg.Group != "" {
|
||||
l, err = pxy.rc.TCPMuxGroupCtl.Listen(pxy.ctx, pxy.cfg.Multiplexer, pxy.cfg.Group, pxy.cfg.GroupKey, *routeConfig)
|
||||
|
@ -60,14 +64,15 @@ func (pxy *TCPMuxProxy) httpConnectRun() (remoteAddr string, err error) {
|
|||
continue
|
||||
}
|
||||
|
||||
addrs, err = pxy.httpConnectListen(domain, pxy.cfg.RouteByHTTPUser, addrs)
|
||||
addrs, err = pxy.httpConnectListen(domain, pxy.cfg.RouteByHTTPUser, pxy.cfg.HTTPUser, pxy.cfg.HTTPPwd, addrs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if pxy.cfg.SubDomain != "" {
|
||||
addrs, err = pxy.httpConnectListen(pxy.cfg.SubDomain+"."+pxy.serverCfg.SubDomainHost, pxy.cfg.RouteByHTTPUser, addrs)
|
||||
addrs, err = pxy.httpConnectListen(pxy.cfg.SubDomain+"."+pxy.serverCfg.SubDomainHost,
|
||||
pxy.cfg.RouteByHTTPUser, pxy.cfg.HTTPUser, pxy.cfg.HTTPPwd, addrs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
"github.com/fatedier/frp/test/e2e/framework"
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
|
||||
"github.com/fatedier/frp/test/e2e/framework"
|
||||
"github.com/fatedier/frp/test/e2e/framework/consts"
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
|
||||
"github.com/fatedier/frp/test/e2e/framework"
|
||||
"github.com/fatedier/frp/test/e2e/framework/consts"
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
|
||||
"github.com/fatedier/frp/test/e2e/framework"
|
||||
"github.com/fatedier/frp/test/e2e/pkg/request"
|
||||
|
|
|
@ -3,7 +3,7 @@ package basic
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
|
||||
"github.com/fatedier/frp/test/e2e/framework"
|
||||
"github.com/fatedier/frp/test/e2e/framework/consts"
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"strconv"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
|
||||
"github.com/fatedier/frp/test/e2e/framework"
|
||||
"github.com/fatedier/frp/test/e2e/framework/consts"
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
|
||||
"github.com/fatedier/frp/test/e2e/framework"
|
||||
"github.com/fatedier/frp/test/e2e/framework/consts"
|
||||
|
|
|
@ -0,0 +1,218 @@
|
|||
package basic
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
"github.com/fatedier/frp/test/e2e/framework"
|
||||
"github.com/fatedier/frp/test/e2e/framework/consts"
|
||||
"github.com/fatedier/frp/test/e2e/mock/server/streamserver"
|
||||
"github.com/fatedier/frp/test/e2e/pkg/request"
|
||||
"github.com/fatedier/frp/test/e2e/pkg/rpc"
|
||||
)
|
||||
|
||||
var _ = ginkgo.Describe("[Feature: TCPMUX httpconnect]", func() {
|
||||
f := framework.NewDefaultFramework()
|
||||
|
||||
getDefaultServerConf := func(httpconnectPort int) string {
|
||||
conf := consts.DefaultServerConfig + `
|
||||
tcpmux_httpconnect_port = %d
|
||||
`
|
||||
return fmt.Sprintf(conf, httpconnectPort)
|
||||
}
|
||||
newServer := func(port int, respContent string) *streamserver.Server {
|
||||
return streamserver.New(
|
||||
streamserver.TCP,
|
||||
streamserver.WithBindPort(port),
|
||||
streamserver.WithRespContent([]byte(respContent)),
|
||||
)
|
||||
}
|
||||
|
||||
proxyURLWithAuth := func(username, password string, port int) string {
|
||||
if username == "" {
|
||||
return fmt.Sprintf("http://127.0.0.1:%d", port)
|
||||
}
|
||||
return fmt.Sprintf("http://%s:%s@127.0.0.1:%d", username, password, port)
|
||||
}
|
||||
|
||||
ginkgo.It("Route by HTTP user", func() {
|
||||
vhostPort := f.AllocPort()
|
||||
serverConf := getDefaultServerConf(vhostPort)
|
||||
|
||||
fooPort := f.AllocPort()
|
||||
f.RunServer("", newServer(fooPort, "foo"))
|
||||
|
||||
barPort := f.AllocPort()
|
||||
f.RunServer("", newServer(barPort, "bar"))
|
||||
|
||||
otherPort := f.AllocPort()
|
||||
f.RunServer("", newServer(otherPort, "other"))
|
||||
|
||||
clientConf := consts.DefaultClientConfig
|
||||
clientConf += fmt.Sprintf(`
|
||||
[foo]
|
||||
type = tcpmux
|
||||
multiplexer = httpconnect
|
||||
local_port = %d
|
||||
custom_domains = normal.example.com
|
||||
route_by_http_user = user1
|
||||
|
||||
[bar]
|
||||
type = tcpmux
|
||||
multiplexer = httpconnect
|
||||
local_port = %d
|
||||
custom_domains = normal.example.com
|
||||
route_by_http_user = user2
|
||||
|
||||
[catchAll]
|
||||
type = tcpmux
|
||||
multiplexer = httpconnect
|
||||
local_port = %d
|
||||
custom_domains = normal.example.com
|
||||
`, fooPort, barPort, otherPort)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
// user1
|
||||
framework.NewRequestExpect(f).Explain("user1").
|
||||
RequestModify(func(r *request.Request) {
|
||||
r.Addr("normal.example.com").Proxy(proxyURLWithAuth("user1", "", vhostPort))
|
||||
}).
|
||||
ExpectResp([]byte("foo")).
|
||||
Ensure()
|
||||
|
||||
// user2
|
||||
framework.NewRequestExpect(f).Explain("user2").
|
||||
RequestModify(func(r *request.Request) {
|
||||
r.Addr("normal.example.com").Proxy(proxyURLWithAuth("user2", "", vhostPort))
|
||||
}).
|
||||
ExpectResp([]byte("bar")).
|
||||
Ensure()
|
||||
|
||||
// other user
|
||||
framework.NewRequestExpect(f).Explain("other user").
|
||||
RequestModify(func(r *request.Request) {
|
||||
r.Addr("normal.example.com").Proxy(proxyURLWithAuth("user3", "", vhostPort))
|
||||
}).
|
||||
ExpectResp([]byte("other")).
|
||||
Ensure()
|
||||
})
|
||||
|
||||
ginkgo.It("Proxy auth", func() {
|
||||
vhostPort := f.AllocPort()
|
||||
serverConf := getDefaultServerConf(vhostPort)
|
||||
|
||||
fooPort := f.AllocPort()
|
||||
f.RunServer("", newServer(fooPort, "foo"))
|
||||
|
||||
clientConf := consts.DefaultClientConfig
|
||||
clientConf += fmt.Sprintf(`
|
||||
[test]
|
||||
type = tcpmux
|
||||
multiplexer = httpconnect
|
||||
local_port = %d
|
||||
custom_domains = normal.example.com
|
||||
http_user = test
|
||||
http_pwd = test
|
||||
`, fooPort)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
// not set auth header
|
||||
framework.NewRequestExpect(f).Explain("no auth").
|
||||
RequestModify(func(r *request.Request) {
|
||||
r.Addr("normal.example.com").Proxy(proxyURLWithAuth("", "", vhostPort))
|
||||
}).
|
||||
ExpectError(true).
|
||||
Ensure()
|
||||
|
||||
// set incorrect auth header
|
||||
framework.NewRequestExpect(f).Explain("incorrect auth").
|
||||
RequestModify(func(r *request.Request) {
|
||||
r.Addr("normal.example.com").Proxy(proxyURLWithAuth("test", "invalid", vhostPort))
|
||||
}).
|
||||
ExpectError(true).
|
||||
Ensure()
|
||||
|
||||
// set correct auth header
|
||||
framework.NewRequestExpect(f).Explain("correct auth").
|
||||
RequestModify(func(r *request.Request) {
|
||||
r.Addr("normal.example.com").Proxy(proxyURLWithAuth("test", "test", vhostPort))
|
||||
}).
|
||||
ExpectResp([]byte("foo")).
|
||||
Ensure()
|
||||
})
|
||||
|
||||
ginkgo.It("TCPMux Passthrough", func() {
|
||||
vhostPort := f.AllocPort()
|
||||
serverConf := getDefaultServerConf(vhostPort)
|
||||
serverConf += `
|
||||
tcpmux_passthrough = true
|
||||
`
|
||||
|
||||
var (
|
||||
respErr error
|
||||
connectRequestHost string
|
||||
)
|
||||
newServer := func(port int) *streamserver.Server {
|
||||
return streamserver.New(
|
||||
streamserver.TCP,
|
||||
streamserver.WithBindPort(port),
|
||||
streamserver.WithCustomHandler(func(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
// read HTTP CONNECT request
|
||||
bufioReader := bufio.NewReader(conn)
|
||||
req, err := http.ReadRequest(bufioReader)
|
||||
if err != nil {
|
||||
respErr = err
|
||||
return
|
||||
}
|
||||
connectRequestHost = req.Host
|
||||
|
||||
// return ok response
|
||||
res := util.OkResponse()
|
||||
if res.Body != nil {
|
||||
defer res.Body.Close()
|
||||
}
|
||||
_ = res.Write(conn)
|
||||
|
||||
buf, err := rpc.ReadBytes(conn)
|
||||
if err != nil {
|
||||
respErr = err
|
||||
return
|
||||
}
|
||||
_, _ = rpc.WriteBytes(conn, buf)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
localPort := f.AllocPort()
|
||||
f.RunServer("", newServer(localPort))
|
||||
|
||||
clientConf := consts.DefaultClientConfig
|
||||
clientConf += fmt.Sprintf(`
|
||||
[test]
|
||||
type = tcpmux
|
||||
multiplexer = httpconnect
|
||||
local_port = %d
|
||||
custom_domains = normal.example.com
|
||||
`, localPort)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
framework.NewRequestExpect(f).
|
||||
RequestModify(func(r *request.Request) {
|
||||
r.Addr("normal.example.com").Proxy(proxyURLWithAuth("", "", vhostPort)).Body([]byte("frp"))
|
||||
}).
|
||||
ExpectResp([]byte("frp")).
|
||||
Ensure()
|
||||
framework.ExpectNoError(respErr)
|
||||
framework.ExpectEqualValues(connectRequestHost, "normal.example.com")
|
||||
})
|
||||
})
|
|
@ -3,8 +3,7 @@ package e2e
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/onsi/ginkgo/config"
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
"github.com/onsi/gomega"
|
||||
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
|
@ -33,9 +32,15 @@ var _ = ginkgo.SynchronizedAfterSuite(func() {
|
|||
func RunE2ETests(t *testing.T) {
|
||||
gomega.RegisterFailHandler(framework.Fail)
|
||||
|
||||
suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration()
|
||||
// Turn on EmitSpecProgress to get spec progress (especially on interrupt)
|
||||
suiteConfig.EmitSpecProgress = true
|
||||
// Randomize specs as well as suites
|
||||
suiteConfig.RandomizeAllSpecs = true
|
||||
|
||||
log.Info("Starting e2e run %q on Ginkgo node %d of total %d",
|
||||
framework.RunID, config.GinkgoConfig.ParallelNode, config.GinkgoConfig.ParallelTotal)
|
||||
ginkgo.RunSpecs(t, "frp e2e suite")
|
||||
framework.RunID, suiteConfig.ParallelProcess, suiteConfig.ParallelTotal)
|
||||
ginkgo.RunSpecs(t, "frp e2e suite", suiteConfig, reporterConfig)
|
||||
}
|
||||
|
||||
// setupSuite is the boilerplate that can be used to setup ginkgo test suites, on the SynchronizedBeforeSuite step.
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"os"
|
||||
"testing"
|
||||
|
||||
_ "github.com/onsi/ginkgo"
|
||||
_ "github.com/onsi/ginkgo/v2"
|
||||
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
// test source
|
||||
|
|
|
@ -3,7 +3,7 @@ package e2e
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
|
||||
"github.com/fatedier/frp/test/e2e/framework"
|
||||
"github.com/fatedier/frp/test/e2e/framework/consts"
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
|
||||
plugin "github.com/fatedier/frp/pkg/plugin/server"
|
||||
"github.com/fatedier/frp/test/e2e/framework"
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
|
||||
"github.com/fatedier/frp/test/e2e/framework"
|
||||
)
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
|
||||
"github.com/fatedier/frp/test/e2e/framework"
|
||||
"github.com/fatedier/frp/test/e2e/framework/consts"
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
|
||||
"github.com/fatedier/frp/test/e2e/framework"
|
||||
)
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
"github.com/fatedier/frp/test/e2e/framework"
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
pp "github.com/pires/go-proxyproto"
|
||||
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
|
|
|
@ -9,8 +9,7 @@ import (
|
|||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/onsi/ginkgo/config"
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
|
||||
"github.com/fatedier/frp/test/e2e/mock/server"
|
||||
"github.com/fatedier/frp/test/e2e/pkg/port"
|
||||
|
@ -63,9 +62,10 @@ type Framework struct {
|
|||
}
|
||||
|
||||
func NewDefaultFramework() *Framework {
|
||||
suiteConfig, _ := ginkgo.GinkgoConfiguration()
|
||||
options := Options{
|
||||
TotalParallelNode: config.GinkgoConfig.ParallelTotal,
|
||||
CurrentNodeIndex: config.GinkgoConfig.ParallelNode,
|
||||
TotalParallelNode: suiteConfig.ParallelTotal,
|
||||
CurrentNodeIndex: suiteConfig.ParallelProcess,
|
||||
FromPortIndex: 20000,
|
||||
ToPortIndex: 50000,
|
||||
}
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
// Package ginkgowrapper wraps Ginkgo Fail and Skip functions to panic
|
||||
// with structured data instead of a constant string.
|
||||
package ginkgowrapper
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
)
|
||||
|
||||
// FailurePanic is the value that will be panicked from Fail.
|
||||
type FailurePanic struct {
|
||||
Message string // The failure message passed to Fail
|
||||
Filename string // The filename that is the source of the failure
|
||||
Line int // The line number of the filename that is the source of the failure
|
||||
FullStackTrace string // A full stack trace starting at the source of the failure
|
||||
}
|
||||
|
||||
// String makes FailurePanic look like the old Ginkgo panic when printed.
|
||||
func (FailurePanic) String() string { return ginkgo.GINKGO_PANIC }
|
||||
|
||||
// Fail wraps ginkgo.Fail so that it panics with more useful
|
||||
// information about the failure. This function will panic with a
|
||||
// FailurePanic.
|
||||
func Fail(message string, callerSkip ...int) {
|
||||
skip := 1
|
||||
if len(callerSkip) > 0 {
|
||||
skip += callerSkip[0]
|
||||
}
|
||||
|
||||
_, file, line, _ := runtime.Caller(skip)
|
||||
fp := FailurePanic{
|
||||
Message: message,
|
||||
Filename: file,
|
||||
Line: line,
|
||||
FullStackTrace: pruneStack(skip),
|
||||
}
|
||||
|
||||
defer func() {
|
||||
e := recover()
|
||||
if e != nil {
|
||||
panic(fp)
|
||||
}
|
||||
}()
|
||||
|
||||
ginkgo.Fail(message, skip)
|
||||
}
|
||||
|
||||
// ginkgo adds a lot of test running infrastructure to the stack, so
|
||||
// we filter those out
|
||||
var stackSkipPattern = regexp.MustCompile(`onsi/ginkgo`)
|
||||
|
||||
func pruneStack(skip int) string {
|
||||
skip += 2 // one for pruneStack and one for debug.Stack
|
||||
stack := debug.Stack()
|
||||
scanner := bufio.NewScanner(bytes.NewBuffer(stack))
|
||||
var prunedStack []string
|
||||
|
||||
// skip the top of the stack
|
||||
for i := 0; i < 2*skip+1; i++ {
|
||||
scanner.Scan()
|
||||
}
|
||||
|
||||
for scanner.Scan() {
|
||||
if stackSkipPattern.Match(scanner.Bytes()) {
|
||||
scanner.Scan() // these come in pairs
|
||||
} else {
|
||||
prunedStack = append(prunedStack, scanner.Text())
|
||||
scanner.Scan() // these come in pairs
|
||||
prunedStack = append(prunedStack, scanner.Text())
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(prunedStack, "\n")
|
||||
}
|
|
@ -1,15 +1,10 @@
|
|||
package framework
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
|
||||
e2eginkgowrapper "github.com/fatedier/frp/test/e2e/framework/ginkgowrapper"
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
)
|
||||
|
||||
func nowStamp() string {
|
||||
|
@ -25,71 +20,14 @@ func Logf(format string, args ...interface{}) {
|
|||
log("INFO", format, args...)
|
||||
}
|
||||
|
||||
// Failf logs the fail info, including a stack trace.
|
||||
// Failf logs the fail info, including a stack trace starts with its direct caller
|
||||
// (for example, for call chain f -> g -> Failf("foo", ...) error would be logged for "g").
|
||||
func Failf(format string, args ...interface{}) {
|
||||
FailfWithOffset(1, format, args...)
|
||||
}
|
||||
|
||||
// FailfWithOffset calls "Fail" and logs the error with a stack trace that starts at "offset" levels above its caller
|
||||
// (for example, for call chain f -> g -> FailfWithOffset(1, ...) error would be logged for "f").
|
||||
func FailfWithOffset(offset int, format string, args ...interface{}) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
skip := offset + 1
|
||||
log("FAIL", "%s\n\nFull Stack Trace\n%s", msg, PrunedStack(skip))
|
||||
e2eginkgowrapper.Fail(nowStamp()+": "+msg, skip)
|
||||
}
|
||||
|
||||
// Fail is a replacement for ginkgo.Fail which logs the problem as it occurs
|
||||
// together with a stack trace and then calls ginkgowrapper.Fail.
|
||||
func Fail(msg string, callerSkip ...int) {
|
||||
skip := 1
|
||||
if len(callerSkip) > 0 {
|
||||
skip += callerSkip[0]
|
||||
}
|
||||
log("FAIL", "%s\n\nFull Stack Trace\n%s", msg, PrunedStack(skip))
|
||||
e2eginkgowrapper.Fail(nowStamp()+": "+msg, skip)
|
||||
ginkgo.Fail(msg, skip)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
var codeFilterRE = regexp.MustCompile(`/github.com/onsi/ginkgo/`)
|
||||
|
||||
// PrunedStack is a wrapper around debug.Stack() that removes information
|
||||
// about the current goroutine and optionally skips some of the initial stack entries.
|
||||
// With skip == 0, the returned stack will start with the caller of PruneStack.
|
||||
// From the remaining entries it automatically filters out useless ones like
|
||||
// entries coming from Ginkgo.
|
||||
//
|
||||
// This is a modified copy of PruneStack in
|
||||
// https://github.com/onsi/ginkgo/blob/f90f37d87fa6b1dd9625e2b1e83c23ffae3de228/internal/codelocation/code_location.go#L25:
|
||||
// - simplified API and thus renamed (calls debug.Stack() instead of taking a parameter)
|
||||
// - source code filtering updated to be specific to Kubernetes
|
||||
// - optimized to use bytes and in-place slice filtering from
|
||||
// https://github.com/golang/go/wiki/SliceTricks#filter-in-place
|
||||
func PrunedStack(skip int) []byte {
|
||||
fullStackTrace := debug.Stack()
|
||||
stack := bytes.Split(fullStackTrace, []byte("\n"))
|
||||
// Ensure that the even entries are the method names and the
|
||||
// odd entries the source code information.
|
||||
if len(stack) > 0 && bytes.HasPrefix(stack[0], []byte("goroutine ")) {
|
||||
// Ignore "goroutine 29 [running]:" line.
|
||||
stack = stack[1:]
|
||||
}
|
||||
// The "+2" is for skipping over:
|
||||
// - runtime/debug.Stack()
|
||||
// - PrunedStack()
|
||||
skip += 2
|
||||
if len(stack) > 2*skip {
|
||||
stack = stack[2*skip:]
|
||||
}
|
||||
n := 0
|
||||
for i := 0; i < len(stack)/2; i++ {
|
||||
// We filter out based on the source code file name.
|
||||
if !codeFilterRE.Match(stack[i*2+1]) {
|
||||
stack[n] = stack[i*2]
|
||||
stack[n+1] = stack[i*2+1]
|
||||
n += 2
|
||||
}
|
||||
}
|
||||
stack = stack[:n]
|
||||
|
||||
return bytes.Join(stack, []byte("\n"))
|
||||
}
|
||||
// Fail is an alias for ginkgo.Fail.
|
||||
var Fail = ginkgo.Fail
|
||||
|
|
|
@ -38,7 +38,7 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
|
|||
err = p.Start()
|
||||
ExpectNoError(err)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
currentClientProcesses := make([]*process.Process, 0, len(clientTemplates))
|
||||
for i := range clientTemplates {
|
||||
|
@ -56,7 +56,7 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
|
|||
ExpectNoError(err)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
return currentServerProcesses, currentClientProcesses
|
||||
}
|
||||
|
|
|
@ -4,8 +4,6 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/onsi/ginkgo/config"
|
||||
)
|
||||
|
||||
type TestContextType struct {
|
||||
|
@ -25,12 +23,6 @@ var TestContext TestContextType
|
|||
// test-specific flags. However, those settings then get added
|
||||
// regardless whether the test is actually in the test suite.
|
||||
func RegisterCommonFlags(flags *flag.FlagSet) {
|
||||
// Turn on EmitSpecProgress to get spec progress (especially on interrupt)
|
||||
config.GinkgoConfig.EmitSpecProgress = true
|
||||
|
||||
// Randomize specs as well as suites
|
||||
config.GinkgoConfig.RandomizeAllSpecs = true
|
||||
|
||||
flags.StringVar(&TestContext.FRPClientPath, "frpc-path", "../../bin/frpc", "The frp client binary to use.")
|
||||
flags.StringVar(&TestContext.FRPServerPath, "frps-path", "../../bin/frps", "The frp server binary to use.")
|
||||
flags.StringVar(&TestContext.LogLevel, "log-level", "debug", "Log level.")
|
||||
|
|
|
@ -10,8 +10,8 @@ import (
|
|||
)
|
||||
|
||||
type Allocator struct {
|
||||
reserved sets.Int
|
||||
used sets.Int
|
||||
reserved sets.Set[int]
|
||||
used sets.Set[int]
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
|
@ -20,8 +20,8 @@ type Allocator struct {
|
|||
// Reserved ports: 13, 17
|
||||
func NewAllocator(from int, to int, mod int, index int) *Allocator {
|
||||
pa := &Allocator{
|
||||
reserved: sets.NewInt(),
|
||||
used: sets.NewInt(),
|
||||
reserved: sets.New[int](),
|
||||
used: sets.New[int](),
|
||||
}
|
||||
|
||||
for i := from; i <= to; i++ {
|
||||
|
|
|
@ -145,7 +145,10 @@ func (r *Request) Do() (*Response, error) {
|
|||
err error
|
||||
)
|
||||
|
||||
addr := net.JoinHostPort(r.addr, strconv.Itoa(r.port))
|
||||
addr := r.addr
|
||||
if r.port > 0 {
|
||||
addr = net.JoinHostPort(r.addr, strconv.Itoa(r.port))
|
||||
}
|
||||
// for protocol http and https
|
||||
if r.protocol == "http" || r.protocol == "https" {
|
||||
return r.sendHTTPRequest(r.method, fmt.Sprintf("%s://%s%s", r.protocol, addr, r.path),
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
|
@ -22,6 +23,9 @@ func ReadBytes(r io.Reader) ([]byte, error) {
|
|||
if err := binary.Read(r, binary.BigEndian, &length); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if length < 0 || length > 10*1024*1024 {
|
||||
return nil, fmt.Errorf("invalid length")
|
||||
}
|
||||
buffer := make([]byte, length)
|
||||
n, err := io.ReadFull(r, buffer)
|
||||
if err != nil {
|
||||
|
|
|
@ -39,43 +39,15 @@ func (c *Client) GetProxyStatus(name string) (*client.ProxyStatusResp, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allStatus := &client.StatusResp{}
|
||||
allStatus := make(client.StatusResp)
|
||||
if err = json.Unmarshal([]byte(content), &allStatus); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(content))
|
||||
}
|
||||
for _, s := range allStatus.TCP {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.UDP {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.HTTP {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.HTTPS {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.STCP {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.XTCP {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.SUDP {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
for _, pss := range allStatus {
|
||||
for _, ps := range pss {
|
||||
if ps.Name == name {
|
||||
return &ps, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("no proxy status found")
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
"github.com/fatedier/frp/test/e2e/framework"
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
|
||||
plugin "github.com/fatedier/frp/pkg/plugin/server"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"presets": [
|
||||
["es2015", { "modules": false }]
|
||||
],
|
||||
"plugins": [
|
||||
[
|
||||
"component",
|
||||
{
|
||||
"libraryName": "element-ui",
|
||||
"styleLibraryName": "theme-chalk"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/* eslint-env node */
|
||||
require('@rushstack/eslint-patch/modern-module-resolution')
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [
|
||||
'plugin:vue/vue3-essential',
|
||||
'eslint:recommended',
|
||||
'@vue/eslint-config-typescript',
|
||||
'@vue/eslint-config-prettier',
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'vue/multi-word-component-names': [
|
||||
'error',
|
||||
{
|
||||
ignores: ['Overview'],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
|
@ -1,6 +1,28 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
node_modules/
|
||||
dist/
|
||||
npm-debug.log
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.vscode/settings.json
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
|
@ -1,6 +1,13 @@
|
|||
.PHONY: dist build
|
||||
.PHONY: dist build preview lint
|
||||
|
||||
build:
|
||||
@npm run build
|
||||
|
||||
dev:
|
||||
dev:
|
||||
@npm run dev
|
||||
|
||||
preview:
|
||||
@npm run preview
|
||||
|
||||
lint:
|
||||
@npm run lint
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# frpc-dashboard
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
yarn install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
make dev
|
||||
```
|
||||
|
||||
### Type-Check, Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
make build
|
||||
```
|
||||
|
||||
### Lint with [ESLint](https://eslint.org/)
|
||||
|
||||
```sh
|
||||
make lint
|
||||
```
|
|
@ -0,0 +1,8 @@
|
|||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-auto-import
|
||||
export {}
|
||||
declare global {
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
import '@vue/runtime-core'
|
||||
|
||||
export {}
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
ClientConfigure: typeof import('./src/components/ClientConfigure.vue')['default']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
Overview: typeof import('./src/components/Overview.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
/// <reference types="vite/client" />
|
|
@ -8,8 +8,7 @@
|
|||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<!--<script src="https://code.jquery.com/jquery-3.2.0.min.js"></script>-->
|
||||
<!--<script src="//cdn.bootcss.com/echarts/3.4.0/echarts.min.js"></script>-->
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,46 +1,35 @@
|
|||
{
|
||||
"name": "frpc-web",
|
||||
"description": "An admin web ui for frp client.",
|
||||
"author": "fatedier",
|
||||
"name": "-frpc-dashboard",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "webpack-dev-server -d --inline --hot --env.dev",
|
||||
"build": "rimraf dist && webpack -p --progress --hide-modules"
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check build-only",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --noEmit",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
|
||||
},
|
||||
"dependencies": {
|
||||
"element-ui": "^2.5.3",
|
||||
"vue": "^2.5.22",
|
||||
"vue-resource": "^1.5.1",
|
||||
"vue-router": "^3.0.2",
|
||||
"whatwg-fetch": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
"element-plus": "^2.2.28",
|
||||
"vue": "^3.2.45",
|
||||
"vue-router": "^4.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^9.4.7",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-loader": "^7.1.5",
|
||||
"babel-plugin-component": "^1.1.1",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"css-loader": "^2.1.0",
|
||||
"eslint": "^5.12.1",
|
||||
"eslint-config-enough": "^0.3.4",
|
||||
"eslint-loader": "^2.1.1",
|
||||
"file-loader": "^3.0.1",
|
||||
"html-loader": "^0.5.5",
|
||||
"html-webpack-plugin": "^2.24.1",
|
||||
"less": "^3.9.0",
|
||||
"less-loader": "^4.1.0",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"rimraf": "^2.6.3",
|
||||
"style-loader": "^0.23.1",
|
||||
"url-loader": "^1.1.2",
|
||||
"vue-loader": "^15.6.2",
|
||||
"vue-template-compiler": "^2.5.22",
|
||||
"webpack": "^2.7.0",
|
||||
"webpack-cli": "^3.2.1",
|
||||
"webpack-dev-server": "^3.1.14"
|
||||
"@rushstack/eslint-patch": "^1.1.4",
|
||||
"@types/node": "^18.11.12",
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"@vue/eslint-config-prettier": "^7.0.0",
|
||||
"@vue/eslint-config-typescript": "^11.0.0",
|
||||
"@vue/tsconfig": "^0.1.3",
|
||||
"eslint": "^8.22.0",
|
||||
"eslint-plugin-vue": "^9.3.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "~4.7.4",
|
||||
"unplugin-auto-import": "^0.14.3",
|
||||
"unplugin-vue-components": "^0.24.0",
|
||||
"vite": "^4.0.0",
|
||||
"vue-tsc": "^1.0.12"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
module.exports = {
|
||||
plugins: [
|
||||
require('autoprefixer')()
|
||||
]
|
||||
}
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
|
@ -1,73 +1,116 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<header class="grid-content header-color">
|
||||
<el-row>
|
||||
<a class="brand" href="#">frp client</a>
|
||||
</el-row>
|
||||
</header>
|
||||
<section>
|
||||
<el-row>
|
||||
<el-col id="side-nav" :xs="24" :md="4">
|
||||
<el-menu default-active="1" mode="vertical" theme="light" router="false" @select="handleSelect">
|
||||
<el-menu-item index="/">Overview</el-menu-item>
|
||||
<el-menu-item index="/configure">Configure</el-menu-item>
|
||||
<el-menu-item index="">Help</el-menu-item>
|
||||
</el-menu>
|
||||
</el-col>
|
||||
<div id="app">
|
||||
<header class="grid-content header-color">
|
||||
<div class="header-content">
|
||||
<div class="brand">
|
||||
<a href="#">frp client</a>
|
||||
</div>
|
||||
<div class="dark-switch">
|
||||
<el-switch
|
||||
v-model="darkmodeSwitch"
|
||||
inline-prompt
|
||||
active-text="Dark"
|
||||
inactive-text="Light"
|
||||
@change="toggleDark"
|
||||
style="
|
||||
--el-switch-on-color: #444452;
|
||||
--el-switch-off-color: #589ef8;
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<section>
|
||||
<el-row>
|
||||
<el-col id="side-nav" :xs="24" :md="4">
|
||||
<el-menu
|
||||
default-active="1"
|
||||
mode="vertical"
|
||||
theme="light"
|
||||
router="false"
|
||||
@select="handleSelect"
|
||||
>
|
||||
<el-menu-item index="/">Overview</el-menu-item>
|
||||
<el-menu-item index="/configure">Configure</el-menu-item>
|
||||
<el-menu-item index="">Help</el-menu-item>
|
||||
</el-menu>
|
||||
</el-col>
|
||||
|
||||
<el-col :xs="24" :md="20">
|
||||
<div id="content">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</section>
|
||||
<footer></footer>
|
||||
</div>
|
||||
<el-col :xs="24" :md="20">
|
||||
<div id="content">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</section>
|
||||
<footer></footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
handleSelect(key, path) {
|
||||
if (key == '') {
|
||||
window.open("https://github.com/fatedier/frp")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useDark, useToggle } from '@vueuse/core'
|
||||
|
||||
const isDark = useDark()
|
||||
const darkmodeSwitch = ref(isDark)
|
||||
const toggleDark = useToggle(isDark)
|
||||
|
||||
const handleSelect = (key: string) => {
|
||||
if (key == '') {
|
||||
window.open('https://github.com/fatedier/frp')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
background-color: #fafafa;
|
||||
margin: 0px;
|
||||
font-family: -apple-system,BlinkMacSystemFont,Helvetica Neue,sans-serif;
|
||||
}
|
||||
|
||||
header {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.header-color {
|
||||
background: #58B7FF;
|
||||
}
|
||||
|
||||
#content {
|
||||
margin-top: 20px;
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
.brand {
|
||||
color: #fff;
|
||||
background-color: transparent;
|
||||
margin-left: 20px;
|
||||
float: left;
|
||||
line-height: 25px;
|
||||
font-size: 25px;
|
||||
padding: 15px 15px;
|
||||
height: 30px;
|
||||
text-decoration: none;
|
||||
}
|
||||
body {
|
||||
margin: 0px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, sans-serif;
|
||||
}
|
||||
|
||||
header {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.header-color {
|
||||
background: #58b7ff;
|
||||
}
|
||||
|
||||
html.dark .header-color {
|
||||
background: #395c74;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#content {
|
||||
margin-top: 20px;
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.brand a {
|
||||
color: #fff;
|
||||
background-color: transparent;
|
||||
margin-left: 20px;
|
||||
line-height: 25px;
|
||||
font-size: 25px;
|
||||
padding: 15px 15px;
|
||||
height: 30px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.dark-switch {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
flex-grow: 1;
|
||||
padding-right: 40px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
html.dark {
|
||||
--el-bg-color: #343432;
|
||||
--el-fill-color-blank: #343432;
|
||||
background-color: #343432;
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-row id="head">
|
||||
<el-button type="primary" @click="fetchData">Refresh</el-button>
|
||||
<el-button type="primary" @click="uploadConfig">Upload</el-button>
|
||||
</el-row>
|
||||
<el-input
|
||||
type="textarea"
|
||||
autosize
|
||||
v-model="textarea"
|
||||
placeholder="frpc configrue file, can not be empty..."
|
||||
></el-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
let textarea = ref('')
|
||||
|
||||
const fetchData = () => {
|
||||
fetch('/api/config', { credentials: 'include' })
|
||||
.then((res) => {
|
||||
return res.text()
|
||||
})
|
||||
.then((text) => {
|
||||
textarea.value = text
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
showClose: true,
|
||||
message: 'Get configure content from frpc failed!',
|
||||
type: 'warning',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const uploadConfig = () => {
|
||||
ElMessageBox.confirm(
|
||||
'This operation will upload your frpc configure file content and hot reload it, do you want to continue?',
|
||||
'Notice',
|
||||
{
|
||||
confirmButtonText: 'Yes',
|
||||
cancelButtonText: 'No',
|
||||
type: 'warning',
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
if (textarea.value == '') {
|
||||
ElMessage({
|
||||
message: 'Configure content can not be empty!',
|
||||
type: 'warning',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
fetch('/api/config', {
|
||||
credentials: 'include',
|
||||
method: 'PUT',
|
||||
body: textarea.value,
|
||||
})
|
||||
.then(() => {
|
||||
fetch('/api/reload', { credentials: 'include' })
|
||||
.then(() => {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: 'Success',
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
ElMessage({
|
||||
showClose: true,
|
||||
message: 'Reload frpc configure file error, ' + err,
|
||||
type: 'warning',
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
showClose: true,
|
||||
message: 'Put config to frpc and hot reload failed!',
|
||||
type: 'warning',
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
message: 'Canceled',
|
||||
type: 'info',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fetchData()
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#head {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
|
@ -1,93 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-row id="head">
|
||||
<el-button type="primary" @click="fetchData">Refresh</el-button>
|
||||
<el-button type="primary" @click="uploadConfig">Upload</el-button>
|
||||
</el-row>
|
||||
<el-input type="textarea" autosize v-model="textarea" placeholder="frpc configrue file, can not be empty..."></el-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
textarea: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchData()
|
||||
},
|
||||
watch: {
|
||||
'$route': 'fetchData'
|
||||
},
|
||||
methods: {
|
||||
fetchData() {
|
||||
fetch('/api/config', {credentials: 'include'})
|
||||
.then(res => {
|
||||
return res.text()
|
||||
}).then(text => {
|
||||
this.textarea= text
|
||||
}).catch( err => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: 'Get configure content from frpc failed!',
|
||||
type: 'warning'
|
||||
})
|
||||
})
|
||||
},
|
||||
uploadConfig() {
|
||||
this.$confirm('This operation will upload your frpc configure file content and hot reload it, do you want to continue?', 'Notice', {
|
||||
confirmButtonText: 'Yes',
|
||||
cancelButtonText: 'No',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
if (this.textarea == "") {
|
||||
this.$message({
|
||||
type: 'warning',
|
||||
message: 'Configure content can not be empty!'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
fetch('/api/config', {
|
||||
credentials: 'include',
|
||||
method: 'PUT',
|
||||
body: this.textarea,
|
||||
}).then(() => {
|
||||
fetch('/api/reload', {credentials: 'include'})
|
||||
.then(() => {
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: 'Success'
|
||||
})
|
||||
}).catch(err => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: 'Reload frpc configure file error, ' + err,
|
||||
type: 'warning'
|
||||
})
|
||||
})
|
||||
}).catch(err => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: 'Put config to frpc and hot reload failed!',
|
||||
type: 'warning'
|
||||
})
|
||||
})
|
||||
}).catch(() => {
|
||||
this.$message({
|
||||
type: 'info',
|
||||
message: 'Canceled'
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#head {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
|
@ -1,75 +1,85 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-row>
|
||||
<el-col :md="24">
|
||||
<div>
|
||||
<el-table :data="status" stripe style="width: 100%" :default-sort="{prop: 'type', order: 'ascending'}">
|
||||
<el-table-column prop="name" label="name"></el-table-column>
|
||||
<el-table-column prop="type" label="type" width="150"></el-table-column>
|
||||
<el-table-column prop="local_addr" label="local address" width="200"></el-table-column>
|
||||
<el-table-column prop="plugin" label="plugin" width="200"></el-table-column>
|
||||
<el-table-column prop="remote_addr" label="remote address"></el-table-column>
|
||||
<el-table-column prop="status" label="status" width="150"></el-table-column>
|
||||
<el-table-column prop="err" label="info"></el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div>
|
||||
<el-row>
|
||||
<el-col :md="24">
|
||||
<div>
|
||||
<el-table
|
||||
:data="status"
|
||||
stripe
|
||||
style="width: 100%"
|
||||
:default-sort="{ prop: 'type', order: 'ascending' }"
|
||||
>
|
||||
<el-table-column
|
||||
prop="name"
|
||||
label="name"
|
||||
sortable
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="type"
|
||||
label="type"
|
||||
width="150"
|
||||
sortable
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="local_addr"
|
||||
label="local address"
|
||||
width="200"
|
||||
sortable
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="plugin"
|
||||
label="plugin"
|
||||
width="200"
|
||||
sortable
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="remote_addr"
|
||||
label="remote address"
|
||||
sortable
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
prop="status"
|
||||
label="status"
|
||||
width="150"
|
||||
sortable
|
||||
></el-table-column>
|
||||
<el-table-column prop="err" label="info"></el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
status: new Array(),
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchData()
|
||||
},
|
||||
watch: {
|
||||
'$route': 'fetchData'
|
||||
},
|
||||
methods: {
|
||||
fetchData() {
|
||||
fetch('/api/status', {credentials: 'include'})
|
||||
.then(res => {
|
||||
return res.json()
|
||||
}).then(json => {
|
||||
this.status = new Array()
|
||||
for (let s of json.tcp) {
|
||||
this.status.push(s)
|
||||
}
|
||||
for (let s of json.udp) {
|
||||
this.status.push(s)
|
||||
}
|
||||
for (let s of json.http) {
|
||||
this.status.push(s)
|
||||
}
|
||||
for (let s of json.https) {
|
||||
this.status.push(s)
|
||||
}
|
||||
for (let s of json.stcp) {
|
||||
this.status.push(s)
|
||||
}
|
||||
for (let s of json.sudp) {
|
||||
this.status.push(s)
|
||||
}
|
||||
for (let s of json.xtcp) {
|
||||
this.status.push(s)
|
||||
}
|
||||
}).catch( err => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: 'Get status info from frpc failed!',
|
||||
type: 'warning'
|
||||
})
|
||||
})
|
||||
}
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
let status = ref<any[]>([])
|
||||
|
||||
const fetchData = () => {
|
||||
fetch('/api/status', { credentials: 'include' })
|
||||
.then((res) => {
|
||||
return res.json()
|
||||
})
|
||||
.then((json) => {
|
||||
status.value = new Array()
|
||||
for (let key in json) {
|
||||
for (let ps of json[key]) {
|
||||
console.log(ps)
|
||||
status.value.push(ps)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
ElMessage({
|
||||
showClose: true,
|
||||
message: 'Get status info from frpc failed!' + err,
|
||||
type: 'warning',
|
||||
})
|
||||
})
|
||||
}
|
||||
fetchData()
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
import Vue from 'vue'
|
||||
// import ElementUI from 'element-ui'
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
FormItem,
|
||||
Row,
|
||||
Col,
|
||||
Table,
|
||||
TableColumn,
|
||||
Menu,
|
||||
MenuItem,
|
||||
MessageBox,
|
||||
Message,
|
||||
Input
|
||||
} from 'element-ui'
|
||||
import lang from 'element-ui/lib/locale/lang/en'
|
||||
import locale from 'element-ui/lib/locale'
|
||||
import 'element-ui/lib/theme-chalk/index.css'
|
||||
import './utils/less/custom.less'
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import 'whatwg-fetch'
|
||||
|
||||
locale.use(lang)
|
||||
|
||||
Vue.use(Button)
|
||||
Vue.use(Form)
|
||||
Vue.use(FormItem)
|
||||
Vue.use(Row)
|
||||
Vue.use(Col)
|
||||
Vue.use(Table)
|
||||
Vue.use(TableColumn)
|
||||
Vue.use(Menu)
|
||||
Vue.use(MenuItem)
|
||||
Vue.use(Input)
|
||||
|
||||
Vue.prototype.$msgbox = MessageBox;
|
||||
Vue.prototype.$confirm = MessageBox.confirm
|
||||
Vue.prototype.$message = Message
|
||||
|
||||
//Vue.use(ElementUI)
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
new Vue({
|
||||
el: '#app',
|
||||
router,
|
||||
template: '<App/>',
|
||||
components: { App }
|
||||
})
|
|
@ -0,0 +1,13 @@
|
|||
import { createApp } from 'vue'
|
||||
import 'element-plus/dist/index.css'
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
import './assets/dark.css'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(router)
|
||||
|
||||
app.mount('#app')
|
|
@ -1,18 +0,0 @@
|
|||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
import Overview from '../components/Overview.vue'
|
||||
import Configure from '../components/Configure.vue'
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
export default new Router({
|
||||
routes: [{
|
||||
path: '/',
|
||||
name: 'Overview',
|
||||
component: Overview
|
||||
},{
|
||||
path: '/configure',
|
||||
name: 'Configure',
|
||||
component: Configure,
|
||||
}]
|
||||
})
|
|
@ -0,0 +1,21 @@
|
|||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
import Overview from '../components/Overview.vue'
|
||||
import ClientConfigure from '../components/ClientConfigure.vue'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Overview',
|
||||
component: Overview,
|
||||
},
|
||||
{
|
||||
path: '/configure',
|
||||
name: 'ClientConfigure',
|
||||
component: ClientConfigure,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export default router
|
|
@ -1,22 +0,0 @@
|
|||
@color: red;
|
||||
|
||||
.el-form-item {
|
||||
span {
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.demo-table-expand {
|
||||
font-size: 0;
|
||||
|
||||
label {
|
||||
width: 90px;
|
||||
color: #99a9bf;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
width: 50%;
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
class ProxyStatus {
|
||||
constructor(status) {
|
||||
this.name = status.name
|
||||
this.type = status.type
|
||||
this.status = status.status
|
||||
this.err = status.err
|
||||
this.local_addr = status.local_addr
|
||||
this.plugin = status.plugin
|
||||
this.remote_addr = status.remote_addr
|
||||
}
|
||||
}
|
||||
|
||||
export {ProxyStatus}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.node.json",
|
||||
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.web.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.config.json"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
base: '',
|
||||
plugins: [
|
||||
vue(),
|
||||
AutoImport({
|
||||
resolvers: [ElementPlusResolver()],
|
||||
}),
|
||||
Components({
|
||||
resolvers: [ElementPlusResolver()],
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
},
|
||||
},
|
||||
build: {
|
||||
assetsDir: '',
|
||||
},
|
||||
})
|
|
@ -1,107 +0,0 @@
|
|||
const path = require('path')
|
||||
var webpack = require('webpack')
|
||||
var HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
var VueLoaderPlugin = require('vue-loader/lib/plugin')
|
||||
var url = require('url')
|
||||
var publicPath = ''
|
||||
|
||||
module.exports = (options = {}) => ({
|
||||
entry: {
|
||||
vendor: './src/main'
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: options.dev ? '[name].js' : '[name].js?[chunkhash]',
|
||||
chunkFilename: '[id].js?[chunkhash]',
|
||||
publicPath: options.dev ? '/assets/' : publicPath
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.vue', '.json'],
|
||||
alias: {
|
||||
'vue$': 'vue/dist/vue.esm.js',
|
||||
'@': path.resolve(__dirname, 'src'),
|
||||
}
|
||||
},
|
||||
module: {
|
||||
rules: [{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader'
|
||||
}, {
|
||||
test: /\.js$/,
|
||||
use: ['babel-loader'],
|
||||
exclude: /node_modules/
|
||||
}, {
|
||||
test: /\.html$/,
|
||||
use: [{
|
||||
loader: 'html-loader',
|
||||
options: {
|
||||
root: path.resolve(__dirname, 'src'),
|
||||
attrs: ['img:src', 'link:href']
|
||||
}
|
||||
}]
|
||||
}, {
|
||||
test: /\.less$/,
|
||||
loader: 'style-loader!css-loader!postcss-loader!less-loader'
|
||||
}, {
|
||||
test: /\.css$/,
|
||||
use: ['style-loader', 'css-loader', 'postcss-loader']
|
||||
}, {
|
||||
test: /favicon\.png$/,
|
||||
use: [{
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: '[name].[ext]?[hash]'
|
||||
}
|
||||
}]
|
||||
}, {
|
||||
test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2|svg|svgz)(\?.+)?$/,
|
||||
exclude: /favicon\.png$/,
|
||||
use: [{
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000
|
||||
}
|
||||
}]
|
||||
}]
|
||||
},
|
||||
plugins: [
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
names: ['vendor', 'manifest']
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
favicon: 'src/assets/favicon.ico',
|
||||
template: 'src/index.html'
|
||||
}),
|
||||
new webpack.NormalModuleReplacementPlugin(/element-ui[\/\\]lib[\/\\]locale[\/\\]lang[\/\\]zh-CN/, 'element-ui/lib/locale/lang/en'),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: '"production"'
|
||||
}
|
||||
}),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
sourceMap: false,
|
||||
comments: false,
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
}),
|
||||
new VueLoaderPlugin()
|
||||
],
|
||||
devServer: {
|
||||
host: '127.0.0.1',
|
||||
port: 8010,
|
||||
proxy: {
|
||||
'/api/': {
|
||||
target: 'http://127.0.0.1:8080',
|
||||
changeOrigin: true,
|
||||
pathRewrite: {
|
||||
'^/api': ''
|
||||
}
|
||||
}
|
||||
},
|
||||
historyApiFallback: {
|
||||
index: url.parse(options.dev ? '/assets/' : publicPath).pathname
|
||||
}
|
||||
}//,
|
||||
//devtool: options.dev ? '#eval-source-map' : '#source-map'
|
||||
})
|
7633
web/frpc/yarn.lock
7633
web/frpc/yarn.lock
File diff suppressed because it is too large
Load Diff
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"presets": [
|
||||
["es2015", { "modules": false }]
|
||||
],
|
||||
"plugins": [
|
||||
[
|
||||
"component",
|
||||
{
|
||||
"libraryName": "element-ui",
|
||||
"styleLibraryName": "theme-chalk"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/* eslint-env node */
|
||||
require('@rushstack/eslint-patch/modern-module-resolution')
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [
|
||||
'plugin:vue/vue3-essential',
|
||||
'eslint:recommended',
|
||||
'@vue/eslint-config-typescript',
|
||||
'@vue/eslint-config-prettier',
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'vue/multi-word-component-names': [
|
||||
'error',
|
||||
{
|
||||
ignores: ['Traffic'],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
|
@ -1,6 +1,28 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
node_modules/
|
||||
dist/
|
||||
npm-debug.log
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.vscode/settings.json
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue