func apiReload(c *gin.Context) { res := &GeneralResponse{} defer func() { buf, _ := json.Marshal(res) log.Info("Http response [/api/reload]: %s", string(buf)) }() log.Info("Http request: [/api/reload]") err := ReloadConf(ConfigFile) if err != nil { res.Code = 2 res.Msg = fmt.Sprintf("%v", err) log.Error("frps reload error: %v", err) } c.JSON(200, res) }
// 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(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 }
// the function can only reload proxy configures // common section won't be changed func ReloadConf(confFile string) (err error) { loadProxyServers, err := loadProxyConf(confFile) if err != nil { return err } ProxyServersMutex.Lock() for name, proxyServer := range loadProxyServers { oldProxyServer, ok := ProxyServers[name] if ok { if !oldProxyServer.Compare(proxyServer) { oldProxyServer.Close() proxyServer.Init() ProxyServers[name] = proxyServer log.Info("ProxyName [%s] configure change, restart", name) } } else { proxyServer.Init() ProxyServers[name] = proxyServer log.Info("ProxyName [%s] is new, init it", name) } } // proxies created by PrivilegeMode won't be deleted for name, oldProxyServer := range ProxyServers { _, ok := loadProxyServers[name] if !ok { if !oldProxyServer.PrivilegeMode { oldProxyServer.Close() delete(ProxyServers, name) log.Info("ProxyName [%s] deleted, close it", name) } else { log.Info("ProxyName [%s] created by PrivilegeMode, won't be closed", name) } } } ProxyServersMutex.Unlock() return nil }
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") }
// 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 }
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 { server.ConfigFile = args["-c"].(string) } err = server.LoadConf(server.ConfigFile) if err != nil { fmt.Println(err) os.Exit(-1) } // reload check if args["--reload"] != nil { if args["--reload"].(bool) { resp, err := http.Get("http://" + server.BindAddr + ":" + fmt.Sprintf("%d", server.DashboardPort) + "/api/reload") if err != nil { fmt.Printf("frps reload error: %v\n", err) os.Exit(1) } else { defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Printf("frps reload error: %v\n", err) os.Exit(1) } res := &server.GeneralResponse{} err = json.Unmarshal(body, &res) if err != nil { fmt.Printf("http response error: %v\n", err) os.Exit(1) } else if res.Code != 0 { fmt.Printf("reload error: %s\n", res.Msg) os.Exit(1) } fmt.Printf("reload success\n") os.Exit(0) } } } if args["-L"] != nil { if args["-L"].(string) == "console" { server.LogWay = "console" } else { server.LogWay = "file" server.LogFile = args["-L"].(string) } } if args["--log-level"] != nil { server.LogLevel = args["--log-level"].(string) } if args["--addr"] != nil { addr := strings.Split(args["--addr"].(string), ":") if len(addr) != 2 { fmt.Println("--addr format error: example 0.0.0.0:7000") os.Exit(1) } bindPort, err := strconv.ParseInt(addr[1], 10, 64) if err != nil { fmt.Println("--addr format error, example 0.0.0.0:7000") os.Exit(1) } server.BindAddr = addr[0] server.BindPort = bindPort } if args["-v"] != nil { if args["-v"].(bool) { fmt.Println(version.Full()) os.Exit(0) } } log.InitLog(server.LogWay, server.LogFile, server.LogLevel, server.LogMaxDays) l, err := conn.Listen(server.BindAddr, server.BindPort) if err != nil { log.Error("Create server listener error, %v", err) os.Exit(1) } // create vhost if VhostHttpPort != 0 if server.VhostHttpPort != 0 { vhostListener, err := conn.Listen(server.BindAddr, server.VhostHttpPort) if err != nil { log.Error("Create vhost http listener error, %v", err) os.Exit(1) } server.VhostMuxer, err = vhost.NewHttpMuxer(vhostListener, 30*time.Second) if err != nil { log.Error("Create vhost httpMuxer error, %v", err) } } // create dashboard web server if DashboardPort is set, so it won't be 0 if server.DashboardPort != 0 { err := server.RunDashboardServer(server.BindAddr, server.DashboardPort) if err != nil { log.Error("Create dashboard web server error, %v", err) os.Exit(1) } } log.Info("Start frps success") ProcessControlConn(l) }
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!") }