/* * Hearbeating to ensure our connection ngrokd is still live */ func heartbeat(lastPongAddr *int64, c conn.Conn) { lastPing := time.Unix(atomic.LoadInt64(lastPongAddr)-1, 0) ping := time.NewTicker(pingInterval) pongCheck := time.NewTicker(time.Second) defer func() { c.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(c, &msg.PingMsg{}) if err != nil { c.Debug("Got error %v when writing PingMsg", err) return } lastPing = time.Now() } } }
// 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 { tunnel = tunnelRegistry.Get(fmt.Sprintf("%s://%s%s", proto, host, opts.httpAddr)) } 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 (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() } }
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) }