diff --git a/.goreleaser.yml b/.goreleaser.yml index e1aaff0..2c81f7d 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,7 +1,7 @@ builds: - skip: true checksum: - name_template: '{{ .ProjectName }}_{{ .Version }}_sha256_checksums.txt' + name_template: '{{ .ProjectName }}_sha256_checksums.txt' algorithm: sha256 extra_files: - glob: ./release/packages/* diff --git a/Makefile b/Makefile index ae1a013..d3f09bd 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,9 @@ file: fmt: go fmt ./... +vet: + go vet ./... + frps: env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o bin/frps ./cmd/frps @@ -37,7 +40,7 @@ e2e: e2e-trace: DEBUG=true LOG_LEVEL=trace ./hack/run-e2e.sh -alltest: gotest e2e +alltest: vet gotest e2e clean: rm -f ./bin/frpc diff --git a/Release.md b/Release.md index 9be2af7..78f9d43 100644 --- a/Release.md +++ b/Release.md @@ -1,8 +1,7 @@ ### New -* Support go http pprof. +* Added new parameter `config_dir` in frpc to run multiple client instances in one process. -### Improve +### Fix -* Change underlying TCP connection keepalive interval to 2 hours. -* Create new connection to server for `sudp` visitor when needed, to avoid frequent reconnections. +* Equal sign in environment variables causes parsing error. diff --git a/client/proxy/proxy.go b/client/proxy/proxy.go index 0fdecec..df5e87d 100644 --- a/client/proxy/proxy.go +++ b/client/proxy/proxy.go @@ -366,7 +366,7 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { // Listen for clientConn's address and wait for visitor connection lConn, err := net.ListenUDP("udp", laddr) if err != nil { - xl.Error("listen on visitorConn's local adress error: %v", err) + xl.Error("listen on visitorConn's local address error: %v", err) return } defer lConn.Close() diff --git a/client/service.go b/client/service.go index 11e369c..30bd3f8 100644 --- a/client/service.go +++ b/client/service.go @@ -19,9 +19,11 @@ import ( "crypto/tls" "fmt" "io" + "math/rand" "net" "runtime" "strconv" + "strings" "sync" "sync/atomic" "time" @@ -36,11 +38,17 @@ import ( "github.com/fatedier/frp/pkg/util/util" "github.com/fatedier/frp/pkg/util/version" "github.com/fatedier/frp/pkg/util/xlog" + "github.com/fatedier/golib/crypto" libdial "github.com/fatedier/golib/net/dial" fmux "github.com/hashicorp/yamux" ) +func init() { + crypto.DefaultSalt = "frp" + rand.Seed(time.Now().UnixNano()) +} + // Service is a client service. type Service struct { // uniq id got from frps, attach it in loginMsg @@ -98,6 +106,21 @@ func (svr *Service) GetController() *Control { func (svr *Service) Run() error { xl := xlog.FromContextSafe(svr.ctx) + // set custom DNSServer + if svr.cfg.DNSServer != "" { + dnsAddr := svr.cfg.DNSServer + if !strings.Contains(dnsAddr, ":") { + dnsAddr += ":53" + } + // Change default dns server for frpc + net.DefaultResolver = &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + return net.Dial("udp", dnsAddr) + }, + } + } + // login to frps for { conn, session, err := svr.login() @@ -333,7 +356,14 @@ func (svr *Service) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs svr.visitorCfgs = visitorCfgs svr.cfgMu.Unlock() - return svr.ctl.ReloadConf(pxyCfgs, visitorCfgs) + svr.ctlMu.RLock() + ctl := svr.ctl + svr.ctlMu.RUnlock() + + if ctl != nil { + return svr.ctl.ReloadConf(pxyCfgs, visitorCfgs) + } + return nil } func (svr *Service) Close() { @@ -342,8 +372,12 @@ func (svr *Service) Close() { func (svr *Service) GracefulClose(d time.Duration) { atomic.StoreUint32(&svr.exit, 1) + + svr.ctlMu.RLock() if svr.ctl != nil { svr.ctl.GracefulClose(d) } + svr.ctlMu.RUnlock() + svr.cancel() } diff --git a/cmd/frpc/main.go b/cmd/frpc/main.go index 2c1e015..f9a6c25 100644 --- a/cmd/frpc/main.go +++ b/cmd/frpc/main.go @@ -15,18 +15,10 @@ package main import ( - "math/rand" - "time" - _ "github.com/fatedier/frp/assets/frpc" "github.com/fatedier/frp/cmd/frpc/sub" - - "github.com/fatedier/golib/crypto" ) func main() { - crypto.DefaultSalt = "frp" - rand.Seed(time.Now().UnixNano()) - sub.Execute() } diff --git a/cmd/frpc/sub/root.go b/cmd/frpc/sub/root.go index d03cdd1..f8d7eb1 100644 --- a/cmd/frpc/sub/root.go +++ b/cmd/frpc/sub/root.go @@ -15,13 +15,14 @@ package sub import ( - "context" "fmt" + "io/fs" "net" "os" "os/signal" + "path/filepath" "strconv" - "strings" + "sync" "syscall" "time" @@ -41,6 +42,7 @@ const ( var ( cfgFile string + cfgDir string showVersion bool serverAddr string @@ -72,15 +74,12 @@ var ( bindPort int tlsEnable bool - - kcpDoneCh chan struct{} ) func init() { rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./frpc.ini", "config file of frpc") + rootCmd.PersistentFlags().StringVarP(&cfgDir, "config_dir", "", "", "config directory, run one frpc service for each file in config directory") rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc") - - kcpDoneCh = make(chan struct{}) } func RegisterCommonFlags(cmd *cobra.Command) { @@ -104,6 +103,32 @@ var rootCmd = &cobra.Command{ return nil } + // If cfgDir is not empty, run multiple frpc service for each config file in cfgDir. + // Note that it's only designed for testing. It's not guaranteed to be stable. + if cfgDir != "" { + var wg sync.WaitGroup + filepath.WalkDir(cfgDir, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return nil + } + if d.IsDir() { + return nil + } + wg.Add(1) + time.Sleep(time.Millisecond) + go func() { + defer wg.Done() + err := runClient(path) + if err != nil { + fmt.Printf("frpc service error for config file [%s]\n", path) + } + }() + return nil + }) + wg.Wait() + return nil + } + // Do not show command usage here. err := runClient(cfgFile) if err != nil { @@ -120,12 +145,12 @@ func Execute() { } } -func handleSignal(svr *client.Service) { - ch := make(chan os.Signal) +func handleSignal(svr *client.Service, doneCh chan struct{}) { + ch := make(chan os.Signal, 1) signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) <-ch svr.GracefulClose(500 * time.Millisecond) - close(kcpDoneCh) + close(doneCh) } func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) { @@ -182,18 +207,9 @@ func startService( log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel, cfg.LogMaxDays, cfg.DisableLogColor) - if cfg.DNSServer != "" { - s := cfg.DNSServer - if !strings.Contains(s, ":") { - s += ":53" - } - // Change default dns server for frpc - net.DefaultResolver = &net.Resolver{ - PreferGo: true, - Dial: func(ctx context.Context, network, address string) (net.Conn, error) { - return net.Dial("udp", s) - }, - } + if cfgFile != "" { + log.Trace("start frpc service for config file [%s]", cfgFile) + defer log.Trace("frpc service for config file [%s] stopped", cfgFile) } svr, errRet := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile) if errRet != nil { @@ -201,9 +217,10 @@ func startService( return } + kcpDoneCh := make(chan struct{}) // Capture the exit signal if we use kcp. if cfg.Protocol == "kcp" { - go handleSignal(svr) + go handleSignal(svr, kcpDoneCh) } err = svr.Run() diff --git a/conf/frpc_full.ini b/conf/frpc_full.ini index 913c971..a0e7dca 100644 --- a/conf/frpc_full.ini +++ b/conf/frpc_full.ini @@ -105,7 +105,7 @@ tls_enable = true # specify a dns server, so frpc will use this instead of default one # dns_server = 8.8.8.8 -# proxy names you want to start seperated by ',' +# proxy names you want to start separated by ',' # default is empty, means all proxies # start = ssh,dns diff --git a/go.mod b/go.mod index ea9d6b9..0a92dfb 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/leodido/go-urn v1.2.1 // indirect github.com/onsi/ginkgo v1.16.4 github.com/onsi/gomega v1.13.0 - github.com/pires/go-proxyproto v0.5.0 + github.com/pires/go-proxyproto v0.6.2 github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect github.com/prometheus/client_golang v1.11.0 github.com/rodaine/table v1.0.1 diff --git a/go.sum b/go.sum index cfe79d3..433f03e 100644 --- a/go.sum +++ b/go.sum @@ -300,8 +300,8 @@ github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je4 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/pires/go-proxyproto v0.5.0 h1:A4Jv4ZCaV3AFJeGh5mGwkz4iuWUYMlQ7IoO/GTuSuLo= -github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY= +github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8= +github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= diff --git a/pkg/config/client.go b/pkg/config/client.go index 27a47a2..f503711 100644 --- a/pkg/config/client.go +++ b/pkg/config/client.go @@ -133,7 +133,7 @@ type ClientCommonConf struct { // It only works when "tls_enable" is valid and tls configuration of server // has been specified. TLSTrustedCaFile string `ini:"tls_trusted_ca_file" json:"tls_trusted_ca_file"` - // TLSServerName specifices the custom server name of tls certificate. By + // TLSServerName specifies the custom server name of tls certificate. By // default, server name if same to ServerAddr. TLSServerName string `ini:"tls_server_name" json:"tls_server_name"` // By default, frpc will connect frps with first custom byte if tls is enabled. diff --git a/pkg/config/value.go b/pkg/config/value.go index 41c6c65..e44fbd6 100644 --- a/pkg/config/value.go +++ b/pkg/config/value.go @@ -29,11 +29,11 @@ func init() { glbEnvs = make(map[string]string) envs := os.Environ() for _, env := range envs { - kv := strings.Split(env, "=") - if len(kv) != 2 { + pair := strings.SplitN(env, "=", 2) + if len(pair) != 2 { continue } - glbEnvs[kv[0]] = kv[1] + glbEnvs[pair[0]] = pair[1] } } diff --git a/pkg/util/version/version.go b/pkg/util/version/version.go index 07d0da2..67e6bab 100644 --- a/pkg/util/version/version.go +++ b/pkg/util/version/version.go @@ -19,7 +19,7 @@ import ( "strings" ) -var version string = "0.41.0" +var version string = "0.42.0" func Full() string { return version diff --git a/pkg/util/vhost/http.go b/pkg/util/vhost/http.go index b9dc32d..b720862 100644 --- a/pkg/util/vhost/http.go +++ b/pkg/util/vhost/http.go @@ -89,7 +89,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) * BufferPool: newWrapPool(), ErrorLog: log.New(newWrapLogger(), "", 0), ErrorHandler: func(rw http.ResponseWriter, req *http.Request, err error) { - frpLog.Warn("do http proxy request error: %v", err) + frpLog.Warn("do http proxy request [host: %s] error: %v", req.Host, err) rw.WriteHeader(http.StatusNotFound) rw.Write(getNotFoundPageContent()) }, diff --git a/server/control.go b/server/control.go index 0974061..74a3cfa 100644 --- a/server/control.go +++ b/server/control.go @@ -258,7 +258,7 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) { case workConn, ok = <-ctl.workConnCh: if !ok { err = frpErr.ErrCtlClosed - xl.Warn("no work connections avaiable, %v", err) + xl.Warn("no work connections available, %v", err) return } diff --git a/test/e2e/plugin/server.go b/test/e2e/plugin/server.go index b972f78..2af1eba 100644 --- a/test/e2e/plugin/server.go +++ b/test/e2e/plugin/server.go @@ -150,7 +150,7 @@ var _ = Describe("[Feature: Server-Plugins]", func() { type = tcp local_port = {{ .%s }} remote_port = 0 - `, framework.TCPEchoServerPort, remotePort) + `, framework.TCPEchoServerPort) f.RunProcesses([]string{serverConf}, []string{clientConf})