func New(ctl *ui.Controller, state ui.State) *TermView { // initialize terminal display termbox.Init() // make sure ngrok doesn't quit until we've cleaned up ctl.Wait.Add(1) w, _ := termbox.Size() v := &TermView{ ctl: ctl, updates: ctl.Updates.Reg(), flush: make(chan int), subviews: make([]ui.View, 0), state: state, Logger: log.NewPrefixLogger(), area: NewArea(0, 0, w, 10), } v.Logger.AddLogPrefix("view") v.Logger.AddLogPrefix("term") switch p := state.GetProtocol().(type) { case *proto.Http: v.subviews = append(v.subviews, NewHttp(p, v.flush, ctl.Shutdown, 0, 10)) default: } v.Render() go v.run() go v.input() return v }
func NewTunnelRegistry(cacheSize uint64, cacheFile string) *TunnelRegistry { registry := &TunnelRegistry{ tunnels: make(map[string]*Tunnel), affinity: cache.NewLRUCache(cacheSize), Logger: log.NewPrefixLogger("registry", "tun"), } // LRUCache uses Gob encoding. Unfortunately, Gob is fickle and will fail // to encode or decode any non-primitive types that haven't been "registered" // with it. Since we store cacheUrl objects, we need to register them here first // for the encoding/decoding to work var urlobj cacheUrl gob.Register(urlobj) // try to load and then periodically save the affinity cache to file, if specified if cacheFile != "" { err := registry.affinity.LoadItemsFromFile(cacheFile) if err != nil { registry.Error("Failed to load affinity cache %s: %v", cacheFile, err) } registry.SaveCacheThread(cacheFile, cacheSaveInterval) } else { registry.Info("No affinity cache specified") } return registry }
func NewLocalMetrics(reportInterval time.Duration) *LocalMetrics { metrics := LocalMetrics{ Logger: log.NewPrefixLogger(), reportInterval: reportInterval, windowsCounter: gometrics.NewCounter(), linuxCounter: gometrics.NewCounter(), osxCounter: gometrics.NewCounter(), otherCounter: gometrics.NewCounter(), tunnelMeter: gometrics.NewMeter(), tcpTunnelMeter: gometrics.NewMeter(), httpTunnelMeter: gometrics.NewMeter(), connMeter: gometrics.NewMeter(), lostHeartbeatMeter: gometrics.NewMeter(), connTimer: gometrics.NewTimer(), bytesInCount: gometrics.NewCounter(), bytesOutCount: gometrics.NewCounter(), /* metrics.tunnelGauge = gometrics.NewGauge(), metrics.tcpTunnelGauge = gometrics.NewGauge(), metrics.connGauge = gometrics.NewGauge(), */ } go metrics.Report() return &metrics }
// public interface func NewController() *Controller { ctl := &Controller{ Logger: log.NewPrefixLogger("controller"), updates: util.NewBroadcast(), cmds: make(chan command), views: make([]mvc.View, 0), state: make(chan mvc.State), } return ctl }
func wrapConn(conn net.Conn, typ string) *loggedConn { switch c := conn.(type) { case *loggedConn: return c case *net.TCPConn: wrapped := &loggedConn{c, conn, log.NewPrefixLogger(), rand.Int31(), typ} wrapped.AddLogPrefix(wrapped.Id()) return wrapped } return nil }
func newTermHttpView(ctl mvc.Controller, termView *TermView, proto *proto.Http, x, y int) *HttpView { v := &HttpView{ httpProto: proto, HttpRequests: util.NewRing(size), area: NewArea(x, y, 70, size+5), shutdown: make(chan int), termView: termView, Logger: log.NewPrefixLogger("view", "term", "http"), } ctl.Go(v.Run) return v }
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 }
func newWebHttpView(ctl mvc.Controller, wv *WebView, proto *proto.Http) *WebHttpView { whv := &WebHttpView{ Logger: log.NewPrefixLogger("view", "web", "http"), webview: wv, ctl: ctl, httpProto: proto, idToTxn: make(map[string]*SerializedTxn), HttpRequests: util.NewRing(20), } ctl.Go(whv.updateHttp) whv.register() return whv }
func NewKeenIoMetrics(batchInterval time.Duration) *KeenIoMetrics { k := &KeenIoMetrics{ Logger: log.NewPrefixLogger("metrics"), ApiKey: os.Getenv("KEEN_API_KEY"), ProjectToken: os.Getenv("KEEN_PROJECT_TOKEN"), Metrics: make(chan *KeenIoMetric, 1000), } go func() { defer func() { if r := recover(); r != nil { k.Error("KeenIoMetrics failed: %v", r) } }() batch := make(map[string][]interface{}) batchTimer := time.Tick(batchInterval) for { select { case m := <-k.Metrics: list, ok := batch[m.Collection] if !ok { list = make([]interface{}, 0) } batch[m.Collection] = append(list, m.Event) case <-batchTimer: // no metrics to report if len(batch) == 0 { continue } payload, err := json.Marshal(batch) if err != nil { k.Error("Failed to serialize metrics payload: %v, %v", batch, err) } else { for key, val := range batch { k.Debug("Reporting %d metrics for %s", len(val), key) } k.AuthedRequest("POST", "/events", bytes.NewReader(payload)) } batch = make(map[string][]interface{}) } } }() return k }
func NewHttp(proto *proto.Http, shutdown chan int, x, y int) *HttpView { v := &HttpView{ httpProto: proto, HttpRequests: util.NewRing(size), area: NewArea(x, y, 70, size+5), shutdown: shutdown, Logger: log.NewPrefixLogger(), } v.AddLogPrefix("view") v.AddLogPrefix("term") v.AddLogPrefix("http") go v.Run() return v }
func NewWebView(ctl mvc.Controller, addr string) *WebView { wv := &WebView{ Logger: log.NewPrefixLogger("view", "web"), wsMessages: util.NewBroadcast(), ctl: ctl, } // for now, always redirect to the http view http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/http/in", 302) }) // handle web socket connections http.HandleFunc("/_ws", func(w http.ResponseWriter, r *http.Request) { conn, err := websocket.Upgrade(w, r, nil, 1024, 1024) if err != nil { http.Error(w, "Failed websocket upgrade", 400) wv.Warn("Failed websocket upgrade: %v", err) return } msgs := wv.wsMessages.Reg() defer wv.wsMessages.UnReg(msgs) for m := range msgs { err := conn.WriteMessage(websocket.TextMessage, m.([]byte)) if err != nil { // connection is closed break } } }) // serve static assets http.HandleFunc("/static/", func(w http.ResponseWriter, r *http.Request) { // buf, err := assets.Asset(path.Join("assets", "client", r.URL.Path[1:])) buf, err := ioutil.ReadFile(path.Join("assets", "client", r.URL.Path[1:])) if err != nil { wv.Warn("Error serving static file: %s", err.Error()) http.NotFound(w, r) return } w.Write(buf) }) wv.Info("Serving web interface on %s", addr) wv.ctl.Go(func() { http.ListenAndServe(addr, nil) }) return wv }
func NewKeenIoMetrics() *KeenIoMetrics { k := &KeenIoMetrics{ Logger: log.NewPrefixLogger(), ApiKey: os.Getenv("KEEN_API_KEY"), ProjectToken: os.Getenv("KEEN_PROJECT_TOKEN"), Requests: make(chan *KeenIoRequest, 100), } go func() { for req := range k.Requests { k.AuthedRequest("POST", req.Path, bytes.NewReader(req.Body)) } }() return k }
func NewTermView(ctl mvc.Controller) *TermView { // initialize terminal display termbox.Init() w, _ := termbox.Size() v := &TermView{ ctl: ctl, updates: ctl.Updates().Reg(), redraw: util.NewBroadcast(), flush: make(chan int), shutdown: make(chan int), Logger: log.NewPrefixLogger("view", "term"), area: NewArea(0, 0, w, 10), } ctl.Go(v.run) ctl.Go(v.input) return v }
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 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 }
func wrapConn(conn net.Conn, typ string) *loggedConn { c := &loggedConn{conn, log.NewPrefixLogger(), rand.Int31(), typ} c.AddLogPrefix(c.Id()) return c }
func newClientModel(config *Configuration, ctl mvc.Controller) *ClientModel { protoMap := make(map[string]proto.Protocol) protoMap["http"] = proto.NewHttp() protoMap["https"] = protoMap["http"] protoMap["tcp"] = proto.NewTcp() protocols := []proto.Protocol{protoMap["http"], protoMap["tcp"]} m := &ClientModel{ Logger: log.NewPrefixLogger("client"), // server address serverAddr: config.ServerAddr, // proxy address proxyUrl: config.HttpProxy, // auth token authToken: config.AuthToken, password: config.Password, // connection status connStatus: mvc.ConnConnecting, // update status updateStatus: mvc.UpdateNone, // metrics metrics: NewClientMetrics(), // protocols protoMap: protoMap, // protocol list protocols: protocols, // open tunnels tunnels: make(map[string]mvc.Tunnel), // controller ctl: ctl, // tunnel configuration tunnelConfig: config.Tunnels, // config path configPath: config.Path, } // configure TLS if config.TrustHostRootCerts { m.Info("Trusting host's root certificates") m.tlsConfig = &tls.Config{} } else { m.Info("Trusting root CAs: %v", rootCrtPaths) var err error if m.tlsConfig, err = LoadTLSConfig(rootCrtPaths); err != nil { panic(err) } } // configure TLS SNI m.tlsConfig.ServerName = serverName(m.serverAddr) return m }
func newClientModel(config *Configuration, ctl mvc.Controller) *ClientModel { protoMap := make(map[string]proto.Protocol) protoMap["http"] = proto.NewHttp() protoMap["https"] = protoMap["http"] protoMap["tcp"] = proto.NewTcp() protocols := []proto.Protocol{protoMap["http"], protoMap["tcp"]} // configure TLS var tlsConfig *tls.Config if config.TrustHostRootCerts { tlsConfig = &tls.Config{} } else { var err error if tlsConfig, err = LoadTLSConfig(rootCrtPaths); err != nil { panic(err) } } return &ClientModel{ Logger: log.NewPrefixLogger("client"), // server address serverAddr: config.ServerAddr, // proxy address proxyUrl: config.HttpProxy, // auth token authToken: config.AuthToken, // connection status connStatus: mvc.ConnConnecting, // update status updateStatus: mvc.UpdateNone, // metrics metrics: NewClientMetrics(), // protocols protoMap: protoMap, // protocol list protocols: protocols, // open tunnels tunnels: make(map[string]mvc.Tunnel), // controller ctl: ctl, // tls configuration tlsConfig: tlsConfig, // tunnel configuration tunnelConfig: config.Tunnels, // config path configPath: config.Path, } }
func NewControlRegistry() *ControlRegistry { return &ControlRegistry{ controls: make(map[string]*Control), Logger: log.NewPrefixLogger("registry", "ctl"), } }
// Create a new tunnel from a registration message received // on a control channel func NewTunnel(m *msg.ReqTunnel, ctl *Control) (t *Tunnel, err error) { t = &Tunnel{ req: m, start: time.Now(), ctl: ctl, Logger: log.NewPrefixLogger(), } proto := t.req.Protocol switch proto { case "tcp": bindTcp := func(port int) error { if t.listener, err = net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: port}); err != nil { err = t.ctl.conn.Error("Error binding TCP listener: %v", err) return err } // create the url addr := t.listener.Addr().(*net.TCPAddr) t.url = fmt.Sprintf("tcp://%s:%d", opts.domain, addr.Port) // register it if err = tunnelRegistry.RegisterAndCache(t.url, t); err != nil { // This should never be possible because the OS will // only assign available ports to us. t.listener.Close() err = fmt.Errorf("TCP listener bound, but failed to register %s", t.url) return err } go t.listenTcp(t.listener) return nil } // use the custom remote port you asked for if t.req.RemotePort != 0 { bindTcp(int(t.req.RemotePort)) return } // try to return to you the same port you had before cachedUrl := tunnelRegistry.GetCachedRegistration(t) if cachedUrl != "" { var port int 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) } else { // we have a valid, cached port, let's try to bind with it if bindTcp(port) != nil { t.ctl.conn.Warn("Failed to get custom port %d: %v, trying a random one", port, err) } else { // success, we're done return } } } // Bind for TCP connections bindTcp(0) return case "http", "https": l, ok := listeners[proto] if !ok { err = fmt.Errorf("Not listeneing for %s connections", proto) return } if err = registerVhost(t, proto, l.Addr.(*net.TCPAddr).Port); err != nil { return } default: err = fmt.Errorf("Protocol %s is not supported", proto) return } // pre-encode the http basic auth for fast comparisons later if m.HttpAuth != "" { m.HttpAuth = "Basic " + base64.StdEncoding.EncodeToString([]byte(m.HttpAuth)) } t.AddLogPrefix(t.Id()) t.Info("Registered new tunnel on: %s", t.ctl.conn.Id()) metrics.OpenTunnel(t) return }