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) } }
func NewWebView(ctl *ui.Controller, state ui.State, port int) *WebView { w := &WebView{} switch p := state.GetProtocol().(type) { case *proto.Http: NewWebHttpView(ctl, p) } http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/http/in", 302) }) http.HandleFunc("/static/", func(w http.ResponseWriter, r *http.Request) { parts := strings.Split(r.URL.Path, "/") name := parts[len(parts)-1] fn, ok := static.AssetMap[name] if !ok { http.NotFound(w, r) return } w.Write(fn()) }) log.Info("Serving web interface on localhost:%d", port) go http.ListenAndServe(fmt.Sprintf(":%d", port), nil) return w }
func init() { metrics.windowsCounter = gometrics.NewCounter() metrics.linuxCounter = gometrics.NewCounter() metrics.osxCounter = gometrics.NewCounter() metrics.otherCounter = gometrics.NewCounter() /* metrics.bytesInCount = gometrics.NewCounter() metrics.bytesOutCount = gometrics.NewCounter() */ /* metrics.tunnelGauge = gometrics.NewGauge() metrics.tcpTunnelGauge = gometrics.NewGauge() metrics.requestGauge = gometrics.NewGauge() */ metrics.tunnelMeter = gometrics.NewMeter() metrics.tcpTunnelMeter = gometrics.NewMeter() metrics.requestMeter = gometrics.NewMeter() metrics.lostHeartbeatMeter = gometrics.NewMeter() metrics.requestTimer = gometrics.NewTimer() go func() { time.Sleep(reportInterval) log.Info("Server metrics: %s", MetricsJson()) }() }
// Listen for incoming control and proxy connections // We listen for incoming control and proxy connections on the same port // for ease of deployment. The hope is that by running on port 443, using // TLS and running all connections over the same port, we can bust through // restrictive firewalls. func tunnelListener(addr string, tlsConfig *tls.Config) { // listen for incoming connections listener, err := conn.Listen(addr, "tun", tlsConfig) if err != nil { panic(err) } log.Info("Listening for control and proxy connections on %s", listener.Addr.String()) for c := range listener.Conns { go func(tunnelConn conn.Conn) { tunnelConn.SetReadDeadline(time.Now().Add(connReadTimeout)) var rawMsg msg.Message if rawMsg, err = msg.ReadMsg(tunnelConn); err != nil { tunnelConn.Error("Failed to read message: %v", err) tunnelConn.Close() return } // don't timeout after the initial read, tunnel heartbeating will kill // dead connections tunnelConn.SetReadDeadline(time.Time{}) switch m := rawMsg.(type) { case *msg.Auth: NewControl(tunnelConn, m) case *msg.RegProxy: NewProxy(tunnelConn, m) default: tunnelConn.Close() } }(c) } }
// Listen for incoming control and proxy connections // We listen for incoming control and proxy connections on the same port // for ease of deployment. The hope is that by running on port 443, using // TLS and running all connections over the same port, we can bust through // restrictive firewalls. func tunnelListener(addr string, tlsConfig *tls.Config, secret string) { // listen for incoming connections listener, err := conn.Listen(addr, "tun", tlsConfig) if err != nil { panic(err) } log.Info("Listening for control and proxy connections on %s", listener.Addr.String()) if secret != "" { log.Info("Clients should use '%s' as secret", secret) } for c := range listener.Conns { go func(tunnelConn conn.Conn) { // don't crash on panics defer func() { if r := recover(); r != nil { tunnelConn.Info("tunnelListener failed with error %v: %s", r, debug.Stack()) } }() tunnelConn.SetReadDeadline(time.Now().Add(connReadTimeout)) var rawMsg msg.Message if rawMsg, err = msg.ReadMsg(tunnelConn); err != nil { tunnelConn.Warn("Failed to read message: %v", err) tunnelConn.Close() return } // don't timeout after the initial read, tunnel heartbeating will kill // dead connections tunnelConn.SetReadDeadline(time.Time{}) switch m := rawMsg.(type) { case *msg.Auth: NewControl(tunnelConn, m, secret) case *msg.RegProxy: NewProxy(tunnelConn, m) default: tunnelConn.Close() } }(c) } }
/** * Listens for new http connections from the public internet */ func httpListener(addr *net.TCPAddr) { // bind/listen for incoming connections listener, err := conn.Listen(addr, "pub", nil) if err != nil { panic(err) } log.Info("Listening for public http connections on %v", listener.Port) for conn := range listener.Conns { go httpHandler(conn) } }
/** * Listens for new control connections from tunnel clients */ func controlListener(addr *net.TCPAddr, domain string) { // listen for incoming connections listener, err := conn.Listen(addr, "ctl", tls.Config) if err != nil { panic(err) } log.Info("Listening for control connections on %d", listener.Port) for c := range listener.Conns { NewControl(c) } }
func NewWebView(ctl *ui.Controller, state ui.State, port int) *WebView { v := &WebView{ wsMessages: util.NewBroadcast(), } switch p := state.GetProtocol().(type) { case *proto.Http: NewWebHttpView(v, ctl, p) } http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/http/in", 302) }) http.HandleFunc("/_ws", func(w http.ResponseWriter, r *http.Request) { conn, err := websocket.Upgrade(w, r.Header, nil, 1024, 1024) if err != nil { http.Error(w, "Failed websocket upgrade", 400) log.Warn("Failed websocket upgrade: %v", err) return } msgs := v.wsMessages.Reg() defer v.wsMessages.UnReg(msgs) for m := range msgs { err := conn.WriteMessage(websocket.OpText, m.([]byte)) if err != nil { // connection is closed break } } }) http.HandleFunc("/static/", func(w http.ResponseWriter, r *http.Request) { parts := strings.Split(r.URL.Path, "/") name := parts[len(parts)-1] fn, ok := static.AssetMap[name] if !ok { http.NotFound(w, r) return } w.Write(fn()) }) log.Info("Serving web interface on localhost:%d", port) go http.ListenAndServe(fmt.Sprintf(":%d", port), nil) return v }
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 progressWatcher(s mvc.State, progress chan int, complete chan int) { for { select { case pct, ok := <-progress: if !ok { close(complete) return } else if pct == 100 { s.SetUpdateStatus(mvc.UpdateInstalling) close(complete) return } else { if pct%25 == 0 { log.Info("Downloading update %d%% complete", pct) } s.SetUpdateStatus(mvc.UpdateStatus(pct)) } } } }
// Listens for new http(s) connections from the public internet func startHttpListener(addr string, tlsCfg *tls.Config) (listener *conn.Listener) { // bind/listen for incoming connections var err error if listener, err = conn.Listen(addr, "pub", tlsCfg); err != nil { panic(err) } proto := "http" if tlsCfg != nil { proto = "https" } log.Info("Listening for public %s connections on %v", proto, listener.Addr.String()) go func() { for conn := range listener.Conns { go httpHandler(conn, proto) } }() return }
func reconnectingControl(s *State, ctl *ui.Controller) { // how long we should wait before we reconnect maxWait := 30 * time.Second wait := 1 * time.Second for { control(s, ctl) if s.status == "online" { wait = 1 * time.Second } log.Info("Waiting %d seconds before reconnecting", int(wait.Seconds())) time.Sleep(wait) // exponentially increase wait time wait = 2 * wait wait = time.Duration(math.Min(float64(wait), float64(maxWait))) s.status = "reconnecting" ctl.Update(s) } }
func progressWatcher(s *State, ctl *ui.Controller, progress chan int, complete chan int) { for { select { case pct, ok := <-progress: if !ok { close(complete) return } else if pct == 100 { s.update = ui.UpdateInstalling ctl.Update(s) close(complete) return } else { if pct%25 == 0 { log.Info("Downloading update %d%% complete", pct) } s.update = ui.UpdateStatus(pct) ctl.Update(s) } } } }
func (c *ClientModel) Run() { // how long we should wait before we reconnect maxWait := 30 * time.Second wait := 1 * time.Second for { // run the control channel c.control() // control oonly returns when a failure has occurred, so we're going to try to reconnect if c.connStatus == mvc.ConnOnline { wait = 1 * time.Second } log.Info("Waiting %d seconds before reconnecting", int(wait.Seconds())) time.Sleep(wait) // exponentially increase wait time wait = 2 * wait wait = time.Duration(math.Min(float64(wait), float64(maxWait))) c.connStatus = mvc.ConnReconnecting c.update() } }
/** * Listens for new proxy connections from tunnel clients */ func proxyListener(addr *net.TCPAddr, domain string) { listener, err := conn.Listen(addr, "pxy", tls.Config) if err != nil { panic(err) } // set global proxy addr variable proxyAddr = fmt.Sprintf("%s:%d", domain, listener.Port) log.Info("Listening for proxy connection on %d", listener.Port) for proxyConn := range listener.Conns { go func(conn conn.Conn) { // fail gracefully if the proxy connection dies defer func() { if r := recover(); r != nil { conn.Warn("Failed with error: %v", r) conn.Close() } }() // read the proxy register message var regPxy msg.RegProxyMsg if err = msg.ReadMsgInto(conn, ®Pxy); err != nil { panic(err) } // look up the tunnel for this proxy conn.Info("Registering new proxy for %s", regPxy.Url) tunnel := tunnels.Get(regPxy.Url) if tunnel == nil { panic("No tunnel found for: " + regPxy.Url) } // register the proxy connection with the tunnel tunnel.RegisterProxy(conn) }(proxyConn) } }
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 } } }
func LoadConfiguration(opts *Options) (config *Configuration, err error) { configPath := opts.config if configPath == "" { configPath = defaultPath() } log.Info("Reading configuration file %s", configPath) configBuf, err := ioutil.ReadFile(configPath) if err != nil { // failure to read a configuration file is only a fatal error if // the user specified one explicitly if opts.config != "" { err = fmt.Errorf("Failed to read configuration file %s: %v", configPath, err) return } } // deserialize/parse the config config = new(Configuration) if err = goyaml.Unmarshal(configBuf, &config); err != nil { err = fmt.Errorf("Error parsing configuration file %s: %v", configPath, err) return } // try to parse the old .ngrok format for backwards compatibility matched := false content := strings.TrimSpace(string(configBuf)) if matched, err = regexp.MatchString("^[0-9a-zA-Z_\\-!]+$", content); err != nil { return } else if matched { config = &Configuration{AuthToken: content} } // set configuration defaults if config.ServerAddr == "" { config.ServerAddr = defaultServerAddr } if config.InspectAddr == "" { config.InspectAddr = "127.0.0.1:4040" } if config.HttpProxy == "" { config.HttpProxy = os.Getenv("http_proxy") } // validate and normalize configuration if config.InspectAddr != "disabled" { if config.InspectAddr, err = normalizeAddress(config.InspectAddr, "inspect_addr"); err != nil { return } } if config.ServerAddr, err = normalizeAddress(config.ServerAddr, "server_addr"); err != nil { return } if config.HttpProxy != "" { var proxyUrl *url.URL if proxyUrl, err = url.Parse(config.HttpProxy); err != nil { return } else { if proxyUrl.Scheme != "http" && proxyUrl.Scheme != "https" { err = fmt.Errorf("Proxy url scheme must be 'http' or 'https', got %v", proxyUrl.Scheme) return } } } for name, t := range config.Tunnels { if t == nil || t.Protocols == nil || len(t.Protocols) == 0 { err = fmt.Errorf("Tunnel %s does not specify any protocols to tunnel.", name) return } for k, addr := range t.Protocols { tunnelName := fmt.Sprintf("for tunnel %s[%s]", name, k) if t.Protocols[k], err = normalizeAddress(addr, tunnelName); err != nil { return } if err = validateProtocol(k, tunnelName); err != nil { return } } // use the name of the tunnel as the subdomain if none is specified if t.Hostname == "" && t.Subdomain == "" { // XXX: a crude heuristic, really we should be checking if the last part // is a TLD if len(strings.Split(name, ".")) > 1 { t.Hostname = name } else { t.Subdomain = name } } } // override configuration with command-line options config.LogTo = opts.logto config.Path = configPath if opts.authtoken != "" { config.AuthToken = opts.authtoken } switch opts.command { // start a single tunnel, the default, simple ngrok behavior case "default": config.Tunnels = make(map[string]*TunnelConfiguration) config.Tunnels["default"] = &TunnelConfiguration{ Subdomain: opts.subdomain, Hostname: opts.hostname, HttpAuth: opts.httpauth, Protocols: make(map[string]string), } for _, proto := range strings.Split(opts.protocol, "+") { if err = validateProtocol(proto, "default"); err != nil { return } if config.Tunnels["default"].Protocols[proto], err = normalizeAddress(opts.args[0], ""); err != nil { return } } // start tunnels case "start": if len(opts.args) == 0 { err = fmt.Errorf("You must specify at least one tunnel to start") return } requestedTunnels := make(map[string]bool) for _, arg := range opts.args { requestedTunnels[arg] = true if _, ok := config.Tunnels[arg]; !ok { err = fmt.Errorf("Requested to start tunnel %s which is not defined in the config file.", arg) return } } for name, _ := range config.Tunnels { if !requestedTunnels[name] { delete(config.Tunnels, name) } } default: err = fmt.Errorf("Unknown command: %s", opts.command) return } return }
func NewControl(ctlConn conn.Conn, authMsg *msg.Auth) { var err error // create the object c := &Control{ auth: authMsg, conn: ctlConn, out: make(chan msg.Message), in: make(chan msg.Message), proxies: make(chan conn.Conn, 10), lastPing: time.Now(), writerShutdown: util.NewShutdown(), readerShutdown: util.NewShutdown(), managerShutdown: util.NewShutdown(), shutdown: util.NewShutdown(), } failAuth := func(e error) { _ = msg.WriteMsg(ctlConn, &msg.AuthResp{Error: e.Error()}) ctlConn.Close() } failAuth2 := func(e string) { _ = msg.WriteMsg(ctlConn, e) ctlConn.Close() } // check auth_token if authMsg.User == "" { failAuth2("Need auth_token") return } val, err := client.HGet("ngrok", authMsg.User).Result() if err != nil { log.Info(err.Error()) failAuth2("Auth ERROR:" + err.Error()) return } c.UserName = val log.Info("New Client auth ok --> " + val) // register the clientid c.id = authMsg.ClientId if c.id == "" { // it's a new session, assign an ID if c.id, err = util.SecureRandId(16); err != nil { failAuth(err) return } } // set logging prefix ctlConn.SetType("ctl") ctlConn.AddLogPrefix(c.id) if authMsg.Version != version.Proto { failAuth(fmt.Errorf("Incompatible versions. Server %s, client %s. Download a new version at http://ngrok.com", version.MajorMinor(), authMsg.Version)) return } // register the control if replaced := controlRegistry.Add(c.id, c); replaced != nil { replaced.shutdown.WaitComplete() } // start the writer first so that the following messages get sent go c.writer() // Respond to authentication c.out <- &msg.AuthResp{ Version: version.Proto, MmVersion: version.MajorMinor(), ClientId: c.id, } // As a performance optimization, ask for a proxy connection up front c.out <- &msg.ReqProxy{} // manage the connection go c.manager() go c.reader() go c.stopper() }
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() } }