Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/klauspost/compress v1.15.6
github.com/klauspost/cpuid/v2 v2.0.13
github.com/lucas-clemente/quic-go v0.27.2
github.com/mastercactapus/proxyprotocol v0.0.3
github.com/mholt/acmez v1.0.2
github.com/prometheus/client_golang v1.12.1
github.com/smallstep/certificates v0.19.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,8 @@ github.com/marten-seemann/qtls-go1-17 v0.1.2 h1:JADBlm0LYiVbuSySCHeY863dNkcpMmDR
github.com/marten-seemann/qtls-go1-17 v0.1.2/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s=
github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKAHMntXmIV7kM=
github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
github.com/mastercactapus/proxyprotocol v0.0.3 h1:WpDMFKCYdF8NsoA6OrXyNKyZrzMURqqOP1PE7297RCE=
github.com/mastercactapus/proxyprotocol v0.0.3/go.mod h1:X8FRVEDZz9FkrIoL4QYTBF4Ka4ELwTv0sah0/5NxCPw=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
Expand Down
12 changes: 12 additions & 0 deletions modules/caddyhttp/reverseproxy/caddyfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,7 @@ func (h *Handler) FinalizeUnmarshalCaddyfile(helper httpcaddyfile.Helper) error
// read_buffer <size>
// write_buffer <size>
// max_response_header <size>
// proxy_protocol v1|v2
// dial_timeout <duration>
// dial_fallback_delay <duration>
// response_header_timeout <duration>
Expand Down Expand Up @@ -858,6 +859,17 @@ func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}
h.MaxResponseHeaderSize = int64(size)

case "proxy_protocol":
if !d.NextArg() {
return d.ArgErr()
}
switch proxyProtocol := d.Val(); proxyProtocol {
case "v1", "v2":
h.ProxyProtocol = proxyProtocol
default:
return d.Errf("invalid proxy protocol version '%s'", proxyProtocol)
}

case "dial_timeout":
if !d.NextArg() {
return d.ArgErr()
Expand Down
19 changes: 19 additions & 0 deletions modules/caddyhttp/reverseproxy/hosts.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,22 @@ var hosts = caddy.NewUsagePool()
// dialInfoVarKey is the key used for the variable that holds
// the dial info for the upstream connection.
const dialInfoVarKey = "reverse_proxy.dial_info"

// ProxyProtocolInfo contains information needed to write proxy protocol to a
// connection to an upstream host.
type ProxyProtocolInfo struct {
// assume network is tcp because golang http transport can only support tcp now
IP net.IP
Port int
}

// GetProxyProtocolInfo gets the proxy protocol info out of the context,
// and returns true if there was a valid value; false otherwise.
func GetProxyProtocolInfo(ctx context.Context) (ProxyProtocolInfo, bool) {
proxyProtocolInfo, ok := caddyhttp.GetVar(ctx, proxyProtocolInfoVarKey).(ProxyProtocolInfo)
return proxyProtocolInfo, ok
}

// proxyProtocolInfoVarKey is the key used for the variable that holds
// the proxy protocol info for the upstream connection.
const proxyProtocolInfoVarKey = "reverse_proxy.proxy_protocol_info"
32 changes: 32 additions & 0 deletions modules/caddyhttp/reverseproxy/httptransport.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (

"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/modules/caddytls"
"github.com/mastercactapus/proxyprotocol"
"golang.org/x/net/http2"
)

Expand Down Expand Up @@ -63,6 +64,10 @@ type HTTPTransport struct {
// Maximum number of connections per host. Default: 0 (no limit)
MaxConnsPerHost int `json:"max_conns_per_host,omitempty"`

// Which version of proxy protocol to send when connecting to
// an upstream. Default: `don't send proxy protocol`.
ProxyProtocol string `json:"proxy_protocol,omitempty"`

// How long to wait before timing out trying to connect to
// an upstream. Default: `3s`.
DialTimeout caddy.Duration `json:"dial_timeout,omitempty"`
Expand Down Expand Up @@ -201,6 +206,33 @@ func (h *HTTPTransport) NewTransport(ctx caddy.Context) (*http.Transport, error)
// decide whether to retry a request
return nil, DialError{err}
}

if proxyProtocolInfo, ok := GetProxyProtocolInfo(ctx); ok {
switch h.ProxyProtocol {
case "":
return conn, nil
case "v1":
var header proxyprotocol.HeaderV1
header.FromConn(conn, true)
header.SrcIP = proxyProtocolInfo.IP
header.SrcPort = proxyProtocolInfo.Port
_, err = header.WriteTo(conn)
case "v2":
var header proxyprotocol.HeaderV2
header.FromConn(conn, true)
header.Src = &net.TCPAddr{
IP: proxyProtocolInfo.IP,
Port: proxyProtocolInfo.Port,
}
_, err = header.WriteTo(conn)
}
if err != nil {
// identify this error as one that occurred during
// dialing, which can be important when trying to
// decide whether to retry a request
return nil, DialError{err}
}
}
return conn, nil
},
MaxConnsPerHost: h.MaxConnsPerHost,
Expand Down
27 changes: 27 additions & 0 deletions modules/caddyhttp/reverseproxy/reverseproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,33 @@ func (h *Handler) proxyLoopIteration(r *http.Request, origReq *http.Request, w h
// or satisfactorily represented in a URL
caddyhttp.SetVar(r.Context(), dialInfoVarKey, dialInfo)

var proxyProtocolInfo ProxyProtocolInfo
// using X-Forwarded-For header which is already filtered by trusted proxies
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
// addForwardedHeaders ensures xff has at least remoteAddr
xff = strings.TrimSpace(strings.Split(xff, ",")[0])
ip := net.ParseIP(xff)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't safe, X-Forwarded-For could contain a comma separated list of IP addresses.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forget that caddy set it as a string 😇, will change it.

if ip != nil {
proxyProtocolInfo.IP = ip
// X-Forwarded-For is set by caddy, not by prefix matching because ipv6 remoteAddr starts with [
if strings.Contains(r.RemoteAddr, xff) {
// addForwardedHeaders already check this error
_, p, _ := net.SplitHostPort(r.RemoteAddr)

// however port is never checked
port, err := strconv.Atoi(p)
if err != nil {
return true, fmt.Errorf("making proxy protocol info: %v", err)
}
proxyProtocolInfo.Port = port
} else {
// set to zero for unknown
proxyProtocolInfo.Port = 0
}
caddyhttp.SetVar(r.Context(), proxyProtocolInfoVarKey, dialInfo)
}
}

// set placeholders with information about this upstream
repl.Set("http.reverse_proxy.upstream.address", dialInfo.String())
repl.Set("http.reverse_proxy.upstream.hostport", dialInfo.Address)
Expand Down