Browse Source

Merge pull request #3348 from fatedier/dev

bump version
fatedier 1 year ago
parent
commit
8fb99ef7a9
100 changed files with 3439 additions and 5744 deletions
  1. 3 1
      Makefile.cross-compiles
  2. 48 46
      README.md
  3. 5 3
      README_zh.md
  4. 4 4
      Release.md
  5. BIN
      assets/frpc/static/535877f50039c0cb49a6196a5b7517cd.woff
  6. BIN
      assets/frpc/static/732389ded34cb9c52dd88271f1345af9.ttf
  7. 0 0
      assets/frpc/static/index-7dd223da.js
  8. 0 0
      assets/frpc/static/index-aa3c7267.css
  9. 16 1
      assets/frpc/static/index.html
  10. 0 1
      assets/frpc/static/manifest.js
  11. 0 0
      assets/frpc/static/vendor.js
  12. BIN
      assets/frps/static/535877f50039c0cb49a6196a5b7517cd.woff
  13. BIN
      assets/frps/static/732389ded34cb9c52dd88271f1345af9.ttf
  14. 0 0
      assets/frps/static/index-7b4711f8.css
  15. 0 0
      assets/frps/static/index-b8250b3f.js
  16. 16 1
      assets/frps/static/index.html
  17. 0 1
      assets/frps/static/manifest.js
  18. 0 0
      assets/frps/static/vendor.js
  19. 21 90
      client/admin_api.go
  20. 9 49
      cmd/frpc/sub/status.go
  21. BIN
      doc/pic/sponsor_asocks.jpg
  22. 13 16
      go.mod
  23. 28 48
      go.sum
  24. 2 2
      hack/run-e2e.sh
  25. 10 1
      pkg/config/proxy.go
  26. 3 0
      pkg/config/server.go
  27. 1 0
      pkg/config/server_test.go
  28. 26 15
      pkg/util/tcpmux/httpconnect.go
  29. 25 0
      pkg/util/util/slice.go
  30. 49 0
      pkg/util/util/slice_test.go
  31. 1 1
      pkg/util/version/version.go
  32. 4 1
      pkg/util/vhost/https.go
  33. 0 14
      pkg/util/vhost/resource.go
  34. 41 34
      pkg/util/vhost/vhost.go
  35. 1 1
      server/control.go
  36. 32 20
      server/dashboard_api.go
  37. 8 1
      server/group/tcpmux.go
  38. 8 1
      server/proxy/proxy.go
  39. 8 3
      server/proxy/tcpmux.go
  40. 1 1
      test/e2e/basic/basic.go
  41. 1 1
      test/e2e/basic/client.go
  42. 1 1
      test/e2e/basic/client_server.go
  43. 1 1
      test/e2e/basic/cmd.go
  44. 1 1
      test/e2e/basic/config.go
  45. 1 1
      test/e2e/basic/http.go
  46. 1 1
      test/e2e/basic/server.go
  47. 218 0
      test/e2e/basic/tcpmux.go
  48. 9 4
      test/e2e/e2e.go
  49. 1 1
      test/e2e/e2e_test.go
  50. 1 1
      test/e2e/examples.go
  51. 1 1
      test/e2e/features/bandwidth_limit.go
  52. 1 1
      test/e2e/features/chaos.go
  53. 1 1
      test/e2e/features/group.go
  54. 1 1
      test/e2e/features/heartbeat.go
  55. 1 1
      test/e2e/features/monitor.go
  56. 1 1
      test/e2e/features/real_ip.go
  57. 4 4
      test/e2e/framework/framework.go
  58. 0 80
      test/e2e/framework/ginkgowrapper/wrapper.go
  59. 7 69
      test/e2e/framework/log.go
  60. 2 2
      test/e2e/framework/process.go
  61. 0 8
      test/e2e/framework/test_context.go
  62. 4 4
      test/e2e/pkg/port/port.go
  63. 4 1
      test/e2e/pkg/request/request.go
  64. 4 0
      test/e2e/pkg/rpc/rpc.go
  65. 6 34
      test/e2e/pkg/sdk/client/client.go
  66. 1 1
      test/e2e/plugin/client.go
  67. 1 1
      test/e2e/plugin/server.go
  68. 0 14
      web/frpc/.babelrc
  69. 30 0
      web/frpc/.eslintrc.cjs
  70. 26 4
      web/frpc/.gitignore
  71. 5 0
      web/frpc/.prettierrc.json
  72. 9 2
      web/frpc/Makefile
  73. 25 0
      web/frpc/README.md
  74. 8 0
      web/frpc/auto-imports.d.ts
  75. 26 0
      web/frpc/components.d.ts
  76. 1 0
      web/frpc/env.d.ts
  77. 1 2
      web/frpc/index.html
  78. 26 37
      web/frpc/package.json
  79. 0 5
      web/frpc/postcss.config.js
  80. 0 0
      web/frpc/public/favicon.ico
  81. 108 65
      web/frpc/src/App.vue
  82. 5 0
      web/frpc/src/assets/dark.css
  83. 102 0
      web/frpc/src/components/ClientConfigure.vue
  84. 0 93
      web/frpc/src/components/Configure.vue
  85. 79 69
      web/frpc/src/components/Overview.vue
  86. 0 52
      web/frpc/src/main.js
  87. 13 0
      web/frpc/src/main.ts
  88. 0 18
      web/frpc/src/router/index.js
  89. 21 0
      web/frpc/src/router/index.ts
  90. 0 22
      web/frpc/src/utils/less/custom.less
  91. 0 13
      web/frpc/src/utils/status.js
  92. 8 0
      web/frpc/tsconfig.config.json
  93. 16 0
      web/frpc/tsconfig.json
  94. 29 0
      web/frpc/vite.config.ts
  95. 0 107
      web/frpc/webpack.config.js
  96. 2183 4645
      web/frpc/yarn.lock
  97. 0 14
      web/frps/.babelrc
  98. 30 0
      web/frps/.eslintrc.cjs
  99. 26 4
      web/frps/.gitignore
  100. 5 0
      web/frps/.prettierrc.json

+ 3 - 1
Makefile.cross-compiles

@@ -2,7 +2,7 @@ export PATH := $(GOPATH)/bin:$(PATH)
 export GO111MODULE=on
 export GO111MODULE=on
 LDFLAGS := -s -w
 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
 all: build
 
 
@@ -23,3 +23,5 @@ app:
 	@mv ./release/frps_windows_386 ./release/frps_windows_386.exe
 	@mv ./release/frps_windows_386 ./release/frps_windows_386.exe
 	@mv ./release/frpc_windows_amd64 ./release/frpc_windows_amd64.exe
 	@mv ./release/frpc_windows_amd64 ./release/frpc_windows_amd64.exe
 	@mv ./release/frps_windows_amd64 ./release/frps_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

+ 48 - 46
README.md

@@ -8,10 +8,13 @@
 
 
 <h3 align="center">Gold Sponsors</h3>
 <h3 align="center">Gold Sponsors</h3>
 <!--gold sponsors start-->
 <!--gold sponsors start-->
-
 <p align="center">
 <p align="center">
   <a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
   <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>&nbsp</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>
   </a>
 </p>
 </p>
 
 
@@ -19,9 +22,9 @@
 
 
 ## What is frp?
 ## 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
 ## Table of Contents
 
 
@@ -30,12 +33,12 @@ frp also has a P2P connect mode.
 * [Development Status](#development-status)
 * [Development Status](#development-status)
 * [Architecture](#architecture)
 * [Architecture](#architecture)
 * [Example Usage](#example-usage)
 * [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)
     * [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)
     * [Expose your service privately](#expose-your-service-privately)
     * [P2P Mode](#p2p-mode)
     * [P2P Mode](#p2p-mode)
 * [Features](#features)
 * [Features](#features)
@@ -86,11 +89,11 @@ frp also has a P2P connect mode.
 
 
 ## Development Status
 ## 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
 ## Architecture
 
 
@@ -98,15 +101,15 @@ We will switch v0 to v1 at the right time and only accept bug fixes and improvem
 
 
 ## Example Usage
 ## 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
   ```ini
   # frps.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`
   `./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
   ```ini
   # frpc.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
   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:
 4. Start `frpc` on server B:
 
 
   `./frpc -c ./frpc.ini`
   `./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`
   `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
   ```ini
   # frps.ini
   # frps.ini
@@ -162,7 +165,7 @@ However, we can expose an HTTP(S) service using frp.
 
 
   `./frps -c ./frps.ini`
   `./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
   ```ini
   # frpc.ini
   # frpc.ini
@@ -180,11 +183,11 @@ However, we can expose an HTTP(S) service using frp.
 
 
   `./frpc -c ./frpc.ini`
   `./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`:
 1. Modify `frps.ini`:
 
 
@@ -198,7 +201,7 @@ However, we can expose an HTTP(S) service using frp.
 
 
   `./frps -c ./frps.ini`
   `./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
   ```ini
   # frpc.ini
   # frpc.ini
@@ -217,17 +220,17 @@ However, we can expose an HTTP(S) service using frp.
 
 
   `./frpc -c ./frpc.ini`
   `./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`
   `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.
 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
   ```ini
   # frpc.ini
   # frpc.ini
@@ -242,17 +245,17 @@ Configure `frps` same as above.
   plugin_unix_path = /var/run/docker.sock
   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`
   `curl http://x.x.x.x:6000/version`
 
 
 ### Expose a simple HTTP file server
 ### 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
   ```ini
   # frpc.ini
   # frpc.ini
@@ -270,13 +273,13 @@ Configure `frps` same as above.
   plugin_http_passwd = abc
   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.
 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
   ```ini
   # frpc.ini
   # frpc.ini
@@ -300,7 +303,7 @@ You may substitute `https2https` for the plugin, and point the `plugin_local_add
 
 
 ### Expose your service privately
 ### 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.
 Configure `frps` same as above.
 
 
@@ -342,9 +345,9 @@ Configure `frps` same as above.
 
 
 ### P2P Mode
 ### 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:
 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
   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
   ```ini
   # frpc.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
   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
   ```ini
   # frpc.ini
   # frpc.ini
@@ -713,7 +716,6 @@ type = tcp
 local_port = 22
 local_port = 22
 remote_port = 6000
 remote_port = 6000
 bandwidth_limit = 1MB
 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`.
 Set `bandwidth_limit` in each proxy's configure to enable this feature. Supported units are `MB` and `KB`.

+ 5 - 3
README_zh.md

@@ -9,13 +9,15 @@ frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP
 
 
 <h3 align="center">Gold Sponsors</h3>
 <h3 align="center">Gold Sponsors</h3>
 <!--gold sponsors start-->
 <!--gold sponsors start-->
-
 <p align="center">
 <p align="center">
   <a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
   <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>&nbsp</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>
   </a>
 </p>
 </p>
-
 <!--gold sponsors end-->
 <!--gold sponsors end-->
 
 
 ## 为什么使用 frp ?
 ## 为什么使用 frp ?

+ 4 - 4
Release.md

@@ -1,8 +1,8 @@
 ### New
 ### 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.

BIN
assets/frpc/static/535877f50039c0cb49a6196a5b7517cd.woff


BIN
assets/frpc/static/732389ded34cb9c52dd88271f1345af9.ttf


File diff suppressed because it is too large
+ 0 - 0
assets/frpc/static/index-7dd223da.js


File diff suppressed because it is too large
+ 0 - 0
assets/frpc/static/index-aa3c7267.css


+ 16 - 1
assets/frpc/static/index.html

@@ -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>

+ 0 - 1
assets/frpc/static/manifest.js

@@ -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 it is too large
+ 0 - 0
assets/frpc/static/vendor.js


BIN
assets/frps/static/535877f50039c0cb49a6196a5b7517cd.woff


BIN
assets/frps/static/732389ded34cb9c52dd88271f1345af9.ttf


File diff suppressed because it is too large
+ 0 - 0
assets/frps/static/index-7b4711f8.css


File diff suppressed because it is too large
+ 0 - 0
assets/frps/static/index-b8250b3f.js


+ 16 - 1
assets/frps/static/index.html

@@ -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>

+ 0 - 1
assets/frps/static/manifest.js

@@ -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 it is too large
+ 0 - 0
assets/frps/static/vendor.js


+ 21 - 90
client/admin_api.go

@@ -28,6 +28,7 @@ import (
 	"github.com/fatedier/frp/client/proxy"
 	"github.com/fatedier/frp/client/proxy"
 	"github.com/fatedier/frp/pkg/config"
 	"github.com/fatedier/frp/pkg/config"
 	"github.com/fatedier/frp/pkg/util/log"
 	"github.com/fatedier/frp/pkg/util/log"
+	"github.com/fatedier/frp/pkg/util/util"
 )
 )
 
 
 type GeneralResponse struct {
 type GeneralResponse struct {
@@ -70,15 +71,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
 	log.Info("success reload conf")
 	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 {
 type ProxyStatusResp struct {
 	Name       string `json:"name"`
 	Name       string `json:"name"`
@@ -90,12 +83,6 @@ type ProxyStatusResp struct {
 	RemoteAddr string `json:"remote_addr"`
 	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 {
 func NewProxyStatusResp(status *proxy.WorkingStatus, serverAddr string) ProxyStatusResp {
 	psr := ProxyStatusResp{
 	psr := ProxyStatusResp{
 		Name:   status.Name,
 		Name:   status.Name,
@@ -103,53 +90,17 @@ func NewProxyStatusResp(status *proxy.WorkingStatus, serverAddr string) ProxySta
 		Status: status.Phase,
 		Status: status.Phase,
 		Err:    status.Err,
 		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
-		psr.RemoteAddr = status.RemoteAddr
-	case *config.HTTPSProxyConf:
-		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
 		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))
+		if util.InSlice(status.Type, []string{"tcp", "udp"}) {
+			psr.RemoteAddr = serverAddr + psr.RemoteAddr
 		}
 		}
-		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
 	return psr
 }
 }
@@ -158,15 +109,8 @@ func NewProxyStatusResp(status *proxy.WorkingStatus, serverAddr string) ProxySta
 func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
 func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
 	var (
 	var (
 		buf []byte
 		buf []byte
-		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]")
 	log.Info("Http request [/api/status]")
 	defer func() {
 	defer func() {
@@ -177,30 +121,17 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
 
 
 	ps := svr.ctl.pm.GetAllProxyStatus()
 	ps := svr.ctl.pm.GetAllProxyStatus()
 	for _, status := range ps {
 	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
 // GET api/config

+ 9 - 49
cmd/frpc/sub/status.go

@@ -81,67 +81,27 @@ func status(clientCfg config.ClientCommonConf) error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	res := &client.StatusResp{}
+	res := make(client.StatusResp)
 	err = json.Unmarshal(body, &res)
 	err = json.Unmarshal(body, &res)
 	if err != nil {
 	if err != nil {
 		return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
 		return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
 	}
 	}
 
 
 	fmt.Println("Proxy Status...")
 	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)
-		}
-		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)
+	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.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")
+
+		fmt.Println(strings.ToUpper(pxyType))
 		tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
 		tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
-		for _, ps := range res.XTCP {
+		for _, ps := range arrs {
 			tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
 			tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
 		}
 		}
 		tbl.Print()
 		tbl.Print()
 		fmt.Println("")
 		fmt.Println("")
 	}
 	}
-
 	return nil
 	return nil
 }
 }

BIN
doc/pic/sponsor_asocks.jpg


+ 13 - 16
go.mod

@@ -13,20 +13,20 @@ require (
 	github.com/gorilla/mux v1.8.0
 	github.com/gorilla/mux v1.8.0
 	github.com/gorilla/websocket v1.5.0
 	github.com/gorilla/websocket v1.5.0
 	github.com/hashicorp/yamux v0.1.1
 	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/pires/go-proxyproto v0.6.2
 	github.com/prometheus/client_golang v1.13.0
 	github.com/prometheus/client_golang v1.13.0
 	github.com/quic-go/quic-go v0.32.0
 	github.com/quic-go/quic-go v0.32.0
 	github.com/rodaine/table v1.0.1
 	github.com/rodaine/table v1.0.1
 	github.com/spf13/cobra v1.1.3
 	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/oauth2 v0.3.0
 	golang.org/x/time v0.0.0-20220210224613-90d013bbcef8
 	golang.org/x/time v0.0.0-20220210224613-90d013bbcef8
 	gopkg.in/ini.v1 v1.67.0
 	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 (
 require (
@@ -34,22 +34,20 @@ require (
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/cespare/xxhash/v2 v2.1.2 // indirect
 	github.com/cespare/xxhash/v2 v2.1.2 // indirect
 	github.com/davecgh/go-spew v1.1.1 // 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/locales v0.14.0 // indirect
 	github.com/go-playground/universal-translator v0.18.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/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
 	github.com/golang/mock v1.6.0 // indirect
 	github.com/golang/mock v1.6.0 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/golang/snappy v0.0.3 // 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/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 	github.com/klauspost/cpuid/v2 v2.0.6 // indirect
 	github.com/klauspost/cpuid/v2 v2.0.6 // indirect
 	github.com/klauspost/reedsolomon v1.9.15 // indirect
 	github.com/klauspost/reedsolomon v1.9.15 // indirect
 	github.com/leodido/go-urn v1.2.1 // indirect
 	github.com/leodido/go-urn v1.2.1 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.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/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/prometheus/client_model v0.2.0 // indirect
 	github.com/prometheus/client_model v0.2.0 // indirect
@@ -64,14 +62,13 @@ require (
 	github.com/tjfoc/gmsm v1.4.1 // indirect
 	github.com/tjfoc/gmsm v1.4.1 // indirect
 	golang.org/x/crypto v0.4.0 // indirect
 	golang.org/x/crypto v0.4.0 // indirect
 	golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // 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/appengine v1.6.7 // indirect
 	google.golang.org/protobuf v1.28.1 // indirect
 	google.golang.org/protobuf v1.28.1 // indirect
 	gopkg.in/square/go-jose.v2 v2.6.0 // 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
 	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
 )
 )

+ 28 - 48
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/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/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.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/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 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=
 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.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.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
 github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
 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 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
 github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
 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=
 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.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.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.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.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/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 v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 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/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 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
 github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
 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-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/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 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/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-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/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/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/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/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
 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/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/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.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/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 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
 github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
 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/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.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.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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 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.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.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.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.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/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 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=
 github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
 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.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.1/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.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-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-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-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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/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-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-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-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-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-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20200707034311-ab3426394381/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-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-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.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-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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/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-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-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-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-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-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-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-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-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-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-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-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/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-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-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-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-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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/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-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-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.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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 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=
 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.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/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.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-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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/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-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-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-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.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.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 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.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.4/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.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-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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 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/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.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 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
 gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 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/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 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
 gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 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.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.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/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-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.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/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/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/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

+ 2 - 2
hack/run-e2e.sh

@@ -5,7 +5,7 @@ ROOT=$(unset CDPATH && cd $(dirname "${BASH_SOURCE[0]}")/.. && pwd)
 which ginkgo &> /dev/null
 which ginkgo &> /dev/null
 if [ $? -ne 0 ]; then
 if [ $? -ne 0 ]; then
     echo "ginkgo not found, try to install..."
     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
 fi
 
 
 debug=false
 debug=false
@@ -17,4 +17,4 @@ if [ x${LOG_LEVEL} != x"" ]; then
     logLevel=${LOG_LEVEL}
     logLevel=${LOG_LEVEL}
 fi
 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}

+ 10 - 1
pkg/config/proxy.go

@@ -187,6 +187,8 @@ type TCPProxyConf struct {
 type TCPMuxProxyConf struct {
 type TCPMuxProxyConf struct {
 	BaseProxyConf   `ini:",extends"`
 	BaseProxyConf   `ini:",extends"`
 	DomainConf      `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"`
 	RouteByHTTPUser string `ini:"route_by_http_user" json:"route_by_http_user"`
 
 
 	Multiplexer string `ini:"multiplexer"`
 	Multiplexer string `ini:"multiplexer"`
@@ -607,7 +609,10 @@ func (cfg *TCPMuxProxyConf) Compare(cmp ProxyConf) bool {
 		return false
 		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
 		return false
 	}
 	}
 
 
@@ -632,6 +637,8 @@ func (cfg *TCPMuxProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) {
 	cfg.CustomDomains = pMsg.CustomDomains
 	cfg.CustomDomains = pMsg.CustomDomains
 	cfg.SubDomain = pMsg.SubDomain
 	cfg.SubDomain = pMsg.SubDomain
 	cfg.Multiplexer = pMsg.Multiplexer
 	cfg.Multiplexer = pMsg.Multiplexer
+	cfg.HTTPUser = pMsg.HTTPUser
+	cfg.HTTPPwd = pMsg.HTTPPwd
 	cfg.RouteByHTTPUser = pMsg.RouteByHTTPUser
 	cfg.RouteByHTTPUser = pMsg.RouteByHTTPUser
 }
 }
 
 
@@ -642,6 +649,8 @@ func (cfg *TCPMuxProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
 	pMsg.CustomDomains = cfg.CustomDomains
 	pMsg.CustomDomains = cfg.CustomDomains
 	pMsg.SubDomain = cfg.SubDomain
 	pMsg.SubDomain = cfg.SubDomain
 	pMsg.Multiplexer = cfg.Multiplexer
 	pMsg.Multiplexer = cfg.Multiplexer
+	pMsg.HTTPUser = cfg.HTTPUser
+	pMsg.HTTPPwd = cfg.HTTPPwd
 	pMsg.RouteByHTTPUser = cfg.RouteByHTTPUser
 	pMsg.RouteByHTTPUser = cfg.RouteByHTTPUser
 }
 }
 
 

+ 3 - 0
pkg/config/server.go

@@ -154,6 +154,8 @@ type ServerCommonConf struct {
 	// If the length of this value is 0, all ports are allowed. By default,
 	// If the length of this value is 0, all ports are allowed. By default,
 	// this value is an empty set.
 	// this value is an empty set.
 	AllowPorts map[int]struct{} `ini:"-" json:"-"`
 	AllowPorts map[int]struct{} `ini:"-" json:"-"`
+	// Original string.
+	AllowPortsStr string `ini:"-" json:"-"`
 	// MaxPoolCount specifies the maximum pool size for each proxy. By default,
 	// MaxPoolCount specifies the maximum pool size for each proxy. By default,
 	// this value is 5.
 	// this value is 5.
 	MaxPoolCount int64 `ini:"max_pool_count" json:"max_pool_count"`
 	MaxPoolCount int64 `ini:"max_pool_count" json:"max_pool_count"`
@@ -259,6 +261,7 @@ func UnmarshalServerConfFromIni(source interface{}) (ServerCommonConf, error) {
 		for _, port := range allowPorts {
 		for _, port := range allowPorts {
 			common.AllowPorts[int(port)] = struct{}{}
 			common.AllowPorts[int(port)] = struct{}{}
 		}
 		}
+		common.AllowPortsStr = allowPortStr
 	}
 	}
 
 
 	// plugin.xxx
 	// plugin.xxx

+ 1 - 0
pkg/config/server_test.go

@@ -134,6 +134,7 @@ func Test_LoadServerCommonConf(t *testing.T) {
 					12: {},
 					12: {},
 					99: {},
 					99: {},
 				},
 				},
+				AllowPortsStr:           "10-12,99",
 				MaxPoolCount:            59,
 				MaxPoolCount:            59,
 				MaxPortsPerClient:       9,
 				MaxPortsPerClient:       9,
 				TLSOnly:                 true,
 				TLSOnly:                 true,

+ 26 - 15
pkg/util/tcpmux/httpconnect.go

@@ -31,18 +31,21 @@ import (
 type HTTPConnectTCPMuxer struct {
 type HTTPConnectTCPMuxer struct {
 	*vhost.Muxer
 	*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) {
 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
 	ret.Muxer = mux
 	return ret, err
 	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)
 	bufioReader := bufio.NewReader(rd)
 
 
 	req, err := http.ReadRequest(bufioReader)
 	req, err := http.ReadRequest(bufioReader)
@@ -58,7 +61,7 @@ func (muxer *HTTPConnectTCPMuxer) readHTTPConnectRequest(rd io.Reader) (host str
 	host, _ = util.CanonicalHost(req.Host)
 	host, _ = util.CanonicalHost(req.Host)
 	proxyAuth := req.Header.Get("Proxy-Authorization")
 	proxyAuth := req.Header.Get("Proxy-Authorization")
 	if proxyAuth != "" {
 	if proxyAuth != "" {
-		httpUser, _, _ = util.ParseBasicAuth(proxyAuth)
+		httpUser, httpPwd, _ = util.ParseBasicAuth(proxyAuth)
 	}
 	}
 	return
 	return
 }
 }
@@ -74,11 +77,26 @@ func (muxer *HTTPConnectTCPMuxer) sendConnectResponse(c net.Conn, reqInfo map[st
 	return res.Write(c)
 	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) {
 func (muxer *HTTPConnectTCPMuxer) getHostFromHTTPConnect(c net.Conn) (net.Conn, map[string]string, error) {
 	reqInfoMap := make(map[string]string, 0)
 	reqInfoMap := make(map[string]string, 0)
 	sc, rd := gnet.NewSharedConn(c)
 	sc, rd := gnet.NewSharedConn(c)
 
 
-	host, httpUser, err := muxer.readHTTPConnectRequest(rd)
+	host, httpUser, httpPwd, err := muxer.readHTTPConnectRequest(rd)
 	if err != nil {
 	if err != nil {
 		return nil, reqInfoMap, err
 		return nil, reqInfoMap, err
 	}
 	}
@@ -86,18 +104,11 @@ func (muxer *HTTPConnectTCPMuxer) getHostFromHTTPConnect(c net.Conn) (net.Conn,
 	reqInfoMap["Host"] = host
 	reqInfoMap["Host"] = host
 	reqInfoMap["Scheme"] = "tcp"
 	reqInfoMap["Scheme"] = "tcp"
 	reqInfoMap["HTTPUser"] = httpUser
 	reqInfoMap["HTTPUser"] = httpUser
+	reqInfoMap["HTTPPwd"] = httpPwd
 
 
 	outConn := c
 	outConn := c
 	if muxer.passthrough {
 	if muxer.passthrough {
 		outConn = sc
 		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
 	return outConn, reqInfoMap, nil
 }
 }

+ 25 - 0
pkg/util/util/slice.go

@@ -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)
+	}
+}

+ 49 - 0
pkg/util/util/slice_test.go

@@ -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}))
+}

+ 1 - 1
pkg/util/version/version.go

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

+ 4 - 1
pkg/util/vhost/https.go

@@ -28,7 +28,10 @@ type HTTPSMuxer struct {
 }
 }
 
 
 func NewHTTPSMuxer(listener net.Listener, timeout time.Duration) (*HTTPSMuxer, error) {
 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
 	return &HTTPSMuxer{mux}, err
 }
 }
 
 

+ 0 - 14
pkg/util/vhost/resource.go

@@ -85,17 +85,3 @@ func notFoundResponse() *http.Response {
 	}
 	}
 	return res
 	return res
 }
 }
-
-func noAuthResponse() *http.Response {
-	header := make(map[string][]string)
-	header["WWW-Authenticate"] = []string{`Basic realm="Restricted"`}
-	res := &http.Response{
-		Status:     "401 Not authorized",
-		StatusCode: 401,
-		Proto:      "HTTP/1.1",
-		ProtoMajor: 1,
-		ProtoMinor: 1,
-		Header:     header,
-	}
-	return res
-}

+ 41 - 34
pkg/util/vhost/vhost.go

@@ -43,43 +43,55 @@ type RequestRouteInfo struct {
 
 
 type (
 type (
 	muxFunc         func(net.Conn) (net.Conn, map[string]string, error)
 	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)
 	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 {
 type Muxer struct {
-	listener       net.Listener
-	timeout        time.Duration
+	listener net.Listener
+	timeout  time.Duration
+
 	vhostFunc      muxFunc
 	vhostFunc      muxFunc
-	authFunc       httpAuthFunc
-	successFunc    successFunc
-	rewriteFunc    hostRewriteFunc
+	checkAuth      authFunc
+	successHook    successHookFunc
+	rewriteHost    hostRewriteFunc
 	registryRouter *Routers
 	registryRouter *Routers
 }
 }
 
 
 func NewMuxer(
 func NewMuxer(
 	listener net.Listener,
 	listener net.Listener,
 	vhostFunc muxFunc,
 	vhostFunc muxFunc,
-	authFunc httpAuthFunc,
-	successFunc successFunc,
-	rewriteFunc hostRewriteFunc,
 	timeout time.Duration,
 	timeout time.Duration,
 ) (mux *Muxer, err error) {
 ) (mux *Muxer, err error) {
 	mux = &Muxer{
 	mux = &Muxer{
 		listener:       listener,
 		listener:       listener,
 		timeout:        timeout,
 		timeout:        timeout,
 		vhostFunc:      vhostFunc,
 		vhostFunc:      vhostFunc,
-		authFunc:       authFunc,
-		successFunc:    successFunc,
-		rewriteFunc:    rewriteFunc,
 		registryRouter: NewRouters(),
 		registryRouter: NewRouters(),
 	}
 	}
 	go mux.run()
 	go mux.run()
 	return mux, nil
 	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 ChooseEndpointFunc func() (string, error)
 
 
 type CreateConnFunc func(remoteAddr string) (net.Conn, error)
 type CreateConnFunc func(remoteAddr string) (net.Conn, error)
@@ -101,7 +113,7 @@ type RouteConfig struct {
 	CreateConnByEndpointFn CreateConnByEndpointFunc
 	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
 // then rewrite the host header to rewriteHost
 func (v *Muxer) Listen(ctx context.Context, cfg *RouteConfig) (l *Listener, err error) {
 func (v *Muxer) Listen(ctx context.Context, cfg *RouteConfig) (l *Listener, err error) {
 	l = &Listener{
 	l = &Listener{
@@ -109,8 +121,8 @@ func (v *Muxer) Listen(ctx context.Context, cfg *RouteConfig) (l *Listener, err
 		location:        cfg.Location,
 		location:        cfg.Location,
 		routeByHTTPUser: cfg.RouteByHTTPUser,
 		routeByHTTPUser: cfg.RouteByHTTPUser,
 		rewriteHost:     cfg.RewriteHost,
 		rewriteHost:     cfg.RewriteHost,
-		userName:        cfg.Username,
-		passWord:        cfg.Password,
+		username:        cfg.Username,
+		password:        cfg.Password,
 		mux:             v,
 		mux:             v,
 		accept:          make(chan net.Conn),
 		accept:          make(chan net.Conn),
 		ctx:             ctx,
 		ctx:             ctx,
@@ -205,25 +217,20 @@ func (v *Muxer) handle(c net.Conn) {
 	}
 	}
 
 
 	xl := xlog.FromContextSafe(l.ctx)
 	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)
 			xl.Info("success func failure on vhost connection: %v", err)
 			_ = c.Close()
 			_ = c.Close()
 			return
 			return
 		}
 		}
 	}
 	}
 
 
-	// if authFunc is exist and username/password is set
+	// if checkAuth func is exist and username/password is set
 	// then verify user access
 	// 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()
 			_ = c.Close()
 			return
 			return
 		}
 		}
@@ -249,8 +256,8 @@ type Listener struct {
 	location        string
 	location        string
 	routeByHTTPUser string
 	routeByHTTPUser string
 	rewriteHost     string
 	rewriteHost     string
-	userName        string
-	passWord        string
+	username        string
+	password        string
 	mux             *Muxer // for closing Muxer
 	mux             *Muxer // for closing Muxer
 	accept          chan net.Conn
 	accept          chan net.Conn
 	ctx             context.Context
 	ctx             context.Context
@@ -263,11 +270,11 @@ func (l *Listener) Accept() (net.Conn, error) {
 		return nil, fmt.Errorf("Listener closed")
 		return nil, fmt.Errorf("Listener closed")
 	}
 	}
 
 
-	// if rewriteFunc is exist
+	// if rewriteHost func is exist
 	// rewrite http requests with a modified host header
 	// rewrite http requests with a modified host header
 	// if l.rewriteHost is empty, nothing to do
 	// 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 {
 		if err != nil {
 			xl.Warn("host header rewrite failed: %v", err)
 			xl.Warn("host header rewrite failed: %v", err)
 			return nil, fmt.Errorf("host header rewrite failed")
 			return nil, fmt.Errorf("host header rewrite failed")

+ 1 - 1
server/control.go

@@ -514,7 +514,7 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err
 
 
 	// NewProxy will return a interface Proxy.
 	// NewProxy will return a interface Proxy.
 	// In fact it create different proxies by different proxy type, we just call run() here.
 	// 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 {
 	if err != nil {
 		return remoteAddr, err
 		return remoteAddr, err
 	}
 	}

+ 32 - 20
server/dashboard_api.go

@@ -33,16 +33,20 @@ type GeneralResponse struct {
 }
 }
 
 
 type serverInfoResp 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"`
 	TotalTrafficIn  int64            `json:"total_traffic_in"`
 	TotalTrafficOut int64            `json:"total_traffic_out"`
 	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)
 	log.Info("Http request: [%s]", r.URL.Path)
 	serverStats := mem.StatsCollector.GetServer()
 	serverStats := mem.StatsCollector.GetServer()
 	svrResp := serverInfoResp{
 	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,
 		TotalTrafficIn:  serverStats.TotalTrafficIn,
 		TotalTrafficOut: serverStats.TotalTrafficOut,
 		TotalTrafficOut: serverStats.TotalTrafficOut,
@@ -157,6 +165,7 @@ func getConfByType(proxyType string) interface{} {
 type ProxyStatsInfo struct {
 type ProxyStatsInfo struct {
 	Name            string      `json:"name"`
 	Name            string      `json:"name"`
 	Conf            interface{} `json:"conf"`
 	Conf            interface{} `json:"conf"`
+	ClientVersion   string      `json:"client_version,omitempty"`
 	TodayTrafficIn  int64       `json:"today_traffic_in"`
 	TodayTrafficIn  int64       `json:"today_traffic_in"`
 	TodayTrafficOut int64       `json:"today_traffic_out"`
 	TodayTrafficOut int64       `json:"today_traffic_out"`
 	CurConns        int64       `json:"cur_conns"`
 	CurConns        int64       `json:"cur_conns"`
@@ -208,6 +217,9 @@ func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxySt
 				continue
 				continue
 			}
 			}
 			proxyInfo.Status = consts.Online
 			proxyInfo.Status = consts.Online
+			if pxy.GetLoginMsg() != nil {
+				proxyInfo.ClientVersion = pxy.GetLoginMsg().Version
+			}
 		} else {
 		} else {
 			proxyInfo.Status = consts.Offline
 			proxyInfo.Status = consts.Offline
 		}
 		}

+ 8 - 1
server/group/tcpmux.go

@@ -81,6 +81,8 @@ type TCPMuxGroup struct {
 	groupKey        string
 	groupKey        string
 	domain          string
 	domain          string
 	routeByHTTPUser string
 	routeByHTTPUser string
+	username        string
+	password        string
 
 
 	acceptCh chan net.Conn
 	acceptCh chan net.Conn
 	tcpMuxLn net.Listener
 	tcpMuxLn net.Listener
@@ -120,6 +122,8 @@ func (tmg *TCPMuxGroup) HTTPConnectListen(
 		tmg.groupKey = groupKey
 		tmg.groupKey = groupKey
 		tmg.domain = routeConfig.Domain
 		tmg.domain = routeConfig.Domain
 		tmg.routeByHTTPUser = routeConfig.RouteByHTTPUser
 		tmg.routeByHTTPUser = routeConfig.RouteByHTTPUser
+		tmg.username = routeConfig.Username
+		tmg.password = routeConfig.Password
 		tmg.tcpMuxLn = tcpMuxLn
 		tmg.tcpMuxLn = tcpMuxLn
 		tmg.lns = append(tmg.lns, ln)
 		tmg.lns = append(tmg.lns, ln)
 		if tmg.acceptCh == nil {
 		if tmg.acceptCh == nil {
@@ -128,7 +132,10 @@ func (tmg *TCPMuxGroup) HTTPConnectListen(
 		go tmg.worker()
 		go tmg.worker()
 	} else {
 	} else {
 		// route config in the same group must be equal
 		// 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
 			return nil, ErrGroupParamsInvalid
 		}
 		}
 		if tmg.groupKey != groupKey {
 		if tmg.groupKey != groupKey {

+ 8 - 1
server/proxy/proxy.go

@@ -48,6 +48,7 @@ type Proxy interface {
 	GetResourceController() *controller.ResourceController
 	GetResourceController() *controller.ResourceController
 	GetUserInfo() plugin.UserInfo
 	GetUserInfo() plugin.UserInfo
 	GetLimiter() *rate.Limiter
 	GetLimiter() *rate.Limiter
+	GetLoginMsg() *msg.Login
 	Close()
 	Close()
 }
 }
 
 
@@ -61,6 +62,7 @@ type BaseProxy struct {
 	serverCfg     config.ServerCommonConf
 	serverCfg     config.ServerCommonConf
 	limiter       *rate.Limiter
 	limiter       *rate.Limiter
 	userInfo      plugin.UserInfo
 	userInfo      plugin.UserInfo
+	loginMsg      *msg.Login
 
 
 	mu  sync.RWMutex
 	mu  sync.RWMutex
 	xl  *xlog.Logger
 	xl  *xlog.Logger
@@ -87,6 +89,10 @@ func (pxy *BaseProxy) GetUserInfo() plugin.UserInfo {
 	return pxy.userInfo
 	return pxy.userInfo
 }
 }
 
 
+func (pxy *BaseProxy) GetLoginMsg() *msg.Login {
+	return pxy.loginMsg
+}
+
 func (pxy *BaseProxy) Close() {
 func (pxy *BaseProxy) Close() {
 	xl := xlog.FromContextSafe(pxy.ctx)
 	xl := xlog.FromContextSafe(pxy.ctx)
 	xl.Info("proxy closing")
 	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,
 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) {
 ) (pxy Proxy, err error) {
 	xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(pxyConf.GetBaseInfo().ProxyName)
 	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,
 		xl:            xl,
 		ctx:           xlog.NewContext(ctx, xl),
 		ctx:           xlog.NewContext(ctx, xl),
 		userInfo:      userInfo,
 		userInfo:      userInfo,
+		loginMsg:      loginMsg,
 	}
 	}
 	switch cfg := pxyConf.(type) {
 	switch cfg := pxyConf.(type) {
 	case *config.TCPProxyConf:
 	case *config.TCPProxyConf:

+ 8 - 3
server/proxy/tcpmux.go

@@ -32,12 +32,16 @@ type TCPMuxProxy struct {
 	cfg *config.TCPMuxProxyConf
 	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 l net.Listener
 	var err error
 	var err error
 	routeConfig := &vhost.RouteConfig{
 	routeConfig := &vhost.RouteConfig{
 		Domain:          domain,
 		Domain:          domain,
 		RouteByHTTPUser: routeByHTTPUser,
 		RouteByHTTPUser: routeByHTTPUser,
+		Username:        httpUser,
+		Password:        httpPwd,
 	}
 	}
 	if pxy.cfg.Group != "" {
 	if pxy.cfg.Group != "" {
 		l, err = pxy.rc.TCPMuxGroupCtl.Listen(pxy.ctx, pxy.cfg.Multiplexer, pxy.cfg.Group, pxy.cfg.GroupKey, *routeConfig)
 		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
 			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 {
 		if err != nil {
 			return "", err
 			return "", err
 		}
 		}
 	}
 	}
 
 
 	if pxy.cfg.SubDomain != "" {
 	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 {
 		if err != nil {
 			return "", err
 			return "", err
 		}
 		}

+ 1 - 1
test/e2e/basic/basic.go

@@ -5,7 +5,7 @@ import (
 	"fmt"
 	"fmt"
 	"strings"
 	"strings"
 
 
-	"github.com/onsi/ginkgo"
+	"github.com/onsi/ginkgo/v2"
 
 
 	"github.com/fatedier/frp/pkg/transport"
 	"github.com/fatedier/frp/pkg/transport"
 	"github.com/fatedier/frp/test/e2e/framework"
 	"github.com/fatedier/frp/test/e2e/framework"

+ 1 - 1
test/e2e/basic/client.go

@@ -6,7 +6,7 @@ import (
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
-	"github.com/onsi/ginkgo"
+	"github.com/onsi/ginkgo/v2"
 
 
 	"github.com/fatedier/frp/test/e2e/framework"
 	"github.com/fatedier/frp/test/e2e/framework"
 	"github.com/fatedier/frp/test/e2e/framework/consts"
 	"github.com/fatedier/frp/test/e2e/framework/consts"

+ 1 - 1
test/e2e/basic/client_server.go

@@ -4,7 +4,7 @@ import (
 	"fmt"
 	"fmt"
 	"strings"
 	"strings"
 
 
-	"github.com/onsi/ginkgo"
+	"github.com/onsi/ginkgo/v2"
 
 
 	"github.com/fatedier/frp/test/e2e/framework"
 	"github.com/fatedier/frp/test/e2e/framework"
 	"github.com/fatedier/frp/test/e2e/framework/consts"
 	"github.com/fatedier/frp/test/e2e/framework/consts"

+ 1 - 1
test/e2e/basic/cmd.go

@@ -5,7 +5,7 @@ import (
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 
 
-	"github.com/onsi/ginkgo"
+	"github.com/onsi/ginkgo/v2"
 
 
 	"github.com/fatedier/frp/test/e2e/framework"
 	"github.com/fatedier/frp/test/e2e/framework"
 	"github.com/fatedier/frp/test/e2e/pkg/request"
 	"github.com/fatedier/frp/test/e2e/pkg/request"

+ 1 - 1
test/e2e/basic/config.go

@@ -3,7 +3,7 @@ package basic
 import (
 import (
 	"fmt"
 	"fmt"
 
 
-	"github.com/onsi/ginkgo"
+	"github.com/onsi/ginkgo/v2"
 
 
 	"github.com/fatedier/frp/test/e2e/framework"
 	"github.com/fatedier/frp/test/e2e/framework"
 	"github.com/fatedier/frp/test/e2e/framework/consts"
 	"github.com/fatedier/frp/test/e2e/framework/consts"

+ 1 - 1
test/e2e/basic/http.go

@@ -7,7 +7,7 @@ import (
 	"strconv"
 	"strconv"
 
 
 	"github.com/gorilla/websocket"
 	"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"
 	"github.com/fatedier/frp/test/e2e/framework/consts"
 	"github.com/fatedier/frp/test/e2e/framework/consts"

+ 1 - 1
test/e2e/basic/server.go

@@ -5,7 +5,7 @@ import (
 	"net"
 	"net"
 	"strconv"
 	"strconv"
 
 
-	"github.com/onsi/ginkgo"
+	"github.com/onsi/ginkgo/v2"
 
 
 	"github.com/fatedier/frp/test/e2e/framework"
 	"github.com/fatedier/frp/test/e2e/framework"
 	"github.com/fatedier/frp/test/e2e/framework/consts"
 	"github.com/fatedier/frp/test/e2e/framework/consts"

+ 218 - 0
test/e2e/basic/tcpmux.go

@@ -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")
+	})
+})

+ 9 - 4
test/e2e/e2e.go

@@ -3,8 +3,7 @@ package e2e
 import (
 import (
 	"testing"
 	"testing"
 
 
-	"github.com/onsi/ginkgo"
-	"github.com/onsi/ginkgo/config"
+	"github.com/onsi/ginkgo/v2"
 	"github.com/onsi/gomega"
 	"github.com/onsi/gomega"
 
 
 	"github.com/fatedier/frp/pkg/util/log"
 	"github.com/fatedier/frp/pkg/util/log"
@@ -33,9 +32,15 @@ var _ = ginkgo.SynchronizedAfterSuite(func() {
 func RunE2ETests(t *testing.T) {
 func RunE2ETests(t *testing.T) {
 	gomega.RegisterFailHandler(framework.Fail)
 	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",
 	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.
 // setupSuite is the boilerplate that can be used to setup ginkgo test suites, on the SynchronizedBeforeSuite step.

+ 1 - 1
test/e2e/e2e_test.go

@@ -6,7 +6,7 @@ import (
 	"os"
 	"os"
 	"testing"
 	"testing"
 
 
-	_ "github.com/onsi/ginkgo"
+	_ "github.com/onsi/ginkgo/v2"
 
 
 	"github.com/fatedier/frp/pkg/util/log"
 	"github.com/fatedier/frp/pkg/util/log"
 	// test source
 	// test source

+ 1 - 1
test/e2e/examples.go

@@ -3,7 +3,7 @@ package e2e
 import (
 import (
 	"fmt"
 	"fmt"
 
 
-	"github.com/onsi/ginkgo"
+	"github.com/onsi/ginkgo/v2"
 
 
 	"github.com/fatedier/frp/test/e2e/framework"
 	"github.com/fatedier/frp/test/e2e/framework"
 	"github.com/fatedier/frp/test/e2e/framework/consts"
 	"github.com/fatedier/frp/test/e2e/framework/consts"

+ 1 - 1
test/e2e/features/bandwidth_limit.go

@@ -5,7 +5,7 @@ import (
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
-	"github.com/onsi/ginkgo"
+	"github.com/onsi/ginkgo/v2"
 
 
 	plugin "github.com/fatedier/frp/pkg/plugin/server"
 	plugin "github.com/fatedier/frp/pkg/plugin/server"
 	"github.com/fatedier/frp/test/e2e/framework"
 	"github.com/fatedier/frp/test/e2e/framework"

+ 1 - 1
test/e2e/features/chaos.go

@@ -4,7 +4,7 @@ import (
 	"fmt"
 	"fmt"
 	"time"
 	"time"
 
 
-	"github.com/onsi/ginkgo"
+	"github.com/onsi/ginkgo/v2"
 
 
 	"github.com/fatedier/frp/test/e2e/framework"
 	"github.com/fatedier/frp/test/e2e/framework"
 )
 )

+ 1 - 1
test/e2e/features/group.go

@@ -6,7 +6,7 @@ import (
 	"sync"
 	"sync"
 	"time"
 	"time"
 
 
-	"github.com/onsi/ginkgo"
+	"github.com/onsi/ginkgo/v2"
 
 
 	"github.com/fatedier/frp/test/e2e/framework"
 	"github.com/fatedier/frp/test/e2e/framework"
 	"github.com/fatedier/frp/test/e2e/framework/consts"
 	"github.com/fatedier/frp/test/e2e/framework/consts"

+ 1 - 1
test/e2e/features/heartbeat.go

@@ -4,7 +4,7 @@ import (
 	"fmt"
 	"fmt"
 	"time"
 	"time"
 
 
-	"github.com/onsi/ginkgo"
+	"github.com/onsi/ginkgo/v2"
 
 
 	"github.com/fatedier/frp/test/e2e/framework"
 	"github.com/fatedier/frp/test/e2e/framework"
 )
 )

+ 1 - 1
test/e2e/features/monitor.go

@@ -5,7 +5,7 @@ import (
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
-	"github.com/onsi/ginkgo"
+	"github.com/onsi/ginkgo/v2"
 
 
 	"github.com/fatedier/frp/pkg/util/log"
 	"github.com/fatedier/frp/pkg/util/log"
 	"github.com/fatedier/frp/test/e2e/framework"
 	"github.com/fatedier/frp/test/e2e/framework"

+ 1 - 1
test/e2e/features/real_ip.go

@@ -6,7 +6,7 @@ import (
 	"net"
 	"net"
 	"net/http"
 	"net/http"
 
 
-	"github.com/onsi/ginkgo"
+	"github.com/onsi/ginkgo/v2"
 	pp "github.com/pires/go-proxyproto"
 	pp "github.com/pires/go-proxyproto"
 
 
 	"github.com/fatedier/frp/pkg/util/log"
 	"github.com/fatedier/frp/pkg/util/log"

+ 4 - 4
test/e2e/framework/framework.go

@@ -9,8 +9,7 @@ import (
 	"strings"
 	"strings"
 	"text/template"
 	"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/mock/server"
 	"github.com/fatedier/frp/test/e2e/pkg/port"
 	"github.com/fatedier/frp/test/e2e/pkg/port"
@@ -63,9 +62,10 @@ type Framework struct {
 }
 }
 
 
 func NewDefaultFramework() *Framework {
 func NewDefaultFramework() *Framework {
+	suiteConfig, _ := ginkgo.GinkgoConfiguration()
 	options := Options{
 	options := Options{
-		TotalParallelNode: config.GinkgoConfig.ParallelTotal,
-		CurrentNodeIndex:  config.GinkgoConfig.ParallelNode,
+		TotalParallelNode: suiteConfig.ParallelTotal,
+		CurrentNodeIndex:  suiteConfig.ParallelProcess,
 		FromPortIndex:     20000,
 		FromPortIndex:     20000,
 		ToPortIndex:       50000,
 		ToPortIndex:       50000,
 	}
 	}

+ 0 - 80
test/e2e/framework/ginkgowrapper/wrapper.go

@@ -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")
-}

+ 7 - 69
test/e2e/framework/log.go

@@ -1,15 +1,10 @@
 package framework
 package framework
 
 
 import (
 import (
-	"bytes"
 	"fmt"
 	"fmt"
-	"regexp"
-	"runtime/debug"
 	"time"
 	"time"
 
 
-	"github.com/onsi/ginkgo"
-
-	e2eginkgowrapper "github.com/fatedier/frp/test/e2e/framework/ginkgowrapper"
+	"github.com/onsi/ginkgo/v2"
 )
 )
 
 
 func nowStamp() string {
 func nowStamp() string {
@@ -25,71 +20,14 @@ func Logf(format string, args ...interface{}) {
 	log("INFO", format, args...)
 	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{}) {
 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...)
 	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
 	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

+ 2 - 2
test/e2e/framework/process.go

@@ -38,7 +38,7 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
 		err = p.Start()
 		err = p.Start()
 		ExpectNoError(err)
 		ExpectNoError(err)
 	}
 	}
-	time.Sleep(time.Second)
+	time.Sleep(2 * time.Second)
 
 
 	currentClientProcesses := make([]*process.Process, 0, len(clientTemplates))
 	currentClientProcesses := make([]*process.Process, 0, len(clientTemplates))
 	for i := range clientTemplates {
 	for i := range clientTemplates {
@@ -56,7 +56,7 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
 		ExpectNoError(err)
 		ExpectNoError(err)
 		time.Sleep(500 * time.Millisecond)
 		time.Sleep(500 * time.Millisecond)
 	}
 	}
-	time.Sleep(2 * time.Second)
+	time.Sleep(5 * time.Second)
 
 
 	return currentServerProcesses, currentClientProcesses
 	return currentServerProcesses, currentClientProcesses
 }
 }

+ 0 - 8
test/e2e/framework/test_context.go

@@ -4,8 +4,6 @@ import (
 	"flag"
 	"flag"
 	"fmt"
 	"fmt"
 	"os"
 	"os"
-
-	"github.com/onsi/ginkgo/config"
 )
 )
 
 
 type TestContextType struct {
 type TestContextType struct {
@@ -25,12 +23,6 @@ var TestContext TestContextType
 // test-specific flags. However, those settings then get added
 // test-specific flags. However, those settings then get added
 // regardless whether the test is actually in the test suite.
 // regardless whether the test is actually in the test suite.
 func RegisterCommonFlags(flags *flag.FlagSet) {
 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.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.FRPServerPath, "frps-path", "../../bin/frps", "The frp server binary to use.")
 	flags.StringVar(&TestContext.LogLevel, "log-level", "debug", "Log level.")
 	flags.StringVar(&TestContext.LogLevel, "log-level", "debug", "Log level.")

+ 4 - 4
test/e2e/pkg/port/port.go

@@ -10,8 +10,8 @@ import (
 )
 )
 
 
 type Allocator struct {
 type Allocator struct {
-	reserved sets.Int
-	used     sets.Int
+	reserved sets.Set[int]
+	used     sets.Set[int]
 	mu       sync.Mutex
 	mu       sync.Mutex
 }
 }
 
 
@@ -20,8 +20,8 @@ type Allocator struct {
 // Reserved ports: 13, 17
 // Reserved ports: 13, 17
 func NewAllocator(from int, to int, mod int, index int) *Allocator {
 func NewAllocator(from int, to int, mod int, index int) *Allocator {
 	pa := &Allocator{
 	pa := &Allocator{
-		reserved: sets.NewInt(),
-		used:     sets.NewInt(),
+		reserved: sets.New[int](),
+		used:     sets.New[int](),
 	}
 	}
 
 
 	for i := from; i <= to; i++ {
 	for i := from; i <= to; i++ {

+ 4 - 1
test/e2e/pkg/request/request.go

@@ -145,7 +145,10 @@ func (r *Request) Do() (*Response, error) {
 		err  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
 	// for protocol http and https
 	if r.protocol == "http" || r.protocol == "https" {
 	if r.protocol == "http" || r.protocol == "https" {
 		return r.sendHTTPRequest(r.method, fmt.Sprintf("%s://%s%s", r.protocol, addr, r.path),
 		return r.sendHTTPRequest(r.method, fmt.Sprintf("%s://%s%s", r.protocol, addr, r.path),

+ 4 - 0
test/e2e/pkg/rpc/rpc.go

@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"bytes"
 	"encoding/binary"
 	"encoding/binary"
 	"errors"
 	"errors"
+	"fmt"
 	"io"
 	"io"
 )
 )
 
 
@@ -22,6 +23,9 @@ func ReadBytes(r io.Reader) ([]byte, error) {
 	if err := binary.Read(r, binary.BigEndian, &length); err != nil {
 	if err := binary.Read(r, binary.BigEndian, &length); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+	if length < 0 || length > 10*1024*1024 {
+		return nil, fmt.Errorf("invalid length")
+	}
 	buffer := make([]byte, length)
 	buffer := make([]byte, length)
 	n, err := io.ReadFull(r, buffer)
 	n, err := io.ReadFull(r, buffer)
 	if err != nil {
 	if err != nil {

+ 6 - 34
test/e2e/pkg/sdk/client/client.go

@@ -39,43 +39,15 @@ func (c *Client) GetProxyStatus(name string) (*client.ProxyStatusResp, error) {
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	allStatus := &client.StatusResp{}
+	allStatus := make(client.StatusResp)
 	if err = json.Unmarshal([]byte(content), &allStatus); err != nil {
 	if err = json.Unmarshal([]byte(content), &allStatus); err != nil {
 		return nil, fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(content))
 		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")
 	return nil, fmt.Errorf("no proxy status found")

+ 1 - 1
test/e2e/plugin/client.go

@@ -5,7 +5,7 @@ import (
 	"fmt"
 	"fmt"
 	"strconv"
 	"strconv"
 
 
-	"github.com/onsi/ginkgo"
+	"github.com/onsi/ginkgo/v2"
 
 
 	"github.com/fatedier/frp/pkg/transport"
 	"github.com/fatedier/frp/pkg/transport"
 	"github.com/fatedier/frp/test/e2e/framework"
 	"github.com/fatedier/frp/test/e2e/framework"

+ 1 - 1
test/e2e/plugin/server.go

@@ -4,7 +4,7 @@ import (
 	"fmt"
 	"fmt"
 	"time"
 	"time"
 
 
-	"github.com/onsi/ginkgo"
+	"github.com/onsi/ginkgo/v2"
 
 
 	plugin "github.com/fatedier/frp/pkg/plugin/server"
 	plugin "github.com/fatedier/frp/pkg/plugin/server"
 	"github.com/fatedier/frp/pkg/transport"
 	"github.com/fatedier/frp/pkg/transport"

+ 0 - 14
web/frpc/.babelrc

@@ -1,14 +0,0 @@
-{
-    "presets": [
-        ["es2015", { "modules": false }]
-    ],
-    "plugins": [
-        [
-            "component",
-            {
-                "libraryName": "element-ui",
-                "styleLibraryName": "theme-chalk"
-            }
-        ]
-    ]
-}

+ 30 - 0
web/frpc/.eslintrc.cjs

@@ -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'],
+      },
+    ],
+  },
+}

+ 26 - 4
web/frpc/.gitignore

@@ -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
 .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
 .idea
-.vscode/settings.json
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 5 - 0
web/frpc/.prettierrc.json

@@ -0,0 +1,5 @@
+{
+  "tabWidth": 2,
+  "semi": false,
+  "singleQuote": true
+}

+ 9 - 2
web/frpc/Makefile

@@ -1,6 +1,13 @@
-.PHONY: dist build
+.PHONY: dist build preview lint
+
 build:
 build:
 	@npm run build
 	@npm run build
 
 
-dev: 
+dev:
 	@npm run dev
 	@npm run dev
+
+preview:
+	@npm run preview
+
+lint:
+	@npm run lint

+ 25 - 0
web/frpc/README.md

@@ -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
+```

+ 8 - 0
web/frpc/auto-imports.d.ts

@@ -0,0 +1,8 @@
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// Generated by unplugin-auto-import
+export {}
+declare global {
+
+}

+ 26 - 0
web/frpc/components.d.ts

@@ -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']
+  }
+}

+ 1 - 0
web/frpc/env.d.ts

@@ -0,0 +1 @@
+/// <reference types="vite/client" />

+ 1 - 2
web/frpc/src/index.html → web/frpc/index.html

@@ -8,8 +8,7 @@
 
 
 <body>
 <body>
     <div id="app"></div>
     <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>
 </body>
 
 
 </html>
 </html>

+ 26 - 37
web/frpc/package.json

@@ -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,
   "private": true,
   "scripts": {
   "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": {
   "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": {
   "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"
   }
   }
 }
 }

+ 0 - 5
web/frpc/postcss.config.js

@@ -1,5 +0,0 @@
-module.exports = {
-    plugins: [
-        require('autoprefixer')()
-    ]
-}

+ 0 - 0
web/frpc/src/assets/favicon.ico → web/frpc/public/favicon.ico


+ 108 - 65
web/frpc/src/App.vue

@@ -1,73 +1,116 @@
 <template>
 <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>
 </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>
 </script>
 
 
 <style>
 <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>
 </style>

+ 5 - 0
web/frpc/src/assets/dark.css

@@ -0,0 +1,5 @@
+html.dark {
+  --el-bg-color: #343432;
+  --el-fill-color-blank: #343432;
+  background-color: #343432;
+}

+ 102 - 0
web/frpc/src/components/ClientConfigure.vue

@@ -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>

+ 0 - 93
web/frpc/src/components/Configure.vue

@@ -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>

+ 79 - 69
web/frpc/src/components/Overview.vue

@@ -1,75 +1,85 @@
 <template>
 <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>
 </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>
 </script>
 
 
-<style>
-</style>
+<style></style>

+ 0 - 52
web/frpc/src/main.js

@@ -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 }
-})

+ 13 - 0
web/frpc/src/main.ts

@@ -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')

+ 0 - 18
web/frpc/src/router/index.js

@@ -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,
-    }]
-})

+ 21 - 0
web/frpc/src/router/index.ts

@@ -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

+ 0 - 22
web/frpc/src/utils/less/custom.less

@@ -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%;
-    }
-}

+ 0 - 13
web/frpc/src/utils/status.js

@@ -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}

+ 8 - 0
web/frpc/tsconfig.config.json

@@ -0,0 +1,8 @@
+{
+  "extends": "@vue/tsconfig/tsconfig.node.json",
+  "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
+  "compilerOptions": {
+    "composite": true,
+    "types": ["node"]
+  }
+}

+ 16 - 0
web/frpc/tsconfig.json

@@ -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"
+    }
+  ]
+}

+ 29 - 0
web/frpc/vite.config.ts

@@ -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: '',
+  },
+})

+ 0 - 107
web/frpc/webpack.config.js

@@ -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'
-})

File diff suppressed because it is too large
+ 2183 - 4645
web/frpc/yarn.lock


+ 0 - 14
web/frps/.babelrc

@@ -1,14 +0,0 @@
-{
-    "presets": [
-        ["es2015", { "modules": false }]
-    ],
-    "plugins": [
-        [
-            "component",
-            {
-                "libraryName": "element-ui",
-                "styleLibraryName": "theme-chalk"
-            }
-        ]
-    ]
-}

+ 30 - 0
web/frps/.eslintrc.cjs

@@ -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'],
+      },
+    ],
+  },
+}

+ 26 - 4
web/frps/.gitignore

@@ -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
 .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
 .idea
-.vscode/settings.json
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 5 - 0
web/frps/.prettierrc.json

@@ -0,0 +1,5 @@
+{
+  "tabWidth": 2,
+  "semi": false,
+  "singleQuote": true
+}

Some files were not shown because too many files changed in this diff