// loop for reading messages from frpc after control connection is established func msgReader(cli *client.ProxyClient, c *conn.Conn, msgSendChan chan interface{}) error { // for heartbeat var heartbeatTimeout bool = false timer := time.AfterFunc(time.Duration(client.HeartBeatTimeout)*time.Second, func() { heartbeatTimeout = true c.Close() 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() { c.Close() log.Warn("ProxyName [%s], frps close this control conn!", cli.Name) var delayTime time.Duration = 1 // loop until reconnect to frps for { log.Info("ProxyName [%s], try to reconnect to frps [%s:%d]...", cli.Name, client.ServerAddr, client.ServerPort) c, err = loginToServer(cli) if err == nil { close(msgSendChan) msgSendChan = make(chan interface{}, 1024) go heartbeatSender(c, msgSendChan) go msgSender(cli, c, msgSendChan) break } if delayTime < 60 { delayTime = delayTime * 2 } time.Sleep(delayTime * time.Second) } continue } else if err != nil { log.Warn("ProxyName [%s], read from frps error: %v", cli.Name, err) continue } ctlRes := &msg.ControlRes{} if err := json.Unmarshal([]byte(buf), &ctlRes); err != nil { log.Warn("ProxyName [%s], parse msg from frps error: %v : %s", cli.Name, err, buf) continue } switch ctlRes.Type { case consts.HeartbeatRes: log.Debug("ProxyName [%s], receive heartbeat response", cli.Name) timer.Reset(time.Duration(client.HeartBeatTimeout) * time.Second) case consts.NoticeUserConn: log.Debug("ProxyName [%s], new user connection", cli.Name) // join local and remote connections, async go cli.StartTunnel(client.ServerAddr, client.ServerPort) default: log.Warn("ProxyName [%s}, unsupport msgType [%d]", cli.Name, ctlRes.Type) } } return nil }
func loginToServer(cli *client.ProxyClient) (c *conn.Conn, err error) { c, err = conn.ConnectServer(client.ServerAddr, client.ServerPort) if err != nil { log.Error("ProxyName [%s], connect to server [%s:%d] error, %v", cli.Name, client.ServerAddr, client.ServerPort, err) return } nowTime := time.Now().Unix() req := &msg.ControlReq{ Type: consts.NewCtlConn, ProxyName: cli.Name, UseEncryption: cli.UseEncryption, UseGzip: cli.UseGzip, PrivilegeMode: cli.PrivilegeMode, ProxyType: cli.Type, Timestamp: nowTime, } if cli.PrivilegeMode { privilegeKey := pcrypto.GetAuthKey(cli.Name + client.PrivilegeToken + fmt.Sprintf("%d", nowTime)) req.RemotePort = cli.RemotePort req.CustomDomains = cli.CustomDomains req.PrivilegeKey = privilegeKey } else { authKey := pcrypto.GetAuthKey(cli.Name + cli.AuthToken + fmt.Sprintf("%d", nowTime)) req.AuthKey = authKey } buf, _ := json.Marshal(req) err = c.Write(string(buf) + "\n") if err != nil { log.Error("ProxyName [%s], write to server error, %v", cli.Name, err) return } res, err := c.ReadLine() if err != nil { log.Error("ProxyName [%s], read from server error, %v", cli.Name, err) return } log.Debug("ProxyName [%s], read [%s]", cli.Name, res) ctlRes := &msg.ControlRes{} if err = json.Unmarshal([]byte(res), &ctlRes); err != nil { log.Error("ProxyName [%s], format server response error, %v", cli.Name, err) return } if ctlRes.Code != 0 { log.Error("ProxyName [%s], start proxy error, %s", cli.Name, ctlRes.Msg) return c, fmt.Errorf("%s", ctlRes.Msg) } log.Debug("ProxyName [%s], connect to server [%s:%d] success!", cli.Name, client.ServerAddr, client.ServerPort) return }
// when frps get one new user connection, send NoticeUserConn message to frpc and accept one new WorkConn later func noticeUserConn(s *server.ProxyServer, msgSendChan chan interface{}) { for { closeFlag := s.WaitUserConn() if closeFlag { log.Debug("ProxyName [%s], goroutine for noticing user conn is closed", s.Name) break } notice := &msg.ControlRes{ Type: consts.NoticeUserConn, } msgSendChan <- notice log.Debug("ProxyName [%s], notice client to add work conn", s.Name) } }
func heartbeatSender(c *conn.Conn, msgSendChan chan interface{}) { heartbeatReq := &msg.ControlReq{ Type: consts.HeartbeatReq, } log.Info("Start to send heartbeat to frps") for { time.Sleep(time.Duration(client.HeartBeatInterval) * time.Second) if c != nil && !c.IsClosed() { log.Debug("Send heartbeat to server") msgSendChan <- heartbeatReq } else { break } } log.Debug("Heartbeat goroutine exit") }
// join two connections and do some operations func JoinMore(c1 *conn.Conn, c2 *conn.Conn, conf config.BaseConf) { var wait sync.WaitGroup encryptPipe := func(from *conn.Conn, to *conn.Conn) { defer from.Close() defer to.Close() defer wait.Done() // we don't care about errors here pipeEncrypt(from.TcpConn, to.TcpConn, conf) } decryptPipe := func(to *conn.Conn, from *conn.Conn) { defer from.Close() defer to.Close() defer wait.Done() // we don't care about errors here pipeDecrypt(to.TcpConn, from.TcpConn, conf) } wait.Add(2) go encryptPipe(c1, c2) go decryptPipe(c2, c1) wait.Wait() log.Debug("ProxyName [%s], One tunnel stopped", conf.Name) return }
// messages from c1 to c2 will be encrypted // and from c2 to c1 will be decrypted func JoinMore(c1 *Conn, c2 *Conn, cryptKey string) { var wait sync.WaitGroup encryptPipe := func(from *Conn, to *Conn, key string) { defer from.Close() defer to.Close() defer wait.Done() // we don't care about errors here PipeEncrypt(from.TcpConn, to.TcpConn, key) } decryptPipe := func(to *Conn, from *Conn, key string) { defer from.Close() defer to.Close() defer wait.Done() // we don't care about errors here PipeDecrypt(to.TcpConn, from.TcpConn, key) } wait.Add(2) go encryptPipe(c1, c2, cryptKey) go decryptPipe(c2, c1, cryptKey) wait.Wait() log.Debug("One tunnel stopped") return }
func ProcessControlConn(l *conn.Listener) { for { c, err := l.Accept() if err != nil { return } log.Debug("Get new connection, %v", c.GetRemoteAddr()) go controlWorker(c) } }
func (p *ProxyClient) StartTunnel(serverAddr string, serverPort int64) (err error) { localConn, err := p.GetLocalConn() if err != nil { return } remoteConn, err := p.GetRemoteConn(serverAddr, serverPort) if err != nil { return } // l means local, r means remote log.Debug("Join two connections, (l[%s] r[%s]) (l[%s] r[%s])", localConn.GetLocalAddr(), localConn.GetRemoteAddr(), remoteConn.GetLocalAddr(), remoteConn.GetRemoteAddr()) go msg.JoinMore(localConn, remoteConn, p.BaseConf) return nil }
// loop for reading messages from frpc after control connection is established func msgReader(s *server.ProxyServer, c *conn.Conn, msgSendChan chan interface{}) error { // for heartbeat var heartbeatTimeout bool = false timer := time.AfterFunc(time.Duration(server.HeartBeatTimeout)*time.Second, func() { heartbeatTimeout = true s.Close() log.Error("ProxyName [%s], client heartbeat timeout", s.Name) }) defer timer.Stop() for { buf, err := c.ReadLine() if err != nil { if err == io.EOF { log.Warn("ProxyName [%s], client is dead!", s.Name) return err } else if c == nil || c.IsClosed() { log.Warn("ProxyName [%s], client connection is closed", s.Name) return err } log.Warn("ProxyName [%s], read error: %v", s.Name, err) continue } cliReq := &msg.ControlReq{} if err := json.Unmarshal([]byte(buf), &cliReq); err != nil { log.Warn("ProxyName [%s], parse msg from frpc error: %v : %s", s.Name, err, buf) continue } switch cliReq.Type { case consts.HeartbeatReq: log.Debug("ProxyName [%s], get heartbeat", s.Name) timer.Reset(time.Duration(server.HeartBeatTimeout) * time.Second) heartbeatRes := &msg.ControlRes{ Type: consts.HeartbeatRes, } msgSendChan <- heartbeatRes default: log.Warn("ProxyName [%s}, unsupport msgType [%d]", s.Name, cliReq.Type) } } return nil }
func (p *ProxyClient) StartTunnel(serverAddr string, serverPort int64) (err error) { localConn, err := p.GetLocalConn() if err != nil { return } remoteConn, err := p.GetRemoteConn(serverAddr, serverPort) if err != nil { return } // l means local, r means remote log.Debug("Join two connections, (l[%s] r[%s]) (l[%s] r[%s])", localConn.GetLocalAddr(), localConn.GetRemoteAddr(), remoteConn.GetLocalAddr(), remoteConn.GetRemoteAddr()) if p.UseEncryption { go conn.JoinMore(localConn, remoteConn, p.AuthToken) } else { go conn.Join(localConn, remoteConn) } return nil }
// connection from every client and server func controlWorker(c *conn.Conn) { // if login message type is NewWorkConn, don't close this connection var closeFlag bool = true var s *server.ProxyServer defer func() { if closeFlag { c.Close() if s != nil { s.Close() } } }() // get login message buf, err := c.ReadLine() if err != nil { log.Warn("Read error, %v", err) return } log.Debug("Get msg from frpc: %s", buf) cliReq := &msg.ControlReq{} if err := json.Unmarshal([]byte(buf), &cliReq); err != nil { log.Warn("Parse msg from frpc error: %v : %s", err, buf) return } // login when type is NewCtlConn or NewWorkConn ret, info := doLogin(cliReq, c) s, ok := server.ProxyServers[cliReq.ProxyName] if !ok { log.Warn("ProxyName [%s] is not exist", cliReq.ProxyName) return } // if login type is NewWorkConn, nothing will be send to frpc if cliReq.Type != consts.NewWorkConn { cliRes := &msg.ControlRes{ Type: consts.NewCtlConnRes, Code: ret, Msg: info, } byteBuf, _ := json.Marshal(cliRes) err = c.Write(string(byteBuf) + "\n") if err != nil { log.Warn("ProxyName [%s], write to client error, proxy exit", s.Name) time.Sleep(1 * time.Second) return } } else { closeFlag = false return } // if login failed, just return if ret > 0 { return } // create a channel for sending messages msgSendChan := make(chan interface{}, 1024) go msgSender(s, c, msgSendChan) go noticeUserConn(s, msgSendChan) // loop for reading control messages from frpc and deal with different types msgReader(s, c, msgSendChan) close(msgSendChan) log.Info("ProxyName [%s], I'm dead!", s.Name) return }
// start listening for user conns func (p *ProxyServer) Start(c *conn.Conn) (err error) { p.CtlConn = c p.Init() if p.Type == "tcp" { l, err := conn.Listen(p.BindAddr, p.ListenPort) if err != nil { return err } p.listeners = append(p.listeners, l) } else if p.Type == "http" { for _, domain := range p.CustomDomains { l, err := VhostMuxer.Listen(domain) if err != nil { return err } p.listeners = append(p.listeners, l) } } p.Lock() p.Status = consts.Working p.Unlock() // start a goroutine for every listener to accept user connection for _, listener := range p.listeners { go func(l Listener) { for { // block // if listener is closed, err returned c, err := l.Accept() if err != nil { log.Info("ProxyName [%s], listener is closed", p.Name) return } log.Debug("ProxyName [%s], get one new user conn [%s]", p.Name, c.GetRemoteAddr()) if p.Status != consts.Working { log.Debug("ProxyName [%s] is not working, new user conn close", p.Name) c.Close() return } // start another goroutine for join two conns from frpc and user go func() { workConn, err := p.getWorkConn() if err != nil { return } userConn := c // msg will transfer to another without modifying // l means local, r means remote log.Debug("Join two connections, (l[%s] r[%s]) (l[%s] r[%s])", workConn.GetLocalAddr(), workConn.GetRemoteAddr(), userConn.GetLocalAddr(), userConn.GetRemoteAddr()) if p.UseEncryption { go conn.JoinMore(userConn, workConn, p.AuthToken) } else { go conn.Join(userConn, workConn) } }() } }(listener) } return nil }
// if success, ret equals 0, otherwise greater than 0 func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) { ret = 1 if req.PrivilegeMode && !server.PrivilegeMode { info = fmt.Sprintf("ProxyName [%s], PrivilegeMode is disabled in frps", req.ProxyName) log.Warn("info") return } var ( s *server.ProxyServer ok bool ) s, ok = server.ProxyServers[req.ProxyName] if req.PrivilegeMode && req.Type == consts.NewCtlConn { log.Debug("ProxyName [%s], doLogin and privilege mode is enabled", req.ProxyName) } else { if !ok { info = fmt.Sprintf("ProxyName [%s] is not exist", req.ProxyName) log.Warn(info) return } } // check authKey or privilegeKey nowTime := time.Now().Unix() if req.PrivilegeMode { privilegeKey := pcrypto.GetAuthKey(req.ProxyName + server.PrivilegeToken + fmt.Sprintf("%d", req.Timestamp)) // privilegeKey avaiable in 15 minutes if nowTime-req.Timestamp > 15*60 { info = fmt.Sprintf("ProxyName [%s], privilege mode authorization timeout", req.ProxyName) log.Warn(info) return } else if req.PrivilegeKey != privilegeKey { info = fmt.Sprintf("ProxyName [%s], privilege mode authorization failed", req.ProxyName) log.Warn(info) return } } else { authKey := pcrypto.GetAuthKey(req.ProxyName + s.AuthToken + fmt.Sprintf("%d", req.Timestamp)) // authKey avaiable in 15 minutes if nowTime-req.Timestamp > 15*60 { info = fmt.Sprintf("ProxyName [%s], authorization timeout", req.ProxyName) log.Warn(info) return } else if req.AuthKey != authKey { info = fmt.Sprintf("ProxyName [%s], authorization failed", req.ProxyName) log.Warn(info) return } } // control conn if req.Type == consts.NewCtlConn { if req.PrivilegeMode { s = server.NewProxyServerFromCtlMsg(req) err := server.CreateProxy(s) if err != nil { info = fmt.Sprintf("ProxyName [%s], %v", req.ProxyName, err) log.Warn(info) return } } if s.Status == consts.Working { info = fmt.Sprintf("ProxyName [%s], already in use", req.ProxyName) log.Warn(info) return } // check if vhost_port is set if s.Type == "http" && server.VhostHttpMuxer == nil { info = fmt.Sprintf("ProxyName [%s], type [http] not support when vhost_http_port is not set", req.ProxyName) log.Warn(info) return } if s.Type == "https" && server.VhostHttpsMuxer == nil { info = fmt.Sprintf("ProxyName [%s], type [https] not support when vhost_https_port is not set", req.ProxyName) log.Warn(info) return } // set infomations from frpc s.UseEncryption = req.UseEncryption s.UseGzip = req.UseGzip // start proxy and listen for user connections, no block err := s.Start(c) if err != nil { info = fmt.Sprintf("ProxyName [%s], start proxy error: %v", req.ProxyName, err) log.Warn(info) return } log.Info("ProxyName [%s], start proxy success", req.ProxyName) if req.PrivilegeMode { log.Info("ProxyName [%s], created by PrivilegeMode", req.ProxyName) } } else if req.Type == consts.NewWorkConn { // work conn if s.Status != consts.Working { log.Warn("ProxyName [%s], is not working when it gets one new work connnection", req.ProxyName) return } // the connection will close after join over s.RegisterNewWorkConn(c) } else { info = fmt.Sprintf("Unsupport login message type [%d]", req.Type) log.Warn("Unsupport login message type [%d]", req.Type) return } ret = 0 return }