2020-10-08 10:12:26 +00:00
|
|
|
package connection
|
|
|
|
|
|
|
|
import (
|
2021-07-16 15:14:37 +00:00
|
|
|
"context"
|
2022-03-22 12:46:07 +00:00
|
|
|
"encoding/base64"
|
2021-01-15 17:25:56 +00:00
|
|
|
"fmt"
|
2020-10-08 10:12:26 +00:00
|
|
|
"io"
|
2021-10-08 12:48:20 +00:00
|
|
|
"math"
|
2023-03-29 16:21:19 +00:00
|
|
|
"net"
|
2020-10-08 10:12:26 +00:00
|
|
|
"net/http"
|
|
|
|
"strconv"
|
2020-10-23 14:49:24 +00:00
|
|
|
"strings"
|
2020-10-08 10:12:26 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/google/uuid"
|
2022-03-22 12:46:07 +00:00
|
|
|
"github.com/pkg/errors"
|
2021-03-23 14:30:43 +00:00
|
|
|
|
2022-04-06 23:20:29 +00:00
|
|
|
"github.com/cloudflare/cloudflared/tracing"
|
2021-03-23 14:30:43 +00:00
|
|
|
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
2021-07-16 15:14:37 +00:00
|
|
|
"github.com/cloudflare/cloudflared/websocket"
|
2020-10-08 10:12:26 +00:00
|
|
|
)
|
|
|
|
|
2021-07-16 15:14:37 +00:00
|
|
|
const (
|
|
|
|
lbProbeUserAgentPrefix = "Mozilla/5.0 (compatible; Cloudflare-Traffic-Manager/1.0; +https://www.cloudflare.com/traffic-manager/;"
|
|
|
|
LogFieldConnIndex = "connIndex"
|
2021-09-21 10:02:59 +00:00
|
|
|
MaxGracePeriod = time.Minute * 3
|
2021-10-08 12:48:20 +00:00
|
|
|
MaxConcurrentStreams = math.MaxUint32
|
2022-08-16 11:21:58 +00:00
|
|
|
|
|
|
|
contentTypeHeader = "content-type"
|
|
|
|
sseContentType = "text/event-stream"
|
|
|
|
grpcContentType = "application/grpc"
|
2021-07-16 15:14:37 +00:00
|
|
|
)
|
|
|
|
|
2022-08-16 11:21:58 +00:00
|
|
|
var (
|
|
|
|
switchingProtocolText = fmt.Sprintf("%d %s", http.StatusSwitchingProtocols, http.StatusText(http.StatusSwitchingProtocols))
|
|
|
|
flushableContentTypes = []string{sseContentType, grpcContentType}
|
|
|
|
)
|
2020-10-08 10:12:26 +00:00
|
|
|
|
2022-02-11 10:49:06 +00:00
|
|
|
type Orchestrator interface {
|
|
|
|
UpdateConfig(version int32, config []byte) *pogs.UpdateConfigurationResponse
|
2022-04-27 10:51:06 +00:00
|
|
|
GetConfigJSON() ([]byte, error)
|
2022-02-11 10:49:06 +00:00
|
|
|
GetOriginProxy() (OriginProxy, error)
|
2022-09-30 09:43:39 +00:00
|
|
|
WarpRoutingEnabled() (enabled bool)
|
2020-10-08 10:12:26 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 09:42:07 +00:00
|
|
|
type NamedTunnelProperties struct {
|
2021-07-09 17:52:41 +00:00
|
|
|
Credentials Credentials
|
|
|
|
Client pogs.ClientInfo
|
|
|
|
QuickTunnelUrl string
|
2020-11-23 21:36:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Credentials are stored in the credentials file and contain all info needed to run a tunnel.
|
|
|
|
type Credentials struct {
|
|
|
|
AccountTag string
|
|
|
|
TunnelSecret []byte
|
|
|
|
TunnelID uuid.UUID
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Credentials) Auth() pogs.TunnelAuth {
|
|
|
|
return pogs.TunnelAuth{
|
|
|
|
AccountTag: c.AccountTag,
|
|
|
|
TunnelSecret: c.TunnelSecret,
|
|
|
|
}
|
2020-10-08 10:12:26 +00:00
|
|
|
}
|
|
|
|
|
2022-02-21 11:49:13 +00:00
|
|
|
// TunnelToken are Credentials but encoded with custom fields namings.
|
|
|
|
type TunnelToken struct {
|
|
|
|
AccountTag string `json:"a"`
|
|
|
|
TunnelSecret []byte `json:"s"`
|
|
|
|
TunnelID uuid.UUID `json:"t"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t TunnelToken) Credentials() Credentials {
|
|
|
|
return Credentials{
|
|
|
|
AccountTag: t.AccountTag,
|
|
|
|
TunnelSecret: t.TunnelSecret,
|
|
|
|
TunnelID: t.TunnelID,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-22 12:46:07 +00:00
|
|
|
func (t TunnelToken) Encode() (string, error) {
|
|
|
|
val, err := json.Marshal(t)
|
|
|
|
if err != nil {
|
|
|
|
return "", errors.Wrap(err, "could not JSON encode token")
|
|
|
|
}
|
|
|
|
|
|
|
|
return base64.StdEncoding.EncodeToString(val), nil
|
|
|
|
}
|
|
|
|
|
2022-02-07 09:42:07 +00:00
|
|
|
type ClassicTunnelProperties struct {
|
2020-10-08 10:12:26 +00:00
|
|
|
Hostname string
|
|
|
|
OriginCert []byte
|
|
|
|
// feature-flag to use new edge reconnect tokens
|
|
|
|
UseReconnectToken bool
|
|
|
|
}
|
|
|
|
|
2021-01-11 19:59:45 +00:00
|
|
|
// Type indicates the connection type of the connection.
|
|
|
|
type Type int
|
|
|
|
|
|
|
|
const (
|
|
|
|
TypeWebsocket Type = iota
|
|
|
|
TypeTCP
|
2021-01-15 17:25:56 +00:00
|
|
|
TypeControlStream
|
2021-01-11 19:59:45 +00:00
|
|
|
TypeHTTP
|
2022-03-04 11:35:57 +00:00
|
|
|
TypeConfiguration
|
2021-01-11 19:59:45 +00:00
|
|
|
)
|
|
|
|
|
2021-01-15 17:25:56 +00:00
|
|
|
// ShouldFlush returns whether this kind of connection should actively flush data
|
|
|
|
func (t Type) shouldFlush() bool {
|
|
|
|
switch t {
|
|
|
|
case TypeWebsocket, TypeTCP, TypeControlStream:
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t Type) String() string {
|
|
|
|
switch t {
|
|
|
|
case TypeWebsocket:
|
|
|
|
return "websocket"
|
|
|
|
case TypeTCP:
|
|
|
|
return "tcp"
|
|
|
|
case TypeControlStream:
|
|
|
|
return "control stream"
|
|
|
|
case TypeHTTP:
|
|
|
|
return "http"
|
|
|
|
default:
|
|
|
|
return fmt.Sprintf("Unknown Type %d", t)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-16 15:14:37 +00:00
|
|
|
// OriginProxy is how data flows from cloudflared to the origin services running behind it.
|
2020-12-09 21:46:53 +00:00
|
|
|
type OriginProxy interface {
|
2022-07-26 21:00:53 +00:00
|
|
|
ProxyHTTP(w ResponseWriter, tr *tracing.TracedHTTPRequest, isWebsocket bool) error
|
2021-07-16 15:14:37 +00:00
|
|
|
ProxyTCP(ctx context.Context, rwa ReadWriteAcker, req *TCPRequest) error
|
|
|
|
}
|
|
|
|
|
|
|
|
// TCPRequest defines the input format needed to perform a TCP proxy.
|
|
|
|
type TCPRequest struct {
|
2022-07-26 21:00:53 +00:00
|
|
|
Dest string
|
|
|
|
CFRay string
|
|
|
|
LBProbe bool
|
|
|
|
FlowID string
|
|
|
|
CfTraceID string
|
2023-02-22 14:52:44 +00:00
|
|
|
ConnIndex uint8
|
2021-07-16 15:14:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ReadWriteAcker is a readwriter with the ability to Acknowledge to the downstream (edge) that the origin has
|
|
|
|
// accepted the connection.
|
|
|
|
type ReadWriteAcker interface {
|
|
|
|
io.ReadWriter
|
2022-07-26 21:00:53 +00:00
|
|
|
AckConnection(tracePropagation string) error
|
2021-07-16 15:14:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// HTTPResponseReadWriteAcker is an HTTP implementation of ReadWriteAcker.
|
|
|
|
type HTTPResponseReadWriteAcker struct {
|
|
|
|
r io.Reader
|
|
|
|
w ResponseWriter
|
|
|
|
req *http.Request
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewHTTPResponseReadWriterAcker returns a new instance of HTTPResponseReadWriteAcker.
|
|
|
|
func NewHTTPResponseReadWriterAcker(w ResponseWriter, req *http.Request) *HTTPResponseReadWriteAcker {
|
|
|
|
return &HTTPResponseReadWriteAcker{
|
|
|
|
r: req.Body,
|
|
|
|
w: w,
|
|
|
|
req: req,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *HTTPResponseReadWriteAcker) Read(p []byte) (int, error) {
|
|
|
|
return h.r.Read(p)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *HTTPResponseReadWriteAcker) Write(p []byte) (int, error) {
|
|
|
|
return h.w.Write(p)
|
|
|
|
}
|
|
|
|
|
|
|
|
// AckConnection acks an HTTP connection by sending a switch protocols status code that enables the caller to
|
|
|
|
// upgrade to streams.
|
2022-07-26 21:00:53 +00:00
|
|
|
func (h *HTTPResponseReadWriteAcker) AckConnection(tracePropagation string) error {
|
2021-07-16 15:14:37 +00:00
|
|
|
resp := &http.Response{
|
|
|
|
Status: switchingProtocolText,
|
|
|
|
StatusCode: http.StatusSwitchingProtocols,
|
|
|
|
ContentLength: -1,
|
2022-08-11 21:54:12 +00:00
|
|
|
Header: http.Header{},
|
2021-07-16 15:14:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if secWebsocketKey := h.req.Header.Get("Sec-WebSocket-Key"); secWebsocketKey != "" {
|
|
|
|
resp.Header = websocket.NewResponseHeader(h.req)
|
|
|
|
}
|
|
|
|
|
2022-07-26 21:00:53 +00:00
|
|
|
if tracePropagation != "" {
|
|
|
|
resp.Header.Add(tracing.CanonicalCloudflaredTracingHeader, tracePropagation)
|
|
|
|
}
|
|
|
|
|
2021-07-16 15:14:37 +00:00
|
|
|
return h.w.WriteRespHeaders(resp.StatusCode, resp.Header)
|
2020-10-08 10:12:26 +00:00
|
|
|
}
|
|
|
|
|
2023-03-29 16:21:19 +00:00
|
|
|
// localProxyConnection emulates an incoming connection to cloudflared as a net.Conn.
|
|
|
|
// Used when handling a "hijacked" connection from connection.ResponseWriter
|
|
|
|
type localProxyConnection struct {
|
|
|
|
io.ReadWriteCloser
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *localProxyConnection) Read(b []byte) (int, error) {
|
|
|
|
return c.ReadWriteCloser.Read(b)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *localProxyConnection) Write(b []byte) (int, error) {
|
|
|
|
return c.ReadWriteCloser.Write(b)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *localProxyConnection) Close() error {
|
|
|
|
return c.ReadWriteCloser.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *localProxyConnection) LocalAddr() net.Addr {
|
|
|
|
// Unused LocalAddr
|
|
|
|
return &net.TCPAddr{IP: net.IPv6loopback, Port: 0, Zone: ""}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *localProxyConnection) RemoteAddr() net.Addr {
|
|
|
|
// Unused RemoteAddr
|
|
|
|
return &net.TCPAddr{IP: net.IPv6loopback, Port: 0, Zone: ""}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *localProxyConnection) SetDeadline(t time.Time) error {
|
|
|
|
// ignored since we can't set the read/write Deadlines for the tunnel back to origintunneld
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *localProxyConnection) SetReadDeadline(t time.Time) error {
|
|
|
|
// ignored since we can't set the read/write Deadlines for the tunnel back to origintunneld
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *localProxyConnection) SetWriteDeadline(t time.Time) error {
|
|
|
|
// ignored since we can't set the read/write Deadlines for the tunnel back to origintunneld
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ResponseWriter is the response path for a request back through cloudflared's tunnel.
|
2020-10-08 10:12:26 +00:00
|
|
|
type ResponseWriter interface {
|
2020-12-09 21:46:53 +00:00
|
|
|
WriteRespHeaders(status int, header http.Header) error
|
2022-08-16 11:21:58 +00:00
|
|
|
AddTrailer(trailerName, trailerValue string)
|
2023-03-07 18:41:15 +00:00
|
|
|
http.ResponseWriter
|
2023-03-29 16:21:19 +00:00
|
|
|
http.Hijacker
|
2021-02-11 14:36:42 +00:00
|
|
|
io.Writer
|
2020-10-08 10:12:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type ConnectedFuse interface {
|
|
|
|
Connected()
|
|
|
|
IsConnected() bool
|
|
|
|
}
|
|
|
|
|
2022-08-16 11:21:58 +00:00
|
|
|
// Helper method to let the caller know what content-types should require a flush on every
|
|
|
|
// write to a ResponseWriter.
|
|
|
|
func shouldFlush(headers http.Header) bool {
|
|
|
|
if contentType := headers.Get(contentTypeHeader); contentType != "" {
|
|
|
|
contentType = strings.ToLower(contentType)
|
|
|
|
for _, c := range flushableContentTypes {
|
|
|
|
if strings.HasPrefix(contentType, c) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
2020-11-18 11:53:59 +00:00
|
|
|
}
|
2022-08-16 11:21:58 +00:00
|
|
|
|
2020-11-18 11:53:59 +00:00
|
|
|
return false
|
2020-10-08 10:12:26 +00:00
|
|
|
}
|
2020-10-23 14:49:24 +00:00
|
|
|
|
2020-11-18 11:53:59 +00:00
|
|
|
func uint8ToString(input uint8) string {
|
|
|
|
return strconv.FormatUint(uint64(input), 10)
|
2020-10-23 14:49:24 +00:00
|
|
|
}
|
2021-07-16 15:14:37 +00:00
|
|
|
|
|
|
|
func FindCfRayHeader(req *http.Request) string {
|
|
|
|
return req.Header.Get("Cf-Ray")
|
|
|
|
}
|
|
|
|
|
|
|
|
func IsLBProbeRequest(req *http.Request) bool {
|
|
|
|
return strings.HasPrefix(req.UserAgent(), lbProbeUserAgentPrefix)
|
|
|
|
}
|