// 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 }
// decrypt msg from reader, then write into writer func pipeDecrypt(r net.Conn, w net.Conn, conf config.BaseConf) (err error) { laes := new(pcrypto.Pcrypto) key := conf.AuthToken if conf.PrivilegeMode { key = conf.PrivilegeToken } if err := laes.Init([]byte(key)); err != nil { log.Warn("ProxyName [%s], Pcrypto Init error: %v", conf.Name, err) return fmt.Errorf("Pcrypto Init error: %v", err) } buf := make([]byte, 5*1024+4) var left, res []byte var cnt int nreader := bufio.NewReader(r) for { // there may be more than 1 package in variable // and we read more bytes if unpkgMsg returns an error var newBuf []byte if cnt < 0 { n, err := nreader.Read(buf) if err != nil { return err } newBuf = append(left, buf[0:n]...) } else { newBuf = left } cnt, res, left = unpkgMsg(newBuf) if cnt < 0 { continue } // aes if conf.UseEncryption { res, err = laes.Decrypt(res) if err != nil { log.Warn("ProxyName [%s], decrypt error, %v", conf.Name, err) return fmt.Errorf("Decrypt error: %v", err) } } // gzip if conf.UseGzip { res, err = laes.Decompression(res) if err != nil { log.Warn("ProxyName [%s], decompression error, %v", conf.Name, err) return fmt.Errorf("Decompression error: %v", err) } } _, err = w.Write(res) if err != nil { return err } } return nil }
// when frps get one user connection, we get one work connection from the pool and return it // if no workConn available in the pool, send message to frpc to get one or more // and wait until it is available // return an error if wait timeout func (p *ProxyServer) getWorkConn() (workConn *conn.Conn, err error) { var ok bool // get a work connection from the pool select { case workConn, ok = <-p.workConnChan: if !ok { err = fmt.Errorf("ProxyName [%s], no work connections available, control is closing", p.Name) return } default: // no work connections available in the poll, send message to frpc to get one p.ctlMsgChan <- 1 select { case workConn, ok = <-p.workConnChan: if !ok { err = fmt.Errorf("ProxyName [%s], no work connections available, control is closing", p.Name) return } case <-time.After(time.Duration(UserConnTimeout) * time.Second): log.Warn("ProxyName [%s], timeout trying to get work connection", p.Name) err = fmt.Errorf("ProxyName [%s], timeout trying to get work connection", p.Name) return } } return }
// recvive msg from reader, then encrypt msg into writer func pipeEncrypt(r net.Conn, w net.Conn, conf config.BaseConf) (err error) { laes := new(pcrypto.Pcrypto) key := conf.AuthToken if conf.PrivilegeMode { key = conf.PrivilegeToken } if err := laes.Init([]byte(key)); err != nil { log.Warn("ProxyName [%s], Pcrypto Init error: %v", conf.Name, err) return fmt.Errorf("Pcrypto Init error: %v", err) } nreader := bufio.NewReader(r) buf := make([]byte, 5*1024) for { n, err := nreader.Read(buf) if err != nil { return err } res := buf[0:n] // gzip if conf.UseGzip { res, err = laes.Compression(res) if err != nil { log.Warn("ProxyName [%s], compression error: %v", conf.Name, err) return fmt.Errorf("Compression error: %v", err) } } // aes if conf.UseEncryption { res, err = laes.Encrypt(res) if err != nil { log.Warn("ProxyName [%s], encrypt error: %v", conf.Name, err) return fmt.Errorf("Encrypt error: %v", err) } } res = pkgMsg(res) _, err = w.Write(res) if err != nil { return err } } 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 // check if proxy name exist s, ok := server.ProxyServers[req.ProxyName] if !ok { info = fmt.Sprintf("ProxyName [%s] is not exist", req.ProxyName) log.Warn(info) return } // check authKey nowTime := time.Now().Unix() 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 s.Status == consts.Working { info = fmt.Sprintf("ProxyName [%s], already in use", req.ProxyName) log.Warn(info) return } // set infomations from frpc s.UseEncryption = req.UseEncryption // 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) } 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 }
// 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 }
// loop for sending messages from channel to frpc func msgSender(s *server.ProxyServer, c *conn.Conn, msgSendChan chan interface{}) { for { msg, ok := <-msgSendChan if !ok { break } buf, _ := json.Marshal(msg) err := c.Write(string(buf) + "\n") if err != nil { log.Warn("ProxyName [%s], write to client error, proxy exit", s.Name) s.Close() break } } }
// will block until connection close func Join(c1 *conn.Conn, c2 *conn.Conn) { var wait sync.WaitGroup pipe := func(to *conn.Conn, from *conn.Conn) { defer to.Close() defer from.Close() defer wait.Done() var err error _, err = io.Copy(to.TcpConn, from.TcpConn) if err != nil { log.Warn("join connections error, %v", err) } } wait.Add(2) go pipe(c1, c2) go pipe(c2, c1) wait.Wait() return }
// 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 }
// 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 }
func main() { // the configures parsed from file will be replaced by those from command line if exist args, err := docopt.Parse(usage, nil, true, version.Full(), false) if args["-c"] != nil { configFile = args["-c"].(string) } err = client.LoadConf(configFile) if err != nil { fmt.Println(err) os.Exit(-1) } if args["-L"] != nil { if args["-L"].(string) == "console" { client.LogWay = "console" } else { client.LogWay = "file" client.LogFile = args["-L"].(string) } } if args["--log-level"] != nil { client.LogLevel = args["--log-level"].(string) } if args["--server-addr"] != nil { addr := strings.Split(args["--server-addr"].(string), ":") if len(addr) != 2 { fmt.Println("--server-addr format error: example 0.0.0.0:7000") os.Exit(1) } serverPort, err := strconv.ParseInt(addr[1], 10, 64) if err != nil { fmt.Println("--server-addr format error, example 0.0.0.0:7000") os.Exit(1) } client.ServerAddr = addr[0] client.ServerPort = serverPort } if args["-v"] != nil { if args["-v"].(bool) { fmt.Println(version.Full()) os.Exit(0) } } log.InitLog(client.LogWay, client.LogFile, client.LogLevel, client.LogMaxDays) // wait until all control goroutine exit var wait sync.WaitGroup wait.Add(len(client.ProxyClients)) for _, client := range client.ProxyClients { go ControlProcess(client, &wait) } log.Info("Start frpc success") wait.Wait() log.Warn("All proxy exit!") }