2019-06-05 15:08:55 +00:00
package streamhandler
import (
2019-06-18 16:47:29 +00:00
"context"
2019-06-05 15:08:55 +00:00
"fmt"
"net/http"
2019-06-21 00:05:39 +00:00
"strconv"
2019-06-05 15:08:55 +00:00
"github.com/cloudflare/cloudflared/h2mux"
2020-04-29 20:51:32 +00:00
"github.com/cloudflare/cloudflared/logger"
2019-06-05 15:08:55 +00:00
"github.com/cloudflare/cloudflared/tunnelhostnamemapper"
2019-06-18 16:47:29 +00:00
"github.com/cloudflare/cloudflared/tunnelrpc"
2019-06-05 15:08:55 +00:00
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
2019-06-21 00:05:39 +00:00
"github.com/pkg/errors"
2019-06-18 16:47:29 +00:00
"zombiezen.com/go/capnproto2/rpc"
2019-06-05 15:08:55 +00:00
)
2019-06-21 00:05:39 +00:00
const (
statusPseudoHeader = ":status"
)
type httpErrorStatus struct {
status string
text [ ] byte
}
var (
statusBadRequest = newHTTPErrorStatus ( http . StatusBadRequest )
statusNotFound = newHTTPErrorStatus ( http . StatusNotFound )
statusBadGateway = newHTTPErrorStatus ( http . StatusBadGateway )
)
func newHTTPErrorStatus ( status int ) * httpErrorStatus {
return & httpErrorStatus {
status : strconv . Itoa ( status ) ,
text : [ ] byte ( http . StatusText ( status ) ) ,
}
}
2019-06-05 15:08:55 +00:00
// StreamHandler handles new stream opened by the edge. The streams can be used to proxy requests or make RPC.
type StreamHandler struct {
// newConfigChan is a send-only channel to notify Supervisor of a new ClientConfig
newConfigChan chan <- * pogs . ClientConfig
// useConfigResultChan is a receive-only channel for Supervisor to communicate the result of applying a new ClientConfig
useConfigResultChan <- chan * pogs . UseConfigurationResult
// originMapper maps tunnel hostname to origin service
tunnelHostnameMapper * tunnelhostnamemapper . TunnelHostnameMapper
2020-04-29 20:51:32 +00:00
logger logger . Service
2019-06-05 15:08:55 +00:00
}
// NewStreamHandler creates a new StreamHandler
func NewStreamHandler ( newConfigChan chan <- * pogs . ClientConfig ,
useConfigResultChan <- chan * pogs . UseConfigurationResult ,
2020-04-29 20:51:32 +00:00
logger logger . Service ,
2019-06-05 15:08:55 +00:00
) * StreamHandler {
return & StreamHandler {
newConfigChan : newConfigChan ,
useConfigResultChan : useConfigResultChan ,
tunnelHostnameMapper : tunnelhostnamemapper . NewTunnelHostnameMapper ( ) ,
2020-04-29 20:51:32 +00:00
logger : logger ,
2019-06-05 15:08:55 +00:00
}
}
2019-06-18 16:47:29 +00:00
// UseConfiguration implements ClientService
func ( s * StreamHandler ) UseConfiguration ( ctx context . Context , config * pogs . ClientConfig ) ( * pogs . UseConfigurationResult , error ) {
select {
case <- ctx . Done ( ) :
err := fmt . Errorf ( "Timeout while sending new config to Supervisor" )
2020-04-29 20:51:32 +00:00
s . logger . Errorf ( "streamHandler: %s" , err )
2019-06-18 16:47:29 +00:00
return nil , err
case s . newConfigChan <- config :
}
select {
case <- ctx . Done ( ) :
err := fmt . Errorf ( "Timeout applying new configuration" )
2020-04-29 20:51:32 +00:00
s . logger . Errorf ( "streamHandler: %s" , err )
2019-06-18 16:47:29 +00:00
return nil , err
case result := <- s . useConfigResultChan :
return result , nil
}
}
// UpdateConfig replaces current originmapper mapping with mappings from newConfig
func ( s * StreamHandler ) UpdateConfig ( newConfig [ ] * pogs . ReverseProxyConfig ) ( failedConfigs [ ] * pogs . FailedConfig ) {
2019-08-28 15:41:39 +00:00
// Delete old configs that aren't in the `newConfig`
toRemove := s . tunnelHostnameMapper . ToRemove ( newConfig )
for _ , hostnameToRemove := range toRemove {
s . tunnelHostnameMapper . Delete ( hostnameToRemove )
}
// Add new configs that weren't in the old mapper
toAdd := s . tunnelHostnameMapper . ToAdd ( newConfig )
for _ , tunnelConfig := range toAdd {
2019-06-18 16:47:29 +00:00
tunnelHostname := tunnelConfig . TunnelHostname
2020-04-29 20:51:32 +00:00
originSerice , err := tunnelConfig . OriginConfig . Service ( s . logger )
2019-06-18 16:47:29 +00:00
if err != nil {
2020-04-29 20:51:32 +00:00
s . logger . Errorf ( "streamHandler: tunnelHostname: %s Invalid origin service config: %s" , tunnelHostname , err )
2019-06-18 16:47:29 +00:00
failedConfigs = append ( failedConfigs , & pogs . FailedConfig {
Config : tunnelConfig ,
Reason : tunnelConfig . FailReason ( err ) ,
} )
continue
}
s . tunnelHostnameMapper . Add ( tunnelConfig . TunnelHostname , originSerice )
2020-04-29 20:51:32 +00:00
s . logger . Infof ( "streamHandler: tunnelHostname: %s New origin service config: %v" , tunnelHostname , originSerice . Summary ( ) )
2019-06-18 16:47:29 +00:00
}
return
}
2019-06-05 15:08:55 +00:00
// ServeStream implements MuxedStreamHandler interface
func ( s * StreamHandler ) ServeStream ( stream * h2mux . MuxedStream ) error {
if stream . IsRPCStream ( ) {
2019-06-18 16:47:29 +00:00
return s . serveRPC ( stream )
2019-06-05 15:08:55 +00:00
}
2019-06-21 00:05:39 +00:00
if err := s . serveRequest ( stream ) ; err != nil {
2020-04-29 20:51:32 +00:00
s . logger . Errorf ( "streamHandler: %s" , err )
2019-06-21 00:05:39 +00:00
return err
}
return nil
2019-06-05 15:08:55 +00:00
}
2019-06-18 16:47:29 +00:00
func ( s * StreamHandler ) serveRPC ( stream * h2mux . MuxedStream ) error {
stream . WriteHeaders ( [ ] h2mux . Header { { Name : ":status" , Value : "200" } } )
main := pogs . ClientService_ServerToClient ( s )
rpcConn := rpc . NewConn (
2020-04-29 20:51:32 +00:00
tunnelrpc . NewTransportLogger ( s . logger , rpc . StreamTransport ( stream ) ) ,
2019-06-18 16:47:29 +00:00
rpc . MainInterface ( main . Client ) ,
2020-04-29 20:51:32 +00:00
tunnelrpc . ConnLog ( s . logger ) ,
2019-06-18 16:47:29 +00:00
)
return rpcConn . Wait ( )
}
2019-06-05 15:08:55 +00:00
func ( s * StreamHandler ) serveRequest ( stream * h2mux . MuxedStream ) error {
tunnelHostname := stream . TunnelHostname ( )
if ! tunnelHostname . IsSet ( ) {
2019-06-21 00:05:39 +00:00
s . writeErrorStatus ( stream , statusBadRequest )
return fmt . Errorf ( "stream doesn't have tunnelHostname" )
2019-06-05 15:08:55 +00:00
}
originService , ok := s . tunnelHostnameMapper . Get ( tunnelHostname )
if ! ok {
2019-06-21 00:05:39 +00:00
s . writeErrorStatus ( stream , statusNotFound )
return fmt . Errorf ( "cannot map tunnel hostname %s to origin" , tunnelHostname )
2019-06-05 15:08:55 +00:00
}
2019-06-20 16:18:59 +00:00
req , err := createRequest ( stream , originService . URL ( ) )
2019-06-05 15:08:55 +00:00
if err != nil {
2019-06-21 00:05:39 +00:00
s . writeErrorStatus ( stream , statusBadRequest )
return errors . Wrap ( err , "cannot create request" )
2019-06-05 15:08:55 +00:00
}
2020-04-29 20:51:32 +00:00
cfRay := s . logRequest ( req , tunnelHostname )
s . logger . Debugf ( "streamHandler: tunnelHostname: %s CF-RAY: %s Request Headers %+v" , tunnelHostname , cfRay , req . Header )
2019-06-05 15:08:55 +00:00
resp , err := originService . Proxy ( stream , req )
if err != nil {
2019-06-21 00:05:39 +00:00
s . writeErrorStatus ( stream , statusBadGateway )
return errors . Wrap ( err , "cannot proxy request" )
2019-06-05 15:08:55 +00:00
}
2020-04-29 20:51:32 +00:00
s . logger . Debugf ( "streamHandler: tunnelHostname: %s CF-RAY: %s status: %s Response Headers %+v" , tunnelHostname , cfRay , resp . Status , resp . Header )
2019-06-05 15:08:55 +00:00
return nil
}
2020-04-29 20:51:32 +00:00
func ( s * StreamHandler ) logRequest ( req * http . Request , tunnelHostname h2mux . TunnelHostname ) string {
2019-06-05 15:08:55 +00:00
cfRay := FindCfRayHeader ( req )
lbProbe := IsLBProbeRequest ( req )
2020-04-29 20:51:32 +00:00
logger := s . logger
2019-06-05 15:08:55 +00:00
if cfRay != "" {
2020-04-29 20:51:32 +00:00
logger . Debugf ( "streamHandler: tunnelHostname: %s CF-RAY: %s %s %s %s" , tunnelHostname , cfRay , req . Method , req . URL , req . Proto )
2019-06-05 15:08:55 +00:00
} else if lbProbe {
2020-04-29 20:51:32 +00:00
logger . Debugf ( "streamHandler: tunnelHostname: %s CF-RAY: %s Load Balancer health check %s %s %s" , tunnelHostname , cfRay , req . Method , req . URL , req . Proto )
2019-06-05 15:08:55 +00:00
} else {
2020-04-29 20:51:32 +00:00
logger . Infof ( "streamHandler: tunnelHostname: %s CF-RAY: %s Requests %v does not have CF-RAY header. Please open a support ticket with Cloudflare." , tunnelHostname , cfRay , req )
2019-06-05 15:08:55 +00:00
}
2020-04-29 20:51:32 +00:00
return cfRay
2019-06-05 15:08:55 +00:00
}
2019-06-21 00:05:39 +00:00
func ( s * StreamHandler ) writeErrorStatus ( stream * h2mux . MuxedStream , status * httpErrorStatus ) {
2020-04-09 20:59:15 +00:00
_ = stream . WriteHeaders ( [ ] h2mux . Header {
2019-06-21 00:05:39 +00:00
{
Name : statusPseudoHeader ,
Value : status . status ,
} ,
2020-04-10 19:26:09 +00:00
h2mux . CreateResponseMetaHeader ( h2mux . ResponseMetaHeaderField , h2mux . ResponseSourceCloudflared ) ,
2019-06-21 00:05:39 +00:00
} )
2020-04-09 20:59:15 +00:00
_ , _ = stream . Write ( status . text )
2019-06-21 00:05:39 +00:00
}