proxyproto

package module
v0.15.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 9, 2026 License: Apache-2.0 Imports: 14 Imported by: 601

README

go-proxyproto

Actions Status Coverage Status Go Report Card Go Reference

A Go library implementation of the HAProxy PROXY protocol specification 3.4. It covers protocol versions 1 (text) and 2 (binary), carrying the original client connection information across NAT and proxy layers.

Use it on either side of a PROXY-aware hop: send headers with Header.WriteTo or Header.FormatUDPDatagram, and receive them with Listener, NewConn, Read, or ParseUDPDatagram. The stream APIs are for TCP and Unix streams; UDP uses packet helpers because the spec requires a header in every datagram.

Installation

$ go get github.com/pires/go-proxyproto

Examples

The fastest way to get started is the runnable programs under examples/ and the API examples on pkg.go.dev:

Goal Where to look
Minimal client examples/client
Minimal server examples/server
HTTP server examples/httpserver
Server + client over TLS (PROXY header before TLS) examples/tlsserver, examples/tlsclient
UDP receiver and sender examples/udpserver, examples/udpclient
UDP net.PacketConn wrapper pattern examples/udppacketconn
Listener, NewConn, Read, UDP, and TLS API examples Package examples

Usage

Use the runnable examples above for complete programs. The core API shape is small.

Client side
header := proxyproto.HeaderProxyFromAddrs(1, sourceAddr, destinationAddr)
_, err := header.WriteTo(conn) // write the PROXY header before application data

See examples/client for a complete TCP client.

Server side
proxyListener := &proxyproto.Listener{Listener: ln}
conn, err := proxyListener.Accept()
// Connections must open with a PROXY header (the default policy is REQUIRE);
// conn.RemoteAddr() then reports the client address from that header.

See examples/server for a complete TCP server. For HTTP/1 and HTTP/2, see examples/httpserver, which uses helper/http2 so one server can accept proxied HTTP/1 and HTTP/2 connections.

[!WARNING] The zero-value configuration requires the PROXY header but still honors it from any peer. It is not safe for listeners reachable by untrusted clients without a trusted-source policy. See Security.

Security

The PROXY header replaces what your application sees as the client address, so whoever is allowed to send one can spoof their origin. The spec (section 2) says receivers "MUST not try to guess" whether the header is present, and requires access filtering so only trusted proxies can use the protocol.

Important stream defaults and policies:

  • With no policy configured, Listener and NewConn use proxyproto.DefaultPolicy, which is REQUIRE. A connection that does not open with a PROXY header fails its first I/O with ErrNoProxyProtocol, so header presence is never guessed.
  • REQUIRE still honors headers from any peer. Use it only when the listener is reachable exclusively by trusted proxies, such as a private network segment behind your load balancer.
  • Deployments that need historical optional-header behavior can restore it process-wide with proxyproto.DefaultPolicy = proxyproto.USE, per connection with WithPolicy(USE), or per listener with a policy returning USE.
  • For exposed listeners, restrict the senders with TrustProxyHeaderFrom or TrustProxyHeaderFromRanges. Trusted peers must send a header; untrusted peers are dropped by Accept. For example:
proxyListener := &proxyproto.Listener{
	Listener: ln,
	// Connections from the load balancer must open with a PROXY header
	// (REQUIRE); connections from any other source are dropped. For CIDR
	// ranges, use TrustProxyHeaderFromRanges([]string{"10.0.0.0/24"}).
	// For mixed traffic (e.g. optional headers from some sources), spell the
	// two policies out with PolicyFromRanges(ranges, matched, unmatched).
	ConnPolicy: proxyproto.TrustProxyHeaderFrom(net.ParseIP("10.0.0.10")),
}

For UDP, ParseUDPDatagram has no built-in trusted-source policy because it only sees packet bytes. Check the net.PacketConn.ReadFrom sender address yourself before trusting header.SourceAddr.

Related knobs are documented in the package docs: ReadHeaderTimeout (default 10s), MaxV2HeaderSize (default 4KiB), V1AcceptIPv4InTCP6 (default off), and Listener.ValidateHeader.

UDP

The spec requires the header and proxied payload in the same UDP datagram, and the receiver must parse the header independently for every datagram. Listener and Conn cannot provide those semantics; use ParseUDPDatagram and Header.FormatUDPDatagram with your own net.PacketConn. Headers with UDPv4/UDPv6 families carried over stream connections describe the proxied protocol and remain fully supported.

The library deliberately does not export a net.PacketConn wrapper. A wrapper that returns the client address from the header also needs an application-owned client-to-proxy flow table for replies, including bounds, expiry, and spoofing policy.

TLS

When combining PROXY protocol with TLS, match the wrapper order to the upstream order. If the header is sent in cleartext before the handshake, put proxyproto inside TLS: tls.NewListener(&proxyproto.Listener{Listener: l}, tlsConfig). If the header is sent inside the TLS session, decrypt first: &proxyproto.Listener{Listener: tls.NewListener(l, tlsConfig)}. In both cases conn.RemoteAddr() reports the client carried by the PROXY header.

Runnable code lives in examples/tlsserver and examples/tlsclient. Package examples show both orderings.

Special notes

AWS

AWS Network Load Balancer (NLB) does not send the PROXY v2 header until the client sends payload: the target group attribute proxy_protocol_v2.client_to_server.header_placement defaults to on_first_ack_with_payload. Server-first protocols such as SMTP, FTP, and SSH fail in that mode; contact AWS support to change the attribute to on_first_ack so the header arrives before the backend speaks.

Documentation

Overview

Package proxyproto implements Proxy Protocol (v1 and v2) parser and writer, as per specification: https://www.haproxy.org/download/3.4/doc/proxy-protocol.txt

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// SIGV1 is the signature for PROXY protocol v1.
	SIGV1 = []byte{'\x50', '\x52', '\x4F', '\x58', '\x59'}
	// SIGV2 is the signature for PROXY protocol v2.
	SIGV2 = []byte{'\x0D', '\x0A', '\x0D', '\x0A', '\x00', '\x0D', '\x0A', '\x51', '\x55', '\x49', '\x54', '\x0A'}

	// ErrCantReadVersion1Header indicates a v1 header could not be read.
	ErrCantReadVersion1Header = errors.New("proxyproto: can't read version 1 header")
	// ErrVersion1HeaderTooLong indicates a v1 header is too long.
	ErrVersion1HeaderTooLong = errors.New("proxyproto: version 1 header must be 107 bytes or less")
	// ErrLineMustEndWithCrlf indicates a v1 header is invalid, must end with \r\n.
	ErrLineMustEndWithCrlf = errors.New("proxyproto: version 1 header is invalid, must end with \\r\\n")
	// ErrCantReadProtocolVersionAndCommand indicates a protocol version and command could not be read.
	ErrCantReadProtocolVersionAndCommand = errors.New("proxyproto: can't read proxy protocol version and command")
	// ErrCantReadAddressFamilyAndProtocol indicates an address family and protocol could not be read.
	ErrCantReadAddressFamilyAndProtocol = errors.New("proxyproto: can't read address family or protocol")
	// ErrCantReadLength indicates a length could not be read.
	ErrCantReadLength = errors.New("proxyproto: can't read length")
	// ErrCantResolveSourceUnixAddress indicates a source Unix address could not be resolved.
	ErrCantResolveSourceUnixAddress = errors.New("proxyproto: can't resolve source Unix address")
	// ErrCantResolveDestinationUnixAddress indicates a destination Unix address could not be resolved.
	ErrCantResolveDestinationUnixAddress = errors.New("proxyproto: can't resolve destination Unix address")
	// ErrNoProxyProtocol indicates a proxy protocol signature is not present.
	ErrNoProxyProtocol = errors.New("proxyproto: proxy protocol signature not present")
	// ErrUnknownProxyProtocolVersion indicates an unknown proxy protocol version.
	ErrUnknownProxyProtocolVersion = errors.New("proxyproto: unknown proxy protocol version")
	// ErrUnsupportedProtocolVersionAndCommand indicates an unsupported protocol version and command.
	ErrUnsupportedProtocolVersionAndCommand = errors.New("proxyproto: unsupported proxy protocol version and command")
	// ErrUnsupportedAddressFamilyAndProtocol indicates an unsupported address family and protocol.
	ErrUnsupportedAddressFamilyAndProtocol = errors.New("proxyproto: unsupported address family and protocol")
	// ErrInvalidLength indicates an invalid length.
	ErrInvalidLength = errors.New("proxyproto: invalid length")
	// ErrInvalidAddress indicates an invalid address.
	ErrInvalidAddress = errors.New("proxyproto: invalid address")
	// ErrInvalidPortNumber indicates an invalid port number.
	ErrInvalidPortNumber = errors.New("proxyproto: invalid port number")
	// ErrSuperfluousProxyHeader indicates an upstream connection sent a PROXY header but isn't allowed to send one.
	ErrSuperfluousProxyHeader = errors.New("proxyproto: upstream connection sent PROXY header but isn't allowed to send one")
)
View Source
var (
	// DefaultReadHeaderTimeout is how long header processing waits for header to
	// be read from the wire, if Listener.ReaderHeaderTimeout is not set.
	// It's kept as a global variable so to make it easier to find and override,
	// e.g. go build -ldflags -X "github.com/pires/go-proxyproto.DefaultReadHeaderTimeout=1s".
	DefaultReadHeaderTimeout = 10 * time.Second

	// ErrInvalidUpstream should be returned (possibly wrapped) by a policy
	// function when an upstream connection address is not trusted or cannot be
	// classified. Listener.Accept closes that connection and keeps listening;
	// a policy error that does not wrap ErrInvalidUpstream is returned by
	// Accept itself, which typically stops the caller's accept loop. All
	// built-in policies wrap address-classification failures in
	// ErrInvalidUpstream so a single unclassifiable peer cannot stop the
	// listener.
	ErrInvalidUpstream = fmt.Errorf("proxyproto: upstream connection address not trusted for PROXY information")
)
View Source
var (
	// ErrTruncatedTLV indicates a TLV was truncated.
	ErrTruncatedTLV = errors.New("proxyproto: truncated TLV")
	// ErrMalformedTLV indicates a TLV has malformed data.
	ErrMalformedTLV = errors.New("proxyproto: malformed TLV Value")
	// ErrIncompatibleTLV indicates a TLV is of an unexpected type.
	ErrIncompatibleTLV = errors.New("proxyproto: incompatible TLV type")
)
View Source
var DefaultPolicy = REQUIRE

DefaultPolicy is the policy applied when none is configured explicitly: by NewConn when no WithPolicy option is given, and by Listener.Accept when neither Policy nor ConnPolicy is set.

It defaults to REQUIRE, per the spec's mandate that a receiver "MUST not try to guess whether the protocol header is present or not": a connection that does not open with a PROXY header fails its first Read/Write with ErrNoProxyProtocol. Note REQUIRE alone still honors headers from any peer; restricting who may send one needs a policy such as TrustProxyHeaderFrom or TrustProxyHeaderFromRanges. (The deprecated *WhiteListPolicy helpers are NOT equivalent: they return USE for allowed peers, which replaces this default and makes the header optional again — that is why they were deprecated in favor of the explicit PolicyFromRanges.)

Deployments that relied on the historical optional-header behavior can restore it process-wide with:

proxyproto.DefaultPolicy = proxyproto.USE

Like DefaultReadHeaderTimeout, this is a package-level variable to keep it easy to override. Set it at program init; it must not be modified concurrently with accepting connections.

View Source
var MaxV2HeaderSize uint16 = 4096

MaxV2HeaderSize is the maximum accepted value of a v2 header's 16-bit length field, i.e. the number of bytes following the fixed 16-byte prefix (address block plus TLVs).

The spec allows up to 65535, but parseVersion2 allocates this many bytes before reading, so a lower limit mitigates memory-allocation DoS from untrusted peers while allowing real-world legitimate headers. PP2_SUBTYPE_SSL_CLIENT_CERT (a DER-encoded certificate) is typically between 1 and 2KiB, so the 4KiB default leaves room for other TLVs; deployments expecting larger headers may raise it.

Like DefaultReadHeaderTimeout, this is a package-level variable to keep it easy to override. Set it at program init; it must not be modified concurrently with parsing.

View Source
var V1AcceptIPv4InTCP6 = false

V1AcceptIPv4InTCP6 permits plain IPv4 literals in the address fields of a v1 TCP6 line, promoting them to v4-mapped IPv6 (::ffff:x.x.x.x). Some proxies (notably the nginx OSS stream module) emit such lines when the client and backend address families differ. The spec forbids them — "the advertised protocol family dictates what format to use" — so this compatibility mode is disabled by default.

Note the promotion is lossy: round-trip serialization renders the address as "::ffff:x.x.x.x" rather than the original "x.x.x.x".

Like DefaultReadHeaderTimeout, this is a package-level variable to keep it easy to override. Set it at program init; it must not be modified concurrently with parsing.

Functions

func JoinTLVs added in v0.2.0

func JoinTLVs(tlvs []TLV) ([]byte, error)

JoinTLVs joins multiple Type-Length-Value records.

func SetReadHeaderTimeout added in v0.8.0

func SetReadHeaderTimeout(t time.Duration) func(*Conn)

SetReadHeaderTimeout sets the readHeaderTimeout for a connection when passed as option to NewConn(). A value of 0 disables the header read timeout; negative values are ignored, leaving the connection's current timeout (the NewConn default) in place.

func ValidateHeader

func ValidateHeader(v Validator) func(*Conn)

ValidateHeader adds given validator for proxy headers to a connection when passed as option to NewConn().

func WithBufferSize added in v0.10.0

func WithBufferSize(length int) func(*Conn)

WithBufferSize sets the size of the read buffer used for proxy header detection. Values <= 0 are ignored and the default (256 bytes) is used. Values < 16 are effectively 16 due to bufio's minimum. The default is tuned for typical proxy protocol header lengths.

The buffer must be able to hold an entire v1 header line (up to 107 bytes): v1 parsing requires the line to be available without refilling the buffer (the slow-loris defense), so a smaller buffer rejects every v1 connection with ErrCantReadVersion1Header even from well-behaved senders. v2 parsing refills freely and works with any size.

func WithPolicy

func WithPolicy(p Policy) func(*Conn)

WithPolicy adds given policy to a connection when passed as option to NewConn().

Types

type AddressFamilyAndProtocol

type AddressFamilyAndProtocol byte

AddressFamilyAndProtocol represents address family and transport protocol.

const (
	UNSPEC       AddressFamilyAndProtocol = '\x00'
	TCPv4        AddressFamilyAndProtocol = '\x11'
	UDPv4        AddressFamilyAndProtocol = '\x12'
	TCPv6        AddressFamilyAndProtocol = '\x21'
	UDPv6        AddressFamilyAndProtocol = '\x22'
	UnixStream   AddressFamilyAndProtocol = '\x31'
	UnixDatagram AddressFamilyAndProtocol = '\x32'
)

AddressFamilyAndProtocol enum values.

func (AddressFamilyAndProtocol) IsDatagram

func (ap AddressFamilyAndProtocol) IsDatagram() bool

IsDatagram returns true if the transport protocol is UDP or DGRAM (SOCK_DGRAM), false otherwise.

func (AddressFamilyAndProtocol) IsIPv4

func (ap AddressFamilyAndProtocol) IsIPv4() bool

IsIPv4 returns true if the address family is IPv4 (AF_INET4), false otherwise.

func (AddressFamilyAndProtocol) IsIPv6

func (ap AddressFamilyAndProtocol) IsIPv6() bool

IsIPv6 returns true if the address family is IPv6 (AF_INET6), false otherwise.

func (AddressFamilyAndProtocol) IsStream

func (ap AddressFamilyAndProtocol) IsStream() bool

IsStream returns true if the transport protocol is TCP or STREAM (SOCK_STREAM), false otherwise.

func (AddressFamilyAndProtocol) IsUnix

func (ap AddressFamilyAndProtocol) IsUnix() bool

IsUnix returns true if the address family is UNIX (AF_UNIX), false otherwise.

func (AddressFamilyAndProtocol) IsUnspec

func (ap AddressFamilyAndProtocol) IsUnspec() bool

IsUnspec returns true if the transport protocol or address family is unspecified, false otherwise.

type Conn

type Conn struct {
	ProxyHeaderPolicy Policy
	Validate          Validator
	// contains filtered or unexported fields
}

Conn is used to wrap and underlying connection which may be speaking the Proxy Protocol. If it is, the RemoteAddr() will return the address of the client instead of the proxy address. Each connection will have its own readHeaderTimeout and readDeadline set by the Accept() call.

func NewConn

func NewConn(conn net.Conn, opts ...func(*Conn)) *Conn

NewConn is used to wrap a net.Conn that may be speaking the PROXY protocol into a proxyproto.Conn.

Conn is stream-oriented; see the note on Listener about the PROXY protocol over UDP datagrams.

By default the returned Conn applies DefaultPolicy (REQUIRE, so the peer must open with a PROXY header; override with the WithPolicy option) and DefaultReadHeaderTimeout (10s) while detecting the PROXY protocol header, so a client that connects but never sends data cannot make header detection block forever.

The timeout bounds header detection only, not the first Read end-to-end: under a non-REQUIRE policy, when no header is present Read falls through to a normal read of the underlying connection, which can still block on a silent client (pinning a goroutine and file descriptor). For an end-to-end bound, set a read deadline on the connection, or keep the REQUIRE policy, which makes the first Read fail when no header arrives within the timeout.

Override the timeout with the SetReadHeaderTimeout option; pass SetReadHeaderTimeout(0) to disable it entirely.

NOTE: NewConn may interfere with previously set ReadDeadline on the provided net.Conn, because it sets a temporary deadline when detecting and reading the PROXY protocol header. If you need to enforce a specific ReadDeadline on the connection, be sure to call Conn.SetReadDeadline again after NewConn returns, to restore your desired deadline.

Example (Combined)
serverConn, clientConn := net.Pipe()
defer func() { _ = serverConn.Close() }()
defer func() { _ = clientConn.Close() }()

go func() {
	_, _ = clientConn.Write([]byte(proxyV1Line))
	_, _ = clientConn.Write([]byte("c"))
	_ = clientConn.Close()
}()

conn := proxyproto.NewConn(serverConn,
	proxyproto.WithBufferSize(2048),
	proxyproto.SetReadHeaderTimeout(2*time.Second),
)
buf := make([]byte, 1)
_, _ = conn.Read(buf)
Example (Default)
serverConn, clientConn := net.Pipe()
defer func() { _ = serverConn.Close() }()
defer func() { _ = clientConn.Close() }()

go func() {
	_, _ = clientConn.Write([]byte(proxyV1Line))
	_, _ = clientConn.Write([]byte("x"))
	_ = clientConn.Close()
}()

// By default a connection must open with a PROXY header (DefaultPolicy is
// REQUIRE): a headerless connection fails its first Read with
// ErrNoProxyProtocol. Detection is bounded by DefaultReadHeaderTimeout, so
// a silent client cannot block it forever. RemoteAddr then reports the
// client address carried by the header.
conn := proxyproto.NewConn(serverConn)
buf := make([]byte, 1)
if _, err := conn.Read(buf); err != nil {
	fmt.Println("read error:", err)
	return
}
fmt.Println(conn.RemoteAddr())
Output:
192.168.1.1:12345
Example (DisableReadHeaderTimeout)
serverConn, clientConn := net.Pipe()
defer func() { _ = serverConn.Close() }()
defer func() { _ = clientConn.Close() }()

go func() {
	_, _ = clientConn.Write([]byte(proxyV1Line))
	_, _ = clientConn.Write([]byte("d"))
	_ = clientConn.Close()
}()

// NewConn applies DefaultReadHeaderTimeout by default; pass
// SetReadHeaderTimeout(0) to opt out and manage read deadlines yourself.
conn := proxyproto.NewConn(serverConn, proxyproto.SetReadHeaderTimeout(0))
buf := make([]byte, 1)
_, _ = conn.Read(buf)
Example (WithBufferSize)
serverConn, clientConn := net.Pipe()
defer func() { _ = serverConn.Close() }()
defer func() { _ = clientConn.Close() }()

go func() {
	_, _ = clientConn.Write([]byte(proxyV1Line))
	_, _ = clientConn.Write([]byte("y"))
	_ = clientConn.Close()
}()

conn := proxyproto.NewConn(serverConn, proxyproto.WithBufferSize(4096))
buf := make([]byte, 1)
_, _ = conn.Read(buf)
Example (WithPolicy)
serverConn, clientConn := net.Pipe()
defer func() { _ = serverConn.Close() }()
defer func() { _ = clientConn.Close() }()

go func() {
	_, _ = clientConn.Write([]byte(proxyV1Line))
	_, _ = clientConn.Write([]byte("p"))
	_ = clientConn.Close()
}()

conn := proxyproto.NewConn(serverConn, proxyproto.WithPolicy(proxyproto.REQUIRE))
buf := make([]byte, 1)
_, _ = conn.Read(buf)
Example (WithReadHeaderTimeout)
serverConn, clientConn := net.Pipe()
defer func() { _ = serverConn.Close() }()
defer func() { _ = clientConn.Close() }()

go func() {
	_, _ = clientConn.Write([]byte(proxyV1Line))
	_, _ = clientConn.Write([]byte("z"))
	_ = clientConn.Close()
}()

conn := proxyproto.NewConn(serverConn, proxyproto.SetReadHeaderTimeout(time.Second))
buf := make([]byte, 1)
_, _ = conn.Read(buf)

func (*Conn) Close

func (p *Conn) Close() error

Close wraps original conn.Close.

func (*Conn) LocalAddr

func (p *Conn) LocalAddr() net.Addr

LocalAddr returns the address of the server if the proxy protocol is being used, otherwise just returns the address of the socket server. In case an error happens on reading the proxy header the original LocalAddr is returned, not the one from the proxy header even if the proxy header itself is syntactically correct.

func (*Conn) ProxyHeader added in v0.3.2

func (p *Conn) ProxyHeader() *Header

ProxyHeader returns the proxy protocol header, if any. If an error occurs while reading the proxy header, nil is returned.

func (*Conn) Raw added in v0.3.3

func (p *Conn) Raw() net.Conn

Raw returns the underlying connection which can be casted to a concrete type, allowing access to specialized functions.

Use this ONLY if you know exactly what you are doing.

func (*Conn) Read

func (p *Conn) Read(b []byte) (int, error)

Read checks for the proxy protocol header on the first call, then reads from the connection. If there is an error processing the header, it is returned by this and every subsequent call. The connection is NOT closed by this package; the caller should close it.

func (*Conn) ReadFrom added in v0.5.0

func (p *Conn) ReadFrom(r io.Reader) (int64, error)

ReadFrom implements the io.ReaderFrom ReadFrom method.

func (*Conn) RemoteAddr

func (p *Conn) RemoteAddr() net.Addr

RemoteAddr returns the address of the client if the proxy protocol is being used, otherwise just returns the address of the socket peer. In case an error happens on reading the proxy header the original RemoteAddr is returned, not the one from the proxy header even if the proxy header itself is syntactically correct.

func (*Conn) SetDeadline

func (p *Conn) SetDeadline(t time.Time) error

SetDeadline wraps original conn.SetDeadline.

func (*Conn) SetReadDeadline

func (p *Conn) SetReadDeadline(t time.Time) error

SetReadDeadline wraps original conn.SetReadDeadline.

func (*Conn) SetWriteDeadline

func (p *Conn) SetWriteDeadline(t time.Time) error

SetWriteDeadline wraps original conn.SetWriteDeadline.

func (*Conn) TCPConn added in v0.3.1

func (p *Conn) TCPConn() (conn *net.TCPConn, ok bool)

TCPConn returns the underlying TCP connection, allowing access to specialized functions.

Use this ONLY if you know exactly what you are doing.

func (*Conn) UDPConn added in v0.3.1

func (p *Conn) UDPConn() (conn *net.UDPConn, ok bool)

UDPConn returns the underlying UDP connection, allowing access to specialized functions.

Use this ONLY if you know exactly what you are doing.

func (*Conn) UnixConn added in v0.3.1

func (p *Conn) UnixConn() (conn *net.UnixConn, ok bool)

UnixConn returns the underlying Unix socket connection, allowing access to specialized functions.

Use this ONLY if you know exactly what you are doing.

func (*Conn) Write

func (p *Conn) Write(b []byte) (int, error)

Write wraps original conn.Write.

func (*Conn) WriteTo added in v0.5.0

func (p *Conn) WriteTo(w io.Writer) (int64, error)

WriteTo implements io.WriterTo.

type ConnPolicyFunc added in v0.8.0

type ConnPolicyFunc func(connPolicyOptions ConnPolicyOptions) (Policy, error)

ConnPolicyFunc can be used to decide whether to trust the PROXY info based on connection policy options. If set, the connecting addresses (remote and local) are passed in as argument.

See below for the different policies.

In case an error is returned the connection is denied: an error wrapping ErrInvalidUpstream denies just that connection while Listener.Accept keeps listening; any other error is returned by Accept itself, which typically stops the caller's accept loop.

func ConnLaxWhiteListPolicy deprecated added in v0.9.0

func ConnLaxWhiteListPolicy(allowed []string) (ConnPolicyFunc, error)

ConnLaxWhiteListPolicy returns a ConnPolicyFunc which decides whether the upstream ip is allowed to send a proxy header based on a list of allowed IP addresses and IP ranges. In case upstream IP is not in list the proxy header will be ignored. If one of the provided IP addresses or IP ranges is invalid it will return an error instead of a ConnPolicyFunc.

Deprecated: the name hides that the header stays optional (USE) for allowed peers, overriding the REQUIRE default. Use the equivalent, explicit PolicyFromRanges(allowed, USE, IGNORE), or TrustProxyHeaderFromRanges for the spec-strict header-mandatory posture.

func ConnMustLaxWhiteListPolicy deprecated added in v0.9.0

func ConnMustLaxWhiteListPolicy(allowed []string) ConnPolicyFunc

ConnMustLaxWhiteListPolicy returns a ConnLaxWhiteListPolicy but will panic if one of the provided IP addresses or IP ranges is invalid.

Deprecated: use MustPolicyFromRanges(allowed, USE, IGNORE) instead; see ConnLaxWhiteListPolicy for why.

func ConnMustStrictWhiteListPolicy deprecated added in v0.9.0

func ConnMustStrictWhiteListPolicy(allowed []string) ConnPolicyFunc

ConnMustStrictWhiteListPolicy returns a ConnStrictWhiteListPolicy but will panic if one of the provided IP addresses or IP ranges is invalid.

Deprecated: use MustPolicyFromRanges(allowed, USE, REJECT) instead; see ConnStrictWhiteListPolicy for why.

func ConnSkipProxyHeaderForCIDR added in v0.9.0

func ConnSkipProxyHeaderForCIDR(skipHeaderCIDR *net.IPNet, def Policy) ConnPolicyFunc

ConnSkipProxyHeaderForCIDR returns a ConnPolicyFunc which can be used to accept a connection from a skipHeaderCIDR without requiring a PROXY header, e.g. Kubernetes pods local traffic. The def is a policy to use when an upstream address doesn't match the skipHeaderCIDR.

func ConnStrictWhiteListPolicy deprecated added in v0.9.0

func ConnStrictWhiteListPolicy(allowed []string) (ConnPolicyFunc, error)

ConnStrictWhiteListPolicy returns a ConnPolicyFunc which decides whether the upstream ip is allowed to send a proxy header based on a list of allowed IP addresses and IP ranges. In case upstream IP is not in list reading on the connection will be refused: the first read returns ErrSuperfluousProxyHeader and every subsequent read returns the same error, so the connection should be closed. If one of the provided IP addresses or IP ranges is invalid it will return an error instead of a ConnPolicyFunc.

Deprecated: "strict" only refers to refusing headers from unlisted peers; the header stays optional (USE) for allowed peers, overriding the REQUIRE default. Use the equivalent, explicit PolicyFromRanges(allowed, USE, REJECT), or TrustProxyHeaderFromRanges for the spec-strict header-mandatory posture.

func IgnoreProxyHeaderNotOnInterface added in v0.8.0

func IgnoreProxyHeaderNotOnInterface(allowedIP net.IP) ConnPolicyFunc

IgnoreProxyHeaderNotOnInterface returns a ConnPolicyFunc which can be used to decide whether to use or ignore PROXY headers depending on the connection being made on specific interfaces. This policy can be used when the server is bound to multiple interfaces but wants to allow on one or more interfaces.

func MustPolicyFromRanges added in v0.15.0

func MustPolicyFromRanges(ranges []string, matched, unmatched Policy) ConnPolicyFunc

MustPolicyFromRanges returns PolicyFromRanges and panics if any entry in ranges is invalid. Intended for static configuration known at program init.

func PolicyFromRanges added in v0.15.0

func PolicyFromRanges(ranges []string, matched, unmatched Policy) (ConnPolicyFunc, error)

PolicyFromRanges returns a ConnPolicyFunc that applies the matched policy to connections whose upstream address belongs to ranges, and the unmatched policy to every other connection. Each entry in ranges may be an individual IP address ("10.0.0.10") or a CIDR range ("10.0.0.0/24"); an invalid entry returns an error instead of a ConnPolicyFunc. Connections whose upstream address cannot be classified are dropped by Listener.Accept via an ErrInvalidUpstream-wrapping error.

It is the explicit replacement for the deprecated *WhiteListPolicy helpers:

PolicyFromRanges(ranges, USE, IGNORE) // ConnLaxWhiteListPolicy
PolicyFromRanges(ranges, USE, REJECT) // ConnStrictWhiteListPolicy

Both of those combinations leave the header optional for matched peers. For the spec-strict posture — trusted sources MUST send a header, everything else is dropped — use TrustProxyHeaderFromRanges instead.

func TrustProxyHeaderFrom added in v0.9.0

func TrustProxyHeaderFrom(trustedIPs ...net.IP) ConnPolicyFunc

TrustProxyHeaderFrom returns a ConnPolicyFunc implementing the spec's receiver posture end to end: connections from trusted IPs MUST carry a PROXY header (REQUIRE — absence fails the first Read with ErrNoProxyProtocol, so header presence is never guessed), and connections from any other source are dropped by Listener.Accept without stopping the listener.

Note the REJECT policy alone cannot provide the second half: REJECT refuses connections that DO send a header, but a headerless connection from an untrusted peer would still be served raw, sharing the port between PROXY and non-PROXY traffic — exactly what the spec's security model forbids.

func TrustProxyHeaderFromRanges added in v0.15.0

func TrustProxyHeaderFromRanges(trusted []string) (ConnPolicyFunc, error)

TrustProxyHeaderFromRanges is the CIDR-capable variant of TrustProxyHeaderFrom, with the same spec posture: connections from the trusted set MUST carry a PROXY header (REQUIRE), and connections from any other source are dropped by Listener.Accept without stopping the listener. Each entry in trusted may be an individual IP address ("10.0.0.10") or a CIDR range ("10.0.0.0/24"); an invalid entry returns an error instead of a ConnPolicyFunc.

Unlike the *WhiteListPolicy helpers — which make the header optional (USE) for allowed peers and merely ignore or refuse the header for denied ones — this helper never guesses whether a header is present.

type ConnPolicyOptions added in v0.8.0

type ConnPolicyOptions struct {
	Upstream   net.Addr
	Downstream net.Addr
}

ConnPolicyOptions contains the remote and local addresses of a connection.

type Header struct {
	Version           byte
	Command           ProtocolVersionAndCommand
	TransportProtocol AddressFamilyAndProtocol
	SourceAddr        net.Addr
	DestinationAddr   net.Addr
	// contains filtered or unexported fields
}

Header is the placeholder for proxy protocol header.

func HeaderProxyFromAddrs added in v0.2.0

func HeaderProxyFromAddrs(version byte, sourceAddr, destAddr net.Addr) *Header

HeaderProxyFromAddrs creates a new PROXY header from a source and a destination address. If version is zero, the latest protocol version is used.

The header is filled on a best-effort basis: if hints cannot be inferred from the provided addresses, the header will be left unspecified.

func ParseUDPDatagram added in v0.15.0

func ParseUDPDatagram(datagram []byte) (*Header, []byte, error)

ParseUDPDatagram parses a PROXY protocol header at the start of a UDP datagram and returns the header together with the proxied payload that follows it. The returned payload aliases the datagram slice.

Per spec (section 2), when the PROXY protocol is carried over UDP the header and the proxied payload MUST be sent in the same datagram, and the receiver MUST parse the header independently for each received datagram. Call this for every datagram received; there is no connection state to carry over. Conn and Listener are stream-oriented and cannot provide those semantics.

A datagram that does not begin with a complete, valid header fails with the same errors Read returns — ErrNoProxyProtocol when the signature is absent. The spec forbids guessing whether a header is present, so on a receiver configured for the PROXY protocol such datagrams must be dropped, not treated as raw payload.

Example
package main

import (
	"fmt"
	"net"

	"github.com/pires/go-proxyproto"
)

func main() {
	// A receiver behind a proxy that speaks the PROXY protocol over UDP. The
	// spec (section 2) requires the header and the proxied payload to share a
	// single datagram and the header to be parsed independently for each
	// datagram, so the socket stays a plain net.PacketConn and every ReadFrom
	// is followed by ParseUDPDatagram.
	server, _ := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)})
	defer func() { _ = server.Close() }()

	go func() {
		// The proxy side: one FormatUDPDatagram plus one Write keeps header
		// and payload in the same datagram.
		header := &proxyproto.Header{
			Version:           2,
			Command:           proxyproto.PROXY,
			TransportProtocol: proxyproto.UDPv4,
			SourceAddr:        &net.UDPAddr{IP: net.ParseIP("192.168.1.1"), Port: 12345},
			DestinationAddr:   &net.UDPAddr{IP: net.ParseIP("192.168.1.2"), Port: 443},
		}
		datagram, _ := header.FormatUDPDatagram([]byte("ping"))
		c, _ := net.Dial("udp", server.LocalAddr().String())
		if c != nil {
			_, _ = c.Write(datagram)
			_ = c.Close()
		}
	}()

	buf := make([]byte, 65535)
	n, _, _ := server.ReadFrom(buf)
	header, payload, err := proxyproto.ParseUDPDatagram(buf[:n])
	if err != nil {
		// The spec forbids guessing: a datagram without a valid header is
		// dropped, never treated as raw payload.
		fmt.Println("drop:", err)
		return
	}
	fmt.Printf("%s says %q\n", header.SourceAddr, payload)
}
Output:
192.168.1.1:12345 says "ping"

func Read

func Read(reader *bufio.Reader) (*Header, error)

Read identifies the proxy protocol version and reads the remaining of the header, accordingly.

If proxy protocol header signature is not present, the reader buffer remains untouched and is safe for reading outside of this code.

If proxy protocol header signature is present but an error is raised while processing the remaining header, assume the reader buffer to be in a corrupt state. Also, this operation will block until enough bytes are available for peeking.

func ReadHeaderTimeout added in v0.14.0

func ReadHeaderTimeout(conn net.Conn, reader *bufio.Reader, timeout time.Duration) (*Header, error)

ReadHeaderTimeout reads the PROXY protocol header from conn, giving up after timeout. It is the cancellable replacement for the deprecated ReadTimeout: because it is given the net.Conn, it sets a read deadline so a stalled read is actually interrupted instead of leaking a blocked goroutine and the connection's file descriptor. If the timeout is reached it returns ErrNoProxyProtocol, assuming no header is present.

reader must be buffered over conn (for example bufio.NewReader(conn)); it is used for the header read so that any bytes buffered past the header remain available for the caller to read afterwards. A timeout <= 0 reads without a deadline.

ReadHeaderTimeout overwrites conn's read deadline and restores the zero (no) deadline before returning; re-apply your own read deadline afterwards if you had one set.

Example

ExampleReadHeaderTimeout demonstrates the cancellable, low-level way to read a PROXY protocol header directly from a net.Conn you manage yourself. Unlike the deprecated ReadTimeout, it is given the conn and sets a real read deadline, so a peer that connects but never sends the header cannot block the read forever.

serverConn, clientConn := net.Pipe()
defer func() { _ = serverConn.Close() }()

go func() {
	// A PROXY v1 header naming the real client, followed by application data.
	_, _ = clientConn.Write([]byte(proxyV1Line))
	_, _ = clientConn.Write([]byte("HELO"))
	_ = clientConn.Close()
}()

// reader must be buffered over conn: any bytes read past the header remain
// available for the caller to consume afterwards.
reader := bufio.NewReader(serverConn)
header, err := proxyproto.ReadHeaderTimeout(serverConn, reader, time.Second)
if err != nil && err != proxyproto.ErrNoProxyProtocol {
	fmt.Println("error:", err)
	return
}
if header != nil {
	fmt.Println("client:", header.SourceAddr)
}

// Continue reading the application data from the same buffered reader.
data, _ := io.ReadAll(reader)
fmt.Printf("data: %s\n", data)
Output:
client: 192.168.1.1:12345
data: HELO

func ReadTimeout deprecated

func ReadTimeout(reader *bufio.Reader, timeout time.Duration) (*Header, error)

ReadTimeout acts as Read but takes a timeout. If that timeout is reached, it's assumed there's no proxy protocol header.

Deprecated: ReadTimeout cannot cancel the read it starts. It only receives a *bufio.Reader, so on timeout it has no way to set a deadline on or close the underlying connection: the goroutine it spawns stays blocked in Read, peeking at the stalled connection, until the peer sends data or the connection is closed elsewhere. Each timed-out call therefore leaks that goroutine and the connection's file descriptor. Use ReadHeaderTimeout instead, which also takes the net.Conn and sets a real read deadline so the read is actually cancelled on timeout; or wrap the connection with NewConn or a Listener and configure the header timeout via the SetReadHeaderTimeout option or Listener.ReadHeaderTimeout.

func (*Header) EqualTo

func (header *Header) EqualTo(otherHeader *Header) bool

EqualTo returns true if headers are equivalent, false otherwise. Deprecated: use EqualsTo instead. This method will eventually be removed.

func (*Header) EqualsTo

func (header *Header) EqualsTo(otherHeader *Header) bool

EqualsTo returns true if headers are equivalent, false otherwise.

func (*Header) Format

func (header *Header) Format() ([]byte, error)

Format renders a proxy protocol header in a format to write over the wire.

func (*Header) FormatUDPDatagram added in v0.15.0

func (header *Header) FormatUDPDatagram(payload []byte) ([]byte, error)

FormatUDPDatagram renders the header followed by the proxied payload, producing the exact bytes to send as one UDP datagram. Per spec (section 2) the header and the payload MUST share a single datagram, which a single write of the returned slice guarantees; formatting the header and payload separately risks a sender splitting them across datagrams.

Example
package main

import (
	"fmt"
	"net"

	"github.com/pires/go-proxyproto"
)

func main() {
	header := &proxyproto.Header{
		Version:           2,
		Command:           proxyproto.PROXY,
		TransportProtocol: proxyproto.UDPv4,
		SourceAddr:        &net.UDPAddr{IP: net.ParseIP("192.168.1.1"), Port: 12345},
		DestinationAddr:   &net.UDPAddr{IP: net.ParseIP("192.168.1.2"), Port: 443},
	}

	// The returned slice is the exact bytes to hand to a single WriteTo;
	// writing header and payload separately risks splitting them across
	// datagrams, which the spec forbids.
	datagram, err := header.FormatUDPDatagram([]byte("ping"))
	if err != nil {
		fmt.Println("format error:", err)
		return
	}

	parsed, payload, _ := proxyproto.ParseUDPDatagram(datagram)
	fmt.Printf("%d bytes on the wire: %s -> %s carrying %q\n",
		len(datagram), parsed.SourceAddr, parsed.DestinationAddr, payload)
}
Output:
32 bytes on the wire: 192.168.1.1:12345 -> 192.168.1.2:443 carrying "ping"

func (*Header) IPs added in v0.3.0

func (header *Header) IPs() (sourceIP, destIP net.IP, ok bool)

IPs returns source/destination IPs for TCP/UDP headers.

func (*Header) Ports added in v0.3.0

func (header *Header) Ports() (sourcePort, destPort int, ok bool)

Ports returns source/destination ports for TCP/UDP headers.

func (*Header) SetTLVs added in v0.2.0

func (header *Header) SetTLVs(tlvs []TLV) error

SetTLVs sets the TLVs stored in this header. This method replaces any previous TLV.

func (*Header) TCPAddrs added in v0.3.0

func (header *Header) TCPAddrs() (sourceAddr, destAddr *net.TCPAddr, ok bool)

TCPAddrs returns TCP source/destination addresses if the header is stream-based.

func (*Header) TLVs

func (header *Header) TLVs() ([]TLV, error)

TLVs returns the TLVs stored into this header, if they exist. TLVs are optional for v2 of the protocol.

func (*Header) UDPAddrs added in v0.3.0

func (header *Header) UDPAddrs() (sourceAddr, destAddr *net.UDPAddr, ok bool)

UDPAddrs returns UDP source/destination addresses if the header is datagram-based.

func (*Header) UnixAddrs added in v0.3.0

func (header *Header) UnixAddrs() (sourceAddr, destAddr *net.UnixAddr, ok bool)

UnixAddrs returns UNIX source/destination addresses if the header is UNIX-based.

func (*Header) WriteTo

func (header *Header) WriteTo(w io.Writer) (int64, error)

WriteTo renders a proxy protocol header in a format and writes it to an io.Writer.

type Listener

type Listener struct {
	// Listener is the underlying listener.
	Listener net.Listener
	// Deprecated: use ConnPolicyFunc instead. This will be removed in future release.
	Policy PolicyFunc
	// ConnPolicy is the policy function for accepted connections.
	ConnPolicy ConnPolicyFunc
	// ValidateHeader is the validator function for the proxy header.
	ValidateHeader Validator
	// ReadHeaderTimeout is the timeout for reading the proxy header.
	ReadHeaderTimeout time.Duration
	// ReadBufferSize is the read buffer size for accepted connections. When > 0,
	// each accepted connection uses this size for proxy header detection; 0 means default.
	// See the sizing note on WithBufferSize: values below 107 bytes break v1
	// header parsing.
	ReadBufferSize int
}

Listener is used to wrap an underlying listener, whose connections may be using the HAProxy Proxy Protocol. If the connection is using the protocol, the RemoteAddr() will return the correct client address. ReadHeaderTimeout will be applied to all connections in order to prevent blocking operations. If no ReadHeaderTimeout is set, a default of 10s will be used. This can be disabled by setting the timeout to < 0.

When neither Policy nor ConnPolicy is set, DefaultPolicy applies: REQUIRE, so connections that do not open with a PROXY header fail their first Read/Write with ErrNoProxyProtocol. Headers are still honored from ANY peer under REQUIRE; a listener reachable by untrusted clients should set a trusted-source policy (e.g. TrustProxyHeaderFrom or TrustProxyHeaderFromRanges).

Listener is stream-oriented (TCP, Unix stream): the header is read once at the start of the byte stream. It cannot implement the PROXY protocol over UDP, where the spec requires a header parsed independently in each datagram; use ParseUDPDatagram and Header.FormatUDPDatagram for that.

Note that ReadHeaderTimeout only bounds how long a single slow connection can hold a goroutine and file descriptor during header detection; it is not a connection count or accept-rate limit. Deployments exposed to untrusted clients should keep ReadHeaderTimeout low and enforce connection/rate limits upstream (or around Accept).

Only one of Policy or ConnPolicy should be provided. If both are provided then a panic would occur during accept.

Example (Default)
package main

import (
	"fmt"
	"net"

	"github.com/pires/go-proxyproto"
)

// proxyV1Line is a minimal PROXY protocol v1 header for examples.
const proxyV1Line = "PROXY TCP4 192.168.1.1 192.168.1.2 12345 443\r\n"

func main() {
	l, _ := net.Listen("tcp", "127.0.0.1:0")
	// By default every connection must open with a PROXY header (DefaultPolicy
	// is REQUIRE); RemoteAddr then reports the address the header carries.
	pl := &proxyproto.Listener{Listener: l}
	defer func() { _ = pl.Close() }()

	go func() {
		c, _ := net.Dial("tcp", pl.Addr().String())
		if c != nil {
			_, _ = c.Write([]byte(proxyV1Line))
			_, _ = c.Write([]byte("x"))
			_ = c.Close()
		}
	}()

	conn, _ := pl.Accept()
	if conn != nil {
		buf := make([]byte, 1)
		if _, err := conn.Read(buf); err != nil {
			fmt.Println("read error:", err)
			return
		}
		fmt.Println(conn.RemoteAddr())
		_ = conn.Close()
	}
}
Output:
192.168.1.1:12345
Example (PolicyRequire)
package main

import (
	"net"

	"github.com/pires/go-proxyproto"
)

// proxyV1Line is a minimal PROXY protocol v1 header for examples.
const proxyV1Line = "PROXY TCP4 192.168.1.1 192.168.1.2 12345 443\r\n"

func main() {
	l, _ := net.Listen("tcp", "127.0.0.1:0")
	pl := &proxyproto.Listener{
		Listener: l,
		Policy:   func(net.Addr) (proxyproto.Policy, error) { return proxyproto.REQUIRE, nil },
	}
	defer func() { _ = pl.Close() }()

	go func() {
		c, _ := net.Dial("tcp", pl.Addr().String())
		if c != nil {
			_, _ = c.Write([]byte(proxyV1Line))
			_, _ = c.Write([]byte("p"))
			_ = c.Close()
		}
	}()

	conn, _ := pl.Accept()
	if conn != nil {
		buf := make([]byte, 1)
		_, _ = conn.Read(buf)
		_ = conn.Close()
	}
}
Example (ReadBufferSize)
package main

import (
	"net"

	"github.com/pires/go-proxyproto"
)

// proxyV1Line is a minimal PROXY protocol v1 header for examples.
const proxyV1Line = "PROXY TCP4 192.168.1.1 192.168.1.2 12345 443\r\n"

func main() {
	l, _ := net.Listen("tcp", "127.0.0.1:0")
	pl := &proxyproto.Listener{
		Listener:       l,
		ReadBufferSize: 4096,
	}
	defer func() { _ = pl.Close() }()

	go func() {
		c, _ := net.Dial("tcp", pl.Addr().String())
		if c != nil {
			_, _ = c.Write([]byte(proxyV1Line))
			_, _ = c.Write([]byte("b"))
			_ = c.Close()
		}
	}()

	conn, _ := pl.Accept()
	if conn != nil {
		buf := make([]byte, 1)
		_, _ = conn.Read(buf)
		_ = conn.Close()
	}
}
Example (ReadHeaderTimeout)
package main

import (
	"net"
	"time"

	"github.com/pires/go-proxyproto"
)

// proxyV1Line is a minimal PROXY protocol v1 header for examples.
const proxyV1Line = "PROXY TCP4 192.168.1.1 192.168.1.2 12345 443\r\n"

func main() {
	l, _ := net.Listen("tcp", "127.0.0.1:0")
	pl := &proxyproto.Listener{
		Listener:          l,
		ReadHeaderTimeout: 2 * time.Second,
	}
	defer func() { _ = pl.Close() }()

	go func() {
		c, _ := net.Dial("tcp", pl.Addr().String())
		if c != nil {
			_, _ = c.Write([]byte(proxyV1Line))
			_, _ = c.Write([]byte("a"))
			_ = c.Close()
		}
	}()

	conn, _ := pl.Accept()
	if conn != nil {
		_ = conn.SetReadDeadline(time.Now().Add(time.Second))
		buf := make([]byte, 1)
		_, _ = conn.Read(buf)
		_ = conn.Close()
	}
}
Example (Tls)

ExampleListener_tls shows how to combine the PROXY protocol with TLS.

The only real decision is the listener wrapping order, and it depends on where the upstream places the PROXY header relative to the TLS handshake:

  • Header in cleartext, BEFORE the handshake. This is the common case (e.g. AWS NLB with proxy protocol v2, or HAProxy "send-proxy" in front of a TLS listener). proxyproto must read the header first, so it goes INSIDE the TLS listener: tls.NewListener(&proxyproto.Listener{...}, cfg).

  • Header INSIDE the TLS session, AFTER the handshake. TLS must be decrypted first, so proxyproto goes OUTSIDE the TLS listener: &proxyproto.Listener{Listener: tls.NewListener(l, cfg)}.

This example demonstrates the first (cleartext-header) ordering, which is what most deployments need. Because the header is parsed before TLS, conn.RemoteAddr() reports the real client address carried by the PROXY header rather than the immediate peer (the proxy). See ExampleListener_tlsHeaderInsideTLS for the second ordering.

package main

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"math/big"
	"net"
	"time"

	proxyproto "github.com/pires/go-proxyproto"
)

func main() {
	l, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		return
	}

	// proxyproto INNER, tls OUTER: the PROXY header is read from cleartext, then
	// the TLS handshake runs on the remaining stream.
	proxyListener := &proxyproto.Listener{Listener: l}
	tlsListener := tls.NewListener(proxyListener, exampleSelfSignedConfig())
	defer func() { _ = tlsListener.Close() }()

	go func() {
		// Client side: open a raw TCP connection, write the PROXY header in
		// cleartext, and only THEN start the TLS handshake.
		raw, err := net.Dial("tcp", l.Addr().String())
		if err != nil {
			return
		}
		defer func() { _ = raw.Close() }()

		header := &proxyproto.Header{
			Version:           2,
			Command:           proxyproto.PROXY,
			TransportProtocol: proxyproto.TCPv4,
			SourceAddr:        &net.TCPAddr{IP: net.ParseIP("10.1.1.1"), Port: 1000},
			DestinationAddr:   &net.TCPAddr{IP: net.ParseIP("20.2.2.2"), Port: 2000},
		}
		if _, err := header.WriteTo(raw); err != nil {
			return
		}

		client := tls.Client(raw, &tls.Config{
			InsecureSkipVerify: true, //nolint:gosec // example uses a throwaway self-signed cert.
		})
		_ = client.Handshake()
		_ = client.Close()
	}()

	conn, err := tlsListener.Accept()
	if err != nil {
		return
	}
	defer func() { _ = conn.Close() }()

	// conn is a *tls.Conn whose underlying connection is a *proxyproto.Conn that
	// has already parsed the header, so RemoteAddr() is the real client. From
	// here, use conn like any other tls.Conn (Read/Write) to serve the client.
	fmt.Println(conn.RemoteAddr())
}

// exampleSelfSignedConfig returns a *tls.Config with a throwaway self-signed
// certificate, so the example is self-contained. Real servers load a real
// certificate, e.g. via tls.LoadX509KeyPair.
func exampleSelfSignedConfig() *tls.Config {
	priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		panic(err)
	}
	template := &x509.Certificate{
		SerialNumber: big.NewInt(1),
		NotBefore:    time.Now().Add(-time.Hour),
		NotAfter:     time.Now().Add(time.Hour),
	}
	der, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv)
	if err != nil {
		panic(err)
	}
	return &tls.Config{
		MinVersion: tls.VersionTLS12,
		Certificates: []tls.Certificate{{
			Certificate: [][]byte{der},
			PrivateKey:  priv,
		}},
	}
}
Output:
10.1.1.1:1000
Example (TlsHeaderInsideTLS)

ExampleListener_tlsHeaderInsideTLS shows the second wrapping order, where the upstream completes the TLS handshake first and only then sends the PROXY header inside the encrypted session. TLS must be decrypted before the header can be read, so proxyproto wraps the TLS listener: proxyproto OUTER, tls INNER (the inverse of ExampleListener_tls). Use this only when you control the upstream and it deliberately speaks the header after the handshake.

package main

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"math/big"
	"net"
	"time"

	proxyproto "github.com/pires/go-proxyproto"
)

func main() {
	l, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		return
	}

	// tls INNER, proxyproto OUTER: TLS is decrypted first, then the PROXY header
	// is parsed from the decrypted stream.
	proxyListener := &proxyproto.Listener{
		Listener: tls.NewListener(l, exampleSelfSignedConfig()),
	}
	defer func() { _ = proxyListener.Close() }()

	go func() {
		// Client side: complete the TLS handshake first, then write the PROXY
		// header inside the encrypted session.
		raw, err := net.Dial("tcp", l.Addr().String())
		if err != nil {
			return
		}
		defer func() { _ = raw.Close() }()

		client := tls.Client(raw, &tls.Config{
			InsecureSkipVerify: true, //nolint:gosec // example uses a throwaway self-signed cert.
		})
		if err := client.Handshake(); err != nil {
			return
		}

		header := &proxyproto.Header{
			Version:           2,
			Command:           proxyproto.PROXY,
			TransportProtocol: proxyproto.TCPv4,
			SourceAddr:        &net.TCPAddr{IP: net.ParseIP("10.1.1.1"), Port: 1000},
			DestinationAddr:   &net.TCPAddr{IP: net.ParseIP("20.2.2.2"), Port: 2000},
		}
		_, _ = header.WriteTo(client)
		_ = client.Close()
	}()

	conn, err := proxyListener.Accept()
	if err != nil {
		return
	}
	defer func() { _ = conn.Close() }()

	// conn is a *proxyproto.Conn wrapping a *tls.Conn. Reading the header (here
	// via RemoteAddr) transparently runs the TLS handshake, decrypts the stream,
	// and then parses the PROXY header, so RemoteAddr() is the real client.
	fmt.Println(conn.RemoteAddr())
}

// exampleSelfSignedConfig returns a *tls.Config with a throwaway self-signed
// certificate, so the example is self-contained. Real servers load a real
// certificate, e.g. via tls.LoadX509KeyPair.
func exampleSelfSignedConfig() *tls.Config {
	priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		panic(err)
	}
	template := &x509.Certificate{
		SerialNumber: big.NewInt(1),
		NotBefore:    time.Now().Add(-time.Hour),
		NotAfter:     time.Now().Add(time.Hour),
	}
	der, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv)
	if err != nil {
		panic(err)
	}
	return &tls.Config{
		MinVersion: tls.VersionTLS12,
		Certificates: []tls.Certificate{{
			Certificate: [][]byte{der},
			PrivateKey:  priv,
		}},
	}
}
Output:
10.1.1.1:1000
Example (ValidateHeader)
package main

import (
	"net"

	"github.com/pires/go-proxyproto"
)

// proxyV1Line is a minimal PROXY protocol v1 header for examples.
const proxyV1Line = "PROXY TCP4 192.168.1.1 192.168.1.2 12345 443\r\n"

func main() {
	l, _ := net.Listen("tcp", "127.0.0.1:0")
	pl := &proxyproto.Listener{
		Listener:       l,
		ValidateHeader: func(*proxyproto.Header) error { return nil },
	}
	defer func() { _ = pl.Close() }()

	go func() {
		c, _ := net.Dial("tcp", pl.Addr().String())
		if c != nil {
			_, _ = c.Write([]byte(proxyV1Line))
			_, _ = c.Write([]byte("v"))
			_ = c.Close()
		}
	}()

	conn, _ := pl.Accept()
	if conn != nil {
		buf := make([]byte, 1)
		_, _ = conn.Read(buf)
		_ = conn.Close()
	}
}

func (*Listener) Accept

func (p *Listener) Accept() (net.Conn, error)

Accept waits for and returns the next valid connection to the listener.

func (*Listener) Addr

func (p *Listener) Addr() net.Addr

Addr returns the underlying listener's network address.

func (*Listener) Close

func (p *Listener) Close() error

Close closes the underlying listener.

type PP2Type

type PP2Type byte

PP2Type is the proxy protocol v2 type.

const (
	// Section 2.2.
	PP2_TYPE_ALPN               PP2Type = 0x01
	PP2_TYPE_AUTHORITY          PP2Type = 0x02
	PP2_TYPE_CRC32C             PP2Type = 0x03
	PP2_TYPE_NOOP               PP2Type = 0x04
	PP2_TYPE_UNIQUE_ID          PP2Type = 0x05
	PP2_TYPE_SSL                PP2Type = 0x20
	PP2_SUBTYPE_SSL_VERSION     PP2Type = 0x21
	PP2_SUBTYPE_SSL_CN          PP2Type = 0x22
	PP2_SUBTYPE_SSL_CIPHER      PP2Type = 0x23
	PP2_SUBTYPE_SSL_SIG_ALG     PP2Type = 0x24
	PP2_SUBTYPE_SSL_KEY_ALG     PP2Type = 0x25
	PP2_SUBTYPE_SSL_GROUP       PP2Type = 0x26
	PP2_SUBTYPE_SSL_SIG_SCHEME  PP2Type = 0x27
	PP2_SUBTYPE_SSL_CLIENT_CERT PP2Type = 0x28
	PP2_TYPE_NETNS              PP2Type = 0x30

	// Section 2.2.8, reserved types.
	PP2_TYPE_MIN_CUSTOM     PP2Type = 0xE0
	PP2_TYPE_MAX_CUSTOM     PP2Type = 0xEF
	PP2_TYPE_MIN_EXPERIMENT PP2Type = 0xF0
	PP2_TYPE_MAX_EXPERIMENT PP2Type = 0xF7
	PP2_TYPE_MIN_FUTURE     PP2Type = 0xF8
	PP2_TYPE_MAX_FUTURE     PP2Type = 0xFF
)

TLV type constants defined by the PROXY protocol spec.

func (PP2Type) App

func (p PP2Type) App() bool

App is true if the type is reserved for application specific data, see section 2.2.8.

func (PP2Type) Experiment

func (p PP2Type) Experiment() bool

Experiment is true if the type is reserved for temporary experimental use by application developers, see section 2.2.8.

func (PP2Type) Future

func (p PP2Type) Future() bool

Future is true is the type is reserved for future use, see section 2.2.8.

func (PP2Type) Registered

func (p PP2Type) Registered() bool

Registered is true if the type is registered in the spec, see section 2.2.

func (PP2Type) Spec

func (p PP2Type) Spec() bool

Spec is true if the type is covered by the spec, see section 2.2 and 2.2.8.

type Policy

type Policy int

Policy defines how a connection with a PROXY header address is treated.

const (
	// USE address from PROXY header.
	USE Policy = iota
	// IGNORE address from PROXY header, but accept connection.
	IGNORE
	// REJECT connection when PROXY header is sent
	// Note: if a PROXY header is present the first read returns
	// ErrSuperfluousProxyHeader, and every subsequent read returns the same
	// error, so the connection should be closed.
	REJECT
	// REQUIRE connection to send PROXY header, reject if not present
	// Note: if no PROXY header is present the first read returns
	// ErrNoProxyProtocol, and every subsequent read returns the same error,
	// so the connection should be closed.
	REQUIRE
	// SKIP accepts a connection without requiring the PROXY header.
	// Note: an example usage can be found in the SkipProxyHeaderForCIDR
	// function.
	//
	// On a Listener, SKIP short-circuits Accept and returns the raw, unwrapped
	// connection. On a Conn (via WithPolicy), a PROXY header that is present is
	// still consumed from the stream but discarded: ProxyHeader returns nil and
	// no validation runs.
	SKIP
)

type PolicyFunc deprecated

type PolicyFunc func(upstream net.Addr) (Policy, error)

PolicyFunc can be used to decide whether to trust the PROXY info from upstream. If set, the connecting address is passed in as an argument.

See below for the different policies.

In case an error is returned the connection is denied: an error wrapping ErrInvalidUpstream denies just that connection while Listener.Accept keeps listening; any other error is returned by Accept itself, which typically stops the caller's accept loop.

Deprecated: use ConnPolicyFunc instead.

func LaxWhiteListPolicy deprecated

func LaxWhiteListPolicy(allowed []string) (PolicyFunc, error)

LaxWhiteListPolicy returns a PolicyFunc which decides whether the upstream ip is allowed to send a proxy header based on a list of allowed IP addresses and IP ranges. In case upstream IP is not in list the proxy header will be ignored. If one of the provided IP addresses or IP ranges is invalid it will return an error instead of a PolicyFunc.

Deprecated: use PolicyFromRanges(allowed, USE, IGNORE) instead; see ConnLaxWhiteListPolicy for why.

func MustLaxWhiteListPolicy deprecated

func MustLaxWhiteListPolicy(allowed []string) PolicyFunc

MustLaxWhiteListPolicy returns a LaxWhiteListPolicy but will panic if one of the provided IP addresses or IP ranges is invalid.

Deprecated: use MustPolicyFromRanges(allowed, USE, IGNORE) instead; see ConnLaxWhiteListPolicy for why.

func MustStrictWhiteListPolicy deprecated

func MustStrictWhiteListPolicy(allowed []string) PolicyFunc

MustStrictWhiteListPolicy returns a StrictWhiteListPolicy but will panic if one of the provided IP addresses or IP ranges is invalid.

Deprecated: use MustPolicyFromRanges(allowed, USE, REJECT) instead; see ConnStrictWhiteListPolicy for why.

func SkipProxyHeaderForCIDR deprecated added in v0.7.0

func SkipProxyHeaderForCIDR(skipHeaderCIDR *net.IPNet, def Policy) PolicyFunc

SkipProxyHeaderForCIDR returns a PolicyFunc which can be used to accept a connection from a skipHeaderCIDR without requiring a PROXY header, e.g. Kubernetes pods local traffic. The def is a policy to use when an upstream address doesn't match the skipHeaderCIDR.

Deprecated: use ConnSkipProxyHeaderForCIDR instead.

func StrictWhiteListPolicy deprecated

func StrictWhiteListPolicy(allowed []string) (PolicyFunc, error)

StrictWhiteListPolicy returns a PolicyFunc which decides whether the upstream ip is allowed to send a proxy header based on a list of allowed IP addresses and IP ranges. In case upstream IP is not in list reading on the connection will be refused: the first read returns ErrSuperfluousProxyHeader and every subsequent read returns the same error, so the connection should be closed. If one of the provided IP addresses or IP ranges is invalid it will return an error instead of a PolicyFunc.

Deprecated: use PolicyFromRanges(allowed, USE, REJECT) instead; see ConnStrictWhiteListPolicy for why.

type ProtocolVersionAndCommand

type ProtocolVersionAndCommand byte

ProtocolVersionAndCommand represents the command in proxy protocol v2. Command doesn't exist in v1 but it should be set since other parts of this library may rely on it for determining connection details.

const (
	// LOCAL represents the LOCAL command in v2 or UNKNOWN transport in v1,
	// in which case no address information is expected.
	LOCAL ProtocolVersionAndCommand = '\x20'
	// PROXY represents the PROXY command in v2 or transport is not UNKNOWN in v1,
	// in which case valid local/remote address and port information is expected.
	PROXY ProtocolVersionAndCommand = '\x21'
)

func (ProtocolVersionAndCommand) IsLocal

func (pvc ProtocolVersionAndCommand) IsLocal() bool

IsLocal returns true if the command in v2 is LOCAL or the transport in v1 is UNKNOWN, i.e. when no address information is expected, false otherwise.

func (ProtocolVersionAndCommand) IsProxy

func (pvc ProtocolVersionAndCommand) IsProxy() bool

IsProxy returns true if the command in v2 is PROXY or the transport in v1 is not UNKNOWN, i.e. when valid local/remote address and port information is expected, false otherwise.

func (ProtocolVersionAndCommand) IsUnspec

func (pvc ProtocolVersionAndCommand) IsUnspec() bool

IsUnspec returns true if the command is unspecified, false otherwise.

type TLV

type TLV struct {
	Type  PP2Type
	Value []byte
}

TLV is a uninterpreted Type-Length-Value for V2 protocol, see section 2.2.

func SplitTLVs

func SplitTLVs(raw []byte) ([]TLV, error)

SplitTLVs splits the Type-Length-Value vector, returns the vector or an error.

type Validator

type Validator func(*Header) error

Validator receives a header and decides whether it is a valid one In case the header is not deemed valid it should return an error.

Directories

Path Synopsis
examples
client command
Package main provides a proxyproto client example.
Package main provides a proxyproto client example.
httpserver command
Package main provides a proxyproto HTTP server example.
Package main provides a proxyproto HTTP server example.
server command
Package main provides a proxyproto server example.
Package main provides a proxyproto server example.
tlsclient command
Package main provides a proxyproto + TLS client example.
Package main provides a proxyproto + TLS client example.
tlsserver command
Package main provides a proxyproto + TLS server example.
Package main provides a proxyproto + TLS server example.
udpclient command
Package main provides a proxyproto UDP client example.
Package main provides a proxyproto UDP client example.
udppacketconn command
Package main sketches a net.PacketConn wrapper for the PROXY protocol over UDP.
Package main sketches a net.PacketConn wrapper for the PROXY protocol over UDP.
udpserver command
Package main provides a proxyproto UDP server example.
Package main provides a proxyproto UDP server example.
helper
http2
Package http2 provides helpers for HTTP/2.
Package http2 provides helpers for HTTP/2.
Package tlvparse provides helpers for PROXY protocol TLVs.
Package tlvparse provides helpers for PROXY protocol TLVs.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL