mirror of
https://github.com/coredns/coredns.git
synced 2026-02-12 10:23:10 -05:00
feat(proxyproto): add proxy protocol support (#7738)
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
This commit is contained in:
@@ -10,6 +10,8 @@ import (
|
|||||||
"github.com/coredns/caddy"
|
"github.com/coredns/caddy"
|
||||||
"github.com/coredns/coredns/plugin"
|
"github.com/coredns/coredns/plugin"
|
||||||
"github.com/coredns/coredns/request"
|
"github.com/coredns/coredns/request"
|
||||||
|
|
||||||
|
"github.com/pires/go-proxyproto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config configuration for a single server.
|
// Config configuration for a single server.
|
||||||
@@ -66,6 +68,11 @@ type Config struct {
|
|||||||
// This is nil if not specified, allowing for a default to be used.
|
// This is nil if not specified, allowing for a default to be used.
|
||||||
MaxQUICWorkerPoolSize *int
|
MaxQUICWorkerPoolSize *int
|
||||||
|
|
||||||
|
// ProxyProtoConnPolicy is the function that will be used to
|
||||||
|
// configure the PROXY protocol settings on listeners.
|
||||||
|
// If nil, PROXY protocol is disabled.
|
||||||
|
ProxyProtoConnPolicy proxyproto.ConnPolicyFunc
|
||||||
|
|
||||||
// MaxGRPCStreams defines the maximum number of concurrent streams per gRPC connection.
|
// MaxGRPCStreams defines the maximum number of concurrent streams per gRPC connection.
|
||||||
// This is nil if not specified, allowing for a default to be used.
|
// This is nil if not specified, allowing for a default to be used.
|
||||||
MaxGRPCStreams *int
|
MaxGRPCStreams *int
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/coredns/coredns/plugin/metrics/vars"
|
"github.com/coredns/coredns/plugin/metrics/vars"
|
||||||
"github.com/coredns/coredns/plugin/pkg/edns"
|
"github.com/coredns/coredns/plugin/pkg/edns"
|
||||||
"github.com/coredns/coredns/plugin/pkg/log"
|
"github.com/coredns/coredns/plugin/pkg/log"
|
||||||
|
cproxyproto "github.com/coredns/coredns/plugin/pkg/proxyproto"
|
||||||
"github.com/coredns/coredns/plugin/pkg/rcode"
|
"github.com/coredns/coredns/plugin/pkg/rcode"
|
||||||
"github.com/coredns/coredns/plugin/pkg/reuseport"
|
"github.com/coredns/coredns/plugin/pkg/reuseport"
|
||||||
"github.com/coredns/coredns/plugin/pkg/trace"
|
"github.com/coredns/coredns/plugin/pkg/trace"
|
||||||
@@ -24,6 +25,7 @@ import (
|
|||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
ot "github.com/opentracing/opentracing-go"
|
ot "github.com/opentracing/opentracing-go"
|
||||||
|
"github.com/pires/go-proxyproto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server represents an instance of a server, which serves
|
// Server represents an instance of a server, which serves
|
||||||
@@ -37,6 +39,8 @@ type Server struct {
|
|||||||
ReadTimeout time.Duration // Read timeout for TCP
|
ReadTimeout time.Duration // Read timeout for TCP
|
||||||
WriteTimeout time.Duration // Write timeout for TCP
|
WriteTimeout time.Duration // Write timeout for TCP
|
||||||
|
|
||||||
|
connPolicy proxyproto.ConnPolicyFunc // Proxy Protocol connection policy function
|
||||||
|
|
||||||
server [2]*dns.Server // 0 is a net.Listener, 1 is a net.PacketConn (a *UDPConn) in our case.
|
server [2]*dns.Server // 0 is a net.Listener, 1 is a net.PacketConn (a *UDPConn) in our case.
|
||||||
m sync.Mutex // protects the servers
|
m sync.Mutex // protects the servers
|
||||||
|
|
||||||
@@ -123,6 +127,9 @@ func NewServer(addr string, group []*Config) (*Server, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
site.pluginChain = stack
|
site.pluginChain = stack
|
||||||
|
if site.ProxyProtoConnPolicy != nil {
|
||||||
|
s.connPolicy = site.ProxyProtoConnPolicy
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !s.debug {
|
if !s.debug {
|
||||||
@@ -181,6 +188,9 @@ func (s *Server) Listen() (net.Listener, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if s.connPolicy != nil {
|
||||||
|
l = &proxyproto.Listener{Listener: l, ConnPolicy: s.connPolicy}
|
||||||
|
}
|
||||||
return l, nil
|
return l, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,7 +205,9 @@ func (s *Server) ListenPacket() (net.PacketConn, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if s.connPolicy != nil {
|
||||||
|
p = &cproxyproto.PacketConn{PacketConn: p, ConnPolicy: s.connPolicy}
|
||||||
|
}
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc"
|
"github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"github.com/opentracing/opentracing-go"
|
"github.com/opentracing/opentracing-go"
|
||||||
|
"github.com/pires/go-proxyproto"
|
||||||
"golang.org/x/net/netutil"
|
"golang.org/x/net/netutil"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/peer"
|
"google.golang.org/grpc/peer"
|
||||||
@@ -136,6 +137,9 @@ func (s *ServergRPC) Listen() (net.Listener, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if s.connPolicy != nil {
|
||||||
|
l = &proxyproto.Listener{Listener: l, ConnPolicy: s.connPolicy}
|
||||||
|
}
|
||||||
return l, nil
|
return l, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/coredns/coredns/plugin/pkg/reuseport"
|
"github.com/coredns/coredns/plugin/pkg/reuseport"
|
||||||
"github.com/coredns/coredns/plugin/pkg/transport"
|
"github.com/coredns/coredns/plugin/pkg/transport"
|
||||||
|
|
||||||
|
"github.com/pires/go-proxyproto"
|
||||||
"golang.org/x/net/netutil"
|
"golang.org/x/net/netutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -136,6 +137,9 @@ func (s *ServerHTTPS) Listen() (net.Listener, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if s.connPolicy != nil {
|
||||||
|
l = &proxyproto.Listener{Listener: l, ConnPolicy: s.connPolicy}
|
||||||
|
}
|
||||||
return l, nil
|
return l, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/coredns/coredns/plugin/metrics/vars"
|
"github.com/coredns/coredns/plugin/metrics/vars"
|
||||||
"github.com/coredns/coredns/plugin/pkg/dnsutil"
|
"github.com/coredns/coredns/plugin/pkg/dnsutil"
|
||||||
"github.com/coredns/coredns/plugin/pkg/doh"
|
"github.com/coredns/coredns/plugin/pkg/doh"
|
||||||
|
cproxyproto "github.com/coredns/coredns/plugin/pkg/proxyproto"
|
||||||
"github.com/coredns/coredns/plugin/pkg/response"
|
"github.com/coredns/coredns/plugin/pkg/response"
|
||||||
"github.com/coredns/coredns/plugin/pkg/reuseport"
|
"github.com/coredns/coredns/plugin/pkg/reuseport"
|
||||||
"github.com/coredns/coredns/plugin/pkg/transport"
|
"github.com/coredns/coredns/plugin/pkg/transport"
|
||||||
@@ -89,7 +90,7 @@ func NewServerHTTPS3(addr string, group []*Config) (*ServerHTTPS3, error) {
|
|||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsConfig,
|
||||||
EnableDatagrams: true,
|
EnableDatagrams: true,
|
||||||
QUICConfig: qconf,
|
QUICConfig: qconf,
|
||||||
//Logger: stdlog.New(&loggerAdapter{}, "", 0), TODO: Fix it
|
// Logger: stdlog.New(&loggerAdapter{}, "", 0), TODO: Fix it
|
||||||
}
|
}
|
||||||
|
|
||||||
sh := &ServerHTTPS3{
|
sh := &ServerHTTPS3{
|
||||||
@@ -110,7 +111,14 @@ var _ caddy.GracefulServer = &ServerHTTPS3{}
|
|||||||
|
|
||||||
// ListenPacket opens the UDP socket for QUIC.
|
// ListenPacket opens the UDP socket for QUIC.
|
||||||
func (s *ServerHTTPS3) ListenPacket() (net.PacketConn, error) {
|
func (s *ServerHTTPS3) ListenPacket() (net.PacketConn, error) {
|
||||||
return reuseport.ListenPacket("udp", s.Addr[len(transport.HTTPS3+"://"):])
|
p, err := reuseport.ListenPacket("udp", s.Addr[len(transport.HTTPS3+"://"):])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if s.connPolicy != nil {
|
||||||
|
p = &cproxyproto.PacketConn{PacketConn: p, ConnPolicy: s.connPolicy}
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServePacket starts serving QUIC+HTTP/3 on an existing UDP socket.
|
// ServePacket starts serving QUIC+HTTP/3 on an existing UDP socket.
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/coredns/coredns/plugin/metrics/vars"
|
"github.com/coredns/coredns/plugin/metrics/vars"
|
||||||
clog "github.com/coredns/coredns/plugin/pkg/log"
|
clog "github.com/coredns/coredns/plugin/pkg/log"
|
||||||
|
cproxyproto "github.com/coredns/coredns/plugin/pkg/proxyproto"
|
||||||
"github.com/coredns/coredns/plugin/pkg/reuseport"
|
"github.com/coredns/coredns/plugin/pkg/reuseport"
|
||||||
"github.com/coredns/coredns/plugin/pkg/transport"
|
"github.com/coredns/coredns/plugin/pkg/transport"
|
||||||
|
|
||||||
@@ -241,6 +242,10 @@ func (s *ServerQUIC) ListenPacket() (net.PacketConn, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.connPolicy != nil {
|
||||||
|
p = &cproxyproto.PacketConn{PacketConn: p, ConnPolicy: s.connPolicy}
|
||||||
|
}
|
||||||
|
|
||||||
s.m.Lock()
|
s.m.Lock()
|
||||||
defer s.m.Unlock()
|
defer s.m.Unlock()
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/coredns/coredns/plugin/pkg/transport"
|
"github.com/coredns/coredns/plugin/pkg/transport"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
"github.com/pires/go-proxyproto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ServerTLS represents an instance of a TLS-over-DNS-server.
|
// ServerTLS represents an instance of a TLS-over-DNS-server.
|
||||||
@@ -79,6 +80,9 @@ func (s *ServerTLS) Listen() (net.Listener, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if s.connPolicy != nil {
|
||||||
|
l = &proxyproto.Listener{Listener: l, ConnPolicy: s.connPolicy}
|
||||||
|
}
|
||||||
return l, nil
|
return l, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ var Directives = []string{
|
|||||||
"geoip",
|
"geoip",
|
||||||
"cancel",
|
"cancel",
|
||||||
"tls",
|
"tls",
|
||||||
|
"proxyproto",
|
||||||
"quic",
|
"quic",
|
||||||
"grpc_server",
|
"grpc_server",
|
||||||
"https",
|
"https",
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import (
|
|||||||
_ "github.com/coredns/coredns/plugin/nomad"
|
_ "github.com/coredns/coredns/plugin/nomad"
|
||||||
_ "github.com/coredns/coredns/plugin/nsid"
|
_ "github.com/coredns/coredns/plugin/nsid"
|
||||||
_ "github.com/coredns/coredns/plugin/pprof"
|
_ "github.com/coredns/coredns/plugin/pprof"
|
||||||
|
_ "github.com/coredns/coredns/plugin/proxyproto"
|
||||||
_ "github.com/coredns/coredns/plugin/quic"
|
_ "github.com/coredns/coredns/plugin/quic"
|
||||||
_ "github.com/coredns/coredns/plugin/ready"
|
_ "github.com/coredns/coredns/plugin/ready"
|
||||||
_ "github.com/coredns/coredns/plugin/reload"
|
_ "github.com/coredns/coredns/plugin/reload"
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -146,6 +146,7 @@ require (
|
|||||||
github.com/outcaste-io/ristretto v0.2.3 // indirect
|
github.com/outcaste-io/ristretto v0.2.3 // indirect
|
||||||
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect
|
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect
|
||||||
github.com/philhofer/fwd v1.2.0 // indirect
|
github.com/philhofer/fwd v1.2.0 // indirect
|
||||||
|
github.com/pires/go-proxyproto v0.8.1 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -298,6 +298,8 @@ github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe h1:vHpqOnPlnkba8i
|
|||||||
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||||
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||||
|
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
||||||
|
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||||
|
|||||||
136
plugin/pkg/proxyproto/proxyproto.go
Normal file
136
plugin/pkg/proxyproto/proxyproto.go
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
package proxyproto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
clog "github.com/coredns/coredns/plugin/pkg/log"
|
||||||
|
|
||||||
|
"github.com/pires/go-proxyproto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ net.PacketConn = (*PacketConn)(nil)
|
||||||
|
_ net.Addr = (*Addr)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
type PacketConn struct {
|
||||||
|
net.PacketConn
|
||||||
|
ConnPolicy proxyproto.ConnPolicyFunc
|
||||||
|
ValidateHeader proxyproto.Validator
|
||||||
|
ReadHeaderTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||||
|
for {
|
||||||
|
n, addr, err = c.PacketConn.ReadFrom(p)
|
||||||
|
if err != nil {
|
||||||
|
return n, addr, err
|
||||||
|
}
|
||||||
|
n, addr, err = c.readFrom(p[:n], addr)
|
||||||
|
if err != nil {
|
||||||
|
// drop invalid packet as returning error would cause the ReadFrom caller to exit
|
||||||
|
// which could result in DoS if an attacker sends intentional invalid packets
|
||||||
|
clog.Warningf("dropping invalid Proxy Protocol packet from %s: %v", addr.String(), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return n, addr, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||||
|
if pa, ok := addr.(*Addr); ok {
|
||||||
|
addr = pa.u
|
||||||
|
}
|
||||||
|
return c.PacketConn.WriteTo(p, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) readFrom(p []byte, addr net.Addr) (_ int, _ net.Addr, err error) {
|
||||||
|
var policy proxyproto.Policy
|
||||||
|
if c.ConnPolicy != nil {
|
||||||
|
policy, err = c.ConnPolicy(proxyproto.ConnPolicyOptions{
|
||||||
|
Upstream: addr,
|
||||||
|
Downstream: c.LocalAddr(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, fmt.Errorf("applying Proxy Protocol connection policy: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if policy == proxyproto.SKIP {
|
||||||
|
return len(p), addr, nil
|
||||||
|
}
|
||||||
|
header, payload, err := parseProxyProtocol(p)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
if header != nil && c.ValidateHeader != nil {
|
||||||
|
if err := c.ValidateHeader(header); err != nil {
|
||||||
|
return 0, nil, fmt.Errorf("validating Proxy Protocol header: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch policy {
|
||||||
|
case proxyproto.REJECT:
|
||||||
|
if header != nil {
|
||||||
|
return 0, nil, errors.New("connection rejected by Proxy Protocol connection policy")
|
||||||
|
}
|
||||||
|
case proxyproto.REQUIRE:
|
||||||
|
if header == nil {
|
||||||
|
return 0, nil, errors.New("PROXY Protocol header required but not present")
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case proxyproto.USE:
|
||||||
|
if header != nil {
|
||||||
|
srcAddr, _, _ := header.UDPAddrs()
|
||||||
|
addr = &Addr{u: addr, r: srcAddr}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
copy(p, payload)
|
||||||
|
return len(payload), addr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Addr struct {
|
||||||
|
u net.Addr
|
||||||
|
r net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Addr) Network() string {
|
||||||
|
return a.u.Network()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Addr) String() string {
|
||||||
|
return a.r.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseProxyProtocol(packet []byte) (*proxyproto.Header, []byte, error) {
|
||||||
|
reader := bufio.NewReader(bytes.NewReader(packet))
|
||||||
|
|
||||||
|
header, err := proxyproto.Read(reader)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, proxyproto.ErrNoProxyProtocol) {
|
||||||
|
return nil, packet, nil
|
||||||
|
}
|
||||||
|
return nil, nil, fmt.Errorf("parsing Proxy Protocol header (packet size: %d): %w", len(packet), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if header.Version != 2 {
|
||||||
|
return nil, nil, fmt.Errorf("unsupported Proxy Protocol version %d (only v2 supported for UDP)", header.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, ok := header.UDPAddrs()
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, fmt.Errorf("PROXY Protocol header is not UDP type (transport protocol: 0x%x)", header.TransportProtocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
headerLen := len(packet) - reader.Buffered()
|
||||||
|
if headerLen < 0 || headerLen > len(packet) {
|
||||||
|
return nil, nil, fmt.Errorf("invalid header length: %d", headerLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := packet[headerLen:]
|
||||||
|
return header, payload, nil
|
||||||
|
}
|
||||||
63
plugin/proxyproto/README.md
Normal file
63
plugin/proxyproto/README.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# proxyproto
|
||||||
|
|
||||||
|
## Name
|
||||||
|
|
||||||
|
*proxyproto* - add [PROXY protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) support.
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
This plugin adds support for the PROXY protocol version 1 and 2. It allows CoreDNS to receive
|
||||||
|
connections from a load balancer or proxy that uses the PROXY protocol to forward the original
|
||||||
|
client's IP address and port information.
|
||||||
|
|
||||||
|
## Syntax
|
||||||
|
|
||||||
|
~~~ txt
|
||||||
|
proxyproto {
|
||||||
|
allow <CIDR...>
|
||||||
|
default <use|ignore|reject|skip>
|
||||||
|
}
|
||||||
|
~~~
|
||||||
|
|
||||||
|
If `allow` is unspecified, PROXY protocol headers are accepted from all IP addresses.
|
||||||
|
The `default` option controls how connections from sources not listed in `allow` are handled.
|
||||||
|
If `default` is unspecified, it defaults to `ignore`.
|
||||||
|
The possible values are:
|
||||||
|
- `use`: accept and use PROXY protocol headers from these sources
|
||||||
|
- `ignore`: accept and ignore PROXY protocol headers from other sources
|
||||||
|
- `reject`: reject connections with PROXY protocol headers from other sources
|
||||||
|
- `skip`: skip PROXY protocol processing for connections from other sources, treating them as normal connections preserving the PROXY protocol headers.
|
||||||
|
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
In this configuration, we allow PROXY protocol connections from all IP addresses:
|
||||||
|
~~~ corefile
|
||||||
|
. {
|
||||||
|
proxyproto
|
||||||
|
forward . /etc/resolv.conf
|
||||||
|
}
|
||||||
|
~~~
|
||||||
|
|
||||||
|
In this configuration, we only allow PROXY protocol connections from the specified CIDR ranges
|
||||||
|
and ignore proxy protocol headers from other sources:
|
||||||
|
~~~ corefile
|
||||||
|
. {
|
||||||
|
proxyproto {
|
||||||
|
allow 192.168.1.1/32 192.168.0.1/32
|
||||||
|
}
|
||||||
|
forward . /etc/resolv.conf
|
||||||
|
}
|
||||||
|
~~~
|
||||||
|
|
||||||
|
In this configuration, we only allow PROXY protocol headers from the specified CIDR ranges and reject
|
||||||
|
connections without valid PROXY protocol headers from those sources:
|
||||||
|
~~~ corefile
|
||||||
|
. {
|
||||||
|
proxyproto {
|
||||||
|
allow 192.168.1.1/32
|
||||||
|
default reject
|
||||||
|
}
|
||||||
|
forward . /etc/resolv.conf
|
||||||
|
}
|
||||||
|
~~~
|
||||||
81
plugin/proxyproto/setup.go
Normal file
81
plugin/proxyproto/setup.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package proxyproto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"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 (
|
||||||
|
allowedIPNets []*net.IPNet
|
||||||
|
policy = proxyproto.IGNORE
|
||||||
|
)
|
||||||
|
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":
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return c.Errf("unknown option '%s'", c.Val())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.ProxyProtoConnPolicy = func(connPolicyOptions proxyproto.ConnPolicyOptions) (proxyproto.Policy, error) {
|
||||||
|
if len(allowedIPNets) == 0 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
57
plugin/proxyproto/setup_test.go
Normal file
57
plugin/proxyproto/setup_test.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package proxyproto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coredns/caddy"
|
||||||
|
"github.com/coredns/coredns/core/dnsserver"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSetup(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
shouldErr bool
|
||||||
|
expectedRoot string // expected root, set to the controller. Empty for negative cases.
|
||||||
|
expectedErrContent string // substring from the expected error. Empty for positive cases.
|
||||||
|
config bool
|
||||||
|
}{
|
||||||
|
// positive
|
||||||
|
{"proxyproto", false, "", "", true},
|
||||||
|
{"proxyproto {\nallow 127.0.0.1/8 ::1/128\n}", false, "", "", true},
|
||||||
|
{"proxyproto {\nallow 127.0.0.1/8 ::1/128\ndefault ignore\n}", false, "", "", true},
|
||||||
|
// Allow without any IPs is also valid
|
||||||
|
{"proxyproto {\nallow\n}", false, "", "", true},
|
||||||
|
// negative
|
||||||
|
{"proxyproto {\nunknown\n}", true, "", "unknown option", false},
|
||||||
|
{"proxyproto extra_arg", true, "", "Wrong argument", false},
|
||||||
|
{"proxyproto {\nallow invalid_ip\n}", true, "", "invalid CIDR address", false},
|
||||||
|
{"proxyproto {\nallow 127.0.0.1/8\ndefault invalid_policy\n}", true, "", "Wrong argument", false},
|
||||||
|
}
|
||||||
|
for i, test := range tests {
|
||||||
|
c := caddy.NewTestController("dns", test.input)
|
||||||
|
err := setup(c)
|
||||||
|
cfg := dnsserver.GetConfig(c)
|
||||||
|
|
||||||
|
if test.config && cfg.ProxyProtoConnPolicy == nil {
|
||||||
|
t.Errorf("Test %d: Expected ProxyProtoConnPolicy to be configured for input %s", i, test.input)
|
||||||
|
}
|
||||||
|
if !test.config && cfg.ProxyProtoConnPolicy != nil {
|
||||||
|
t.Errorf("Test %d: Expected ProxyProtoConnPolicy to NOT be configured for input %s", i, test.input)
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.shouldErr && err == nil {
|
||||||
|
t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if !test.shouldErr {
|
||||||
|
t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(err.Error(), test.expectedErrContent) {
|
||||||
|
t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user