Browse Source

Merge pull request #3299 from fatedier/dev

sync
fatedier 2 years ago
parent
commit
534dc99d55

+ 2 - 2
.circleci/config.yml

@@ -2,7 +2,7 @@ version: 2
 jobs:
 jobs:
   go-version-latest:
   go-version-latest:
     docker:
     docker:
-      - image: cimg/go:1.19-node
+      - image: cimg/go:1.20-node
     resource_class: large
     resource_class: large
     steps:
     steps:
       - checkout
       - checkout
@@ -10,7 +10,7 @@ jobs:
       - run: make alltest
       - run: make alltest
   go-version-last:
   go-version-last:
     docker:
     docker:
-      - image: cimg/go:1.18-node
+      - image: cimg/go:1.19-node
     resource_class: large
     resource_class: large
     steps:
     steps:
       - checkout
       - checkout

+ 2 - 2
.github/workflows/golangci-lint.yml

@@ -16,13 +16,13 @@ jobs:
     steps:
     steps:
       - uses: actions/setup-go@v3
       - uses: actions/setup-go@v3
         with:
         with:
-          go-version: 1.19
+          go-version: '1.20'
       - uses: actions/checkout@v3
       - uses: actions/checkout@v3
       - name: golangci-lint
       - name: golangci-lint
         uses: golangci/golangci-lint-action@v3
         uses: golangci/golangci-lint-action@v3
         with:
         with:
           # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
           # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
-          version: v1.49.0
+          version: v1.51
 
 
           # Optional: golangci-lint command line arguments.
           # Optional: golangci-lint command line arguments.
           # args: --issues-exit-code=0
           # args: --issues-exit-code=0

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

@@ -15,7 +15,7 @@ jobs:
       - name: Set up Go
       - name: Set up Go
         uses: actions/setup-go@v3
         uses: actions/setup-go@v3
         with:
         with:
-          go-version: 1.19
+          go-version: '1.20'
           
           
       - name: Make All
       - name: Make All
         run: |
         run: |

+ 6 - 2
.golangci.yml

@@ -1,6 +1,5 @@
 service:
 service:
-  # When updating this, also update the version stored in docker/build-tools/Dockerfile in the istio/tools repo.
-  golangci-lint-version: 1.49.x # use the fixed version to not introduce new linters unexpectedly
+  golangci-lint-version: 1.51.x # use the fixed version to not introduce new linters unexpectedly
   
   
 run:
 run:
   concurrency: 4
   concurrency: 4
@@ -127,6 +126,11 @@ issues:
         - errcheck
         - errcheck
         - maligned
         - maligned
 
 
+    # keep it until we only support go1.20
+    - linters:
+        - staticcheck
+      text: "SA1019: rand.Seed has been deprecated"
+
   # Independently from option `exclude` we use default exclude patterns,
   # Independently from option `exclude` we use default exclude patterns,
   # it can be disabled by this option. To list all
   # it can be disabled by this option. To list all
   # excluded by default patterns execute `golangci-lint run --help`.
   # excluded by default patterns execute `golangci-lint run --help`.

+ 3 - 0
Makefile

@@ -16,6 +16,9 @@ file:
 fmt:
 fmt:
 	go fmt ./...
 	go fmt ./...
 
 
+fmt-more:
+	gofumpt -l -w .
+
 vet:
 vet:
 	go vet ./...
 	go vet ./...
 
 

+ 4 - 5
README.md

@@ -17,10 +17,6 @@
 
 
 <!--gold sponsors end-->
 <!--gold sponsors end-->
 
 
-<h3 align="center">Silver Sponsors</h3>
-
-* Sakura Frp - 欢迎点击 "加入我们"
-
 ## 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 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.
@@ -143,7 +139,7 @@ Note that `local_port` (listened on client) and `remote_port` (exposed on server
 
 
   `./frpc -c ./frpc.ini`
   `./frpc -c ./frpc.ini`
 
 
-5. From another machine, SSH to server B like this (assuming that username is `test`):
+5. From another machine, SSH to server B via server A  like this (assuming that username is `test`):
 
 
   `ssh -oPort=6000 test@x.x.x.x`
   `ssh -oPort=6000 test@x.x.x.x`
 
 
@@ -717,10 +713,13 @@ 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`.
 
 
+Set `bandwidth_limit_mode` to `client` or `server` to limit bandwidth on the client or server side. Default is `client`.
+
 ### TCP Stream Multiplexing
 ### TCP Stream Multiplexing
 
 
 frp supports tcp stream multiplexing since v0.10.0 like HTTP2 Multiplexing, in which case all logic connections to the same frpc are multiplexed into the same TCP connection.
 frp supports tcp stream multiplexing since v0.10.0 like HTTP2 Multiplexing, in which case all logic connections to the same frpc are multiplexed into the same TCP connection.

+ 0 - 4
README_zh.md

@@ -18,10 +18,6 @@ frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP
 
 
 <!--gold sponsors end-->
 <!--gold sponsors end-->
 
 
-<h3 align="center">Silver Sponsors</h3>
-
-* Sakura Frp - 欢迎点击 "加入我们"
-
 ## 为什么使用 frp ?
 ## 为什么使用 frp ?
 
 
 通过在具有公网 IP 的节点上部署 frp 服务端,可以轻松地将内网服务穿透到公网,同时提供诸多专业的功能特性,这包括:
 通过在具有公网 IP 的节点上部署 frp 服务端,可以轻松地将内网服务穿透到公网,同时提供诸多专业的功能特性,这包括:

+ 7 - 3
Release.md

@@ -1,4 +1,8 @@
-### Fix
+### New
 
 
-* Server Plugin send incorrect op name for NewWorkConn.
-* QUIC stream leak.
+* 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.
+
+### Improve
+
+* `dns_server` supports ipv6.
+* frpc supports graceful shutdown for protocol `quic`.

+ 1 - 1
client/proxy/proxy.go

@@ -54,7 +54,7 @@ type Proxy interface {
 func NewProxy(ctx context.Context, pxyConf config.ProxyConf, clientCfg config.ClientCommonConf, serverUDPPort int) (pxy Proxy) {
 func NewProxy(ctx context.Context, pxyConf config.ProxyConf, clientCfg config.ClientCommonConf, serverUDPPort int) (pxy Proxy) {
 	var limiter *rate.Limiter
 	var limiter *rate.Limiter
 	limitBytes := pxyConf.GetBaseInfo().BandwidthLimit.Bytes()
 	limitBytes := pxyConf.GetBaseInfo().BandwidthLimit.Bytes()
-	if limitBytes > 0 {
+	if limitBytes > 0 && pxyConf.GetBaseInfo().BandwidthLimitMode == config.BandwidthLimitModeClient {
 		limiter = rate.NewLimiter(rate.Limit(float64(limitBytes)), int(limitBytes))
 		limiter = rate.NewLimiter(rate.Limit(float64(limitBytes)), int(limitBytes))
 	}
 	}
 
 

+ 4 - 3
client/service.go

@@ -31,7 +31,7 @@ import (
 	"github.com/fatedier/golib/crypto"
 	"github.com/fatedier/golib/crypto"
 	libdial "github.com/fatedier/golib/net/dial"
 	libdial "github.com/fatedier/golib/net/dial"
 	fmux "github.com/hashicorp/yamux"
 	fmux "github.com/hashicorp/yamux"
-	quic "github.com/lucas-clemente/quic-go"
+	quic "github.com/quic-go/quic-go"
 
 
 	"github.com/fatedier/frp/assets"
 	"github.com/fatedier/frp/assets"
 	"github.com/fatedier/frp/pkg/auth"
 	"github.com/fatedier/frp/pkg/auth"
@@ -47,6 +47,7 @@ import (
 
 
 func init() {
 func init() {
 	crypto.DefaultSalt = "frp"
 	crypto.DefaultSalt = "frp"
+	// TODO: remove this when we drop support for go1.19
 	rand.Seed(time.Now().UnixNano())
 	rand.Seed(time.Now().UnixNano())
 }
 }
 
 
@@ -114,8 +115,8 @@ func (svr *Service) Run() error {
 	// set custom DNSServer
 	// set custom DNSServer
 	if svr.cfg.DNSServer != "" {
 	if svr.cfg.DNSServer != "" {
 		dnsAddr := svr.cfg.DNSServer
 		dnsAddr := svr.cfg.DNSServer
-		if !strings.Contains(dnsAddr, ":") {
-			dnsAddr += ":53"
+		if _, _, err := net.SplitHostPort(dnsAddr); err != nil {
+			dnsAddr = net.JoinHostPort(dnsAddr, "53")
 		}
 		}
 		// Change default dns server for frpc
 		// Change default dns server for frpc
 		net.DefaultResolver = &net.Resolver{
 		net.DefaultResolver = &net.Resolver{

+ 8 - 0
cmd/frpc/sub/http.go

@@ -39,6 +39,8 @@ func init() {
 	httpCmd.PersistentFlags().StringVarP(&hostHeaderRewrite, "host_header_rewrite", "", "", "host header rewrite")
 	httpCmd.PersistentFlags().StringVarP(&hostHeaderRewrite, "host_header_rewrite", "", "", "host header rewrite")
 	httpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
 	httpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
 	httpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
 	httpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
+	httpCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
+	httpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
 
 
 	rootCmd.AddCommand(httpCmd)
 	rootCmd.AddCommand(httpCmd)
 }
 }
@@ -70,6 +72,12 @@ var httpCmd = &cobra.Command{
 		cfg.HostHeaderRewrite = hostHeaderRewrite
 		cfg.HostHeaderRewrite = hostHeaderRewrite
 		cfg.UseEncryption = useEncryption
 		cfg.UseEncryption = useEncryption
 		cfg.UseCompression = useCompression
 		cfg.UseCompression = useCompression
+		cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
+		if err != nil {
+			fmt.Println(err)
+			os.Exit(1)
+		}
+		cfg.BandwidthLimitMode = bandwidthLimitMode
 
 
 		err = cfg.CheckForCli()
 		err = cfg.CheckForCli()
 		if err != nil {
 		if err != nil {

+ 8 - 0
cmd/frpc/sub/https.go

@@ -35,6 +35,8 @@ func init() {
 	httpsCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
 	httpsCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain")
 	httpsCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
 	httpsCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
 	httpsCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
 	httpsCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
+	httpsCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
+	httpsCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
 
 
 	rootCmd.AddCommand(httpsCmd)
 	rootCmd.AddCommand(httpsCmd)
 }
 }
@@ -62,6 +64,12 @@ var httpsCmd = &cobra.Command{
 		cfg.SubDomain = subDomain
 		cfg.SubDomain = subDomain
 		cfg.UseEncryption = useEncryption
 		cfg.UseEncryption = useEncryption
 		cfg.UseCompression = useCompression
 		cfg.UseCompression = useCompression
+		cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
+		if err != nil {
+			fmt.Println(err)
+			os.Exit(1)
+		}
+		cfg.BandwidthLimitMode = bandwidthLimitMode
 
 
 		err = cfg.CheckForCli()
 		err = cfg.CheckForCli()
 		if err != nil {
 		if err != nil {

+ 27 - 24
cmd/frpc/sub/root.go

@@ -54,24 +54,26 @@ var (
 	logMaxDays      int
 	logMaxDays      int
 	disableLogColor bool
 	disableLogColor bool
 
 
-	proxyName         string
-	localIP           string
-	localPort         int
-	remotePort        int
-	useEncryption     bool
-	useCompression    bool
-	customDomains     string
-	subDomain         string
-	httpUser          string
-	httpPwd           string
-	locations         string
-	hostHeaderRewrite string
-	role              string
-	sk                string
-	multiplexer       string
-	serverName        string
-	bindAddr          string
-	bindPort          int
+	proxyName          string
+	localIP            string
+	localPort          int
+	remotePort         int
+	useEncryption      bool
+	useCompression     bool
+	bandwidthLimit     string
+	bandwidthLimitMode string
+	customDomains      string
+	subDomain          string
+	httpUser           string
+	httpPwd            string
+	locations          string
+	hostHeaderRewrite  string
+	role               string
+	sk                 string
+	multiplexer        string
+	serverName         string
+	bindAddr           string
+	bindPort           int
 
 
 	tlsEnable bool
 	tlsEnable bool
 )
 )
@@ -216,15 +218,16 @@ func startService(
 		return
 		return
 	}
 	}
 
 
-	kcpDoneCh := make(chan struct{})
-	// Capture the exit signal if we use kcp.
-	if cfg.Protocol == "kcp" {
-		go handleSignal(svr, kcpDoneCh)
+	closedDoneCh := make(chan struct{})
+	shouldGracefulClose := cfg.Protocol == "kcp" || cfg.Protocol == "quic"
+	// Capture the exit signal if we use kcp or quic.
+	if shouldGracefulClose {
+		go handleSignal(svr, closedDoneCh)
 	}
 	}
 
 
 	err = svr.Run()
 	err = svr.Run()
-	if err == nil && cfg.Protocol == "kcp" {
-		<-kcpDoneCh
+	if err == nil && shouldGracefulClose {
+		<-closedDoneCh
 	}
 	}
 	return
 	return
 }
 }

+ 8 - 0
cmd/frpc/sub/stcp.go

@@ -37,6 +37,8 @@ func init() {
 	stcpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
 	stcpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
 	stcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
 	stcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
 	stcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
 	stcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
+	stcpCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
+	stcpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
 
 
 	rootCmd.AddCommand(stcpCmd)
 	rootCmd.AddCommand(stcpCmd)
 }
 }
@@ -70,6 +72,12 @@ var stcpCmd = &cobra.Command{
 			cfg.Sk = sk
 			cfg.Sk = sk
 			cfg.LocalIP = localIP
 			cfg.LocalIP = localIP
 			cfg.LocalPort = localPort
 			cfg.LocalPort = localPort
+			cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
+			if err != nil {
+				fmt.Println(err)
+				os.Exit(1)
+			}
+			cfg.BandwidthLimitMode = bandwidthLimitMode
 			err = cfg.CheckForCli()
 			err = cfg.CheckForCli()
 			if err != nil {
 			if err != nil {
 				fmt.Println(err)
 				fmt.Println(err)

+ 8 - 0
cmd/frpc/sub/sudp.go

@@ -37,6 +37,8 @@ func init() {
 	sudpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
 	sudpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
 	sudpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
 	sudpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
 	sudpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
 	sudpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
+	sudpCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
+	sudpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
 
 
 	rootCmd.AddCommand(sudpCmd)
 	rootCmd.AddCommand(sudpCmd)
 }
 }
@@ -70,6 +72,12 @@ var sudpCmd = &cobra.Command{
 			cfg.Sk = sk
 			cfg.Sk = sk
 			cfg.LocalIP = localIP
 			cfg.LocalIP = localIP
 			cfg.LocalPort = localPort
 			cfg.LocalPort = localPort
+			cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
+			if err != nil {
+				fmt.Println(err)
+				os.Exit(1)
+			}
+			cfg.BandwidthLimitMode = bandwidthLimitMode
 			err = cfg.CheckForCli()
 			err = cfg.CheckForCli()
 			if err != nil {
 			if err != nil {
 				fmt.Println(err)
 				fmt.Println(err)

+ 8 - 0
cmd/frpc/sub/tcp.go

@@ -33,6 +33,8 @@ func init() {
 	tcpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port")
 	tcpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port")
 	tcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
 	tcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
 	tcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
 	tcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
+	tcpCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
+	tcpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
 
 
 	rootCmd.AddCommand(tcpCmd)
 	rootCmd.AddCommand(tcpCmd)
 }
 }
@@ -59,6 +61,12 @@ var tcpCmd = &cobra.Command{
 		cfg.RemotePort = remotePort
 		cfg.RemotePort = remotePort
 		cfg.UseEncryption = useEncryption
 		cfg.UseEncryption = useEncryption
 		cfg.UseCompression = useCompression
 		cfg.UseCompression = useCompression
+		cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
+		if err != nil {
+			fmt.Println(err)
+			os.Exit(1)
+		}
+		cfg.BandwidthLimitMode = bandwidthLimitMode
 
 
 		err = cfg.CheckForCli()
 		err = cfg.CheckForCli()
 		if err != nil {
 		if err != nil {

+ 8 - 0
cmd/frpc/sub/tcpmux.go

@@ -36,6 +36,8 @@ func init() {
 	tcpMuxCmd.PersistentFlags().StringVarP(&multiplexer, "mux", "", "", "multiplexer")
 	tcpMuxCmd.PersistentFlags().StringVarP(&multiplexer, "mux", "", "", "multiplexer")
 	tcpMuxCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
 	tcpMuxCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
 	tcpMuxCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
 	tcpMuxCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
+	tcpMuxCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
+	tcpMuxCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
 
 
 	rootCmd.AddCommand(tcpMuxCmd)
 	rootCmd.AddCommand(tcpMuxCmd)
 }
 }
@@ -64,6 +66,12 @@ var tcpMuxCmd = &cobra.Command{
 		cfg.Multiplexer = multiplexer
 		cfg.Multiplexer = multiplexer
 		cfg.UseEncryption = useEncryption
 		cfg.UseEncryption = useEncryption
 		cfg.UseCompression = useCompression
 		cfg.UseCompression = useCompression
+		cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
+		if err != nil {
+			fmt.Println(err)
+			os.Exit(1)
+		}
+		cfg.BandwidthLimitMode = bandwidthLimitMode
 
 
 		err = cfg.CheckForCli()
 		err = cfg.CheckForCli()
 		if err != nil {
 		if err != nil {

+ 8 - 0
cmd/frpc/sub/udp.go

@@ -33,6 +33,8 @@ func init() {
 	udpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port")
 	udpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port")
 	udpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
 	udpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
 	udpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
 	udpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
+	udpCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
+	udpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
 
 
 	rootCmd.AddCommand(udpCmd)
 	rootCmd.AddCommand(udpCmd)
 }
 }
@@ -59,6 +61,12 @@ var udpCmd = &cobra.Command{
 		cfg.RemotePort = remotePort
 		cfg.RemotePort = remotePort
 		cfg.UseEncryption = useEncryption
 		cfg.UseEncryption = useEncryption
 		cfg.UseCompression = useCompression
 		cfg.UseCompression = useCompression
+		cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
+		if err != nil {
+			fmt.Println(err)
+			os.Exit(1)
+		}
+		cfg.BandwidthLimitMode = bandwidthLimitMode
 
 
 		err = cfg.CheckForCli()
 		err = cfg.CheckForCli()
 		if err != nil {
 		if err != nil {

+ 8 - 0
cmd/frpc/sub/xtcp.go

@@ -37,6 +37,8 @@ func init() {
 	xtcpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
 	xtcpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port")
 	xtcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
 	xtcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption")
 	xtcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
 	xtcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression")
+	xtcpCmd.PersistentFlags().StringVarP(&bandwidthLimit, "bandwidth_limit", "", "", "bandwidth limit")
+	xtcpCmd.PersistentFlags().StringVarP(&bandwidthLimitMode, "bandwidth_limit_mode", "", config.BandwidthLimitModeClient, "bandwidth limit mode")
 
 
 	rootCmd.AddCommand(xtcpCmd)
 	rootCmd.AddCommand(xtcpCmd)
 }
 }
@@ -70,6 +72,12 @@ var xtcpCmd = &cobra.Command{
 			cfg.Sk = sk
 			cfg.Sk = sk
 			cfg.LocalIP = localIP
 			cfg.LocalIP = localIP
 			cfg.LocalPort = localPort
 			cfg.LocalPort = localPort
+			cfg.BandwidthLimit, err = config.NewBandwidthQuantity(bandwidthLimit)
+			if err != nil {
+				fmt.Println(err)
+				os.Exit(1)
+			}
+			cfg.BandwidthLimitMode = bandwidthLimitMode
 			err = cfg.CheckForCli()
 			err = cfg.CheckForCli()
 			if err != nil {
 			if err != nil {
 				fmt.Println(err)
 				fmt.Println(err)

+ 1 - 0
cmd/frps/main.go

@@ -26,6 +26,7 @@ import (
 
 
 func main() {
 func main() {
 	crypto.DefaultSalt = "frp"
 	crypto.DefaultSalt = "frp"
+	// TODO: remove this when we drop support for go1.19
 	rand.Seed(time.Now().UnixNano())
 	rand.Seed(time.Now().UnixNano())
 
 
 	Execute()
 	Execute()

+ 2 - 0
conf/frpc_full.ini

@@ -154,6 +154,8 @@ local_ip = 127.0.0.1
 local_port = 22
 local_port = 22
 # limit bandwidth for this proxy, unit is KB and MB
 # limit bandwidth for this proxy, unit is KB and MB
 bandwidth_limit = 1MB
 bandwidth_limit = 1MB
+# where to limit bandwidth, can be 'client' or 'server', default is 'client'
+bandwidth_limit_mode = client
 # true or false, if true, messages between frps and frpc will be encrypted, default is false
 # true or false, if true, messages between frps and frpc will be encrypted, default is false
 use_encryption = false
 use_encryption = false
 # if true, message will be compressed
 # if true, message will be compressed

+ 2 - 0
doc/server_plugin.md

@@ -110,6 +110,8 @@ Create new proxy
         "proxy_type": <string>,
         "proxy_type": <string>,
         "use_encryption": <bool>,
         "use_encryption": <bool>,
         "use_compression": <bool>,
         "use_compression": <bool>,
+        "bandwidth_limit": <string>,
+        "bandwidth_limit_mode": <string>,
         "group": <string>,
         "group": <string>,
         "group_key": <string>,
         "group_key": <string>,
 
 

+ 1 - 1
dockerfiles/Dockerfile-for-frpc

@@ -1,4 +1,4 @@
-FROM golang:1.19 AS building
+FROM golang:1.20 AS building
 
 
 COPY . /building
 COPY . /building
 WORKDIR /building
 WORKDIR /building

+ 1 - 1
dockerfiles/Dockerfile-for-frps

@@ -1,4 +1,4 @@
-FROM golang:1.19 AS building
+FROM golang:1.20 AS building
 
 
 COPY . /building
 COPY . /building
 WORKDIR /building
 WORKDIR /building

+ 8 - 7
go.mod

@@ -1,6 +1,6 @@
 module github.com/fatedier/frp
 module github.com/fatedier/frp
 
 
-go 1.19
+go 1.20
 
 
 require (
 require (
 	github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
 	github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
@@ -13,11 +13,11 @@ 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/lucas-clemente/quic-go v0.31.0
 	github.com/onsi/ginkgo v1.16.4
 	github.com/onsi/ginkgo v1.16.4
 	github.com/onsi/gomega v1.20.2
 	github.com/onsi/gomega v1.20.2
 	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/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
 	github.com/stretchr/testify v1.7.0
@@ -47,8 +47,6 @@ require (
 	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/marten-seemann/qtls-go1-18 v0.1.3 // indirect
-	github.com/marten-seemann/qtls-go1-19 v0.1.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/nxadm/tail v1.4.8 // indirect
 	github.com/onsi/ginkgo/v2 v2.2.0 // indirect
 	github.com/onsi/ginkgo/v2 v2.2.0 // indirect
@@ -57,16 +55,19 @@ require (
 	github.com/prometheus/client_model v0.2.0 // indirect
 	github.com/prometheus/client_model v0.2.0 // indirect
 	github.com/prometheus/common v0.37.0 // indirect
 	github.com/prometheus/common v0.37.0 // indirect
 	github.com/prometheus/procfs v0.8.0 // indirect
 	github.com/prometheus/procfs v0.8.0 // indirect
+	github.com/quic-go/qtls-go1-18 v0.2.0 // indirect
+	github.com/quic-go/qtls-go1-19 v0.2.0 // indirect
+	github.com/quic-go/qtls-go1-20 v0.1.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
 	github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
 	github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect
 	github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect
 	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-20220722155223-a9213eeb770e // indirect
-	golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // 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/sys v0.3.0 // indirect
 	golang.org/x/text v0.5.0 // indirect
 	golang.org/x/text v0.5.0 // indirect
-	golang.org/x/tools v0.1.12 // indirect
+	golang.org/x/tools v0.2.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

+ 14 - 12
go.sum

@@ -307,13 +307,7 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
 github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
 github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
 github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
-github.com/lucas-clemente/quic-go v0.31.0 h1:MfNp3fk0wjWRajw6quMFA3ap1AVtlU+2mtwmbVogB2M=
-github.com/lucas-clemente/quic-go v0.31.0/go.mod h1:0wFbizLgYzqHqtlyxyCaJKlE7bYgE6JQ+54TLd/Dq2g=
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/marten-seemann/qtls-go1-18 v0.1.3 h1:R4H2Ks8P6pAtUagjFty2p7BVHn3XiwDAl7TTQf5h7TI=
-github.com/marten-seemann/qtls-go1-18 v0.1.3/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
-github.com/marten-seemann/qtls-go1-19 v0.1.1 h1:mnbxeq3oEyQxQXwI4ReCgW9DPoPR94sNlqWoDZnjRIE=
-github.com/marten-seemann/qtls-go1-19 v0.1.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
 github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
@@ -392,6 +386,14 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
 github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
 github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
 github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
 github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/quic-go/qtls-go1-18 v0.2.0 h1:5ViXqBZ90wpUcZS0ge79rf029yx0dYB0McyPJwqqj7U=
+github.com/quic-go/qtls-go1-18 v0.2.0/go.mod h1:moGulGHK7o6O8lSPSZNoOwcLvJKJ85vVNc7oJFD65bc=
+github.com/quic-go/qtls-go1-19 v0.2.0 h1:Cvn2WdhyViFUHoOqK52i51k4nDX8EwIh5VJiVM4nttk=
+github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
+github.com/quic-go/qtls-go1-20 v0.1.0 h1:d1PK3ErFy9t7zxKsG3NXBJXZjp/kMLoIb3y/kV54oAI=
+github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
+github.com/quic-go/quic-go v0.32.0 h1:lY02md31s1JgPiiyfqJijpu/UX/Iun304FI3yUqX7tA=
+github.com/quic-go/quic-go v0.32.0/go.mod h1:/fCsKANhQIeD5l76c2JFU+07gVE3KaA0FP+0zMWwfwo=
 github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ=
 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/rodaine/table v1.0.1/go.mod h1:UVEtfBsflpeEcD56nF4F5AocNFta0ZuolpSVdPtlmP4=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
@@ -478,8 +480,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
-golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
+golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
+golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -505,8 +507,8 @@ 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-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
-golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+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/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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -748,8 +750,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.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
-golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+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/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=

+ 28 - 7
pkg/config/client_test.go

@@ -74,6 +74,7 @@ var testClientBytesWithFull = []byte(`
 		local_ip = 127.0.0.9
 		local_ip = 127.0.0.9
 		local_port = 29
 		local_port = 29
 		bandwidth_limit = 19MB
 		bandwidth_limit = 19MB
+		bandwidth_limit_mode = server
 		use_encryption
 		use_encryption
 		use_compression
 		use_compression
 		remote_port = 6009
 		remote_port = 6009
@@ -309,13 +310,14 @@ func Test_LoadClientBasicConf(t *testing.T) {
 	proxyExpected := map[string]ProxyConf{
 	proxyExpected := map[string]ProxyConf{
 		testUser + ".ssh": &TCPProxyConf{
 		testUser + ".ssh": &TCPProxyConf{
 			BaseProxyConf: BaseProxyConf{
 			BaseProxyConf: BaseProxyConf{
-				ProxyName:      testUser + ".ssh",
-				ProxyType:      consts.TCPProxy,
-				UseCompression: true,
-				UseEncryption:  true,
-				Group:          "test_group",
-				GroupKey:       "123456",
-				BandwidthLimit: MustBandwidthQuantity("19MB"),
+				ProxyName:          testUser + ".ssh",
+				ProxyType:          consts.TCPProxy,
+				UseCompression:     true,
+				UseEncryption:      true,
+				Group:              "test_group",
+				GroupKey:           "123456",
+				BandwidthLimit:     MustBandwidthQuantity("19MB"),
+				BandwidthLimitMode: BandwidthLimitModeServer,
 				Metas: map[string]string{
 				Metas: map[string]string{
 					"var1": "123",
 					"var1": "123",
 					"var2": "234",
 					"var2": "234",
@@ -342,6 +344,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
 					LocalIP:   "127.0.0.9",
 					LocalIP:   "127.0.0.9",
 					LocalPort: 29,
 					LocalPort: 29,
 				},
 				},
+				BandwidthLimitMode: BandwidthLimitModeClient,
 			},
 			},
 			RemotePort: 9,
 			RemotePort: 9,
 		},
 		},
@@ -353,6 +356,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
 					LocalIP:   "127.0.0.9",
 					LocalIP:   "127.0.0.9",
 					LocalPort: 6010,
 					LocalPort: 6010,
 				},
 				},
+				BandwidthLimitMode: BandwidthLimitModeClient,
 			},
 			},
 			RemotePort: 6010,
 			RemotePort: 6010,
 		},
 		},
@@ -364,6 +368,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
 					LocalIP:   "127.0.0.9",
 					LocalIP:   "127.0.0.9",
 					LocalPort: 6011,
 					LocalPort: 6011,
 				},
 				},
+				BandwidthLimitMode: BandwidthLimitModeClient,
 			},
 			},
 			RemotePort: 6011,
 			RemotePort: 6011,
 		},
 		},
@@ -375,6 +380,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
 					LocalIP:   "127.0.0.9",
 					LocalIP:   "127.0.0.9",
 					LocalPort: 6019,
 					LocalPort: 6019,
 				},
 				},
+				BandwidthLimitMode: BandwidthLimitModeClient,
 			},
 			},
 			RemotePort: 6019,
 			RemotePort: 6019,
 		},
 		},
@@ -388,6 +394,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
 					LocalIP:   "114.114.114.114",
 					LocalIP:   "114.114.114.114",
 					LocalPort: 59,
 					LocalPort: 59,
 				},
 				},
+				BandwidthLimitMode: BandwidthLimitModeClient,
 			},
 			},
 			RemotePort: 6009,
 			RemotePort: 6009,
 		},
 		},
@@ -401,6 +408,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
 					LocalIP:   "114.114.114.114",
 					LocalIP:   "114.114.114.114",
 					LocalPort: 6000,
 					LocalPort: 6000,
 				},
 				},
+				BandwidthLimitMode: BandwidthLimitModeClient,
 			},
 			},
 			RemotePort: 6000,
 			RemotePort: 6000,
 		},
 		},
@@ -414,6 +422,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
 					LocalIP:   "114.114.114.114",
 					LocalIP:   "114.114.114.114",
 					LocalPort: 6010,
 					LocalPort: 6010,
 				},
 				},
+				BandwidthLimitMode: BandwidthLimitModeClient,
 			},
 			},
 			RemotePort: 6010,
 			RemotePort: 6010,
 		},
 		},
@@ -427,6 +436,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
 					LocalIP:   "114.114.114.114",
 					LocalIP:   "114.114.114.114",
 					LocalPort: 6011,
 					LocalPort: 6011,
 				},
 				},
+				BandwidthLimitMode: BandwidthLimitModeClient,
 			},
 			},
 			RemotePort: 6011,
 			RemotePort: 6011,
 		},
 		},
@@ -447,6 +457,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
 					HealthCheckIntervalS: 19,
 					HealthCheckIntervalS: 19,
 					HealthCheckURL:       "http://127.0.0.9:89/status",
 					HealthCheckURL:       "http://127.0.0.9:89/status",
 				},
 				},
+				BandwidthLimitMode: BandwidthLimitModeClient,
 			},
 			},
 			DomainConf: DomainConf{
 			DomainConf: DomainConf{
 				CustomDomains: []string{"web02.yourdomain.com"},
 				CustomDomains: []string{"web02.yourdomain.com"},
@@ -471,6 +482,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
 					LocalPort: 8009,
 					LocalPort: 8009,
 				},
 				},
 				ProxyProtocolVersion: "v2",
 				ProxyProtocolVersion: "v2",
+				BandwidthLimitMode:   BandwidthLimitModeClient,
 			},
 			},
 			DomainConf: DomainConf{
 			DomainConf: DomainConf{
 				CustomDomains: []string{"web02.yourdomain.com"},
 				CustomDomains: []string{"web02.yourdomain.com"},
@@ -485,6 +497,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
 					LocalIP:   "127.0.0.1",
 					LocalIP:   "127.0.0.1",
 					LocalPort: 22,
 					LocalPort: 22,
 				},
 				},
+				BandwidthLimitMode: BandwidthLimitModeClient,
 			},
 			},
 			Role: "server",
 			Role: "server",
 			Sk:   "abcdefg",
 			Sk:   "abcdefg",
@@ -497,6 +510,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
 					LocalIP:   "127.0.0.1",
 					LocalIP:   "127.0.0.1",
 					LocalPort: 22,
 					LocalPort: 22,
 				},
 				},
+				BandwidthLimitMode: BandwidthLimitModeClient,
 			},
 			},
 			Role: "server",
 			Role: "server",
 			Sk:   "abcdefg",
 			Sk:   "abcdefg",
@@ -509,6 +523,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
 					LocalIP:   "127.0.0.1",
 					LocalIP:   "127.0.0.1",
 					LocalPort: 10701,
 					LocalPort: 10701,
 				},
 				},
+				BandwidthLimitMode: BandwidthLimitModeClient,
 			},
 			},
 			DomainConf: DomainConf{
 			DomainConf: DomainConf{
 				CustomDomains: []string{"tunnel1"},
 				CustomDomains: []string{"tunnel1"},
@@ -527,6 +542,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
 						"plugin_unix_path": "/var/run/docker.sock",
 						"plugin_unix_path": "/var/run/docker.sock",
 					},
 					},
 				},
 				},
+				BandwidthLimitMode: BandwidthLimitModeClient,
 			},
 			},
 			RemotePort: 6003,
 			RemotePort: 6003,
 		},
 		},
@@ -542,6 +558,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
 						"plugin_http_passwd": "abc",
 						"plugin_http_passwd": "abc",
 					},
 					},
 				},
 				},
+				BandwidthLimitMode: BandwidthLimitModeClient,
 			},
 			},
 			RemotePort: 6004,
 			RemotePort: 6004,
 		},
 		},
@@ -557,6 +574,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
 						"plugin_passwd": "abc",
 						"plugin_passwd": "abc",
 					},
 					},
 				},
 				},
+				BandwidthLimitMode: BandwidthLimitModeClient,
 			},
 			},
 			RemotePort: 6005,
 			RemotePort: 6005,
 		},
 		},
@@ -574,6 +592,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
 						"plugin_http_passwd":  "abc",
 						"plugin_http_passwd":  "abc",
 					},
 					},
 				},
 				},
+				BandwidthLimitMode: BandwidthLimitModeClient,
 			},
 			},
 			RemotePort: 6006,
 			RemotePort: 6006,
 		},
 		},
@@ -592,6 +611,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
 						"plugin_header_X-From-Where": "frp",
 						"plugin_header_X-From-Where": "frp",
 					},
 					},
 				},
 				},
+				BandwidthLimitMode: BandwidthLimitModeClient,
 			},
 			},
 			DomainConf: DomainConf{
 			DomainConf: DomainConf{
 				CustomDomains: []string{"test.yourdomain.com"},
 				CustomDomains: []string{"test.yourdomain.com"},
@@ -610,6 +630,7 @@ func Test_LoadClientBasicConf(t *testing.T) {
 						"plugin_header_X-From-Where": "frp",
 						"plugin_header_X-From-Where": "frp",
 					},
 					},
 				},
 				},
+				BandwidthLimitMode: BandwidthLimitModeClient,
 			},
 			},
 			DomainConf: DomainConf{
 			DomainConf: DomainConf{
 				CustomDomains: []string{"test.yourdomain.com"},
 				CustomDomains: []string{"test.yourdomain.com"},

+ 48 - 0
pkg/config/proxy.go

@@ -141,6 +141,10 @@ type BaseProxyConf struct {
 	// BandwidthLimit limit the bandwidth
 	// BandwidthLimit limit the bandwidth
 	// 0 means no limit
 	// 0 means no limit
 	BandwidthLimit BandwidthQuantity `ini:"bandwidth_limit" json:"bandwidth_limit"`
 	BandwidthLimit BandwidthQuantity `ini:"bandwidth_limit" json:"bandwidth_limit"`
+	// BandwidthLimitMode specifies whether to limit the bandwidth on the
+	// client or server side. Valid values include "client" and "server".
+	// By default, this value is "client".
+	BandwidthLimitMode string `ini:"bandwidth_limit_mode" json:"bandwidth_limit_mode"`
 
 
 	// meta info for each proxy
 	// meta info for each proxy
 	Metas map[string]string `ini:"-" json:"metas"`
 	Metas map[string]string `ini:"-" json:"metas"`
@@ -319,6 +323,7 @@ func defaultBaseProxyConf(proxyType string) BaseProxyConf {
 		LocalSvrConf: LocalSvrConf{
 		LocalSvrConf: LocalSvrConf{
 			LocalIP: "127.0.0.1",
 			LocalIP: "127.0.0.1",
 		},
 		},
+		BandwidthLimitMode: BandwidthLimitModeClient,
 	}
 	}
 }
 }
 
 
@@ -335,6 +340,7 @@ func (cfg *BaseProxyConf) compare(cmp *BaseProxyConf) bool {
 		cfg.GroupKey != cmp.GroupKey ||
 		cfg.GroupKey != cmp.GroupKey ||
 		cfg.ProxyProtocolVersion != cmp.ProxyProtocolVersion ||
 		cfg.ProxyProtocolVersion != cmp.ProxyProtocolVersion ||
 		!cfg.BandwidthLimit.Equal(&cmp.BandwidthLimit) ||
 		!cfg.BandwidthLimit.Equal(&cmp.BandwidthLimit) ||
+		cfg.BandwidthLimitMode != cmp.BandwidthLimitMode ||
 		!reflect.DeepEqual(cfg.Metas, cmp.Metas) {
 		!reflect.DeepEqual(cfg.Metas, cmp.Metas) {
 		return false
 		return false
 	}
 	}
@@ -389,6 +395,8 @@ func (cfg *BaseProxyConf) marshalToMsg(pMsg *msg.NewProxy) {
 	pMsg.ProxyType = cfg.ProxyType
 	pMsg.ProxyType = cfg.ProxyType
 	pMsg.UseEncryption = cfg.UseEncryption
 	pMsg.UseEncryption = cfg.UseEncryption
 	pMsg.UseCompression = cfg.UseCompression
 	pMsg.UseCompression = cfg.UseCompression
+	pMsg.BandwidthLimit = cfg.BandwidthLimit.String()
+	pMsg.BandwidthLimitMode = cfg.BandwidthLimitMode
 	pMsg.Group = cfg.Group
 	pMsg.Group = cfg.Group
 	pMsg.GroupKey = cfg.GroupKey
 	pMsg.GroupKey = cfg.GroupKey
 	pMsg.Metas = cfg.Metas
 	pMsg.Metas = cfg.Metas
@@ -399,6 +407,8 @@ func (cfg *BaseProxyConf) unmarshalFromMsg(pMsg *msg.NewProxy) {
 	cfg.ProxyType = pMsg.ProxyType
 	cfg.ProxyType = pMsg.ProxyType
 	cfg.UseEncryption = pMsg.UseEncryption
 	cfg.UseEncryption = pMsg.UseEncryption
 	cfg.UseCompression = pMsg.UseCompression
 	cfg.UseCompression = pMsg.UseCompression
+	cfg.BandwidthLimit, _ = NewBandwidthQuantity(pMsg.BandwidthLimit)
+	cfg.BandwidthLimitMode = pMsg.BandwidthLimitMode
 	cfg.Group = pMsg.Group
 	cfg.Group = pMsg.Group
 	cfg.GroupKey = pMsg.GroupKey
 	cfg.GroupKey = pMsg.GroupKey
 	cfg.Metas = pMsg.Metas
 	cfg.Metas = pMsg.Metas
@@ -411,6 +421,10 @@ func (cfg *BaseProxyConf) checkForCli() (err error) {
 		}
 		}
 	}
 	}
 
 
+	if cfg.BandwidthLimitMode != "client" && cfg.BandwidthLimitMode != "server" {
+		return fmt.Errorf("bandwidth_limit_mode should be client or server")
+	}
+
 	if err = cfg.LocalSvrConf.checkForCli(); err != nil {
 	if err = cfg.LocalSvrConf.checkForCli(); err != nil {
 		return
 		return
 	}
 	}
@@ -420,6 +434,13 @@ func (cfg *BaseProxyConf) checkForCli() (err error) {
 	return nil
 	return nil
 }
 }
 
 
+func (cfg *BaseProxyConf) checkForSvr() (err error) {
+	if cfg.BandwidthLimitMode != "client" && cfg.BandwidthLimitMode != "server" {
+		return fmt.Errorf("bandwidth_limit_mode should be client or server")
+	}
+	return nil
+}
+
 // DomainConf
 // DomainConf
 func (cfg *DomainConf) check() (err error) {
 func (cfg *DomainConf) check() (err error) {
 	if len(cfg.CustomDomains) == 0 && cfg.SubDomain == "" {
 	if len(cfg.CustomDomains) == 0 && cfg.SubDomain == "" {
@@ -557,6 +578,9 @@ func (cfg *TCPProxyConf) CheckForCli() (err error) {
 }
 }
 
 
 func (cfg *TCPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error {
 func (cfg *TCPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error {
+	if err := cfg.BaseProxyConf.checkForSvr(); err != nil {
+		return err
+	}
 	return nil
 	return nil
 }
 }
 
 
@@ -632,6 +656,10 @@ func (cfg *TCPMuxProxyConf) CheckForCli() (err error) {
 }
 }
 
 
 func (cfg *TCPMuxProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
 func (cfg *TCPMuxProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
+	if err := cfg.BaseProxyConf.checkForSvr(); err != nil {
+		return err
+	}
+
 	if cfg.Multiplexer != consts.HTTPConnectTCPMultiplexer {
 	if cfg.Multiplexer != consts.HTTPConnectTCPMultiplexer {
 		return fmt.Errorf("proxy [%s] incorrect multiplexer [%s]", cfg.ProxyName, cfg.Multiplexer)
 		return fmt.Errorf("proxy [%s] incorrect multiplexer [%s]", cfg.ProxyName, cfg.Multiplexer)
 	}
 	}
@@ -703,6 +731,9 @@ func (cfg *UDPProxyConf) CheckForCli() (err error) {
 }
 }
 
 
 func (cfg *UDPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error {
 func (cfg *UDPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error {
+	if err := cfg.BaseProxyConf.checkForSvr(); err != nil {
+		return err
+	}
 	return nil
 	return nil
 }
 }
 
 
@@ -788,6 +819,10 @@ func (cfg *HTTPProxyConf) CheckForCli() (err error) {
 }
 }
 
 
 func (cfg *HTTPProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
 func (cfg *HTTPProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
+	if err := cfg.BaseProxyConf.checkForSvr(); err != nil {
+		return err
+	}
+
 	if serverCfg.VhostHTTPPort == 0 {
 	if serverCfg.VhostHTTPPort == 0 {
 		return fmt.Errorf("type [http] not support when vhost_http_port is not set")
 		return fmt.Errorf("type [http] not support when vhost_http_port is not set")
 	}
 	}
@@ -860,6 +895,10 @@ func (cfg *HTTPSProxyConf) CheckForCli() (err error) {
 }
 }
 
 
 func (cfg *HTTPSProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
 func (cfg *HTTPSProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
+	if err := cfg.BaseProxyConf.checkForSvr(); err != nil {
+		return err
+	}
+
 	if serverCfg.VhostHTTPSPort == 0 {
 	if serverCfg.VhostHTTPSPort == 0 {
 		return fmt.Errorf("type [https] not support when vhost_https_port is not set")
 		return fmt.Errorf("type [https] not support when vhost_https_port is not set")
 	}
 	}
@@ -932,6 +971,9 @@ func (cfg *SUDPProxyConf) CheckForCli() (err error) {
 }
 }
 
 
 func (cfg *SUDPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error {
 func (cfg *SUDPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error {
+	if err := cfg.BaseProxyConf.checkForSvr(); err != nil {
+		return err
+	}
 	return nil
 	return nil
 }
 }
 
 
@@ -998,6 +1040,9 @@ func (cfg *STCPProxyConf) CheckForCli() (err error) {
 }
 }
 
 
 func (cfg *STCPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error {
 func (cfg *STCPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error {
+	if err := cfg.BaseProxyConf.checkForSvr(); err != nil {
+		return err
+	}
 	return nil
 	return nil
 }
 }
 
 
@@ -1064,5 +1109,8 @@ func (cfg *XTCPProxyConf) CheckForCli() (err error) {
 }
 }
 
 
 func (cfg *XTCPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error {
 func (cfg *XTCPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error {
+	if err := cfg.BaseProxyConf.checkForSvr(); err != nil {
+		return err
+	}
 	return nil
 	return nil
 }
 }

+ 22 - 7
pkg/config/proxy_test.go

@@ -58,6 +58,7 @@ func Test_Proxy_UnmarshalFromIni(t *testing.T) {
 				local_ip = 127.0.0.9
 				local_ip = 127.0.0.9
 				local_port = 29
 				local_port = 29
 				bandwidth_limit = 19MB
 				bandwidth_limit = 19MB
+				bandwidth_limit_mode = server
 				use_encryption
 				use_encryption
 				use_compression
 				use_compression
 				remote_port = 6009
 				remote_port = 6009
@@ -71,13 +72,14 @@ func Test_Proxy_UnmarshalFromIni(t *testing.T) {
 				meta_var2 = 234`),
 				meta_var2 = 234`),
 			expected: &TCPProxyConf{
 			expected: &TCPProxyConf{
 				BaseProxyConf: BaseProxyConf{
 				BaseProxyConf: BaseProxyConf{
-					ProxyName:      testProxyPrefix + "ssh",
-					ProxyType:      consts.TCPProxy,
-					UseCompression: true,
-					UseEncryption:  true,
-					Group:          "test_group",
-					GroupKey:       "123456",
-					BandwidthLimit: MustBandwidthQuantity("19MB"),
+					ProxyName:          testProxyPrefix + "ssh",
+					ProxyType:          consts.TCPProxy,
+					UseCompression:     true,
+					UseEncryption:      true,
+					Group:              "test_group",
+					GroupKey:           "123456",
+					BandwidthLimit:     MustBandwidthQuantity("19MB"),
+					BandwidthLimitMode: BandwidthLimitModeServer,
 					Metas: map[string]string{
 					Metas: map[string]string{
 						"var1": "123",
 						"var1": "123",
 						"var2": "234",
 						"var2": "234",
@@ -114,6 +116,7 @@ func Test_Proxy_UnmarshalFromIni(t *testing.T) {
 						LocalIP:   "127.0.0.9",
 						LocalIP:   "127.0.0.9",
 						LocalPort: 29,
 						LocalPort: 29,
 					},
 					},
+					BandwidthLimitMode: BandwidthLimitModeClient,
 				},
 				},
 				RemotePort: 9,
 				RemotePort: 9,
 			},
 			},
@@ -139,6 +142,7 @@ func Test_Proxy_UnmarshalFromIni(t *testing.T) {
 						LocalIP:   "114.114.114.114",
 						LocalIP:   "114.114.114.114",
 						LocalPort: 59,
 						LocalPort: 59,
 					},
 					},
+					BandwidthLimitMode: BandwidthLimitModeClient,
 				},
 				},
 				RemotePort: 6009,
 				RemotePort: 6009,
 			},
 			},
@@ -182,6 +186,7 @@ func Test_Proxy_UnmarshalFromIni(t *testing.T) {
 						HealthCheckIntervalS: 19,
 						HealthCheckIntervalS: 19,
 						HealthCheckURL:       "http://127.0.0.9:89/status",
 						HealthCheckURL:       "http://127.0.0.9:89/status",
 					},
 					},
+					BandwidthLimitMode: BandwidthLimitModeClient,
 				},
 				},
 				DomainConf: DomainConf{
 				DomainConf: DomainConf{
 					CustomDomains: []string{"web02.yourdomain.com"},
 					CustomDomains: []string{"web02.yourdomain.com"},
@@ -220,6 +225,7 @@ func Test_Proxy_UnmarshalFromIni(t *testing.T) {
 						LocalPort: 8009,
 						LocalPort: 8009,
 					},
 					},
 					ProxyProtocolVersion: "v2",
 					ProxyProtocolVersion: "v2",
+					BandwidthLimitMode:   BandwidthLimitModeClient,
 				},
 				},
 				DomainConf: DomainConf{
 				DomainConf: DomainConf{
 					CustomDomains: []string{"web02.yourdomain.com"},
 					CustomDomains: []string{"web02.yourdomain.com"},
@@ -246,6 +252,7 @@ func Test_Proxy_UnmarshalFromIni(t *testing.T) {
 						LocalIP:   "127.0.0.1",
 						LocalIP:   "127.0.0.1",
 						LocalPort: 22,
 						LocalPort: 22,
 					},
 					},
+					BandwidthLimitMode: BandwidthLimitModeClient,
 				},
 				},
 				Role: "server",
 				Role: "server",
 				Sk:   "abcdefg",
 				Sk:   "abcdefg",
@@ -270,6 +277,7 @@ func Test_Proxy_UnmarshalFromIni(t *testing.T) {
 						LocalIP:   "127.0.0.1",
 						LocalIP:   "127.0.0.1",
 						LocalPort: 22,
 						LocalPort: 22,
 					},
 					},
+					BandwidthLimitMode: BandwidthLimitModeClient,
 				},
 				},
 				Role: "server",
 				Role: "server",
 				Sk:   "abcdefg",
 				Sk:   "abcdefg",
@@ -293,6 +301,7 @@ func Test_Proxy_UnmarshalFromIni(t *testing.T) {
 						LocalIP:   "127.0.0.1",
 						LocalIP:   "127.0.0.1",
 						LocalPort: 10701,
 						LocalPort: 10701,
 					},
 					},
+					BandwidthLimitMode: BandwidthLimitModeClient,
 				},
 				},
 				DomainConf: DomainConf{
 				DomainConf: DomainConf{
 					CustomDomains: []string{"tunnel1"},
 					CustomDomains: []string{"tunnel1"},
@@ -347,6 +356,7 @@ func Test_RangeProxy_UnmarshalFromIni(t *testing.T) {
 							LocalIP:   "127.0.0.9",
 							LocalIP:   "127.0.0.9",
 							LocalPort: 6010,
 							LocalPort: 6010,
 						},
 						},
+						BandwidthLimitMode: BandwidthLimitModeClient,
 					},
 					},
 					RemotePort: 6010,
 					RemotePort: 6010,
 				},
 				},
@@ -358,6 +368,7 @@ func Test_RangeProxy_UnmarshalFromIni(t *testing.T) {
 							LocalIP:   "127.0.0.9",
 							LocalIP:   "127.0.0.9",
 							LocalPort: 6011,
 							LocalPort: 6011,
 						},
 						},
+						BandwidthLimitMode: BandwidthLimitModeClient,
 					},
 					},
 					RemotePort: 6011,
 					RemotePort: 6011,
 				},
 				},
@@ -369,6 +380,7 @@ func Test_RangeProxy_UnmarshalFromIni(t *testing.T) {
 							LocalIP:   "127.0.0.9",
 							LocalIP:   "127.0.0.9",
 							LocalPort: 6019,
 							LocalPort: 6019,
 						},
 						},
+						BandwidthLimitMode: BandwidthLimitModeClient,
 					},
 					},
 					RemotePort: 6019,
 					RemotePort: 6019,
 				},
 				},
@@ -396,6 +408,7 @@ func Test_RangeProxy_UnmarshalFromIni(t *testing.T) {
 							LocalIP:   "114.114.114.114",
 							LocalIP:   "114.114.114.114",
 							LocalPort: 6000,
 							LocalPort: 6000,
 						},
 						},
+						BandwidthLimitMode: BandwidthLimitModeClient,
 					},
 					},
 					RemotePort: 6000,
 					RemotePort: 6000,
 				},
 				},
@@ -409,6 +422,7 @@ func Test_RangeProxy_UnmarshalFromIni(t *testing.T) {
 							LocalIP:   "114.114.114.114",
 							LocalIP:   "114.114.114.114",
 							LocalPort: 6010,
 							LocalPort: 6010,
 						},
 						},
+						BandwidthLimitMode: BandwidthLimitModeClient,
 					},
 					},
 					RemotePort: 6010,
 					RemotePort: 6010,
 				},
 				},
@@ -422,6 +436,7 @@ func Test_RangeProxy_UnmarshalFromIni(t *testing.T) {
 							LocalIP:   "114.114.114.114",
 							LocalIP:   "114.114.114.114",
 							LocalPort: 6011,
 							LocalPort: 6011,
 						},
 						},
+						BandwidthLimitMode: BandwidthLimitModeClient,
 					},
 					},
 					RemotePort: 6011,
 					RemotePort: 6011,
 				},
 				},

+ 3 - 0
pkg/config/types.go

@@ -24,6 +24,9 @@ import (
 const (
 const (
 	MB = 1024 * 1024
 	MB = 1024 * 1024
 	KB = 1024
 	KB = 1024
+
+	BandwidthLimitModeClient = "client"
+	BandwidthLimitModeServer = "server"
 )
 )
 
 
 type BandwidthQuantity struct {
 type BandwidthQuantity struct {

+ 12 - 8
pkg/msg/msg.go

@@ -14,7 +14,9 @@
 
 
 package msg
 package msg
 
 
-import "net"
+import (
+	"net"
+)
 
 
 const (
 const (
 	TypeLogin                 = 'o'
 	TypeLogin                 = 'o'
@@ -83,13 +85,15 @@ type LoginResp struct {
 
 
 // When frpc login success, send this message to frps for running a new proxy.
 // When frpc login success, send this message to frps for running a new proxy.
 type NewProxy struct {
 type NewProxy struct {
-	ProxyName      string            `json:"proxy_name,omitempty"`
-	ProxyType      string            `json:"proxy_type,omitempty"`
-	UseEncryption  bool              `json:"use_encryption,omitempty"`
-	UseCompression bool              `json:"use_compression,omitempty"`
-	Group          string            `json:"group,omitempty"`
-	GroupKey       string            `json:"group_key,omitempty"`
-	Metas          map[string]string `json:"metas,omitempty"`
+	ProxyName          string            `json:"proxy_name,omitempty"`
+	ProxyType          string            `json:"proxy_type,omitempty"`
+	UseEncryption      bool              `json:"use_encryption,omitempty"`
+	UseCompression     bool              `json:"use_compression,omitempty"`
+	BandwidthLimit     string            `json:"bandwidth_limit,omitempty"`
+	BandwidthLimitMode string            `json:"bandwidth_limit_mode,omitempty"`
+	Group              string            `json:"group,omitempty"`
+	GroupKey           string            `json:"group_key,omitempty"`
+	Metas              map[string]string `json:"metas,omitempty"`
 
 
 	// tcp and udp only
 	// tcp and udp only
 	RemotePort int `json:"remote_port,omitempty"`
 	RemotePort int `json:"remote_port,omitempty"`

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

@@ -22,7 +22,7 @@ import (
 	"sync/atomic"
 	"sync/atomic"
 	"time"
 	"time"
 
 
-	quic "github.com/lucas-clemente/quic-go"
+	quic "github.com/quic-go/quic-go"
 
 
 	"github.com/fatedier/frp/pkg/util/xlog"
 	"github.com/fatedier/frp/pkg/util/xlog"
 )
 )

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

@@ -44,9 +44,9 @@ func RandIDWithLen(idLen int) (id string, err error) {
 }
 }
 
 
 func GetAuthKey(token string, timestamp int64) (key string) {
 func GetAuthKey(token string, timestamp int64) (key string) {
-	token += fmt.Sprintf("%d", timestamp)
 	md5Ctx := md5.New()
 	md5Ctx := md5.New()
 	md5Ctx.Write([]byte(token))
 	md5Ctx.Write([]byte(token))
+	md5Ctx.Write([]byte(strconv.FormatInt(timestamp, 10)))
 	data := md5Ctx.Sum(nil)
 	data := md5Ctx.Sum(nil)
 	return hex.EncodeToString(data)
 	return hex.EncodeToString(data)
 }
 }

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

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

+ 13 - 0
server/proxy/http.go

@@ -20,8 +20,10 @@ import (
 	"strings"
 	"strings"
 
 
 	frpIo "github.com/fatedier/golib/io"
 	frpIo "github.com/fatedier/golib/io"
+	"golang.org/x/time/rate"
 
 
 	"github.com/fatedier/frp/pkg/config"
 	"github.com/fatedier/frp/pkg/config"
+	"github.com/fatedier/frp/pkg/util/limit"
 	frpNet "github.com/fatedier/frp/pkg/util/net"
 	frpNet "github.com/fatedier/frp/pkg/util/net"
 	"github.com/fatedier/frp/pkg/util/util"
 	"github.com/fatedier/frp/pkg/util/util"
 	"github.com/fatedier/frp/pkg/util/vhost"
 	"github.com/fatedier/frp/pkg/util/vhost"
@@ -135,6 +137,10 @@ func (pxy *HTTPProxy) GetConf() config.ProxyConf {
 	return pxy.cfg
 	return pxy.cfg
 }
 }
 
 
+func (pxy *HTTPProxy) GetLimiter() *rate.Limiter {
+	return pxy.limiter
+}
+
 func (pxy *HTTPProxy) GetRealConn(remoteAddr string) (workConn net.Conn, err error) {
 func (pxy *HTTPProxy) GetRealConn(remoteAddr string) (workConn net.Conn, err error) {
 	xl := pxy.xl
 	xl := pxy.xl
 	rAddr, errRet := net.ResolveTCPAddr("tcp", remoteAddr)
 	rAddr, errRet := net.ResolveTCPAddr("tcp", remoteAddr)
@@ -160,6 +166,13 @@ func (pxy *HTTPProxy) GetRealConn(remoteAddr string) (workConn net.Conn, err err
 	if pxy.cfg.UseCompression {
 	if pxy.cfg.UseCompression {
 		rwc = frpIo.WithCompression(rwc)
 		rwc = frpIo.WithCompression(rwc)
 	}
 	}
+
+	if pxy.GetLimiter() != nil {
+		rwc = frpIo.WrapReadWriteCloser(limit.NewReader(rwc, pxy.GetLimiter()), limit.NewWriter(rwc, pxy.GetLimiter()), func() error {
+			return rwc.Close()
+		})
+	}
+
 	workConn = frpNet.WrapReadWriteCloserToConn(rwc, tmpConn)
 	workConn = frpNet.WrapReadWriteCloserToConn(rwc, tmpConn)
 	workConn = frpNet.WrapStatsConn(workConn, pxy.updateStatsAfterClosedConn)
 	workConn = frpNet.WrapStatsConn(workConn, pxy.updateStatsAfterClosedConn)
 	metrics.Server.OpenConnection(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)
 	metrics.Server.OpenConnection(pxy.GetName(), pxy.GetConf().GetBaseInfo().ProxyType)

+ 6 - 0
server/proxy/https.go

@@ -17,6 +17,8 @@ package proxy
 import (
 import (
 	"strings"
 	"strings"
 
 
+	"golang.org/x/time/rate"
+
 	"github.com/fatedier/frp/pkg/config"
 	"github.com/fatedier/frp/pkg/config"
 	"github.com/fatedier/frp/pkg/util/util"
 	"github.com/fatedier/frp/pkg/util/util"
 	"github.com/fatedier/frp/pkg/util/vhost"
 	"github.com/fatedier/frp/pkg/util/vhost"
@@ -74,6 +76,10 @@ func (pxy *HTTPSProxy) GetConf() config.ProxyConf {
 	return pxy.cfg
 	return pxy.cfg
 }
 }
 
 
+func (pxy *HTTPSProxy) GetLimiter() *rate.Limiter {
+	return pxy.limiter
+}
+
 func (pxy *HTTPSProxy) Close() {
 func (pxy *HTTPSProxy) Close() {
 	pxy.BaseProxy.Close()
 	pxy.BaseProxy.Close()
 }
 }

+ 19 - 0
server/proxy/proxy.go

@@ -24,10 +24,12 @@ import (
 	"time"
 	"time"
 
 
 	frpIo "github.com/fatedier/golib/io"
 	frpIo "github.com/fatedier/golib/io"
+	"golang.org/x/time/rate"
 
 
 	"github.com/fatedier/frp/pkg/config"
 	"github.com/fatedier/frp/pkg/config"
 	"github.com/fatedier/frp/pkg/msg"
 	"github.com/fatedier/frp/pkg/msg"
 	plugin "github.com/fatedier/frp/pkg/plugin/server"
 	plugin "github.com/fatedier/frp/pkg/plugin/server"
+	"github.com/fatedier/frp/pkg/util/limit"
 	frpNet "github.com/fatedier/frp/pkg/util/net"
 	frpNet "github.com/fatedier/frp/pkg/util/net"
 	"github.com/fatedier/frp/pkg/util/xlog"
 	"github.com/fatedier/frp/pkg/util/xlog"
 	"github.com/fatedier/frp/server/controller"
 	"github.com/fatedier/frp/server/controller"
@@ -45,6 +47,7 @@ type Proxy interface {
 	GetUsedPortsNum() int
 	GetUsedPortsNum() int
 	GetResourceController() *controller.ResourceController
 	GetResourceController() *controller.ResourceController
 	GetUserInfo() plugin.UserInfo
 	GetUserInfo() plugin.UserInfo
+	GetLimiter() *rate.Limiter
 	Close()
 	Close()
 }
 }
 
 
@@ -56,6 +59,7 @@ type BaseProxy struct {
 	poolCount     int
 	poolCount     int
 	getWorkConnFn GetWorkConnFn
 	getWorkConnFn GetWorkConnFn
 	serverCfg     config.ServerCommonConf
 	serverCfg     config.ServerCommonConf
+	limiter       *rate.Limiter
 	userInfo      plugin.UserInfo
 	userInfo      plugin.UserInfo
 
 
 	mu  sync.RWMutex
 	mu  sync.RWMutex
@@ -187,6 +191,13 @@ func NewProxy(ctx context.Context, userInfo plugin.UserInfo, rc *controller.Reso
 	getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf, serverCfg config.ServerCommonConf,
 	getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf, serverCfg config.ServerCommonConf,
 ) (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)
+
+	var limiter *rate.Limiter
+	limitBytes := pxyConf.GetBaseInfo().BandwidthLimit.Bytes()
+	if limitBytes > 0 && pxyConf.GetBaseInfo().BandwidthLimitMode == config.BandwidthLimitModeServer {
+		limiter = rate.NewLimiter(rate.Limit(float64(limitBytes)), int(limitBytes))
+	}
+
 	basePxy := BaseProxy{
 	basePxy := BaseProxy{
 		name:          pxyConf.GetBaseInfo().ProxyName,
 		name:          pxyConf.GetBaseInfo().ProxyName,
 		rc:            rc,
 		rc:            rc,
@@ -194,6 +205,7 @@ func NewProxy(ctx context.Context, userInfo plugin.UserInfo, rc *controller.Reso
 		poolCount:     poolCount,
 		poolCount:     poolCount,
 		getWorkConnFn: getWorkConnFn,
 		getWorkConnFn: getWorkConnFn,
 		serverCfg:     serverCfg,
 		serverCfg:     serverCfg,
+		limiter:       limiter,
 		xl:            xl,
 		xl:            xl,
 		ctx:           xlog.NewContext(ctx, xl),
 		ctx:           xlog.NewContext(ctx, xl),
 		userInfo:      userInfo,
 		userInfo:      userInfo,
@@ -287,6 +299,13 @@ func HandleUserTCPConnection(pxy Proxy, userConn net.Conn, serverCfg config.Serv
 	if cfg.UseCompression {
 	if cfg.UseCompression {
 		local = frpIo.WithCompression(local)
 		local = frpIo.WithCompression(local)
 	}
 	}
+
+	if pxy.GetLimiter() != nil {
+		local = frpIo.WrapReadWriteCloser(limit.NewReader(local, pxy.GetLimiter()), limit.NewWriter(local, pxy.GetLimiter()), func() error {
+			return local.Close()
+		})
+	}
+
 	xl.Debug("join connections, workConn(l[%s] r[%s]) userConn(l[%s] r[%s])", workConn.LocalAddr().String(),
 	xl.Debug("join connections, workConn(l[%s] r[%s]) userConn(l[%s] r[%s])", workConn.LocalAddr().String(),
 		workConn.RemoteAddr().String(), userConn.LocalAddr().String(), userConn.RemoteAddr().String())
 		workConn.RemoteAddr().String(), userConn.LocalAddr().String(), userConn.RemoteAddr().String())
 
 

+ 6 - 0
server/proxy/stcp.go

@@ -15,6 +15,8 @@
 package proxy
 package proxy
 
 
 import (
 import (
+	"golang.org/x/time/rate"
+
 	"github.com/fatedier/frp/pkg/config"
 	"github.com/fatedier/frp/pkg/config"
 )
 )
 
 
@@ -41,6 +43,10 @@ func (pxy *STCPProxy) GetConf() config.ProxyConf {
 	return pxy.cfg
 	return pxy.cfg
 }
 }
 
 
+func (pxy *STCPProxy) GetLimiter() *rate.Limiter {
+	return pxy.limiter
+}
+
 func (pxy *STCPProxy) Close() {
 func (pxy *STCPProxy) Close() {
 	pxy.BaseProxy.Close()
 	pxy.BaseProxy.Close()
 	pxy.rc.VisitorManager.CloseListener(pxy.GetName())
 	pxy.rc.VisitorManager.CloseListener(pxy.GetName())

+ 6 - 0
server/proxy/sudp.go

@@ -15,6 +15,8 @@
 package proxy
 package proxy
 
 
 import (
 import (
+	"golang.org/x/time/rate"
+
 	"github.com/fatedier/frp/pkg/config"
 	"github.com/fatedier/frp/pkg/config"
 )
 )
 
 
@@ -42,6 +44,10 @@ func (pxy *SUDPProxy) GetConf() config.ProxyConf {
 	return pxy.cfg
 	return pxy.cfg
 }
 }
 
 
+func (pxy *SUDPProxy) GetLimiter() *rate.Limiter {
+	return pxy.limiter
+}
+
 func (pxy *SUDPProxy) Close() {
 func (pxy *SUDPProxy) Close() {
 	pxy.BaseProxy.Close()
 	pxy.BaseProxy.Close()
 	pxy.rc.VisitorManager.CloseListener(pxy.GetName())
 	pxy.rc.VisitorManager.CloseListener(pxy.GetName())

+ 6 - 0
server/proxy/tcp.go

@@ -19,6 +19,8 @@ import (
 	"net"
 	"net"
 	"strconv"
 	"strconv"
 
 
+	"golang.org/x/time/rate"
+
 	"github.com/fatedier/frp/pkg/config"
 	"github.com/fatedier/frp/pkg/config"
 )
 )
 
 
@@ -74,6 +76,10 @@ func (pxy *TCPProxy) GetConf() config.ProxyConf {
 	return pxy.cfg
 	return pxy.cfg
 }
 }
 
 
+func (pxy *TCPProxy) GetLimiter() *rate.Limiter {
+	return pxy.limiter
+}
+
 func (pxy *TCPProxy) Close() {
 func (pxy *TCPProxy) Close() {
 	pxy.BaseProxy.Close()
 	pxy.BaseProxy.Close()
 	if pxy.cfg.Group == "" {
 	if pxy.cfg.Group == "" {

+ 6 - 0
server/proxy/tcpmux.go

@@ -19,6 +19,8 @@ import (
 	"net"
 	"net"
 	"strings"
 	"strings"
 
 
+	"golang.org/x/time/rate"
+
 	"github.com/fatedier/frp/pkg/config"
 	"github.com/fatedier/frp/pkg/config"
 	"github.com/fatedier/frp/pkg/consts"
 	"github.com/fatedier/frp/pkg/consts"
 	"github.com/fatedier/frp/pkg/util/util"
 	"github.com/fatedier/frp/pkg/util/util"
@@ -94,6 +96,10 @@ func (pxy *TCPMuxProxy) GetConf() config.ProxyConf {
 	return pxy.cfg
 	return pxy.cfg
 }
 }
 
 
+func (pxy *TCPMuxProxy) GetLimiter() *rate.Limiter {
+	return pxy.limiter
+}
+
 func (pxy *TCPMuxProxy) Close() {
 func (pxy *TCPMuxProxy) Close() {
 	pxy.BaseProxy.Close()
 	pxy.BaseProxy.Close()
 }
 }

+ 12 - 0
server/proxy/udp.go

@@ -24,10 +24,12 @@ import (
 
 
 	"github.com/fatedier/golib/errors"
 	"github.com/fatedier/golib/errors"
 	frpIo "github.com/fatedier/golib/io"
 	frpIo "github.com/fatedier/golib/io"
+	"golang.org/x/time/rate"
 
 
 	"github.com/fatedier/frp/pkg/config"
 	"github.com/fatedier/frp/pkg/config"
 	"github.com/fatedier/frp/pkg/msg"
 	"github.com/fatedier/frp/pkg/msg"
 	"github.com/fatedier/frp/pkg/proto/udp"
 	"github.com/fatedier/frp/pkg/proto/udp"
+	"github.com/fatedier/frp/pkg/util/limit"
 	frpNet "github.com/fatedier/frp/pkg/util/net"
 	frpNet "github.com/fatedier/frp/pkg/util/net"
 	"github.com/fatedier/frp/server/metrics"
 	"github.com/fatedier/frp/server/metrics"
 )
 )
@@ -198,6 +200,12 @@ func (pxy *UDPProxy) Run() (remoteAddr string, err error) {
 				rwc = frpIo.WithCompression(rwc)
 				rwc = frpIo.WithCompression(rwc)
 			}
 			}
 
 
+			if pxy.GetLimiter() != nil {
+				rwc = frpIo.WrapReadWriteCloser(limit.NewReader(rwc, pxy.GetLimiter()), limit.NewWriter(rwc, pxy.GetLimiter()), func() error {
+					return rwc.Close()
+				})
+			}
+
 			pxy.workConn = frpNet.WrapReadWriteCloserToConn(rwc, workConn)
 			pxy.workConn = frpNet.WrapReadWriteCloserToConn(rwc, workConn)
 			ctx, cancel := context.WithCancel(context.Background())
 			ctx, cancel := context.WithCancel(context.Background())
 			go workConnReaderFn(pxy.workConn)
 			go workConnReaderFn(pxy.workConn)
@@ -225,6 +233,10 @@ func (pxy *UDPProxy) GetConf() config.ProxyConf {
 	return pxy.cfg
 	return pxy.cfg
 }
 }
 
 
+func (pxy *UDPProxy) GetLimiter() *rate.Limiter {
+	return pxy.limiter
+}
+
 func (pxy *UDPProxy) Close() {
 func (pxy *UDPProxy) Close() {
 	pxy.mu.Lock()
 	pxy.mu.Lock()
 	defer pxy.mu.Unlock()
 	defer pxy.mu.Unlock()

+ 5 - 0
server/proxy/xtcp.go

@@ -18,6 +18,7 @@ import (
 	"fmt"
 	"fmt"
 
 
 	"github.com/fatedier/golib/errors"
 	"github.com/fatedier/golib/errors"
+	"golang.org/x/time/rate"
 
 
 	"github.com/fatedier/frp/pkg/config"
 	"github.com/fatedier/frp/pkg/config"
 	"github.com/fatedier/frp/pkg/msg"
 	"github.com/fatedier/frp/pkg/msg"
@@ -88,6 +89,10 @@ func (pxy *XTCPProxy) GetConf() config.ProxyConf {
 	return pxy.cfg
 	return pxy.cfg
 }
 }
 
 
+func (pxy *XTCPProxy) GetLimiter() *rate.Limiter {
+	return pxy.limiter
+}
+
 func (pxy *XTCPProxy) Close() {
 func (pxy *XTCPProxy) Close() {
 	pxy.BaseProxy.Close()
 	pxy.BaseProxy.Close()
 	pxy.rc.NatHoleController.CloseClient(pxy.GetName())
 	pxy.rc.NatHoleController.CloseClient(pxy.GetName())

+ 1 - 1
server/service.go

@@ -28,7 +28,7 @@ import (
 
 
 	"github.com/fatedier/golib/net/mux"
 	"github.com/fatedier/golib/net/mux"
 	fmux "github.com/hashicorp/yamux"
 	fmux "github.com/hashicorp/yamux"
-	quic "github.com/lucas-clemente/quic-go"
+	quic "github.com/quic-go/quic-go"
 
 
 	"github.com/fatedier/frp/assets"
 	"github.com/fatedier/frp/assets"
 	"github.com/fatedier/frp/pkg/auth"
 	"github.com/fatedier/frp/pkg/auth"

+ 60 - 2
test/e2e/features/bandwidth_limit.go

@@ -7,16 +7,18 @@ import (
 
 
 	"github.com/onsi/ginkgo"
 	"github.com/onsi/ginkgo"
 
 
+	plugin "github.com/fatedier/frp/pkg/plugin/server"
 	"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"
 	"github.com/fatedier/frp/test/e2e/mock/server/streamserver"
 	"github.com/fatedier/frp/test/e2e/mock/server/streamserver"
 	"github.com/fatedier/frp/test/e2e/pkg/request"
 	"github.com/fatedier/frp/test/e2e/pkg/request"
+	plugintest "github.com/fatedier/frp/test/e2e/plugin"
 )
 )
 
 
 var _ = ginkgo.Describe("[Feature: Bandwidth Limit]", func() {
 var _ = ginkgo.Describe("[Feature: Bandwidth Limit]", func() {
 	f := framework.NewDefaultFramework()
 	f := framework.NewDefaultFramework()
 
 
-	ginkgo.It("Proxy Bandwidth Limit", func() {
+	ginkgo.It("Proxy Bandwidth Limit by Client", func() {
 		serverConf := consts.DefaultServerConfig
 		serverConf := consts.DefaultServerConfig
 		clientConf := consts.DefaultClientConfig
 		clientConf := consts.DefaultClientConfig
 
 
@@ -40,8 +42,64 @@ var _ = ginkgo.Describe("[Feature: Bandwidth Limit]", func() {
 		framework.NewRequestExpect(f).Port(remotePort).RequestModify(func(r *request.Request) {
 		framework.NewRequestExpect(f).Port(remotePort).RequestModify(func(r *request.Request) {
 			r.Body([]byte(content)).Timeout(30 * time.Second)
 			r.Body([]byte(content)).Timeout(30 * time.Second)
 		}).ExpectResp([]byte(content)).Ensure()
 		}).ExpectResp([]byte(content)).Ensure()
+
+		duration := time.Since(start)
+		framework.Logf("request duration: %s", duration.String())
+
+		framework.ExpectTrue(duration.Seconds() > 8, "100Kb with 10KB limit, want > 8 seconds, but got %s", duration.String())
+	})
+
+	ginkgo.It("Proxy Bandwidth Limit by Server", func() {
+		// new test plugin server
+		newFunc := func() *plugin.Request {
+			var r plugin.Request
+			r.Content = &plugin.NewProxyContent{}
+			return &r
+		}
+		pluginPort := f.AllocPort()
+		handler := func(req *plugin.Request) *plugin.Response {
+			var ret plugin.Response
+			content := req.Content.(*plugin.NewProxyContent)
+			content.BandwidthLimit = "10KB"
+			content.BandwidthLimitMode = "server"
+			ret.Content = content
+			return &ret
+		}
+		pluginServer := plugintest.NewHTTPPluginServer(pluginPort, newFunc, handler, nil)
+
+		f.RunServer("", pluginServer)
+
+		serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
+		[plugin.test]
+		addr = 127.0.0.1:%d
+		path = /handler
+		ops = NewProxy
+		`, pluginPort)
+		clientConf := consts.DefaultClientConfig
+
+		localPort := f.AllocPort()
+		localServer := streamserver.New(streamserver.TCP, streamserver.WithBindPort(localPort))
+		f.RunServer("", localServer)
+
+		remotePort := f.AllocPort()
+		clientConf += fmt.Sprintf(`
+			[tcp]
+			type = tcp
+			local_port = %d
+			remote_port = %d
+			`, localPort, remotePort)
+
+		f.RunProcesses([]string{serverConf}, []string{clientConf})
+
+		content := strings.Repeat("a", 50*1024) // 5KB
+		start := time.Now()
+		framework.NewRequestExpect(f).Port(remotePort).RequestModify(func(r *request.Request) {
+			r.Body([]byte(content)).Timeout(30 * time.Second)
+		}).ExpectResp([]byte(content)).Ensure()
+
 		duration := time.Since(start)
 		duration := time.Since(start)
+		framework.Logf("request duration: %s", duration.String())
 
 
-		framework.ExpectTrue(duration.Seconds() > 7, "100Kb with 10KB limit, want > 7 seconds, but got %d seconds", duration.Seconds())
+		framework.ExpectTrue(duration.Seconds() > 8, "100Kb with 10KB limit, want > 8 seconds, but got %s", duration.String())
 	})
 	})
 })
 })

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

@@ -69,7 +69,7 @@ func (f *Framework) RunFrps(args ...string) (*process.Process, string, error) {
 		return p, p.StdOutput(), err
 		return p, p.StdOutput(), err
 	}
 	}
 	// sleep for a while to get std output
 	// sleep for a while to get std output
-	time.Sleep(500 * time.Millisecond)
+	time.Sleep(time.Second)
 	return p, p.StdOutput(), nil
 	return p, p.StdOutput(), nil
 }
 }
 
 
@@ -80,7 +80,7 @@ func (f *Framework) RunFrpc(args ...string) (*process.Process, string, error) {
 	if err != nil {
 	if err != nil {
 		return p, p.StdOutput(), err
 		return p, p.StdOutput(), err
 	}
 	}
-	time.Sleep(500 * time.Millisecond)
+	time.Sleep(time.Second)
 	return p, p.StdOutput(), nil
 	return p, p.StdOutput(), nil
 }
 }