diff --git a/.circleci/config.yml b/.circleci/config.yml index b8f1980..f862369 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,14 +2,14 @@ version: 2 jobs: go-version-latest: docker: - - image: cimg/go:1.17-node + - image: cimg/go:1.18-node steps: - checkout - run: make - run: make alltest go-version-last: docker: - - image: cimg/go:1.16-node + - image: cimg/go:1.17-node steps: - checkout - run: make diff --git a/.github/workflows/build-and-push-image.yml b/.github/workflows/build-and-push-image.yml index ef43625..d929e58 100644 --- a/.github/workflows/build-and-push-image.yml +++ b/.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.17 + go-version: 1.18 - run: | # https://github.com/actions/setup-go/issues/107 diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index a58546a..40914a4 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -15,7 +15,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.17 + go-version: 1.18 - run: | # https://github.com/actions/setup-go/issues/107 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 4ccc266..1a44ddf 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -12,11 +12,11 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v3 + - uses: actions/stale@v5 with: repo-token: ${{ secrets.GITHUB_TOKEN }} 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-pr-message: "PRs go stale after 30d of inactivity. Stale PRs 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' @@ -24,3 +24,5 @@ jobs: days-before-stale: 30 days-before-close: 7 debug-only: ${{ github.event.inputs.debug-only }} + exempt-all-pr-milestones: true + exempt-all-pr-assignees: true diff --git a/.goreleaser.yml b/.goreleaser.yml index 6cf8c6a..e1aaff0 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,7 +1,10 @@ builds: - skip: true checksum: - name_template: 'checksums.txt' + name_template: '{{ .ProjectName }}_{{ .Version }}_sha256_checksums.txt' + algorithm: sha256 + extra_files: + - glob: ./release/packages/* release: # Same as for github # Note: it can only be one: either github, gitlab or gitea diff --git a/Release.md b/Release.md index 306d8b9..9be2af7 100644 --- a/Release.md +++ b/Release.md @@ -1,13 +1,8 @@ ### New -* Added `dial_server_timeout` in frpc to specify connect timeout to frps. -* Additional EndpointParams can be set for OIDC. -* Added CloseProxy operation in server plugin. +* Support go http pprof. ### Improve -* Added some randomness in reconnect delay. - -### Fix - -* TLS server name is ignored when `tls_trusted_ca_file` isn’t set. +* Change underlying TCP connection keepalive interval to 2 hours. +* Create new connection to server for `sudp` visitor when needed, to avoid frequent reconnections. diff --git a/client/admin.go b/client/admin.go index fff169f..6a6ceec 100644 --- a/client/admin.go +++ b/client/admin.go @@ -17,6 +17,7 @@ package client import ( "net" "net/http" + "net/http/pprof" "time" "github.com/fatedier/frp/assets" @@ -26,8 +27,8 @@ import ( ) var ( - httpServerReadTimeout = 10 * time.Second - httpServerWriteTimeout = 10 * time.Second + httpServerReadTimeout = 60 * time.Second + httpServerWriteTimeout = 60 * time.Second ) func (svr *Service) RunAdminServer(address string) (err error) { @@ -36,6 +37,15 @@ func (svr *Service) RunAdminServer(address string) (err error) { router.HandleFunc("/healthz", svr.healthz) + // debug + if svr.cfg.PprofEnable { + router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) + router.HandleFunc("/debug/pprof/profile", pprof.Profile) + router.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + router.HandleFunc("/debug/pprof/trace", pprof.Trace) + router.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index) + } + subRouter := router.NewRoute().Subrouter() user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd subRouter.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware) diff --git a/client/control.go b/client/control.go index 81aaf7b..ef9a766 100644 --- a/client/control.go +++ b/client/control.go @@ -252,6 +252,7 @@ func (ctl *Control) connectServer() (conn net.Conn, err error) { dialOptions = append(dialOptions, libdial.WithProtocol(protocol), libdial.WithTimeout(time.Duration(ctl.clientCfg.DialServerTimeout)*time.Second), + libdial.WithKeepAlive(time.Duration(ctl.clientCfg.DialServerKeepAlive)*time.Second), libdial.WithProxy(proxyType, addr), libdial.WithProxyAuth(auth), libdial.WithTLSConfig(tlsConfig), diff --git a/client/service.go b/client/service.go index 96ddadf..11e369c 100644 --- a/client/service.go +++ b/client/service.go @@ -243,6 +243,7 @@ func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) { dialOptions = append(dialOptions, libdial.WithProtocol(protocol), libdial.WithTimeout(time.Duration(svr.cfg.DialServerTimeout)*time.Second), + libdial.WithKeepAlive(time.Duration(svr.cfg.DialServerKeepAlive)*time.Second), libdial.WithProxy(proxyType, addr), libdial.WithProxyAuth(auth), libdial.WithTLSConfig(tlsConfig), diff --git a/client/visitor.go b/client/visitor.go index 52f4ccd..ea54829 100644 --- a/client/visitor.go +++ b/client/visitor.go @@ -377,29 +377,33 @@ func (sv *SUDPVisitor) Run() (err error) { func (sv *SUDPVisitor) dispatcher() { xl := xlog.FromContextSafe(sv.ctx) + var ( + visitorConn net.Conn + err error + + firstPacket *msg.UDPPacket + ) + for { - // loop for get frpc to frps tcp conn - // setup worker - // wait worker to finished - // retry or exit - visitorConn, err := sv.getNewVisitorConn() - if err != nil { - // check if proxy is closed - // if checkCloseCh is close, we will return, other case we will continue to reconnect - select { - case <-sv.checkCloseCh: + select { + case firstPacket = <-sv.sendCh: + if firstPacket == nil { xl.Info("frpc sudp visitor proxy is closed") return - default: } + case <-sv.checkCloseCh: + xl.Info("frpc sudp visitor proxy is closed") + return + } - time.Sleep(3 * time.Second) - + visitorConn, err = sv.getNewVisitorConn() + if err != nil { xl.Warn("newVisitorConn to frps error: %v, try to reconnect", err) continue } - sv.worker(visitorConn) + // visitorConn always be closed when worker done. + sv.worker(visitorConn, firstPacket) select { case <-sv.checkCloseCh: @@ -407,9 +411,10 @@ func (sv *SUDPVisitor) dispatcher() { default: } } + } -func (sv *SUDPVisitor) worker(workConn net.Conn) { +func (sv *SUDPVisitor) worker(workConn net.Conn, firstPacket *msg.UDPPacket) { xl := xlog.FromContextSafe(sv.ctx) xl.Debug("starting sudp proxy worker") @@ -463,6 +468,14 @@ func (sv *SUDPVisitor) worker(workConn net.Conn) { }() var errRet error + if firstPacket != nil { + if errRet = msg.WriteMsg(conn, firstPacket); errRet != nil { + xl.Warn("sender goroutine for udp work connection closed: %v", errRet) + return + } + xl.Trace("send udp package to workConn: %s", firstPacket.Content) + } + for { select { case udpMsg, ok := <-sv.sendCh: diff --git a/conf/frpc_full.ini b/conf/frpc_full.ini index 58aefde..913c971 100644 --- a/conf/frpc_full.ini +++ b/conf/frpc_full.ini @@ -9,6 +9,10 @@ server_port = 7000 # The maximum amount of time a dial to server will wait for a connect to complete. Default value is 10 seconds. # dial_server_timeout = 10 +# dial_server_keepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps. +# If negative, keep-alive probes are disabled. +# dial_server_keepalive = 7200 + # if you want to connect frps by http proxy or socks5 proxy or ntlm proxy, you can set http_proxy here or in global environment variables # it only works when protocol is tcp # http_proxy = http://user:passwd@192.168.1.128:8080 @@ -69,7 +73,8 @@ admin_pwd = admin pool_count = 5 # if tcp stream multiplexing is used, default is true, it must be same with frps -tcp_mux = true +# tcp_mux = true + # specify keep alive interval for tcp mux. # only valid if tcp_mux is true. # tcp_mux_keepalive_interval = 60 @@ -126,6 +131,10 @@ udp_packet_size = 1500 # If DisableCustomTLSFirstByte is true, frpc will not send that custom byte. disable_custom_tls_first_byte = false +# Enable golang pprof handlers in admin listener. +# Admin port must be set first. +pprof_enable = 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] diff --git a/conf/frps_full.ini b/conf/frps_full.ini index 4aef977..fecb8bf 100644 --- a/conf/frps_full.ini +++ b/conf/frps_full.ini @@ -86,7 +86,6 @@ oidc_audience = # By default, this value is false. oidc_skip_expiry_check = false - # oidc_skip_issuer_check specifies whether to skip checking if the OIDC token's issuer claim matches the issuer specified in OidcIssuer. # By default, this value is false. oidc_skip_issuer_check = false @@ -120,11 +119,16 @@ tls_only = false subdomain_host = frps.com # if tcp stream multiplexing is used, default is true -tcp_mux = true +# tcp_mux = true + # specify keep alive interval for tcp mux. # only valid if tcp_mux is true. # tcp_mux_keepalive_interval = 60 +# tcp_keepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps. +# If negative, keep-alive probes are disabled. +# tcp_keepalive = 7200 + # custom 404 page for HTTP requests # custom_404_page = /path/to/404.html @@ -133,6 +137,10 @@ tcp_mux = true # It affects the udp and sudp proxy. udp_packet_size = 1500 +# Enable golang pprof handlers in dashboard listener. +# Dashboard port must be set first +pprof_enable = false + [plugin.user-manager] addr = 127.0.0.1:9000 path = /handler diff --git a/go.mod b/go.mod index 98398d5..ea9d6b9 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 github.com/coreos/go-oidc v2.2.1+incompatible github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb - github.com/fatedier/golib v0.1.1-0.20220218075713-264f72dfbfd9 + github.com/fatedier/golib v0.1.1-0.20220321042308-c306138b83ac github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible github.com/go-playground/validator/v10 v10.6.1 github.com/google/uuid v1.2.0 diff --git a/go.sum b/go.sum index a42b905..cfe79d3 100644 --- a/go.sum +++ b/go.sum @@ -88,10 +88,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw= github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk= -github.com/fatedier/golib v0.1.1-0.20220218073251-9509a597216b h1:5r5/G3NFsFK+7svxvxZYA8yy8Ubs4hWIq+QYYMgEBe8= -github.com/fatedier/golib v0.1.1-0.20220218073251-9509a597216b/go.mod h1:fLV0TLwHqrnB/L3jbNl67Gn6PCLggDGHniX1wLrA2Qo= -github.com/fatedier/golib v0.1.1-0.20220218075713-264f72dfbfd9 h1:AOGf9Z1ri+3MiyGIAYXe+shEXx6/uVGJlufb6ZfnZls= -github.com/fatedier/golib v0.1.1-0.20220218075713-264f72dfbfd9/go.mod h1:fLV0TLwHqrnB/L3jbNl67Gn6PCLggDGHniX1wLrA2Qo= +github.com/fatedier/golib v0.1.1-0.20220321042308-c306138b83ac h1:td1FJwN/oz8+9GldeEm3YdBX0Husc0FSPywLesZxi4w= +github.com/fatedier/golib v0.1.1-0.20220321042308-c306138b83ac/go.mod h1:fLV0TLwHqrnB/L3jbNl67Gn6PCLggDGHniX1wLrA2Qo= github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible h1:ssXat9YXFvigNge/IkkZvFMn8yeYKFX+uI6wn2mLJ74= github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= diff --git a/pkg/config/client.go b/pkg/config/client.go index e65e106..27a47a2 100644 --- a/pkg/config/client.go +++ b/pkg/config/client.go @@ -40,6 +40,9 @@ type ClientCommonConf struct { ServerPort int `ini:"server_port" json:"server_port"` // The maximum amount of time a dial to server will wait for a connect to complete. DialServerTimeout int64 `ini:"dial_server_timeout" json:"dial_server_timeout"` + // DialServerKeepAlive specifies the interval between keep-alive probes for an active network connection between frpc and frps. + // If negative, keep-alive probes are disabled. + DialServerKeepAlive int64 `ini:"dial_server_keepalive" json:"dial_server_keepalive"` // ConnectServerLocalIP specifies the address of the client bind when it connect to server. // By default, this value is empty. // this value only use in TCP/Websocket protocol. Not support in KCP protocol. @@ -151,6 +154,9 @@ type ClientCommonConf struct { UDPPacketSize int64 `ini:"udp_packet_size" json:"udp_packet_size"` // Include other config files for proxies. IncludeConfigFiles []string `ini:"includes" json:"includes"` + // Enable golang pprof handlers in admin listener. + // Admin port must be set first. + PprofEnable bool `ini:"pprof_enable" json:"pprof_enable"` } // GetDefaultClientConf returns a client configuration with default values. @@ -160,6 +166,7 @@ func GetDefaultClientConf() ClientCommonConf { ServerAddr: "0.0.0.0", ServerPort: 7000, DialServerTimeout: 10, + DialServerKeepAlive: 7200, HTTPProxy: os.Getenv("http_proxy"), LogFile: "console", LogWay: "console", @@ -188,6 +195,7 @@ func GetDefaultClientConf() ClientCommonConf { Metas: make(map[string]string), UDPPacketSize: 1500, IncludeConfigFiles: make([]string, 0), + PprofEnable: false, } } diff --git a/pkg/config/client_test.go b/pkg/config/client_test.go index a64b9ee..12f1c9e 100644 --- a/pkg/config/client_test.go +++ b/pkg/config/client_test.go @@ -262,6 +262,7 @@ func Test_LoadClientCommonConf(t *testing.T) { ServerAddr: "0.0.0.9", ServerPort: 7009, DialServerTimeout: 10, + DialServerKeepAlive: 7200, HTTPProxy: "http://user:passwd@192.168.1.128:8080", LogFile: "./frpc.log9", LogWay: "file", diff --git a/pkg/config/server.go b/pkg/config/server.go index d002f9f..cf298f2 100644 --- a/pkg/config/server.go +++ b/pkg/config/server.go @@ -121,6 +121,9 @@ type ServerCommonConf struct { // TCPMuxKeepaliveInterval specifies the keep alive interval for TCP stream multipler. // If TCPMux is true, heartbeat of application layer is unnecessary because it can only rely on heartbeat in TCPMux. TCPMuxKeepaliveInterval int64 `ini:"tcp_mux_keepalive_interval" json:"tcp_mux_keepalive_interval"` + // TCPKeepAlive specifies the interval between keep-alive probes for an active network connection between frpc and frps. + // If negative, keep-alive probes are disabled. + TCPKeepAlive int64 `ini:"tcp_keepalive" json:"tcp_keepalive"` // Custom404Page specifies a path to a custom 404 page to display. If this // value is "", a default page will be displayed. By default, this value is // "". @@ -167,6 +170,9 @@ type ServerCommonConf struct { // UDPPacketSize specifies the UDP packet size // By default, this value is 1500 UDPPacketSize int64 `ini:"udp_packet_size" json:"udp_packet_size"` + // Enable golang pprof handlers in dashboard listener. + // Dashboard port must be set first. + PprofEnable bool `ini:"pprof_enable" json:"pprof_enable"` } // GetDefaultServerConf returns a server configuration with reasonable @@ -198,6 +204,7 @@ func GetDefaultServerConf() ServerCommonConf { SubDomainHost: "", TCPMux: true, TCPMuxKeepaliveInterval: 60, + TCPKeepAlive: 7200, AllowPorts: make(map[int]struct{}), MaxPoolCount: 5, MaxPortsPerClient: 0, @@ -210,6 +217,7 @@ func GetDefaultServerConf() ServerCommonConf { Custom404Page: "", HTTPPlugins: make(map[string]plugin.HTTPPluginOptions), UDPPacketSize: 1500, + PprofEnable: false, } } diff --git a/pkg/config/server_test.go b/pkg/config/server_test.go index bdf20cb..8646ca6 100644 --- a/pkg/config/server_test.go +++ b/pkg/config/server_test.go @@ -140,6 +140,7 @@ func Test_LoadServerCommonConf(t *testing.T) { SubDomainHost: "frps.com", TCPMux: true, TCPMuxKeepaliveInterval: 60, + TCPKeepAlive: 7200, UDPPacketSize: 1509, HTTPPlugins: map[string]plugin.HTTPPluginOptions{ @@ -191,6 +192,7 @@ func Test_LoadServerCommonConf(t *testing.T) { DetailedErrorsToClient: true, TCPMux: true, TCPMuxKeepaliveInterval: 60, + TCPKeepAlive: 7200, AllowPorts: make(map[int]struct{}), MaxPoolCount: 5, HeartbeatTimeout: 90, diff --git a/pkg/util/version/version.go b/pkg/util/version/version.go index cb15461..07d0da2 100644 --- a/pkg/util/version/version.go +++ b/pkg/util/version/version.go @@ -19,7 +19,7 @@ import ( "strings" ) -var version string = "0.40.0" +var version string = "0.41.0" func Full() string { return version diff --git a/server/dashboard.go b/server/dashboard.go index 0e06ed8..8ae1ea8 100644 --- a/server/dashboard.go +++ b/server/dashboard.go @@ -17,6 +17,7 @@ package server import ( "net" "net/http" + "net/http/pprof" "time" "github.com/fatedier/frp/assets" @@ -27,8 +28,8 @@ import ( ) var ( - httpServerReadTimeout = 10 * time.Second - httpServerWriteTimeout = 10 * time.Second + httpServerReadTimeout = 60 * time.Second + httpServerWriteTimeout = 60 * time.Second ) func (svr *Service) RunDashboardServer(address string) (err error) { @@ -36,6 +37,15 @@ func (svr *Service) RunDashboardServer(address string) (err error) { router := mux.NewRouter() router.HandleFunc("/healthz", svr.Healthz) + // debug + if svr.cfg.PprofEnable { + router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) + router.HandleFunc("/debug/pprof/profile", pprof.Profile) + router.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + router.HandleFunc("/debug/pprof/trace", pprof.Trace) + router.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index) + } + subRouter := router.NewRoute().Subrouter() user, passwd := svr.cfg.DashboardUser, svr.cfg.DashboardPwd diff --git a/server/service.go b/server/service.go index c3c4548..ed66329 100644 --- a/server/service.go +++ b/server/service.go @@ -186,6 +186,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) { } svr.muxer = mux.NewMux(ln) + svr.muxer.SetKeepAlive(time.Duration(cfg.TCPKeepAlive) * time.Second) go svr.muxer.Serve() ln = svr.muxer.DefaultListener()