diff --git a/Makefile.cross-compiles b/Makefile.cross-compiles index 5f4daf6..71d634e 100644 --- a/Makefile.cross-compiles +++ b/Makefile.cross-compiles @@ -3,15 +3,23 @@ export GO15VENDOREXPERIMENT := 1 all: build -build: gox app more - -gox: - go get github.com/mitchellh/gox +build: app app: - gox -osarch "darwin/386 darwin/amd64 linux/386 linux/amd64 linux/arm windows/386 windows/amd64" ./src/... - -more: + env GOOS=darwin GOARCH=386 go build -o ./frpc_darwin_386 ./src/cmd/frpc + env GOOS=darwin GOARCH=386 go build -o ./frps_darwin_386 ./src/cmd/frps + env GOOS=darwin GOARCH=amd64 go build -o ./frpc_darwin_amd64 ./src/cmd/frpc + env GOOS=darwin GOARCH=amd64 go build -o ./frps_darwin_amd64 ./src/cmd/frps + env GOOS=linux GOARCH=386 go build -o ./frpc_linux_386 ./src/cmd/frpc + env GOOS=linux GOARCH=386 go build -o ./frps_linux_386 ./src/cmd/frps + env GOOS=linux GOARCH=amd64 go build -o ./frpc_linux_amd64 ./src/cmd/frpc + env GOOS=linux GOARCH=amd64 go build -o ./frps_linux_amd64 ./src/cmd/frps + env GOOS=linux GOARCH=arm go build -o ./frpc_linux_arm ./src/cmd/frpc + env GOOS=linux GOARCH=arm go build -o ./frps_linux_arm ./src/cmd/frps + env GOOS=windows GOARCH=386 go build -o ./frpc_windows_386.exe ./src/cmd/frpc + env GOOS=windows GOARCH=386 go build -o ./frps_windows_386.exe ./src/cmd/frps + env GOOS=windows GOARCH=amd64 go build -o ./frpc_windows_amd64.exe ./src/cmd/frpc + env GOOS=windows GOARCH=amd64 go build -o ./frps_windows_amd64.exe ./src/cmd/frps env GOOS=linux GOARCH=mips64 go build -o ./frpc_linux_mips64 ./src/cmd/frpc env GOOS=linux GOARCH=mips64 go build -o ./frps_linux_mips64 ./src/cmd/frps env GOOS=linux GOARCH=mips64le go build -o ./frpc_linux_mips64le ./src/cmd/frpc diff --git a/README.md b/README.md index 252776e..d32aa32 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,7 @@ Then visit `http://[server_addr]:7500` to see dashboard, default username and pa Client that want's to register must set a global `auth_token` equals to frps.ini. -Note that time duration bewtween frpc and frps mustn't exceed 15 minutes because timestamp is used for authentication. +Note that time duration between frpc and frps mustn't exceed 15 minutes because timestamp is used for authentication. Howerver, this timeout duration can be modified by setting `authentication_timeout` in frps's configure file. It's defalut value is 900, means 15 minutes. If it is equals 0, then frps will not check authentication timeout. @@ -452,7 +452,6 @@ http_proxy = http://user:pwd@192.168.1.128:8080 ## Development Plan -* Url router. * Log http request information in frps. * Direct reverse proxy, like haproxy. * Load balance to different service in frpc. @@ -497,3 +496,4 @@ Donate money by [paypal](https://www.paypal.me/fatedier) to my account **fatedie * [Damon Zhao](https://github.com/se77en) * [Manfred Touron](https://github.com/moul) * [xuebing1110](https://github.com/xuebing1110) +* [Anbitioner](https://github.com/bingtianbaihua) diff --git a/README_zh.md b/README_zh.md index 69cac27..279e491 100644 --- a/README_zh.md +++ b/README_zh.md @@ -469,7 +469,6 @@ http_proxy = http://user:pwd@192.168.1.128:8080 计划在后续版本中加入的功能与优化,排名不分先后,如果有其他功能建议欢迎在 [issues](https://github.com/fatedier/frp/issues) 中反馈。 -* 支持 url 路由转发。 * frps 记录 http 请求日志。 * frps 支持直接反向代理,类似 haproxy。 * frpc 支持负载均衡到后端不同服务。 @@ -516,3 +515,4 @@ frp 交流群:606194980 (QQ 群号) * [Damon Zhao](https://github.com/se77en) * [Manfred Touron](https://github.com/moul) * [xuebing1110](https://github.com/xuebing1110) +* [Anbitioner](https://github.com/bingtianbaihua) diff --git a/conf/frpc.ini b/conf/frpc.ini index 4ae5a4a..04dac05 100644 --- a/conf/frpc.ini +++ b/conf/frpc.ini @@ -22,6 +22,10 @@ auth_token = 123 # for privilege mode privilege_token = 12345678 +# heartbeat configure, it's not recommended to modify the default value +# the default value of heartbeat_interval is 10 and heartbeat_timeout is 30 +# heartbeat_interval = 10 +# heartbeat_timeout = 30 # ssh is the proxy name same as server's configuration [ssh] diff --git a/conf/frps.ini b/conf/frps.ini index da87186..06b3c6c 100644 --- a/conf/frps.ini +++ b/conf/frps.ini @@ -30,6 +30,10 @@ log_max_days = 3 privilege_mode = true privilege_token = 12345678 +# heartbeat configure, it's not recommended to modify the default value +# the default value of heartbeat_timeout is 30 +# heartbeat_timeout = 30 + # only allow frpc to bind ports you list, if you set nothing, there won't be any limit privilege_allow_ports = 2000-3000,3001,3003,4000-50000 diff --git a/src/cmd/frpc/control.go b/src/cmd/frpc/control.go index 55d8798..01eb8a0 100644 --- a/src/cmd/frpc/control.go +++ b/src/cmd/frpc/control.go @@ -55,15 +55,24 @@ func msgReader(cli *client.ProxyClient, c *conn.Conn, msgSendChan chan interface var heartbeatTimeout bool = false timer := time.AfterFunc(time.Duration(client.HeartBeatTimeout)*time.Second, func() { heartbeatTimeout = true - c.Close() + if c != nil { + c.Close() + } + if cli != nil { + // if it's not udp type, nothing will happen + cli.CloseUdpTunnel() + cli.SetCloseFlag(true) + } log.Error("ProxyName [%s], heartbeatRes from frps timeout", cli.Name) }) defer timer.Stop() for { buf, err := c.ReadLine() - if err == io.EOF || c == nil || c.IsClosed() { + if err == io.EOF || c.IsClosed() { + timer.Stop() c.Close() + cli.SetCloseFlag(true) log.Warn("ProxyName [%s], frps close this control conn!", cli.Name) var delayTime time.Duration = 1 @@ -76,11 +85,14 @@ func msgReader(cli *client.ProxyClient, c *conn.Conn, msgSendChan chan interface msgSendChan = make(chan interface{}, 1024) go heartbeatSender(c, msgSendChan) go msgSender(cli, c, msgSendChan) + cli.SetCloseFlag(false) break } - if delayTime < 60 { + if delayTime < 30 { delayTime = delayTime * 2 + } else { + delayTime = 30 } time.Sleep(delayTime * time.Second) } diff --git a/src/cmd/frps/control.go b/src/cmd/frps/control.go index f6de7aa..a6cd6a3 100644 --- a/src/cmd/frps/control.go +++ b/src/cmd/frps/control.go @@ -85,7 +85,9 @@ func controlWorker(c *conn.Conn) { return } } else { - closeFlag = false + if ret == 0 { + closeFlag = false + } return } diff --git a/src/models/client/client.go b/src/models/client/client.go index f5fe7d7..5c5b2b2 100644 --- a/src/models/client/client.go +++ b/src/models/client/client.go @@ -39,6 +39,9 @@ type ProxyClient struct { udpTunnel *conn.Conn once sync.Once + closeFlag bool + + mutex sync.RWMutex } // if proxy type is udp, keep a tcp connection for transferring udp packages @@ -48,7 +51,7 @@ func (pc *ProxyClient) StartUdpTunnelOnce(addr string, port int64) { var c *conn.Conn udpProcessor := NewUdpProcesser(nil, pc.LocalIp, pc.LocalPort) for { - if pc.udpTunnel == nil || pc.udpTunnel.IsClosed() { + if !pc.IsClosed() && (pc.udpTunnel == nil || pc.udpTunnel.IsClosed()) { if HttpProxy == "" { c, err = conn.ConnectServer(fmt.Sprintf("%s:%d", addr, port)) } else { @@ -59,7 +62,7 @@ func (pc *ProxyClient) StartUdpTunnelOnce(addr string, port int64) { time.Sleep(10 * time.Second) continue } - log.Info("ProxyName [%s], udp tunnel reconnect to server [%s:%d] success", pc.Name, addr, port) + log.Info("ProxyName [%s], udp tunnel connect to server [%s:%d] success", pc.Name, addr, port) nowTime := time.Now().Unix() req := &msg.ControlReq{ @@ -82,8 +85,11 @@ func (pc *ProxyClient) StartUdpTunnelOnce(addr string, port int64) { time.Sleep(1 * time.Second) continue } + pc.mutex.Lock() pc.udpTunnel = c udpProcessor.UpdateTcpConn(pc.udpTunnel) + pc.mutex.Unlock() + udpProcessor.Run() } time.Sleep(1 * time.Second) @@ -91,6 +97,14 @@ func (pc *ProxyClient) StartUdpTunnelOnce(addr string, port int64) { }) } +func (pc *ProxyClient) CloseUdpTunnel() { + pc.mutex.RLock() + defer pc.mutex.RUnlock() + if pc.udpTunnel != nil { + pc.udpTunnel.Close() + } +} + func (pc *ProxyClient) GetLocalConn() (c *conn.Conn, err error) { c, err = conn.ConnectServer(fmt.Sprintf("%s:%d", pc.LocalIp, pc.LocalPort)) if err != nil { @@ -158,3 +172,15 @@ func (pc *ProxyClient) StartTunnel(serverAddr string, serverPort int64) (err err return nil } + +func (pc *ProxyClient) SetCloseFlag(closeFlag bool) { + pc.mutex.Lock() + defer pc.mutex.Unlock() + pc.closeFlag = closeFlag +} + +func (pc *ProxyClient) IsClosed() bool { + pc.mutex.RLock() + defer pc.mutex.RUnlock() + return pc.closeFlag +} diff --git a/src/models/client/config.go b/src/models/client/config.go index 03eabde..d04657b 100644 --- a/src/models/client/config.go +++ b/src/models/client/config.go @@ -33,8 +33,8 @@ var ( LogLevel string = "info" LogMaxDays int64 = 3 PrivilegeToken string = "" - HeartBeatInterval int64 = 20 - HeartBeatTimeout int64 = 90 + HeartBeatInterval int64 = 10 + HeartBeatTimeout int64 = 30 ) var ProxyClients map[string]*ProxyClient = make(map[string]*ProxyClient) @@ -98,6 +98,34 @@ func LoadConf(confFile string) (err error) { authToken = tmpStr } + tmpStr, ok = conf.Get("common", "heartbeat_timeout") + if ok { + v, err := strconv.ParseInt(tmpStr, 10, 64) + if err != nil { + return fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect") + } else { + HeartBeatTimeout = v + } + } + + tmpStr, ok = conf.Get("common", "heartbeat_interval") + if ok { + v, err := strconv.ParseInt(tmpStr, 10, 64) + if err != nil { + return fmt.Errorf("Parse conf error: heartbeat_interval is incorrect") + } else { + HeartBeatInterval = v + } + } + + if HeartBeatInterval <= 0 { + return fmt.Errorf("Parse conf error: heartbeat_interval is incorrect") + } + + if HeartBeatTimeout < HeartBeatInterval { + return fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect, heartbeat_timeout is less than heartbeat_interval") + } + // proxies for name, section := range conf { if name != "common" { diff --git a/src/models/server/config.go b/src/models/server/config.go index 7c22c5a..e52d96c 100644 --- a/src/models/server/config.go +++ b/src/models/server/config.go @@ -51,7 +51,7 @@ var ( // if PrivilegeAllowPorts is not nil, tcp proxies which remote port exist in this map can be connected PrivilegeAllowPorts map[int64]struct{} MaxPoolCount int64 = 100 - HeartBeatTimeout int64 = 90 + HeartBeatTimeout int64 = 30 UserConnTimeout int64 = 10 VhostHttpMuxer *vhost.HttpMuxer @@ -237,6 +237,16 @@ func loadCommonConf(confFile string) error { if ok { SubDomainHost = strings.ToLower(strings.TrimSpace(SubDomainHost)) } + + tmpStr, ok = conf.Get("common", "heartbeat_timeout") + if ok { + v, err := strconv.ParseInt(tmpStr, 10, 64) + if err != nil { + return fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect") + } else { + HeartBeatTimeout = v + } + } return nil } @@ -395,7 +405,9 @@ func CreateProxy(s *ProxyServer) error { if oldServer.Status == consts.Working { return fmt.Errorf("this proxy is already working now") } + oldServer.Lock() oldServer.Release() + oldServer.Unlock() if oldServer.PrivilegeMode { delete(ProxyServers, s.Name) } @@ -403,7 +415,6 @@ func CreateProxy(s *ProxyServer) error { ProxyServers[s.Name] = s metric.SetProxyInfo(s.Name, s.Type, s.BindAddr, s.UseEncryption, s.UseGzip, s.PrivilegeMode, s.CustomDomains, s.Locations, s.ListenPort) - s.Init() return nil } diff --git a/src/models/server/server.go b/src/models/server/server.go index 419f6b8..27590bf 100644 --- a/src/models/server/server.go +++ b/src/models/server/server.go @@ -83,6 +83,8 @@ func NewProxyServerFromCtlMsg(req *msg.ControlReq) (p *ProxyServer) { p.HostHeaderRewrite = req.HostHeaderRewrite p.HttpUserName = req.HttpUserName p.HttpPassWord = req.HttpPassWord + + p.Init() return } @@ -276,10 +278,14 @@ func (p *ProxyServer) Start(c *conn.Conn) (err error) { } func (p *ProxyServer) Close() { + p.Lock() + defer p.Unlock() + + oldStatus := p.Status p.Release() // if the proxy created by PrivilegeMode, delete it when closed - if p.PrivilegeMode { + if p.PrivilegeMode && oldStatus != consts.Closed { // NOTE: this will take the global ProxyServerMap's lock // if we only want to release resources, use Release() instead DeleteProxy(p.Name) @@ -287,9 +293,6 @@ func (p *ProxyServer) Close() { } func (p *ProxyServer) Release() { - p.Lock() - defer p.Unlock() - if p.Status != consts.Closed { p.Status = consts.Closed for _, l := range p.listeners { @@ -297,10 +300,22 @@ func (p *ProxyServer) Release() { l.Close() } } - close(p.ctlMsgChan) - close(p.workConnChan) - close(p.udpSenderChan) - close(p.closeChan) + if p.ctlMsgChan != nil { + close(p.ctlMsgChan) + p.ctlMsgChan = nil + } + if p.workConnChan != nil { + close(p.workConnChan) + p.workConnChan = nil + } + if p.udpSenderChan != nil { + close(p.udpSenderChan) + p.udpSenderChan = nil + } + if p.closeChan != nil { + close(p.closeChan) + p.closeChan = nil + } if p.CtlConn != nil { p.CtlConn.Close() } diff --git a/src/utils/version/version.go b/src/utils/version/version.go index f8c7582..97b1dc1 100644 --- a/src/utils/version/version.go +++ b/src/utils/version/version.go @@ -19,7 +19,7 @@ import ( "strings" ) -var version string = "0.9.1" +var version string = "0.9.2" func Full() string { return version