2026-02-11 02:14:05 +01:00
|
|
|
package proxyproto
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"net"
|
2026-03-28 15:06:36 -07:00
|
|
|
"strconv"
|
2026-02-11 02:14:05 +01:00
|
|
|
"strings"
|
2026-03-28 15:06:36 -07:00
|
|
|
"time"
|
2026-02-11 02:14:05 +01:00
|
|
|
|
|
|
|
|
"github.com/coredns/caddy"
|
|
|
|
|
"github.com/coredns/coredns/core/dnsserver"
|
|
|
|
|
"github.com/coredns/coredns/plugin"
|
|
|
|
|
|
|
|
|
|
"github.com/pires/go-proxyproto"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func init() { plugin.Register("proxyproto", setup) }
|
|
|
|
|
|
|
|
|
|
func setup(c *caddy.Controller) error {
|
|
|
|
|
config := dnsserver.GetConfig(c)
|
|
|
|
|
if config.ProxyProtoConnPolicy != nil {
|
|
|
|
|
return plugin.Error("proxyproto", errors.New("proxy protocol already configured for this server instance"))
|
|
|
|
|
}
|
|
|
|
|
var (
|
2026-03-28 15:06:36 -07:00
|
|
|
allowedIPNets []*net.IPNet
|
|
|
|
|
policy = proxyproto.IGNORE
|
2026-07-13 17:34:30 -07:00
|
|
|
defaultSet bool
|
2026-03-28 15:06:36 -07:00
|
|
|
sessionTrackingTTL time.Duration
|
|
|
|
|
sessionTrackingMaxSessions int
|
plugin/proxyproto: expose PPv2 TLVs to downstream plugins as metadata (#8641)
The proxyproto plugin parsed PROXY protocol v2 headers only to recover the real
client address; the TLV vector was discarded, so downstream plugins had no way to
read values such as the AWS VPC endpoint ID, SSL client CN, or vendor-specific
custom TLVs.
Add an opt-in `tlv_metadata` option. When set, the connection's raw PPv2 TLV
vector is captured and the plugin, acting as a metadata.Provider, publishes it
under the `proxyproto/tlv` label as a hex-encoded string. Consumers decode and
split it with proxyproto.SplitTLVs. The option is off by default, so existing
deployments skip the extra per-connection processing and behave exactly as before.
PROXY protocol v1 connections, which carry no TLVs, are unaffected.
Capture spans all transports: dns:// (UDP and TCP), tls:// (DoT), https:// (DoH),
grpc://, quic:// (DoQ), and https3:// (DoH3). QUIC-based transports require
udp_session_tracking, since the PPv2 header arrives as a standalone first datagram
ahead of the QUIC handshake.
Includes unit tests for capture, the metadata provider (direct and through the
real metadata chain), v1 compatibility, and option parsing, plus README and man
page updates.
Fixes #8633
Signed-off-by: Mekias Yohannes <mekias@amazon.com>
Co-authored-by: Mekias Yohannes <mekias@amazon.com>
2026-10-15 22:28:43 -04:00
|
|
|
tlvMetadata bool
|
2026-02-11 02:14:05 +01:00
|
|
|
)
|
|
|
|
|
for c.Next() {
|
|
|
|
|
args := c.RemainingArgs()
|
|
|
|
|
if len(args) != 0 {
|
|
|
|
|
return plugin.Error("proxyproto", c.ArgErr())
|
|
|
|
|
}
|
|
|
|
|
for c.NextBlock() {
|
|
|
|
|
switch c.Val() {
|
|
|
|
|
case "allow":
|
|
|
|
|
for _, v := range c.RemainingArgs() {
|
|
|
|
|
_, ipnet, err := net.ParseCIDR(v)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return plugin.Error("proxyproto", fmt.Errorf("%s: %w", v, err))
|
|
|
|
|
}
|
|
|
|
|
allowedIPNets = append(allowedIPNets, ipnet)
|
|
|
|
|
}
|
|
|
|
|
case "default":
|
2026-07-13 17:34:30 -07:00
|
|
|
defaultSet = true
|
2026-02-11 02:14:05 +01:00
|
|
|
v := c.RemainingArgs()
|
|
|
|
|
if len(v) != 1 {
|
|
|
|
|
return plugin.Error("proxyproto", c.ArgErr())
|
|
|
|
|
}
|
|
|
|
|
switch strings.ToLower(v[0]) {
|
|
|
|
|
case "use":
|
|
|
|
|
policy = proxyproto.USE
|
|
|
|
|
case "ignore":
|
|
|
|
|
policy = proxyproto.IGNORE
|
|
|
|
|
case "reject":
|
|
|
|
|
policy = proxyproto.REJECT
|
|
|
|
|
case "skip":
|
|
|
|
|
policy = proxyproto.SKIP
|
|
|
|
|
default:
|
|
|
|
|
return plugin.Error("proxyproto", c.ArgErr())
|
|
|
|
|
}
|
2026-03-28 15:06:36 -07:00
|
|
|
case "udp_session_tracking":
|
|
|
|
|
v := c.RemainingArgs()
|
|
|
|
|
if len(v) < 1 || len(v) > 2 {
|
|
|
|
|
return plugin.Error("proxyproto", c.ArgErr())
|
|
|
|
|
}
|
|
|
|
|
d, err := time.ParseDuration(v[0])
|
|
|
|
|
if err != nil {
|
|
|
|
|
return plugin.Error("proxyproto", fmt.Errorf("udp_session_tracking: invalid duration %q: %w", v[0], err))
|
|
|
|
|
}
|
|
|
|
|
sessionTrackingTTL = d
|
|
|
|
|
if len(v) == 2 {
|
|
|
|
|
n, err := strconv.Atoi(v[1])
|
|
|
|
|
if err != nil || n <= 0 {
|
|
|
|
|
return plugin.Error("proxyproto", fmt.Errorf("udp_session_tracking: invalid max sessions %q: must be a positive integer", v[1]))
|
|
|
|
|
}
|
|
|
|
|
sessionTrackingMaxSessions = n
|
|
|
|
|
}
|
plugin/proxyproto: expose PPv2 TLVs to downstream plugins as metadata (#8641)
The proxyproto plugin parsed PROXY protocol v2 headers only to recover the real
client address; the TLV vector was discarded, so downstream plugins had no way to
read values such as the AWS VPC endpoint ID, SSL client CN, or vendor-specific
custom TLVs.
Add an opt-in `tlv_metadata` option. When set, the connection's raw PPv2 TLV
vector is captured and the plugin, acting as a metadata.Provider, publishes it
under the `proxyproto/tlv` label as a hex-encoded string. Consumers decode and
split it with proxyproto.SplitTLVs. The option is off by default, so existing
deployments skip the extra per-connection processing and behave exactly as before.
PROXY protocol v1 connections, which carry no TLVs, are unaffected.
Capture spans all transports: dns:// (UDP and TCP), tls:// (DoT), https:// (DoH),
grpc://, quic:// (DoQ), and https3:// (DoH3). QUIC-based transports require
udp_session_tracking, since the PPv2 header arrives as a standalone first datagram
ahead of the QUIC handshake.
Includes unit tests for capture, the metadata provider (direct and through the
real metadata chain), v1 compatibility, and option parsing, plus README and man
page updates.
Fixes #8633
Signed-off-by: Mekias Yohannes <mekias@amazon.com>
Co-authored-by: Mekias Yohannes <mekias@amazon.com>
2026-10-15 22:28:43 -04:00
|
|
|
case "tlv_metadata":
|
|
|
|
|
if len(c.RemainingArgs()) != 0 {
|
|
|
|
|
return plugin.Error("proxyproto", c.ArgErr())
|
|
|
|
|
}
|
|
|
|
|
tlvMetadata = true
|
2026-02-11 02:14:05 +01:00
|
|
|
default:
|
|
|
|
|
return c.Errf("unknown option '%s'", c.Val())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
config.ProxyProtoConnPolicy = func(connPolicyOptions proxyproto.ConnPolicyOptions) (proxyproto.Policy, error) {
|
|
|
|
|
if len(allowedIPNets) == 0 {
|
2026-07-13 17:34:30 -07:00
|
|
|
if defaultSet {
|
|
|
|
|
return policy, nil
|
|
|
|
|
}
|
2026-02-11 02:14:05 +01:00
|
|
|
return proxyproto.USE, nil
|
|
|
|
|
}
|
|
|
|
|
h, _, _ := net.SplitHostPort(connPolicyOptions.Upstream.String())
|
|
|
|
|
ip := net.ParseIP(h)
|
|
|
|
|
if ip == nil {
|
|
|
|
|
return proxyproto.REJECT, nil
|
|
|
|
|
}
|
|
|
|
|
for _, ipnet := range allowedIPNets {
|
|
|
|
|
if ipnet.Contains(ip) {
|
|
|
|
|
return proxyproto.USE, nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return policy, nil
|
|
|
|
|
}
|
2026-03-28 15:06:36 -07:00
|
|
|
config.ProxyProtoUDPSessionTrackingTTL = sessionTrackingTTL
|
|
|
|
|
config.ProxyProtoUDPSessionTrackingMaxSessions = sessionTrackingMaxSessions
|
plugin/proxyproto: expose PPv2 TLVs to downstream plugins as metadata (#8641)
The proxyproto plugin parsed PROXY protocol v2 headers only to recover the real
client address; the TLV vector was discarded, so downstream plugins had no way to
read values such as the AWS VPC endpoint ID, SSL client CN, or vendor-specific
custom TLVs.
Add an opt-in `tlv_metadata` option. When set, the connection's raw PPv2 TLV
vector is captured and the plugin, acting as a metadata.Provider, publishes it
under the `proxyproto/tlv` label as a hex-encoded string. Consumers decode and
split it with proxyproto.SplitTLVs. The option is off by default, so existing
deployments skip the extra per-connection processing and behave exactly as before.
PROXY protocol v1 connections, which carry no TLVs, are unaffected.
Capture spans all transports: dns:// (UDP and TCP), tls:// (DoT), https:// (DoH),
grpc://, quic:// (DoQ), and https3:// (DoH3). QUIC-based transports require
udp_session_tracking, since the PPv2 header arrives as a standalone first datagram
ahead of the QUIC handshake.
Includes unit tests for capture, the metadata provider (direct and through the
real metadata chain), v1 compatibility, and option parsing, plus README and man
page updates.
Fixes #8633
Signed-off-by: Mekias Yohannes <mekias@amazon.com>
Co-authored-by: Mekias Yohannes <mekias@amazon.com>
2026-10-15 22:28:43 -04:00
|
|
|
config.ProxyProtoTLVMetadata = tlvMetadata
|
|
|
|
|
if tlvMetadata {
|
|
|
|
|
// Add a pass-through handler that implements metadata.Provider so the
|
|
|
|
|
// captured PPv2 TLVs are exposed as the "proxyproto/tlv" label.
|
|
|
|
|
config.AddPlugin(func(next plugin.Handler) plugin.Handler {
|
|
|
|
|
return handler{Next: next}
|
|
|
|
|
})
|
|
|
|
|
}
|
2026-02-11 02:14:05 +01:00
|
|
|
return nil
|
|
|
|
|
}
|