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(), } } } } }
// 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 (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) //log.Info("Proxy authId=%s bytesIn=%d, bytesOut=%d\n", t.ctl.userInfo.Uc.UserId, bytesIn, bytesOut) atomic.AddInt32(&t.ctl.userInfo.TransPerDay, int32(bytesIn+bytesOut)) atomic.AddInt32(&t.ctl.userInfo.TransAll, int32(bytesIn+bytesOut)) }
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() }
/** * 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 (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 (c *Control) writer() { defer func() { if err := recover(); err != nil { c.conn.Info("Control::writer failed with error %v: %s", err, debug.Stack()) } }() // kill everything if the writer() stops defer c.shutdown.Begin() // notify that we've flushed all messages defer c.writerShutdown.Complete() // write messages to the control channel for m := range c.out { c.conn.SetWriteDeadline(time.Now().Add(controlWriteTimeout)) if err := msg.WriteMsg(c.conn, m); err != nil { panic(err) } } }
// 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 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() }
/** * 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 (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()) } // remove from the control registry controlRegistry.Del(c.id) // mark that we're shutting down atomic.StoreInt32(&c.closing, 1) // stop the reaping timer reap.Stop() // close the connection c.conn.Close() // shutdown all of the tunnels for _, t := range c.tunnels { t.Shutdown() } // we're safe to close(c.proxies) because c.closing // protects us inside of RegisterProxy close(c.proxies) // shut down all of the proxy connections for p := range c.proxies { p.Close() } }() for { select { case m := <-c.out: msg.WriteMsg(c.conn, m) case m := <-c.stop: if m != nil { msg.WriteMsg(c.conn, m) } return case <-reap.C: if time.Since(c.lastPing) > pingTimeoutInterval { c.conn.Info("Lost heartbeat") return } case mRaw := <-c.in: switch m := mRaw.(type) { case *msg.ReqTunnel: c.registerTunnel(m) case *msg.Ping: c.lastPing = time.Now() c.out <- &msg.Pong{} } } } }