Exemple #1
0
func versionCheck(s *State, ctl *ui.Controller) {
	check := func() {
		resp, err := http.Get(versionEndpoint)
		if err != nil {
			log.Warn("Failed to get version info %s: %v", versionEndpoint, err)
			return
		}
		defer resp.Body.Close()

		var payload struct {
			Client struct {
				Version string
			}
		}

		err = json.NewDecoder(resp.Body).Decode(&payload)
		if err != nil {
			log.Warn("Failed to read version info: %v", err)
			return
		}

		if payload.Client.Version != version.MajorMinor() {
			s.newVersion = payload.Client.Version
			ctl.Update(s)
		}
	}

	// check immediately and then at a set interval
	check()
	for _ = range time.Tick(versionCheckInterval) {
		check()
	}
}
Exemple #2
0
func parseArgs() *Options {
	authtoken := flag.String(
		"authtoken",
		"",
		"Authentication token for identifying a premium ngrok.com account")

	server := flag.String(
		"server",
		"ngrok.com:4443",
		"The remote ngrok server")

	httpAuth := flag.String(
		"httpauth",
		"",
		"username:password HTTP basic auth creds protecting the public tunnel endpoint")

	subdomain := flag.String(
		"subdomain",
		"",
		"Request a custom subdomain from the ngrok server. (HTTP mode only)")

	protocol := flag.String(
		"proto",
		"http",
		"The protocol of the traffic over the tunnel {'http', 'tcp'} (default: 'http')")

	webport := flag.Int(
		"webport",
		4040,
		"The port on which the web interface is served")

	logto := flag.String(
		"log",
		"none",
		"Write log messages to this file. 'stdout' and 'none' have special meanings")

	v := flag.Bool(
		"version",
		false,
		"Print ngrok version and exit")

	flag.Parse()

	if *v {
		fmt.Println(version.MajorMinor())
		os.Exit(0)
	}

	return &Options{
		server:    *server,
		httpAuth:  *httpAuth,
		subdomain: *subdomain,
		localaddr: parseLocalAddr(),
		protocol:  parseProtocol(*protocol),
		webport:   *webport,
		logto:     *logto,
		authtoken: parseAuthToken(*authtoken),
	}
}
Exemple #3
0
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)
	}
}
Exemple #4
0
func NewControl(ctlConn conn.Conn, authMsg *msg.Auth) {
	var err error

	// create the object
	// channels are buffered because we read and write to them
	// from the same goroutine in managerThread()
	c := &Control{
		auth:     authMsg,
		conn:     ctlConn,
		out:      make(chan msg.Message, 5),
		in:       make(chan msg.Message, 5),
		stop:     make(chan msg.Message, 5),
		proxies:  make(chan conn.Conn, 10),
		lastPing: time.Now(),
	}

	failAuth := func(e error) {
		_ = msg.WriteMsg(ctlConn, &msg.AuthResp{Error: e.Error()})
		ctlConn.Close()
	}

	// 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
		}
	}

	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
	controlRegistry.Add(c.id, c)

	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{}

	// set logging prefix
	ctlConn.SetType("ctl")

	// manage the connection
	go c.managerThread()
	go c.readThread()
}
Exemple #5
0
func (c *Control) managerThread() {
	reap := time.NewTicker(connReapInterval)

	// all shutdown functionality in here
	defer func() {
		if err := recover(); err != nil {
			c.conn.Info("Control::managerThread failed with error %v: %s", err, debug.Stack())
		}
		reap.Stop()
		c.conn.Close()

		// shutdown the tunnel if it's open
		if c.tun != nil {
			c.tun.shutdown()
		}
	}()

	for {
		select {
		case m := <-c.out:
			msg.WriteMsg(c.conn, m)

		case <-reap.C:
			if time.Since(c.lastPing) > pingTimeoutInterval {
				c.conn.Info("Lost heartbeat")
				metrics.lostHeartbeatMeter.Mark(1)
				return
			}

		case m := <-c.stop:
			if m != nil {
				msg.WriteMsg(c.conn, m)
			}
			return

		case mRaw := <-c.in:
			switch m := mRaw.(type) {
			case *msg.RegMsg:
				c.conn.Info("Registering new tunnel")
				c.tun = newTunnel(m, c)

			case *msg.PingMsg:
				c.lastPing = time.Now()
				c.out <- &msg.PongMsg{}

			case *msg.VersionMsg:
				c.out <- &msg.VersionRespMsg{
					Version:   version.Proto,
					MmVersion: version.MajorMinor(),
				}
			}
		}
	}
}
Exemple #6
0
func newTunnel(m *msg.RegMsg, ctl *Control) (t *Tunnel) {
	t = &Tunnel{
		regMsg:  m,
		start:   time.Now(),
		ctl:     ctl,
		proxies: make(chan conn.Conn),
		Logger:  log.NewPrefixLogger(),
	}

	switch t.regMsg.Protocol {
	case "tcp":
		var err error
		t.listener, err = net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: 0})

		if err != nil {
			t.ctl.conn.Error("Failed to create tunnel. Error binding TCP listener: %v", err)

			t.ctl.stop <- &msg.RegAckMsg{Error: "Internal server error"}
		}

		go t.listenTcp(t.listener)

	default:
	}

	if err := tunnels.Add(t); err != nil {
		t.ctl.stop <- &msg.RegAckMsg{Error: fmt.Sprint(err)}
		return
	}

	if m.Version != version.Proto {
		t.ctl.stop <- &msg.RegAckMsg{Error: fmt.Sprintf("Incompatible versions. Server %s, client %s. Download a new version at http://ngrok.com", version.MajorMinor(), m.Version)}
	}

	// pre-encode the http basic auth for fast comparisons later
	if m.HttpAuth != "" {
		m.HttpAuth = "Basic " + base64.StdEncoding.EncodeToString([]byte(m.HttpAuth))
	}

	t.ctl.conn.AddLogPrefix(t.Id())
	t.AddLogPrefix(t.Id())
	t.Info("Registered new tunnel")
	t.ctl.out <- &msg.RegAckMsg{
		Url:       t.url,
		ProxyAddr: fmt.Sprintf("%s", proxyAddr),
		Version:   version.Proto,
		MmVersion: version.MajorMinor(),
	}

	metrics.OpenTunnel(t)
	return
}
Exemple #7
0
// implement client.ui.State
func (s State) GetClientVersion() string    { return version.MajorMinor() }
Exemple #8
0
func ParseArgs() (opts *Options, err error) {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, usage1, os.Args[0])
		flag.PrintDefaults()
		fmt.Fprintf(os.Stderr, usage2)
	}

	config := flag.String(
		"config",
		"",
		"Path to ngrok configuration file. (default: $HOME/.ngrok)")

	logto := flag.String(
		"log",
		"none",
		"Write log messages to this file. 'stdout' and 'none' have special meanings")

	authtoken := flag.String(
		"authtoken",
		"",
		"Authentication token for identifying an ngrok.com account")

	httpauth := flag.String(
		"httpauth",
		"",
		"username:password HTTP basic auth creds protecting the public tunnel endpoint")

	subdomain := flag.String(
		"subdomain",
		"",
		"Request a custom subdomain from the ngrok server. (HTTP only)")

	hostname := flag.String(
		"hostname",
		"",
		"Request a custom hostname from the ngrok server. (HTTP only) (requires CNAME of your DNS)")

	protocol := flag.String(
		"proto",
		"http+https",
		"The protocol of the traffic over the tunnel {'http', 'https', 'tcp'} (default: 'http+https')")

	flag.Parse()

	opts = &Options{
		config:    *config,
		logto:     *logto,
		httpauth:  *httpauth,
		subdomain: *subdomain,
		protocol:  *protocol,
		authtoken: *authtoken,
		hostname:  *hostname,
		command:   flag.Arg(0),
	}

	switch opts.command {
	case "start":
		opts.args = flag.Args()[1:]
	case "version":
		fmt.Println(version.MajorMinor())
		os.Exit(0)
	case "help":
		flag.Usage()
		os.Exit(0)
	case "":
		err = fmt.Errorf("Error: Specify a local port to tunnel to, or " +
			"an ngrok command.\n\nExample: To expose port 80, run " +
			"'ngrok 80'")
		return

	default:
		if len(flag.Args()) > 1 {
			err = fmt.Errorf("You may only specify one port to tunnel to on the command line, got %d: %v",
				len(flag.Args()),
				flag.Args())
			return
		}

		opts.command = "default"
		opts.args = flag.Args()
	}

	return
}
Exemple #9
0
// 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)
		}
	}
}
Exemple #10
0
func (c ClientModel) GetClientVersion() string       { return version.MajorMinor() }
Exemple #11
0
func NewControl(ctlConn conn.Conn, authMsg *msg.Auth, required_secret string) {
	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()
	}

	// 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
	}

	if required_secret != "" && authMsg.User != required_secret {
		failAuth(fmt.Errorf("Invalid authtoken %s", authMsg.User))
		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()
}
Exemple #12
0
/**
 * 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, &regAck); 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())
		}
	}
}
Exemple #13
0
func newTunnel(m *msg.RegMsg, ctl *Control) (t *Tunnel) {
	t = &Tunnel{
		regMsg:  m,
		start:   time.Now(),
		ctl:     ctl,
		proxies: make(chan conn.Conn),
		Logger:  log.NewPrefixLogger(),
	}

	failReg := func(err error) {
		t.ctl.stop <- &msg.RegAckMsg{Error: err.Error()}
	}

	var err error

	switch t.regMsg.Protocol {
	case "tcp":
		var port int = 0

		// try to return to you the same port you had before
		cachedUrl := tunnels.GetCachedRegistration(t)
		if cachedUrl != "" {
			parts := strings.Split(cachedUrl, ":")
			portPart := parts[len(parts)-1]
			port, err = strconv.Atoi(portPart)
			if err != nil {
				t.ctl.conn.Error("Failed to parse cached url port as integer: %s", portPart)
				// continue with zero
				port = 0
			}
		}

		// Bind for TCP connections
		t.listener, err = net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: port})

		// If we failed with a custom port, try with a random one
		if err != nil && port != 0 {
			t.ctl.conn.Warn("Failed to get custom port %d: %v, trying a random one", port, err)
			t.listener, err = net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: 0})
		}

		// we tried to bind with a random port and failed (no more ports available?)
		if err != nil {
			failReg(t.ctl.conn.Error("Error binding TCP listener: %v", err))
			return
		}

		// create the url
		addr := t.listener.Addr().(*net.TCPAddr)
		t.url = fmt.Sprintf("tcp://%s:%d", domain, addr.Port)

		// register it
		if err = tunnels.RegisterAndCache(t.url, t); err != nil {
			// This should never be possible because the OS will
			// only assign available ports to us.
			t.listener.Close()
			failReg(fmt.Errorf("TCP listener bound, but failed to register %s", t.url))
			return
		}

		go t.listenTcp(t.listener)

	case "http":
		if strings.TrimSpace(t.regMsg.Hostname) != "" {
			t.url = fmt.Sprintf("http://%s", t.regMsg.Hostname)
		} else if strings.TrimSpace(t.regMsg.Subdomain) != "" {
			t.url = fmt.Sprintf("http://%s.%s", t.regMsg.Subdomain, domain)
		}

		if t.url != "" {
			if err := tunnels.Register(t.url, t); err != nil {
				failReg(err)
				return
			}
		} else {
			t.url, err = tunnels.RegisterRepeat(func() string {
				return fmt.Sprintf("http://%x.%s", rand.Int31(), domain)
			}, t)

			if err != nil {
				failReg(err)
				return
			}
		}
	}

	if m.Version != version.Proto {
		failReg(fmt.Errorf("Incompatible versions. Server %s, client %s. Download a new version at http://ngrok.com", version.MajorMinor(), m.Version))
		return
	}

	// pre-encode the http basic auth for fast comparisons later
	if m.HttpAuth != "" {
		m.HttpAuth = "Basic " + base64.StdEncoding.EncodeToString([]byte(m.HttpAuth))
	}

	t.ctl.conn.AddLogPrefix(t.Id())
	t.AddLogPrefix(t.Id())
	t.Info("Registered new tunnel")
	t.ctl.out <- &msg.RegAckMsg{
		Url:       t.url,
		ProxyAddr: fmt.Sprintf("%s", proxyAddr),
		Version:   version.Proto,
		MmVersion: version.MajorMinor(),
	}

	metrics.OpenTunnel(t)
	return
}
Exemple #14
0
func newTunnel(m *msg.RegMsg, ctl *Control) (t *Tunnel) {
	t = &Tunnel{
		regMsg:  m,
		start:   time.Now(),
		ctl:     ctl,
		proxies: make(chan conn.Conn),
		Logger:  log.NewPrefixLogger(),
	}

	failReg := func(err error) {
		t.ctl.stop <- &msg.RegAckMsg{Error: err.Error()}
	}

	switch t.regMsg.Protocol {
	case "tcp":
		var err error
		t.listener, err = net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: 0})

		if err != nil {
			t.ctl.conn.Error("Failed to create tunnel. Error binding TCP listener: %v", err)

			t.ctl.stop <- &msg.RegAckMsg{Error: "Internal server error"}
		}

		addr := t.listener.Addr().(*net.TCPAddr)
		t.url = fmt.Sprintf("tcp://%s:%d", domain, addr.Port)

		if err = tunnels.Register(t.url, t); err != nil {
			// This should never be possible because the OS will only assign
			// available ports to us.
			t.Error("TCP listener bound, but failed to register: %s", err.Error())
			t.listener.Close()
			failReg(err)
			return
		}

		go t.listenTcp(t.listener)

	case "http":
		if strings.TrimSpace(t.regMsg.Hostname) != "" {
			t.url = fmt.Sprintf("http://%s", t.regMsg.Hostname)
		} else if strings.TrimSpace(t.regMsg.Subdomain) != "" {
			t.url = fmt.Sprintf("http://%s.%s", t.regMsg.Subdomain, domain)
		}

		if t.url != "" {
			if err := tunnels.Register(t.url, t); err != nil {
				failReg(err)
				return
			}
		} else {
			t.url = tunnels.RegisterRepeat(func() string {
				return fmt.Sprintf("http://%x.%s", rand.Int31(), domain)
			}, t)
		}
	}

	if m.Version != version.Proto {
		failReg(fmt.Errorf("Incompatible versions. Server %s, client %s. Download a new version at http://ngrok.com", version.MajorMinor(), m.Version))
		return
	}

	// pre-encode the http basic auth for fast comparisons later
	if m.HttpAuth != "" {
		m.HttpAuth = "Basic " + base64.StdEncoding.EncodeToString([]byte(m.HttpAuth))
	}

	t.ctl.conn.AddLogPrefix(t.Id())
	t.AddLogPrefix(t.Id())
	t.Info("Registered new tunnel")
	t.ctl.out <- &msg.RegAckMsg{
		Url:       t.url,
		ProxyAddr: fmt.Sprintf("%s", proxyAddr),
		Version:   version.Proto,
		MmVersion: version.MajorMinor(),
	}

	metrics.OpenTunnel(t)
	return
}
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()
	}
}
Exemple #16
0
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
		}
	}
}