Explorar el Código

Merge pull request #2638 from fatedier/dev

bump version to v0.38.0
fatedier hace 3 años
padre
commit
1b6cd284f5
Se han modificado 53 ficheros con 294 adiciones y 220 borrados
  1. 2 4
      .circleci/config.yml
  2. 3 0
      .github/FUNDING.yml
  3. 0 44
      .github/ISSUE_TEMPLATE/bug-report.md
  4. 77 0
      .github/ISSUE_TEMPLATE/bug_report.yaml
  5. 0 4
      .github/ISSUE_TEMPLATE/config.yml
  6. 0 22
      .github/ISSUE_TEMPLATE/feature_request.md
  7. 36 0
      .github/ISSUE_TEMPLATE/feature_request.yaml
  8. 1 1
      .github/workflows/build-and-push-image.yml
  9. 1 1
      .github/workflows/goreleaser.yml
  10. 4 4
      .github/workflows/stale.yml
  11. 0 3
      Makefile
  12. 7 4
      Release.md
  13. 10 39
      assets/assets.go
  14. 14 0
      assets/frpc/embed.go
  15. 0 7
      assets/frpc/statik/statik.go
  16. 14 0
      assets/frps/embed.go
  17. 0 7
      assets/frps/statik/statik.go
  18. 2 1
      client/admin.go
  19. 10 4
      client/admin_api.go
  20. 9 2
      client/control.go
  21. 1 2
      client/health/health.go
  22. 1 2
      client/proxy/proxy.go
  23. 10 9
      client/service.go
  24. 1 2
      client/visitor.go
  25. 1 1
      cmd/frpc/main.go
  26. 2 2
      cmd/frpc/sub/reload.go
  27. 1 2
      cmd/frpc/sub/root.go
  28. 2 2
      cmd/frpc/sub/status.go
  29. 1 1
      cmd/frps/main.go
  30. 6 2
      conf/frpc_full.ini
  31. 0 1
      go.mod
  32. 0 2
      go.sum
  33. 1 1
      hack/run-e2e.sh
  34. 3 0
      pkg/config/client.go
  35. 1 2
      pkg/config/parse.go
  36. 1 2
      pkg/config/value.go
  37. 1 2
      pkg/plugin/client/socks5.go
  38. 2 2
      pkg/plugin/server/http.go
  39. 2 2
      pkg/transport/tls.go
  40. 2 2
      pkg/util/net/conn.go
  41. 13 3
      pkg/util/net/tls.go
  42. 1 1
      pkg/util/version/version.go
  43. 4 3
      pkg/util/vhost/resource.go
  44. 1 0
      server/dashboard.go
  45. 5 0
      server/dashboard_api.go
  46. 10 12
      server/service.go
  47. 18 0
      test/e2e/basic/client_server.go
  48. 2 0
      test/e2e/features/monitor.go
  49. 2 3
      test/e2e/framework/framework.go
  50. 4 4
      test/e2e/framework/process.go
  51. 1 2
      test/e2e/pkg/request/request.go
  52. 2 2
      test/e2e/pkg/sdk/client/client.go
  53. 2 2
      test/e2e/plugin/utils.go

+ 2 - 4
.circleci/config.yml

@@ -2,16 +2,14 @@ version: 2
 jobs:
   go-version-latest:
     docker:
-      - image: circleci/golang:1.16-node
-    working_directory: /go/src/github.com/fatedier/frp
+      - image: cimg/go:1.17-node
     steps:
       - checkout
       - run: make
       - run: make alltest
   go-version-last:
     docker:
-      - image: circleci/golang:1.15-node
-    working_directory: /go/src/github.com/fatedier/frp
+      - image: cimg/go:1.16-node
     steps:
       - checkout
       - run: make

+ 3 - 0
.github/FUNDING.yml

@@ -0,0 +1,3 @@
+# These are supported funding model platforms
+
+github: [fatedier]

+ 0 - 44
.github/ISSUE_TEMPLATE/bug-report.md

@@ -1,44 +0,0 @@
----
-name: Bug Report
-about: Bug Report for FRP
-title: ''
-labels: Requires Testing
-assignees: ''
-
----
-
-<!-- From Chinese to English by machine translation, welcome to revise and polish. -->
-
-<!-- ⚠️⚠️ Incomplete reports will be marked as invalid, and closed, with few exceptions ⚠️⚠️ -->
-<!-- in addition, please use search well so that the same solution can be found in the feedback, we will close it directly -->
-<!-- for convenience of differentiation, use FRPS or FRPC to refer to the FRP server or client -->
-
-**[REQUIRED] hat version of frp are you using** 
-<!-- Use ./frpc -v or ./frps -v -->
-Version:
-
-**[REQUIRED] What operating system and processor architecture are you using**
-OS:
-CPU architecture:
-
-**[REQUIRED] description of errors**
-
-**confile**
-<!-- Please pay attention to hiding the token, server_addr and other privacy information -->
-
-**log file**
-<!--  If the file is too large, use Pastebin, for example https://pastebin.ubuntu.com/ -->
-
-**Steps to reproduce the issue**
-1. 
-2. 
-3. 
-
-**Supplementary information**
-
-**Can you guess what caused this issue**
-
-**Checklist**:
-<!--- Make sure you've completed the following steps (put an "X" between of brackets): -->
-- [] I included all information required in the sections above
-- [] I made sure there are no duplicates of this report [(Use Search)](https://github.com/fatedier/frp/issues?q=is%3Aissue)

+ 77 - 0
.github/ISSUE_TEMPLATE/bug_report.yaml

@@ -0,0 +1,77 @@
+name: Bug report
+description: Report a bug to help us improve frp
+
+body:
+- type: markdown
+  attributes:
+    value: |
+      Thanks for taking the time to fill out this bug report!
+- type: textarea
+  id: bug-description
+  attributes:
+    label: Bug Description
+    description: Tell us what issues you ran into
+    placeholder: Include information about what you tried, what you expected to happen, and what actually happened. The more details, the better!
+  validations:
+    required: true
+- type: input
+  id: frpc-version
+  attributes:
+    label: frpc Version
+    description: Include the output of `frpc -v`
+  validations:
+    required: true
+- type: input
+  id: frps-version
+  attributes:
+    label: frps Version
+    description: Include the output of `frps -v`
+  validations:
+    required: true
+- type: input
+  id: system-architecture
+  attributes:
+    label: System Architecture
+    description: Include which architecture you used, such as `linux/amd64`, `windows/amd64`
+  validations:
+    required: true
+- type: textarea
+  id: config
+  attributes:
+    label: Configurations
+    description: Include what configurrations you used and ran into this problem
+    placeholder: Pay attention to hiding the token and password in your output
+  validations:
+    required: true
+- type: textarea
+  id: log
+  attributes:
+    label: Logs
+    description: Prefer you providing releated error logs here
+    placeholder: Pay attention to hiding your personal informations
+- type: textarea
+  id: steps-to-reproduce
+  attributes:
+    label: Steps to reproduce
+    description: How to reproduce it? It's important for us to find the bug
+    value: |
+      1. 
+      2. 
+      3. 
+      ...
+- type: checkboxes
+  id: area
+  attributes:
+    label: Affected area
+    options:
+    - label: "Docs"
+    - label: "Installation"
+    - label: "Performance and Scalability"
+    - label: "Security"
+    - label: "User Experience"
+    - label: "Test and Release"
+    - label: "Developer Infrastructure"
+    - label: "Client Plugin"
+    - label: "Server Plugin"
+    - label: "Extensions"
+    - label: "Others"

+ 0 - 4
.github/ISSUE_TEMPLATE/config.yml

@@ -1,5 +1 @@
 blank_issues_enabled: false
-contact_links:
-  - name: DOCS
-    url: https://github.com/fatedier/frp
-    about: Here you can find out how to configure frp.

+ 0 - 22
.github/ISSUE_TEMPLATE/feature_request.md

@@ -1,22 +0,0 @@
----
-name: Feature request
-about: Suggest an idea for this project
-title: ''
-labels: "[+] Enhancement"
-assignees: ''
-
----
-
-<!-- From Chinese to English by machine translation, welcome to revise and polish. -->
-
-**The solution you want**
-<!--A clear and concise description of the solution you want. -->
-
-**Alternatives considered**
-<!--A clear and concise description of any alternative solutions or features you have considered. -->
-
-**How to implement this function**
-<!--Implementation steps for the solution you want. -->
-
-**Application scenarios of this function**
-<!--Make a clear and concise description of the application scenario of the solution you want. -->

+ 36 - 0
.github/ISSUE_TEMPLATE/feature_request.yaml

@@ -0,0 +1,36 @@
+name: Feature Request
+description: Suggest an idea to improve frp
+title: "[Feature Request] "
+
+body:
+- type: markdown
+  attributes:
+    value: |
+      This is only used to request new product features.
+- type: textarea
+  id: feature-request
+  attributes:
+    label: Describe the feature request
+    description: Tell us what's you want and why it should be added in frp.
+  validations:
+    required: true
+- type: textarea
+  id: alternatives
+  attributes:
+    label: Describe alternatives you've considered
+- type: checkboxes
+  id: area
+  attributes:
+    label: Affected area
+    options:
+    - label: "Docs"
+    - label: "Installation"
+    - label: "Performance and Scalability"
+    - label: "Security"
+    - label: "User Experience"
+    - label: "Test and Release"
+    - label: "Developer Infrastructure"
+    - label: "Client Plugin"
+    - label: "Server Plugin"
+    - label: "Extensions"
+    - label: "Others"

+ 1 - 1
.github/workflows/build-and-push-image.yml

@@ -17,7 +17,7 @@ jobs:
       - name: Set up Go 1.x
         uses: actions/setup-go@v2
         with:
-          go-version: 1.16
+          go-version: 1.17
 
       - run: |
           # https://github.com/actions/setup-go/issues/107

+ 1 - 1
.github/workflows/goreleaser.yml

@@ -15,7 +15,7 @@ jobs:
       - name: Set up Go
         uses: actions/setup-go@v2
         with:
-          go-version: 1.16
+          go-version: 1.17
           
       - run: |
           # https://github.com/actions/setup-go/issues/107

+ 4 - 4
.github/workflows/stale.yml

@@ -15,12 +15,12 @@ jobs:
     - uses: actions/stale@v3
       with:
         repo-token: ${{ secrets.GITHUB_TOKEN }}
-        stale-issue-message: 'Issues go stale after 45d of inactivity. Stale issues rot after an additional 10d of inactivity and eventually close.'
-        stale-pr-message: 'Issues go stale after 45d of inactivity. Stale issues rot after an additional 10d of inactivity and eventually close.'
+        stale-issue-message: 'Issues go stale after 30d of inactivity. Stale issues rot after an additional 7d of inactivity and eventually close.'
+        stale-pr-message: 'Issues go stale after 30d of inactivity. Stale issues rot after an additional 7d of inactivity and eventually close.'
         stale-issue-label: 'lifecycle/stale'
         exempt-issue-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
         stale-pr-label: 'lifecycle/stale'
         exempt-pr-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned'
-        days-before-stale: 45
-        days-before-close: 10
+        days-before-stale: 30
+        days-before-close: 7
         debug-only: ${{ github.event.inputs.debug-only }}

+ 0 - 3
Makefile

@@ -12,9 +12,6 @@ file:
 	rm -rf ./assets/frpc/static/*
 	cp -rf ./web/frps/dist/* ./assets/frps/static
 	cp -rf ./web/frpc/dist/* ./assets/frpc/static
-	rm -rf ./assets/frps/statik
-	rm -rf ./assets/frpc/statik
-	go generate ./assets/...
 
 fmt:
 	go fmt ./...

+ 7 - 4
Release.md

@@ -1,5 +1,8 @@
-### Fix
+### New
 
-* Plugin `https2https` not work.
-* `context canceled` problem for `http_proxy` plugin when multiple requests reuse same connection.
-* In some cases, frps can't get server name for `https` proxy.
+* Add `/healthz` API.
+* frpc support `disable_custom_tls_first_byte` .If set true, frpc will not send custom header byte.
+
+### Improve
+
+* Use go standard embed package instead of statik.

+ 10 - 39
assets/assets.go

@@ -14,22 +14,15 @@
 
 package assets
 
-//go:generate statik -src=./frps/static -dest=./frps
-//go:generate statik -src=./frpc/static -dest=./frpc
-//go:generate go fmt ./frps/statik/statik.go
-//go:generate go fmt ./frpc/statik/statik.go
-
 import (
-	"io/ioutil"
+	"io/fs"
 	"net/http"
-	"os"
-	"path"
-
-	"github.com/rakyll/statik/fs"
 )
 
 var (
-	// store static files in memory by statik
+	// read-only filesystem created by "embed" for embedded files
+	content fs.FS
+
 	FileSystem http.FileSystem
 
 	// if prefix is not empty, we get file content from disk
@@ -38,40 +31,18 @@ var (
 
 // if path is empty, load assets in memory
 // or set FileSystem using disk files
-func Load(path string) (err error) {
+func Load(path string) {
 	prefixPath = path
 	if prefixPath != "" {
 		FileSystem = http.Dir(prefixPath)
-		return nil
 	} else {
-		FileSystem, err = fs.New()
+		FileSystem = http.FS(content)
 	}
-	return err
 }
 
-func ReadFile(file string) (content string, err error) {
-	if prefixPath == "" {
-		file, err := FileSystem.Open(path.Join("/", file))
-		if err != nil {
-			return content, err
-		}
-		defer file.Close()
-		buf, err := ioutil.ReadAll(file)
-		if err != nil {
-			return content, err
-		}
-		content = string(buf)
-	} else {
-		file, err := os.Open(path.Join(prefixPath, file))
-		if err != nil {
-			return content, err
-		}
-		defer file.Close()
-		buf, err := ioutil.ReadAll(file)
-		if err != nil {
-			return content, err
-		}
-		content = string(buf)
+func Register(fileSystem fs.FS) {
+	subFs, err := fs.Sub(fileSystem, "static")
+	if err == nil {
+		content = subFs
 	}
-	return content, err
 }

+ 14 - 0
assets/frpc/embed.go

@@ -0,0 +1,14 @@
+package frpc
+
+import (
+	"embed"
+
+	"github.com/fatedier/frp/assets"
+)
+
+//go:embed static/*
+var content embed.FS
+
+func init() {
+	assets.Register(content)
+}

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 7
assets/frpc/statik/statik.go


+ 14 - 0
assets/frps/embed.go

@@ -0,0 +1,14 @@
+package frpc
+
+import (
+	"embed"
+
+	"github.com/fatedier/frp/assets"
+)
+
+//go:embed static/*
+var content embed.FS
+
+func init() {
+	assets.Register(content)
+}

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 7
assets/frps/statik/statik.go


+ 2 - 1
client/admin.go

@@ -37,7 +37,8 @@ func (svr *Service) RunAdminServer(address string) (err error) {
 	user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd
 	router.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware)
 
-	// api, see dashboard_api.go
+	// api, see admin_api.go
+	router.HandleFunc("/healthz", svr.healthz)
 	router.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
 	router.HandleFunc("/api/status", svr.apiStatus).Methods("GET")
 	router.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET")

+ 10 - 4
client/admin_api.go

@@ -17,8 +17,9 @@ package client
 import (
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
+	"os"
 	"sort"
 	"strings"
 
@@ -32,6 +33,11 @@ type GeneralResponse struct {
 	Msg  string
 }
 
+// /healthz
+func (svr *Service) healthz(w http.ResponseWriter, r *http.Request) {
+	w.WriteHeader(200)
+}
+
 // GET api/reload
 func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
 	res := GeneralResponse{Code: 200}
@@ -251,7 +257,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
 	}()
 
 	// get new config content
-	body, err := ioutil.ReadAll(r.Body)
+	body, err := io.ReadAll(r.Body)
 	if err != nil {
 		res.Code = 400
 		res.Msg = fmt.Sprintf("read request body error: %v", err)
@@ -268,7 +274,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
 
 	// get token from origin content
 	token := ""
-	b, err := ioutil.ReadFile(svr.cfgFile)
+	b, err := os.ReadFile(svr.cfgFile)
 	if err != nil {
 		res.Code = 400
 		res.Msg = err.Error()
@@ -307,7 +313,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
 	}
 	content = strings.Join(newRows, "\n")
 
-	err = ioutil.WriteFile(svr.cfgFile, []byte(content), 0644)
+	err = os.WriteFile(svr.cfgFile, []byte(content), 0644)
 	if err != nil {
 		res.Code = 500
 		res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)

+ 9 - 2
client/control.go

@@ -182,9 +182,16 @@ func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
 }
 
 func (ctl *Control) Close() error {
+	return ctl.GracefulClose(0)
+}
+
+func (ctl *Control) GracefulClose(d time.Duration) error {
 	ctl.pm.Close()
-	ctl.conn.Close()
 	ctl.vm.Close()
+
+	time.Sleep(d)
+
+	ctl.conn.Close()
 	if ctl.session != nil {
 		ctl.session.Close()
 	}
@@ -228,7 +235,7 @@ func (ctl *Control) connectServer() (conn net.Conn, err error) {
 		}
 
 		address := net.JoinHostPort(ctl.clientCfg.ServerAddr, strconv.Itoa(ctl.clientCfg.ServerPort))
-		conn, err = frpNet.ConnectServerByProxyWithTLS(ctl.clientCfg.HTTPProxy, ctl.clientCfg.Protocol, address, tlsConfig)
+		conn, err = frpNet.ConnectServerByProxyWithTLS(ctl.clientCfg.HTTPProxy, ctl.clientCfg.Protocol, address, tlsConfig, ctl.clientCfg.DisableCustomTLSFirstByte)
 
 		if err != nil {
 			xl.Warn("start new connection to server error: %v", err)

+ 1 - 2
client/health/health.go

@@ -19,7 +19,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"time"
@@ -162,7 +161,7 @@ func (monitor *Monitor) doHTTPCheck(ctx context.Context) error {
 		return err
 	}
 	defer resp.Body.Close()
-	io.Copy(ioutil.Discard, resp.Body)
+	io.Copy(io.Discard, resp.Body)
 
 	if resp.StatusCode/100 != 2 {
 		return fmt.Errorf("do http health check, StatusCode is [%d] not 2xx", resp.StatusCode)

+ 1 - 2
client/proxy/proxy.go

@@ -19,7 +19,6 @@ import (
 	"context"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net"
 	"strconv"
 	"strings"
@@ -401,7 +400,7 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
 
 	fmuxCfg := fmux.DefaultConfig()
 	fmuxCfg.KeepAliveInterval = 5 * time.Second
-	fmuxCfg.LogOutput = ioutil.Discard
+	fmuxCfg.LogOutput = io.Discard
 	sess, err := fmux.Server(kcpConn, fmuxCfg)
 	if err != nil {
 		xl.Error("create yamux server from kcp connection error: %v", err)

+ 10 - 9
client/service.go

@@ -19,7 +19,7 @@ import (
 	"crypto/tls"
 	"errors"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net"
 	"runtime"
 	"strconv"
@@ -124,13 +124,10 @@ func (svr *Service) Run() error {
 
 	if svr.cfg.AdminPort != 0 {
 		// Init admin server assets
-		err := assets.Load(svr.cfg.AssetsDir)
-		if err != nil {
-			return fmt.Errorf("Load assets error: %v", err)
-		}
+		assets.Load(svr.cfg.AssetsDir)
 
 		address := net.JoinHostPort(svr.cfg.AdminAddr, strconv.Itoa(svr.cfg.AdminPort))
-		err = svr.RunAdminServer(address)
+		err := svr.RunAdminServer(address)
 		if err != nil {
 			log.Warn("run admin server error: %v", err)
 		}
@@ -232,7 +229,7 @@ func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
 	}
 
 	address := net.JoinHostPort(svr.cfg.ServerAddr, strconv.Itoa(svr.cfg.ServerPort))
-	conn, err = frpNet.ConnectServerByProxyWithTLS(svr.cfg.HTTPProxy, svr.cfg.Protocol, address, tlsConfig)
+	conn, err = frpNet.ConnectServerByProxyWithTLS(svr.cfg.HTTPProxy, svr.cfg.Protocol, address, tlsConfig, svr.cfg.DisableCustomTLSFirstByte)
 	if err != nil {
 		return
 	}
@@ -249,7 +246,7 @@ func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
 	if svr.cfg.TCPMux {
 		fmuxCfg := fmux.DefaultConfig()
 		fmuxCfg.KeepAliveInterval = 20 * time.Second
-		fmuxCfg.LogOutput = ioutil.Discard
+		fmuxCfg.LogOutput = io.Discard
 		session, err = fmux.Client(conn, fmuxCfg)
 		if err != nil {
 			return
@@ -315,9 +312,13 @@ func (svr *Service) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs
 }
 
 func (svr *Service) Close() {
+	svr.GracefulClose(time.Duration(0))
+}
+
+func (svr *Service) GracefulClose(d time.Duration) {
 	atomic.StoreUint32(&svr.exit, 1)
 	if svr.ctl != nil {
-		svr.ctl.Close()
+		svr.ctl.GracefulClose(d)
 	}
 	svr.cancel()
 }

+ 1 - 2
client/visitor.go

@@ -19,7 +19,6 @@ import (
 	"context"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net"
 	"sync"
 	"time"
@@ -308,7 +307,7 @@ func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
 
 	fmuxCfg := fmux.DefaultConfig()
 	fmuxCfg.KeepAliveInterval = 5 * time.Second
-	fmuxCfg.LogOutput = ioutil.Discard
+	fmuxCfg.LogOutput = io.Discard
 	sess, err := fmux.Client(remote, fmuxCfg)
 	if err != nil {
 		xl.Error("create yamux session error: %v", err)

+ 1 - 1
cmd/frpc/main.go

@@ -18,7 +18,7 @@ import (
 	"math/rand"
 	"time"
 
-	_ "github.com/fatedier/frp/assets/frpc/statik"
+	_ "github.com/fatedier/frp/assets/frpc"
 	"github.com/fatedier/frp/cmd/frpc/sub"
 
 	"github.com/fatedier/golib/crypto"

+ 2 - 2
cmd/frpc/sub/reload.go

@@ -17,7 +17,7 @@ package sub
 import (
 	"encoding/base64"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"os"
 	"strings"
@@ -76,7 +76,7 @@ func reload(clientCfg config.ClientCommonConf) error {
 		return nil
 	}
 
-	body, err := ioutil.ReadAll(resp.Body)
+	body, err := io.ReadAll(resp.Body)
 	if err != nil {
 		return err
 	}

+ 1 - 2
cmd/frpc/sub/root.go

@@ -124,8 +124,7 @@ func handleSignal(svr *client.Service) {
 	ch := make(chan os.Signal)
 	signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
 	<-ch
-	svr.Close()
-	time.Sleep(250 * time.Millisecond)
+	svr.GracefulClose(500 * time.Millisecond)
 	close(kcpDoneCh)
 }
 

+ 2 - 2
cmd/frpc/sub/status.go

@@ -18,7 +18,7 @@ import (
 	"encoding/base64"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"os"
 	"strings"
@@ -77,7 +77,7 @@ func status(clientCfg config.ClientCommonConf) error {
 		return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
 	}
 
-	body, err := ioutil.ReadAll(resp.Body)
+	body, err := io.ReadAll(resp.Body)
 	if err != nil {
 		return err
 	}

+ 1 - 1
cmd/frps/main.go

@@ -20,7 +20,7 @@ import (
 
 	"github.com/fatedier/golib/crypto"
 
-	_ "github.com/fatedier/frp/assets/frps/statik"
+	_ "github.com/fatedier/frp/assets/frps"
 	_ "github.com/fatedier/frp/pkg/metrics"
 )
 

+ 6 - 2
conf/frpc_full.ini

@@ -105,6 +105,10 @@ udp_packet_size = 1500
 # include other config files for proxies.
 # includes = ./confd/*.ini
 
+# By default, frpc will connect frps with first custom byte if tls is enabled.
+# If DisableCustomTLSFirstByte is true, frpc will not send that custom byte.
+disable_custom_tls_first_byte = false
+
 # 'ssh' is the unique proxy name
 # if user in [common] section is not empty, it will be changed to {user}.{proxy} such as 'your_name.ssh'
 [ssh]
@@ -181,9 +185,9 @@ use_compression = true
 # if not set, you can access this custom_domains without certification
 http_user = admin
 http_pwd = admin
-# if domain for frps is frps.com, then you can access [web01] proxy by URL http://test.frps.com
+# if domain for frps is frps.com, then you can access [web01] proxy by URL http://web01.frps.com
 subdomain = web01
-custom_domains = web02.yourdomain.com
+custom_domains = web01.yourdomain.com
 # locations is only available for http type
 locations = /,/pic
 host_header_rewrite = example.com

+ 0 - 1
go.mod

@@ -21,7 +21,6 @@ require (
 	github.com/pires/go-proxyproto v0.5.0
 	github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
 	github.com/prometheus/client_golang v1.11.0
-	github.com/rakyll/statik v0.1.1
 	github.com/rodaine/table v1.0.1
 	github.com/spf13/cobra v1.1.3
 	github.com/stretchr/testify v1.7.0

+ 0 - 2
go.sum

@@ -332,8 +332,6 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
 github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
-github.com/rakyll/statik v0.1.1 h1:fCLHsIMajHqD5RKigbFXpvX3dN7c80Pm12+NCrI3kvg=
-github.com/rakyll/statik v0.1.1/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
 github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ=
 github.com/rodaine/table v1.0.1/go.mod h1:UVEtfBsflpeEcD56nF4F5AocNFta0ZuolpSVdPtlmP4=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=

+ 1 - 1
hack/run-e2e.sh

@@ -5,7 +5,7 @@ ROOT=$(unset CDPATH && cd $(dirname "${BASH_SOURCE[0]}")/.. && pwd)
 which ginkgo &> /dev/null
 if [ $? -ne 0 ]; then
     echo "ginkgo not found, try to install..."
-    go get -u github.com/onsi/ginkgo/ginkgo
+    go install github.com/onsi/ginkgo/ginkgo@latest
 fi
 
 debug=false

+ 3 - 0
pkg/config/client.go

@@ -124,6 +124,9 @@ type ClientCommonConf struct {
 	// TLSServerName specifices the custom server name of tls certificate. By
 	// default, server name if same to ServerAddr.
 	TLSServerName string `ini:"tls_server_name" json:"tls_server_name"`
+	// By default, frpc will connect frps with first custom byte if tls is enabled.
+	// If DisableCustomTLSFirstByte is true, frpc will not send that custom byte.
+	DisableCustomTLSFirstByte bool `ini:"disable_custom_tls_first_byte" json:"disable_custom_tls_first_byte"`
 	// HeartBeatInterval specifies at what interval heartbeats are sent to the
 	// server, in seconds. It is not recommended to change this value. By
 	// default, this value is 30.

+ 1 - 2
pkg/config/parse.go

@@ -17,7 +17,6 @@ package config
 import (
 	"bytes"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 )
@@ -77,7 +76,7 @@ func getIncludeContents(paths []string) ([]byte, error) {
 		if _, err := os.Stat(absDir); os.IsNotExist(err) {
 			return nil, err
 		}
-		files, err := ioutil.ReadDir(absDir)
+		files, err := os.ReadDir(absDir)
 		if err != nil {
 			return nil, err
 		}

+ 1 - 2
pkg/config/value.go

@@ -16,7 +16,6 @@ package config
 
 import (
 	"bytes"
-	"io/ioutil"
 	"os"
 	"strings"
 	"text/template"
@@ -67,7 +66,7 @@ func RenderContent(in []byte) (out []byte, err error) {
 
 func GetRenderedConfFromFile(path string) (out []byte, err error) {
 	var b []byte
-	b, err = ioutil.ReadFile(path)
+	b, err = os.ReadFile(path)
 	if err != nil {
 		return
 	}

+ 1 - 2
pkg/plugin/client/socks5.go

@@ -16,7 +16,6 @@ package plugin
 
 import (
 	"io"
-	"io/ioutil"
 	"log"
 	"net"
 
@@ -43,7 +42,7 @@ func NewSocks5Plugin(params map[string]string) (p Plugin, err error) {
 	passwd := params["plugin_passwd"]
 
 	cfg := &gosocks5.Config{
-		Logger: log.New(ioutil.Discard, "", log.LstdFlags),
+		Logger: log.New(io.Discard, "", log.LstdFlags),
 	}
 	if user != "" || passwd != "" {
 		cfg.Credentials = gosocks5.StaticCredentials(map[string]string{user: passwd})

+ 2 - 2
pkg/plugin/server/http.go

@@ -20,7 +20,7 @@ import (
 	"crypto/tls"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"net/url"
 	"reflect"
@@ -116,7 +116,7 @@ func (p *httpPlugin) do(ctx context.Context, r *Request, res *Response) error {
 	if resp.StatusCode != http.StatusOK {
 		return fmt.Errorf("do http request error code: %d", resp.StatusCode)
 	}
-	buf, err = ioutil.ReadAll(resp.Body)
+	buf, err = io.ReadAll(resp.Body)
 	if err != nil {
 		return err
 	}

+ 2 - 2
pkg/transport/tls.go

@@ -6,8 +6,8 @@ import (
 	"crypto/tls"
 	"crypto/x509"
 	"encoding/pem"
-	"io/ioutil"
 	"math/big"
+	"os"
 )
 
 func newCustomTLSKeyPair(certfile, keyfile string) (*tls.Certificate, error) {
@@ -47,7 +47,7 @@ func newRandomTLSKeyPair() *tls.Certificate {
 func newCertPool(caPath string) (*x509.CertPool, error) {
 	pool := x509.NewCertPool()
 
-	caCrt, err := ioutil.ReadFile(caPath)
+	caCrt, err := os.ReadFile(caPath)
 	if err != nil {
 		return nil, err
 	}

+ 2 - 2
pkg/util/net/conn.go

@@ -228,7 +228,7 @@ func ConnectServerByProxy(proxyURL string, protocol string, addr string) (c net.
 	}
 }
 
-func ConnectServerByProxyWithTLS(proxyURL string, protocol string, addr string, tlsConfig *tls.Config) (c net.Conn, err error) {
+func ConnectServerByProxyWithTLS(proxyURL string, protocol string, addr string, tlsConfig *tls.Config, disableCustomTLSHeadByte bool) (c net.Conn, err error) {
 	c, err = ConnectServerByProxy(proxyURL, protocol, addr)
 	if err != nil {
 		return
@@ -238,6 +238,6 @@ func ConnectServerByProxyWithTLS(proxyURL string, protocol string, addr string,
 		return
 	}
 
-	c = WrapTLSClientConn(c, tlsConfig)
+	c = WrapTLSClientConn(c, tlsConfig, disableCustomTLSHeadByte)
 	return
 }

+ 13 - 3
pkg/util/net/tls.go

@@ -27,13 +27,18 @@ var (
 	FRPTLSHeadByte = 0x17
 )
 
-func WrapTLSClientConn(c net.Conn, tlsConfig *tls.Config) (out net.Conn) {
-	c.Write([]byte{byte(FRPTLSHeadByte)})
+func WrapTLSClientConn(c net.Conn, tlsConfig *tls.Config, disableCustomTLSHeadByte bool) (out net.Conn) {
+	if !disableCustomTLSHeadByte {
+		c.Write([]byte{byte(FRPTLSHeadByte)})
+	}
 	out = tls.Client(c, tlsConfig)
 	return
 }
 
-func CheckAndEnableTLSServerConnWithTimeout(c net.Conn, tlsConfig *tls.Config, tlsOnly bool, timeout time.Duration) (out net.Conn, err error) {
+func CheckAndEnableTLSServerConnWithTimeout(
+	c net.Conn, tlsConfig *tls.Config, tlsOnly bool, timeout time.Duration,
+) (out net.Conn, isTLS bool, custom bool, err error) {
+
 	sc, r := gnet.NewSharedConnSize(c, 2)
 	buf := make([]byte, 1)
 	var n int
@@ -46,6 +51,11 @@ func CheckAndEnableTLSServerConnWithTimeout(c net.Conn, tlsConfig *tls.Config, t
 
 	if n == 1 && int(buf[0]) == FRPTLSHeadByte {
 		out = tls.Server(c, tlsConfig)
+		isTLS = true
+		custom = true
+	} else if n == 1 && int(buf[0]) == 0x16 {
+		out = tls.Server(sc, tlsConfig)
+		isTLS = true
 	} else {
 		if tlsOnly {
 			err = fmt.Errorf("non-TLS connection received on a TlsOnly server")

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

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

+ 4 - 3
pkg/util/vhost/resource.go

@@ -16,8 +16,9 @@ package vhost
 
 import (
 	"bytes"
-	"io/ioutil"
+	"io"
 	"net/http"
+	"os"
 
 	frpLog "github.com/fatedier/frp/pkg/util/log"
 	"github.com/fatedier/frp/pkg/util/version"
@@ -57,7 +58,7 @@ func getNotFoundPageContent() []byte {
 		err error
 	)
 	if NotFoundPagePath != "" {
-		buf, err = ioutil.ReadFile(NotFoundPagePath)
+		buf, err = os.ReadFile(NotFoundPagePath)
 		if err != nil {
 			frpLog.Warn("read custom 404 page error: %v", err)
 			buf = []byte(NotFound)
@@ -80,7 +81,7 @@ func notFoundResponse() *http.Response {
 		ProtoMajor: 1,
 		ProtoMinor: 0,
 		Header:     header,
-		Body:       ioutil.NopCloser(bytes.NewReader(getNotFoundPageContent())),
+		Body:       io.NopCloser(bytes.NewReader(getNotFoundPageContent())),
 	}
 	return res
 }

+ 1 - 0
server/dashboard.go

@@ -48,6 +48,7 @@ func (svr *Service) RunDashboardServer(address string) (err error) {
 	router.HandleFunc("/api/proxy/{type}", svr.APIProxyByType).Methods("GET")
 	router.HandleFunc("/api/proxy/{type}/{name}", svr.APIProxyByTypeAndName).Methods("GET")
 	router.HandleFunc("/api/traffic/{name}", svr.APIProxyTraffic).Methods("GET")
+	router.HandleFunc("/healthz", svr.Healthz)
 
 	// view
 	router.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")

+ 5 - 0
server/dashboard_api.go

@@ -51,6 +51,11 @@ type serverInfoResp struct {
 	ProxyTypeCounts map[string]int64 `json:"proxy_type_count"`
 }
 
+// /healthz
+func (svr *Service) Healthz(w http.ResponseWriter, r *http.Request) {
+	w.WriteHeader(200)
+}
+
 // api/serverinfo
 func (svr *Service) APIServerInfo(w http.ResponseWriter, r *http.Request) {
 	res := GeneralResponse{Code: 200}

+ 10 - 12
server/service.go

@@ -19,7 +19,7 @@ import (
 	"context"
 	"crypto/tls"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net"
 	"net/http"
 	"sort"
@@ -258,8 +258,9 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
 	}
 
 	// frp tls listener
-	svr.tlsListener = svr.muxer.Listen(1, 1, func(data []byte) bool {
-		return int(data[0]) == frpNet.FRPTLSHeadByte
+	svr.tlsListener = svr.muxer.Listen(2, 1, func(data []byte) bool {
+		// tls first byte can be 0x16 only when vhost https port is not same with bind port
+		return int(data[0]) == frpNet.FRPTLSHeadByte || int(data[0]) == 0x16
 	})
 
 	// Create nat hole controller.
@@ -279,11 +280,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
 	// Create dashboard web server.
 	if cfg.DashboardPort > 0 {
 		// Init dashboard assets
-		err = assets.Load(cfg.AssetsDir)
-		if err != nil {
-			err = fmt.Errorf("Load assets error: %v", err)
-			return
-		}
+		assets.Load(cfg.AssetsDir)
 
 		address := net.JoinHostPort(cfg.DashboardAddr, strconv.Itoa(cfg.DashboardPort))
 		err = svr.RunDashboardServer(address)
@@ -395,20 +392,21 @@ func (svr *Service) HandleListener(l net.Listener) {
 
 		log.Trace("start check TLS connection...")
 		originConn := c
-		c, err = frpNet.CheckAndEnableTLSServerConnWithTimeout(c, svr.tlsConfig, svr.cfg.TLSOnly, connReadTimeout)
+		var isTLS, custom bool
+		c, isTLS, custom, err = frpNet.CheckAndEnableTLSServerConnWithTimeout(c, svr.tlsConfig, svr.cfg.TLSOnly, connReadTimeout)
 		if err != nil {
 			log.Warn("CheckAndEnableTLSServerConnWithTimeout error: %v", err)
 			originConn.Close()
 			continue
 		}
-		log.Trace("success check TLS connection")
+		log.Trace("check TLS connection success, isTLS: %v custom: %v", isTLS, custom)
 
-		// Start a new goroutine for dealing connections.
+		// Start a new goroutine to handle connection.
 		go func(ctx context.Context, frpConn net.Conn) {
 			if svr.cfg.TCPMux {
 				fmuxCfg := fmux.DefaultConfig()
 				fmuxCfg.KeepAliveInterval = 20 * time.Second
-				fmuxCfg.LogOutput = ioutil.Discard
+				fmuxCfg.LogOutput = io.Discard
 				session, err := fmux.Server(frpConn, fmuxCfg)
 				if err != nil {
 					log.Warn("Failed to create mux connection: %v", err)

+ 18 - 0
test/e2e/basic/client_server.go

@@ -231,4 +231,22 @@ var _ = Describe("[Feature: Client-Server]", func() {
 			})
 		})
 	})
+
+	Describe("TLS with disable_custom_tls_first_byte", func() {
+		supportProtocols := []string{"tcp", "kcp", "websocket"}
+		for _, protocol := range supportProtocols {
+			tmp := protocol
+			defineClientServerTest("TLS over "+strings.ToUpper(tmp), f, &generalTestConfigures{
+				server: fmt.Sprintf(`
+					kcp_bind_port = {{ .%s }}
+					protocol = %s
+					`, consts.PortServerName, protocol),
+				client: fmt.Sprintf(`
+					tls_enable = true
+					protocol = %s
+					disable_custom_tls_first_byte = true
+					`, protocol),
+			})
+		}
+	})
 })

+ 2 - 0
test/e2e/features/monitor.go

@@ -3,6 +3,7 @@ package features
 import (
 	"fmt"
 	"strings"
+	"time"
 
 	"github.com/fatedier/frp/pkg/util/log"
 	"github.com/fatedier/frp/test/e2e/framework"
@@ -35,6 +36,7 @@ var _ = Describe("[Feature: Monitor]", func() {
 		f.RunProcesses([]string{serverConf}, []string{clientConf})
 
 		framework.NewRequestExpect(f).Port(remotePort).Ensure()
+		time.Sleep(500 * time.Millisecond)
 
 		framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
 			r.HTTP().Port(dashboardPort).HTTPPath("/metrics")

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

@@ -3,7 +3,6 @@ package framework
 import (
 	"bytes"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"regexp"
@@ -90,7 +89,7 @@ func (f *Framework) BeforeEach() {
 
 	f.cleanupHandle = AddCleanupAction(f.AfterEach)
 
-	dir, err := ioutil.TempDir(os.TempDir(), "frp-e2e-test-*")
+	dir, err := os.MkdirTemp(os.TempDir(), "frp-e2e-test-*")
 	ExpectNoError(err)
 	f.TempDirectory = dir
 
@@ -260,7 +259,7 @@ func (f *Framework) SetEnvs(envs []string) {
 
 func (f *Framework) WriteTempFile(name string, content string) string {
 	filePath := filepath.Join(f.TempDirectory, name)
-	err := ioutil.WriteFile(filePath, []byte(content), 0766)
+	err := os.WriteFile(filePath, []byte(content), 0766)
 	ExpectNoError(err)
 	return filePath
 }

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

@@ -2,7 +2,7 @@ package framework
 
 import (
 	"fmt"
-	"io/ioutil"
+	"os"
 	"path/filepath"
 	"time"
 
@@ -30,7 +30,7 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
 
 	for i := range serverTemplates {
 		path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-server-%d", i))
-		err = ioutil.WriteFile(path, []byte(outs[i]), 0666)
+		err = os.WriteFile(path, []byte(outs[i]), 0666)
 		ExpectNoError(err)
 		flog.Trace("[%s] %s", path, outs[i])
 
@@ -45,7 +45,7 @@ func (f *Framework) RunProcesses(serverTemplates []string, clientTemplates []str
 	for i := range clientTemplates {
 		index := i + len(serverTemplates)
 		path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-client-%d", i))
-		err = ioutil.WriteFile(path, []byte(outs[index]), 0666)
+		err = os.WriteFile(path, []byte(outs[index]), 0666)
 		ExpectNoError(err)
 		flog.Trace("[%s] %s", path, outs[index])
 
@@ -85,7 +85,7 @@ func (f *Framework) RunFrpc(args ...string) (*process.Process, string, error) {
 func (f *Framework) GenerateConfigFile(content string) string {
 	f.configFileIndex++
 	path := filepath.Join(f.TempDirectory, fmt.Sprintf("frp-e2e-config-%d", f.configFileIndex))
-	err := ioutil.WriteFile(path, []byte(content), 0666)
+	err := os.WriteFile(path, []byte(content), 0666)
 	ExpectNoError(err)
 	return path
 }

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

@@ -6,7 +6,6 @@ import (
 	"crypto/tls"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"net/url"
@@ -219,7 +218,7 @@ func (r *Request) sendHTTPRequest(method, urlstr string, host string, headers ma
 	}
 
 	ret := &Response{Code: resp.StatusCode, Header: resp.Header}
-	buf, err := ioutil.ReadAll(resp.Body)
+	buf, err := io.ReadAll(resp.Body)
 	if err != nil {
 		return nil, err
 	}

+ 2 - 2
test/e2e/pkg/sdk/client/client.go

@@ -3,7 +3,7 @@ package client
 import (
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net"
 	"net/http"
 	"strconv"
@@ -125,7 +125,7 @@ func (c *Client) do(req *http.Request) (string, error) {
 	if resp.StatusCode != 200 {
 		return "", fmt.Errorf("api status code [%d]", resp.StatusCode)
 	}
-	buf, err := ioutil.ReadAll(resp.Body)
+	buf, err := io.ReadAll(resp.Body)
 	if err != nil {
 		return "", err
 	}

+ 2 - 2
test/e2e/plugin/utils.go

@@ -3,7 +3,7 @@ package plugin
 import (
 	"crypto/tls"
 	"encoding/json"
-	"io/ioutil"
+	"io"
 	"net/http"
 
 	plugin "github.com/fatedier/frp/pkg/plugin/server"
@@ -21,7 +21,7 @@ func NewHTTPPluginServer(port int, newFunc NewPluginRequest, handler PluginHandl
 		httpserver.WithTlsConfig(tlsConfig),
 		httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 			r := newFunc()
-			buf, err := ioutil.ReadAll(req.Body)
+			buf, err := io.ReadAll(req.Body)
 			if err != nil {
 				w.WriteHeader(500)
 				return

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio