// Hearbeating to ensure our connection ngrokd is still live func (c *ClientModel) heartbeat(lastPongAddr *int64, conn conn.Conn) { lastPing := time.Unix(atomic.LoadInt64(lastPongAddr)-1, 0) ping := time.NewTicker(pingInterval) pongCheck := time.NewTicker(time.Second) defer func() { conn.Close() ping.Stop() pongCheck.Stop() }() for { select { case <-pongCheck.C: lastPong := time.Unix(0, atomic.LoadInt64(lastPongAddr)) needPong := lastPong.Sub(lastPing) < 0 pongLatency := time.Since(lastPing) if needPong && pongLatency > maxPongLatency { c.Info("Last ping: %v, Last pong: %v", lastPing, lastPong) c.Info("Connection stale, haven't gotten PongMsg in %d seconds", int(pongLatency.Seconds())) return } case <-ping.C: err := msg.WriteMsg(conn, &msg.Ping{}) if err != nil { conn.Debug("Got error %v when writing PingMsg", err) return } lastPing = time.Now() } } }
func (c *Control) RegisterProxy(conn conn.Conn) { select { case c.proxies <- conn: c.conn.Info("Registered proxy connection %s", conn.Id()) default: // c.proxies buffer is full, discard this one conn.Close() } }
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() }
func (c *Control) RegisterProxy(conn conn.Conn) { conn.AddLogPrefix(c.id) select { case c.proxies <- conn: conn.Info("Registered") default: conn.Info("Proxies buffer is full, discarding.") conn.Close() } }
func (c *Control) RegisterProxy(conn conn.Conn) { conn.AddLogPrefix(c.id) conn.SetDeadline(time.Now().Add(proxyStaleDuration)) select { case c.proxies <- conn: conn.Info("Registered") default: conn.Info("Proxies buffer is full, discarding.") conn.Close() } }
// mvc.Model interface func (c *ClientModel) PlayRequest(tunnel mvc.Tunnel, payload []byte) { var localConn conn.Conn localConn, err := conn.Dial(tunnel.LocalAddr, "prv", nil) if err != nil { c.Warn("Failed to open private leg to %s: %v", tunnel.LocalAddr, err) return } defer localConn.Close() localConn = tunnel.Protocol.WrapConn(localConn, mvc.ConnectionContext{Tunnel: tunnel, ClientAddr: "127.0.0.1"}) localConn.Write(payload) ioutil.ReadAll(localConn) }
func readMsgShared(c conn.Conn) (buffer []byte, err error) { c.Debug("Waiting to read message") var sz int64 err = binary.Read(c, binary.LittleEndian, &sz) if err != nil { return } c.Debug("Reading message with length: %d", sz) buffer = make([]byte, sz) n, err := c.Read(buffer) c.Debug("Read message %s", buffer) if err != nil { return } if int64(n) != sz { err = errors.New(fmt.Sprintf("Expected to read %d bytes, but only read %d", sz, n)) return } return }
func (c *Control) RegisterProxy(conn conn.Conn) { if atomic.LoadInt32(&c.closing) == 1 { c.conn.Debug("Can't register proxies for a control that is closing") conn.Close() return } select { case c.proxies <- conn: c.conn.Info("Registered proxy connection %s", conn.Id()) default: // c.proxies buffer is full, discard this one conn.Close() } }
func WriteMsg(c conn.Conn, msg interface{}) (err error) { buffer, err := Pack(msg) if err != nil { return } c.Debug("Writing message: %s", string(buffer)) err = binary.Write(c, binary.LittleEndian, int64(len(buffer))) if err != nil { return } if _, err = c.Write(buffer); err != nil { return } return nil }
func (t *Tunnel) HandlePublicConnection(publicConn conn.Conn) { defer publicConn.Close() defer func() { if r := recover(); r != nil { publicConn.Warn("HandlePublicConnection failed with error %v", r) } }() metrics.requestTimer.Time(func() { metrics.requestMeter.Mark(1) t.Debug("Requesting new proxy connection") t.ctl.out <- &msg.ReqProxyMsg{} proxyConn := <-t.proxies t.Info("Returning proxy connection %s", proxyConn.Id()) defer proxyConn.Close() conn.Join(publicConn, proxyConn) }) }
func (t *Tunnel) HandlePublicConnection(publicConn conn.Conn) { defer publicConn.Close() defer func() { if r := recover(); r != nil { publicConn.Warn("HandlePublicConnection failed with error %v", r) } }() startTime := time.Now() metrics.OpenConnection(t, publicConn) t.Debug("Requesting new proxy connection") t.ctl.out <- &msg.ReqProxyMsg{} proxyConn := <-t.proxies t.Info("Returning proxy connection %s", proxyConn.Id()) defer proxyConn.Close() bytesIn, bytesOut := conn.Join(publicConn, proxyConn) metrics.CloseConnection(t, publicConn, startTime, bytesIn, bytesOut) }
func NewProxy(pxyConn conn.Conn, regPxy *msg.RegProxy) { // fail gracefully if the proxy connection fails to register defer func() { if r := recover(); r != nil { pxyConn.Warn("Failed with error: %v", r) pxyConn.Close() } }() // set logging prefix pxyConn.SetType("pxy") // look up the control connection for this proxy pxyConn.Info("Registering new proxy for %s", regPxy.ClientId) ctl := controlRegistry.Get(regPxy.ClientId) if ctl == nil { panic("No client found for identifier: " + regPxy.ClientId) } ctl.RegisterProxy(pxyConn) }
// Handles a new http connection from the public internet func httpHandler(c conn.Conn, proto string) { defer c.Close() defer func() { // recover from failures if r := recover(); r != nil { c.Warn("httpHandler failed with error %v", r) } }() // Make sure we detect dead connections while we decide how to multiplex c.SetDeadline(time.Now().Add(connReadTimeout)) // multiplex by extracting the Host header, the vhost library vhostConn, err := vhost.HTTP(c) if err != nil { c.Warn("Failed to read valid %s request: %v", proto, err) c.Write([]byte(BadRequest)) return } // read out the Host header and auth from the request host := strings.ToLower(vhostConn.Host()) auth := vhostConn.Request.Header.Get("Authorization") hostname, _, err := net.SplitHostPort(host) if err != nil { hostname = host } else { _, port, _ := net.SplitHostPort(c.LocalAddr().String()) hostname = fmt.Sprintf("%s:%s", hostname, port) } paramSubdomain := vhostConn.Request.URL.Query().Get(SubDomainParamName) //url param if paramSubdomain == "" { //user-agent reg := regexp.MustCompile(fmt.Sprintf("%s/(\\w+)", SubDomainUserAgentName)) matches := reg.FindStringSubmatch(vhostConn.Request.UserAgent()) if len(matches) > 0 { paramSubdomain = matches[1] } } _, setCookieSubdomain := vhostConn.Request.URL.Query()[SetCookieSubDomainParamName] subdomainCookie, err := vhostConn.Request.Cookie(SubDomainCookieName) cookieSubdomain := "" if err == nil { cookieSubdomain = subdomainCookie.Value } // done reading mux data, free up the request memory vhostConn.Free() // We need to read from the vhost conn now since it mucked around reading the stream c = conn.Wrap(vhostConn, "pub") // multiplex to find the right backend host c.Debug("Found hostname %s in request", host) if paramSubdomain != "" { hostname = fmt.Sprintf("%s.%s", paramSubdomain, hostname) } else if cookieSubdomain != "" { hostname = fmt.Sprintf("%s.%s", cookieSubdomain, hostname) } tunnelKey := fmt.Sprintf("%s://%s", proto, hostname) tunnel := tunnelRegistry.Get(tunnelKey) if tunnel == nil { if setCookieSubdomain && paramSubdomain != "" { c.Info("Set %s to Cookie for hostname %s", paramSubdomain, tunnelKey) c.Write([]byte(fmt.Sprintf(SetCooikeResponse, len(proto)+len(hostname)+len(paramSubdomain)+48, SubDomainCookieName, paramSubdomain, proto, hostname, paramSubdomain))) } else { c.Info("No tunnel found for hostname %s", tunnelKey) c.Write([]byte(fmt.Sprintf(NotFound, len(hostname)+18, hostname))) } return } // If the client specified http auth and it doesn't match this request's auth // then fail the request with 401 Not Authorized and request the client reissue the // request with basic authdeny the request if tunnel.req.HttpAuth != "" && auth != tunnel.req.HttpAuth { c.Info("Authentication failed: %s", auth) c.Write([]byte(NotAuthorized)) return } // dead connections will now be handled by tunnel heartbeating and the client c.SetDeadline(time.Time{}) // let the tunnel handle the connection now tunnel.HandlePublicConnection(c) }
// 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) } } }
func (t *Tunnel) HandlePublicConnection(publicConn conn.Conn) { defer publicConn.Close() defer func() { if r := recover(); r != nil { publicConn.Warn("HandlePublicConnection failed with error %v", r) } }() startTime := time.Now() metrics.OpenConnection(t, publicConn) var proxyConn conn.Conn var attempts int var err error for { // get a proxy connection if proxyConn, err = t.ctl.GetProxy(); err != nil { t.Warn("Failed to get proxy connection: %v", err) return } defer proxyConn.Close() t.Info("Got proxy connection %s", proxyConn.Id()) proxyConn.AddLogPrefix(t.Id()) // tell the client we're going to start using this proxy connection startPxyMsg := &msg.StartProxy{ Url: t.url, ClientAddr: publicConn.RemoteAddr().String(), } if err = msg.WriteMsg(proxyConn, startPxyMsg); err != nil { attempts += 1 proxyConn.Warn("Failed to write StartProxyMessage: %v, attempt %d", err, attempts) if attempts > 3 { // give up publicConn.Error("Too many failures starting proxy connection") return } } else { // success break } } // join the public and proxy connections bytesIn, bytesOut := conn.Join(publicConn, proxyConn) metrics.CloseConnection(t, publicConn, startTime, bytesIn, bytesOut) }
func (t *Tunnel) HandlePublicConnection(publicConn conn.Conn) { defer publicConn.Close() defer func() { if r := recover(); r != nil { publicConn.Warn("HandlePublicConnection failed with error %v", r) } }() startTime := time.Now() metrics.OpenConnection(t, publicConn) var proxyConn conn.Conn var err error for i := 0; i < (2 * proxyMaxPoolSize); i++ { // get a proxy connection if proxyConn, err = t.ctl.GetProxy(); err != nil { t.Warn("Failed to get proxy connection: %v", err) return } defer proxyConn.Close() t.Info("Got proxy connection %s", proxyConn.Id()) proxyConn.AddLogPrefix(t.Id()) // tell the client we're going to start using this proxy connection startPxyMsg := &msg.StartProxy{ Url: t.url, ClientAddr: publicConn.RemoteAddr().String(), } if err = msg.WriteMsg(proxyConn, startPxyMsg); err != nil { proxyConn.Warn("Failed to write StartProxyMessage: %v, attempt %d", err, i) proxyConn.Close() } else { // success break } } if err != nil { // give up publicConn.Error("Too many failures starting proxy connection") return } // To reduce latency handling tunnel connections, we employ the following curde heuristic: // Whenever we take a proxy connection from the pool, replace it with a new one util.PanicToError(func() { t.ctl.out <- &msg.ReqProxy{} }) // no timeouts while connections are joined proxyConn.SetDeadline(time.Time{}) // join the public and proxy connections bytesIn, bytesOut := conn.Join(publicConn, proxyConn) metrics.CloseConnection(t, publicConn, startTime, bytesIn, bytesOut) }
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() }
func (t *Tunnel) RegisterProxy(conn conn.Conn) { t.Info("Registered proxy connection %s", conn.Id()) conn.AddLogPrefix(t.Id()) t.proxies <- conn }
// Handles a new http connection from the public internet func httpHandler(c conn.Conn, proto string) { defer c.Close() defer func() { // recover from failures if r := recover(); r != nil { c.Warn("httpHandler failed with error %v", r) } }() // Make sure we detect dead connections while we decide how to multiplex c.SetDeadline(time.Now().Add(connReadTimeout)) // multiplex by extracting the Host header, the vhost library vhostConn, err := vhost.HTTP(c) if err != nil { c.Warn("Failed to read valid %s request: %v", proto, err) c.Write([]byte(BadRequest)) return } // read out the Host header and auth from the request host := strings.ToLower(vhostConn.Host()) auth := vhostConn.Request.Header.Get("Authorization") // done reading mux data, free up the request memory vhostConn.Free() // We need to read from the vhost conn now since it mucked around reading the stream c = conn.Wrap(vhostConn, "pub") // multiplex to find the right backend host c.Debug("Found hostname %s in request", host) tunnel := tunnelRegistry.Get(fmt.Sprintf("%s://%s", proto, host)) if tunnel == nil { c.Info("No tunnel found for hostname %s", host) c.Write([]byte(fmt.Sprintf(NotFound, len(host)+18, host))) return } // If the client specified http auth and it doesn't match this request's auth // then fail the request with 401 Not Authorized and request the client reissue the // request with basic authdeny the request if tunnel.req.HttpAuth != "" && auth != tunnel.req.HttpAuth { c.Info("Authentication failed: %s", auth) c.Write([]byte(NotAuthorized)) return } // dead connections will now be handled by tunnel heartbeating and the client c.SetDeadline(time.Time{}) // let the tunnel handle the connection now tunnel.HandlePublicConnection(c) }
func Main() { // parse options opts := parseArgs() // set up logging log.LogTo(opts.logto) // set up auth token if opts.authtoken == "" { opts.authtoken = LoadAuthToken() } // init client state s := &State{ status: "connecting", // unique client id id: util.RandIdOrPanic(8), // command-line options opts: opts, // metrics metrics: NewClientMetrics(), } switch opts.protocol { case "http": s.protocol = proto.NewHttp() case "tcp": s.protocol = proto.NewTcp() } // init ui ctl := ui.NewController() if opts.webport != -1 { web.NewWebView(ctl, s, opts.webport) } if opts.logto != "stdout" { term.New(ctl, s) } go reconnectingControl(s, ctl) go autoUpdate(s, ctl, opts.authtoken) quitMessage := "" ctl.Wait.Add(1) go func() { defer ctl.Wait.Done() for { select { case obj := <-ctl.Cmds: switch cmd := obj.(type) { case ui.CmdQuit: quitMessage = cmd.Message ctl.DoShutdown() return case ui.CmdRequest: go func() { var localConn conn.Conn localConn, err := conn.Dial(s.opts.localaddr, "prv", nil) if err != nil { log.Warn("Failed to open private leg %s: %v", s.opts.localaddr, err) return } //defer localConn.Close() localConn = s.protocol.WrapConn(localConn) localConn.Write(cmd.Payload) ioutil.ReadAll(localConn) }() } } } }() ctl.Wait.Wait() fmt.Println(quitMessage) }