func autoUpdate(s mvc.State, token string) { up, err := update.New().VerifySignatureWithPEM([]byte(publicKey)) if err != nil { log.Error("Failed to create update with signature: %v", err) return } update := func() (tryAgain bool) { log.Info("Checking for update") params := check.Params{ AppId: appId, AppVersion: version.MajorMinor(), UserId: token, } result, err := params.CheckForUpdate(updateEndpoint, up) if err == check.NoUpdateAvailable { log.Info("No update available") return true } else if err != nil { log.Error("Error while checking for update: %v", err) return true } if result.Initiative == check.INITIATIVE_AUTO { if err := up.CanUpdate(); err != nil { log.Error("Can't update: insufficient permissions: %v", err) // tell the user to update manually s.SetUpdateStatus(mvc.UpdateAvailable) } else { applyUpdate(s, result) } } else if result.Initiative == check.INITIATIVE_MANUAL { // this is the way the server tells us to update manually log.Info("Server wants us to update manually") s.SetUpdateStatus(mvc.UpdateAvailable) } else { log.Info("Update available, but ignoring") } // stop trying after a single download attempt // XXX: improve this so the we can: // 1. safely update multiple times // 2. only retry after temporary errors return false } // try to update immediately and then at a set interval for { if tryAgain := update(); !tryAgain { break } time.Sleep(updateCheckInterval) } }
/** * Establishes and manages a tunnel proxy connection with the server */ func proxy(proxyAddr string, s *State, ctl *ui.Controller) { start := time.Now() remoteConn, err := conn.Dial(proxyAddr, "pxy", tlsConfig) if err != nil { // XXX: What is the proper response here? // display something to the user? // retry? // reset control connection? log.Error("Failed to establish proxy connection: %v", err) return } defer remoteConn.Close() err = msg.WriteMsg(remoteConn, &msg.RegProxyMsg{Url: s.publicUrl}) if err != nil { // XXX: What is the proper response here? // display something to the user? // retry? // reset control connection? log.Error("Failed to write RegProxyMsg: %v", err) return } localConn, err := conn.Dial(s.opts.localaddr, "prv", nil) if err != nil { remoteConn.Warn("Failed to open private leg %s: %v", s.opts.localaddr, err) badGatewayBody := fmt.Sprintf(BadGateway, s.publicUrl, s.opts.localaddr, s.opts.localaddr) remoteConn.Write([]byte(fmt.Sprintf(`HTTP/1.0 502 Bad Gateway Content-Type: text/html Content-Length: %d %s`, len(badGatewayBody), badGatewayBody))) return } defer localConn.Close() m := s.metrics m.proxySetupTimer.Update(time.Since(start)) m.connMeter.Mark(1) ctl.Update(s) m.connTimer.Time(func() { localConn := s.protocol.WrapConn(localConn) bytesIn, bytesOut := conn.Join(localConn, remoteConn) m.bytesIn.Update(bytesIn) m.bytesOut.Update(bytesOut) m.bytesInCount.Inc(bytesIn) m.bytesOutCount.Inc(bytesOut) }) ctl.Update(s) }
func Listen(addr, typ string, tlsCfg *tls.Config) (l *Listener, err error) { // listen for incoming connections listener, err := net.Listen("tcp", addr) if err != nil { return } l = &Listener{ Addr: listener.Addr(), Conns: make(chan Conn), } go func() { for { rawConn, err := listener.Accept() if err != nil { log.Error("Failed to accept new TCP connection of type %s: %v", typ, err) continue } c := wrapConn(rawConn, typ) if tlsCfg != nil { c.Conn = tls.Server(c.Conn, tlsCfg) } c.Info("New connection from %v", c.RemoteAddr()) l.Conns <- c } }() return }
func applyUpdate(s mvc.State, result *check.Result) { err, errRecover := result.Update() if err == nil { log.Info("Update ready!") s.SetUpdateStatus(mvc.UpdateReady) return } log.Error("Error while updating ngrok: %v", err) if errRecover != nil { log.Error("Error while recovering from failed ngrok update, your binary may be missing: %v", errRecover.Error()) } // tell the user to update manually s.SetUpdateStatus(mvc.UpdateAvailable) }
func (whv *WebHttpView) updateHttp() { // open channels for incoming http state changes // and broadbasts txnUpdates := whv.httpProto.Txns.Reg() for txn := range txnUpdates { // XXX: it's not safe for proto.Http and this code // to be accessing txn and txn.(req/resp) without synchronization htxn := txn.(*proto.HttpTxn) // we haven't processed this transaction yet if we haven't set the // user data if htxn.UserData == nil { id, err := util.RandId(8) if err != nil { log.Error("Failed to generate txn identifier for web storage: %v", err) continue } rawReq, err := httputil.DumpRequest(htxn.Req.Request, true) if err != nil { log.Error("Failed to dump request: %v", err) continue } body := makeBody(htxn.Req.Header, htxn.Req.BodyBytes) whtxn := &SerializedTxn{ Id: id, HttpTxn: htxn, Req: SerializedRequest{ MethodPath: htxn.Req.Method + " " + htxn.Req.URL.Path, Raw: base64.StdEncoding.EncodeToString(rawReq), Params: htxn.Req.URL.Query(), Header: htxn.Req.Header, Body: body, Binary: !utf8.Valid(rawReq), }, Start: htxn.Start.Unix(), } htxn.UserData = whtxn // XXX: unsafe map access from multiple go routines whv.idToTxn[whtxn.Id] = whtxn // XXX: use return value to delete from map so we don't leak memory whv.HttpRequests.Add(whtxn) } else { rawResp, err := httputil.DumpResponse(htxn.Resp.Response, true) if err != nil { log.Error("Failed to dump response: %v", err) continue } txn := htxn.UserData.(*SerializedTxn) body := makeBody(htxn.Resp.Header, htxn.Resp.BodyBytes) txn.Duration = htxn.Duration.Nanoseconds() txn.Resp = SerializedResponse{ Status: htxn.Resp.Status, Raw: base64.StdEncoding.EncodeToString(rawResp), Header: htxn.Resp.Header, Body: body, Binary: !utf8.Valid(rawResp), } payload, err := json.Marshal(txn) if err != nil { log.Error("Failed to serialized txn payload for websocket: %v", err) } whv.webview.wsMessages.In() <- payload } } }
// Establishes and manages a tunnel proxy connection with the server func (c *ClientModel) proxy() { var ( remoteConn conn.Conn err error ) if c.proxyUrl == "" { remoteConn, err = conn.Dial(c.serverAddr, "pxy", c.tlsConfig) } else { remoteConn, err = conn.DialHttpProxy(c.proxyUrl, c.serverAddr, "pxy", c.tlsConfig) } if err != nil { log.Error("Failed to establish proxy connection: %v", err) return } defer remoteConn.Close() err = msg.WriteMsg(remoteConn, &msg.RegProxy{ClientId: c.id}) if err != nil { remoteConn.Error("Failed to write RegProxy: %v", err) return } // wait for the server to ack our register var startPxy msg.StartProxy if err = msg.ReadMsgInto(remoteConn, &startPxy); err != nil { remoteConn.Error("Server failed to write StartProxy: %v", err) return } tunnel, ok := c.tunnels[startPxy.Url] if !ok { remoteConn.Error("Couldn't find tunnel for proxy: %s", startPxy.Url) return } // start up the private connection start := time.Now() localConn, err := conn.Dial(tunnel.LocalAddr, "prv", nil) if err != nil { remoteConn.Warn("Failed to open private leg %s: %v", tunnel.LocalAddr, err) if tunnel.Protocol.GetName() == "http" { // try to be helpful when you're in HTTP mode and a human might see the output badGatewayBody := fmt.Sprintf(BadGateway, tunnel.PublicUrl, tunnel.LocalAddr, tunnel.LocalAddr) remoteConn.Write([]byte(fmt.Sprintf(`HTTP/1.0 502 Bad Gateway Content-Type: text/html Content-Length: %d %s`, len(badGatewayBody), badGatewayBody))) } return } defer localConn.Close() m := c.metrics m.proxySetupTimer.Update(time.Since(start)) m.connMeter.Mark(1) c.update() m.connTimer.Time(func() { localConn := tunnel.Protocol.WrapConn(localConn, mvc.ConnectionContext{Tunnel: tunnel, ClientAddr: startPxy.ClientAddr}) bytesIn, bytesOut := conn.Join(localConn, remoteConn) m.bytesIn.Update(bytesIn) m.bytesOut.Update(bytesOut) m.bytesInCount.Inc(bytesIn) m.bytesOutCount.Inc(bytesOut) }) c.update() }
// Establishes and manages a tunnel control connection with the server func (c *ClientModel) control() { defer func() { if r := recover(); r != nil { log.Error("control recovering from failure %v", r) } }() // establish control channel var ( ctlConn conn.Conn err error ) if c.proxyUrl == "" { // simple non-proxied case, just connect to the server ctlConn, err = conn.Dial(c.serverAddr, "ctl", c.tlsConfig) } else { ctlConn, err = conn.DialHttpProxy(c.proxyUrl, c.serverAddr, "ctl", c.tlsConfig) } if err != nil { panic(err) } defer ctlConn.Close() // authenticate with the server auth := &msg.Auth{ ClientId: c.id, OS: runtime.GOOS, Arch: runtime.GOARCH, Version: version.Proto, MmVersion: version.MajorMinor(), User: c.authToken, } if err = msg.WriteMsg(ctlConn, auth); err != nil { panic(err) } // wait for the server to authenticate us var authResp msg.AuthResp if err = msg.ReadMsgInto(ctlConn, &authResp); err != nil { panic(err) } if authResp.Error != "" { emsg := fmt.Sprintf("Failed to authenticate to server: %s", authResp.Error) c.ctl.Shutdown(emsg) return } c.id = authResp.ClientId c.serverVersion = authResp.MmVersion c.Info("Authenticated with server, client id: %v", c.id) c.update() if err = SaveAuthToken(c.configPath, c.authToken); err != nil { c.Error("Failed to save auth token: %v", err) } // request tunnels reqIdToTunnelConfig := make(map[string]*TunnelConfiguration) for _, config := range c.tunnelConfig { // create the protocol list to ask for var protocols []string for proto, _ := range config.Protocols { protocols = append(protocols, proto) } reqTunnel := &msg.ReqTunnel{ ReqId: util.RandId(8), Protocol: strings.Join(protocols, "+"), Hostname: config.Hostname, Subdomain: config.Subdomain, HttpAuth: config.HttpAuth, RemotePort: config.RemotePort, } // send the tunnel request if err = msg.WriteMsg(ctlConn, reqTunnel); err != nil { panic(err) } // save request id association so we know which local address // to proxy to later reqIdToTunnelConfig[reqTunnel.ReqId] = config } // start the heartbeat lastPong := time.Now().UnixNano() c.ctl.Go(func() { c.heartbeat(&lastPong, ctlConn) }) // main control loop for { var rawMsg msg.Message if rawMsg, err = msg.ReadMsg(ctlConn); err != nil { panic(err) } switch m := rawMsg.(type) { case *msg.ReqProxy: c.ctl.Go(c.proxy) case *msg.Pong: atomic.StoreInt64(&lastPong, time.Now().UnixNano()) case *msg.NewTunnel: if m.Error != "" { emsg := fmt.Sprintf("Server failed to allocate tunnel: %s", m.Error) c.Error(emsg) c.ctl.Shutdown(emsg) continue } tunnel := mvc.Tunnel{ PublicUrl: m.Url, LocalAddr: reqIdToTunnelConfig[m.ReqId].Protocols[m.Protocol], Protocol: c.protoMap[m.Protocol], } c.tunnels[tunnel.PublicUrl] = tunnel c.connStatus = mvc.ConnOnline c.Info("Tunnel established at %v", tunnel.PublicUrl) c.update() default: ctlConn.Warn("Ignoring unknown control message %v ", m) } } }
/** * Establishes and manages a tunnel control connection with the server */ func control(s *State, ctl *ui.Controller) { defer func() { if r := recover(); r != nil { log.Error("control recovering from failure %v", r) } }() // establish control channel conn, err := conn.Dial(s.opts.server, "ctl", tlsConfig) if err != nil { panic(err) } defer conn.Close() // register with the server err = msg.WriteMsg(conn, &msg.RegMsg{ Protocol: s.opts.protocol, OS: runtime.GOOS, HttpAuth: s.opts.httpAuth, Hostname: s.opts.hostname, Subdomain: s.opts.subdomain, ClientId: s.id, Version: version.Proto, MmVersion: version.MajorMinor(), User: s.opts.authtoken, }) if err != nil { panic(err) } // wait for the server to ack our register var regAck msg.RegAckMsg if err = msg.ReadMsgInto(conn, ®Ack); err != nil { panic(err) } if regAck.Error != "" { emsg := fmt.Sprintf("Server failed to allocate tunnel: %s", regAck.Error) ctl.Cmds <- ui.CmdQuit{Message: emsg} return } // update UI state s.publicUrl = regAck.Url conn.Info("Tunnel established at %v", s.GetPublicUrl()) s.status = "online" s.serverVersion = regAck.MmVersion ctl.Update(s) SaveAuthToken(s.opts.authtoken) // start the heartbeat lastPong := time.Now().UnixNano() go heartbeat(&lastPong, conn) // main control loop for { var m msg.Message if m, err = msg.ReadMsg(conn); err != nil { panic(err) } switch m.(type) { case *msg.ReqProxyMsg: go proxy(regAck.ProxyAddr, s, ctl) case *msg.PongMsg: atomic.StoreInt64(&lastPong, time.Now().UnixNano()) } } }
func autoUpdate(s mvc.State, token string) { tryAgain := true params := make(url.Values) params.Add("version", version.MajorMinor()) params.Add("os", runtime.GOOS) params.Add("arch", runtime.GOARCH) params.Add("user", token) updateUrl := updateEndpoint + "?" + params.Encode() checkUrl := checkEndpoint + "?" + params.Encode() update := func() { log.Info("Checking for update") available, err := update.NewDownload(checkUrl).Check() if err != nil { log.Error("Error while checking for update: %v", err) return } if !available { log.Info("No update available") return } // stop trying after a single download attempt // XXX: improve this so the we can: // 1. safely update multiple times // 2. only retry after a network connection failure tryAgain = false download := update.NewDownload(updateUrl) downloadComplete := make(chan int) go progressWatcher(s, download.Progress, downloadComplete) log.Info("Trying to update . . .") err, errRecover := download.GetAndUpdate() <-downloadComplete if err != nil { // log error to console log.Error("Error while updating ngrok: %v", err) if errRecover != nil { log.Error("Error while recovering from failed ngrok update, your binary may be missing: %v", errRecover.Error()) params.Add("errorRecover", errRecover.Error()) } // log error to ngrok.com's servers for debugging purposes params.Add("error", err.Error()) resp, reportErr := http.PostForm("https://dl.ngrok.com/update/error", params) if err != nil { log.Error("Error while reporting update error: %v, %v", err, reportErr) } resp.Body.Close() // tell the user to update manually s.SetUpdateStatus(mvc.UpdateAvailable) } else { if !download.Available { // this is the way the server tells us to update manually log.Info("Server wants us to update manually") s.SetUpdateStatus(mvc.UpdateAvailable) } else { // tell the user the update is ready log.Info("Update ready!") s.SetUpdateStatus(mvc.UpdateReady) } } return } // try to update immediately and then at a set interval update() for _ = range time.Tick(updateCheckInterval) { if !tryAgain { break } update() } }
func autoUpdate(s *State, ctl *ui.Controller, token string) { update := func() (updateSuccessful bool) { params := make(url.Values) params.Add("version", version.MajorMinor()) params.Add("os", runtime.GOOS) params.Add("arch", runtime.GOARCH) download := update.NewDownload() downloadComplete := make(chan int) go func() { for { select { case progress, ok := <-download.Progress: if !ok { close(downloadComplete) return } else if progress == 100 { s.update = ui.UpdateInstalling ctl.Update(s) close(downloadComplete) return } else { if progress%25 == 0 { log.Info("Downloading update %d%% complete", progress) } s.update = ui.UpdateStatus(progress) ctl.Update(s) } } } }() log.Info("Checking for update") err := download.UpdateFromUrl(updateEndpoint + "?" + params.Encode()) <-downloadComplete if err != nil { log.Error("Error while updating ngrok: %v", err) if download.Available { s.update = ui.UpdateError } else { s.update = ui.UpdateNone } // record the error to ngrok.com's servers for debugging purposes params.Add("error", err.Error()) params.Add("user", token) resp, err := http.PostForm("https://dl.ngrok.com/update/error", params) if err != nil { log.Error("Error while reporting update error") } resp.Body.Close() } else { if download.Available { log.Info("Marked update ready") s.update = ui.UpdateReady updateSuccessful = true } else { log.Info("No update available at this time") s.update = ui.UpdateNone } } ctl.Update(s) return } // try to update immediately and then at a set interval update() for _ = range time.Tick(updateCheckInterval) { if update() { // stop trying to update if the update function is successful // XXX: improve this by trying to download versions newer than // the last one we downloaded return } } }