proxyproto

package module
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Mar 15, 2023 License: Apache-2.0 Imports: 14 Imported by: 295

README

go-proxyproto

Actions Status Coverage Status Go Report Card

A Go library implementation of the PROXY protocol, versions 1 and 2, which provides, as per specification:

(...) a convenient way to safely transport connection information such as a client's address across multiple layers of NAT or TCP proxies. It is designed to require little changes to existing components and to limit the performance impact caused by the processing of the transported information.

This library is to be used in one of or both proxy clients and proxy servers that need to support said protocol. Both protocol versions, 1 (text-based) and 2 (binary-based) are supported.

Installation

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

Usage

Client
package main

import (
	"io"
	"log"
	"net"

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

func chkErr(err error) {
	if err != nil {
		log.Fatalf("Error: %s", err.Error())
	}
}

func main() {
	// Dial some proxy listener e.g. https://github.com/mailgun/proxyproto
	target, err := net.ResolveTCPAddr("tcp", "127.0.0.1:2319")
	chkErr(err)

	conn, err := net.DialTCP("tcp", nil, target)
	chkErr(err)

	defer conn.Close()

	// Create a proxyprotocol header or use HeaderProxyFromAddrs() if you
	// have two conn's
	header := &proxyproto.Header{
		Version:            1,
		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,
		},
	}
	// After the connection was created write the proxy headers first
	_, err = header.WriteTo(conn)
	chkErr(err)
	// Then your data... e.g.:
	_, err = io.WriteString(conn, "HELO")
	chkErr(err)
}
Server
package main

import (
	"log"
	"net"

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

func main() {
	// Create a listener
	addr := "localhost:9876"
	list, err := net.Listen("tcp", addr)
	if err != nil {
		log.Fatalf("couldn't listen to %q: %q\n", addr, err.Error())
	}

	// Wrap listener in a proxyproto listener
	proxyListener := &proxyproto.Listener{Listener: list}
	defer proxyListener.Close()

	// Wait for a connection and accept it
	conn, err := proxyListener.Accept()
	defer conn.Close()

	// Print connection details
	if conn.LocalAddr() == nil {
		log.Fatal("couldn't retrieve local address")
	}
	log.Printf("local address: %q", conn.LocalAddr().String())

	if conn.RemoteAddr() == nil {
		log.Fatal("couldn't retrieve remote address")
	}
	log.Printf("remote address: %q", conn.RemoteAddr().String())
}
HTTP Server
package main

import (
	"net"
	"net/http"
	"time"

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

func main() {
	server := http.Server{
		Addr: ":8080",
	}

	ln, err := net.Listen("tcp", server.Addr)
	if err != nil {
		panic(err)
	}

	proxyListener := &proxyproto.Listener{
		Listener:          ln,
		ReadHeaderTimeout: 10 * time.Second,
	}
	defer proxyListener.Close()

	server.Serve(proxyListener)
}

Special notes

AWS

AWS Network Load Balancer (NLB) does not push the PPV2 header until the client starts sending the data. This is a problem if your server speaks first. e.g. SMTP, FTP, SSH etc.

By default, NLB target group attribute proxy_protocol_v2.client_to_server.header_placement has the value on_first_ack_with_payload. You need to contact AWS support to change it to on_first_ack, instead.

Just to be clear, you need this fix only if your server is designed to speak first.

Documentation

Overview

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

Index

Constants

This section is empty.

Variables

View Source
var (
	// Protocol
	SIGV1 = []byte{'\x50', '\x52', '\x4F', '\x58', '\x59'}
	SIGV2 = []byte{'\x0D', '\x0A', '\x0D', '\x0A', '\x00', '\x0D', '\x0A', '\x51', '\x55', '\x49', '\x54', '\x0A'}

	ErrCantReadVersion1Header               = errors.New("proxyproto: can't read version 1 header")
	ErrVersion1HeaderTooLong                = errors.New("proxyproto: version 1 header must be 107 bytes or less")
	ErrLineMustEndWithCrlf                  = errors.New("proxyproto: version 1 header is invalid, must end with \\r\\n")
	ErrCantReadProtocolVersionAndCommand    = errors.New("proxyproto: can't read proxy protocol version and command")
	ErrCantReadAddressFamilyAndProtocol     = errors.New("proxyproto: can't read address family or protocol")
	ErrCantReadLength                       = errors.New("proxyproto: can't read length")
	ErrCantResolveSourceUnixAddress         = errors.New("proxyproto: can't resolve source Unix address")
	ErrCantResolveDestinationUnixAddress    = errors.New("proxyproto: can't resolve destination Unix address")
	ErrNoProxyProtocol                      = errors.New("proxyproto: proxy protocol signature not present")
	ErrUnknownProxyProtocolVersion          = errors.New("proxyproto: unknown proxy protocol version")
	ErrUnsupportedProtocolVersionAndCommand = errors.New("proxyproto: unsupported proxy protocol version and command")
	ErrUnsupportedAddressFamilyAndProtocol  = errors.New("proxyproto: unsupported address family and protocol")
	ErrInvalidLength                        = errors.New("proxyproto: invalid length")
	ErrInvalidAddress                       = errors.New("proxyproto: invalid address")
	ErrInvalidPortNumber                    = errors.New("proxyproto: invalid port number")
	ErrSuperfluousProxyHeader               = errors.New("proxyproto: upstream connection sent PROXY header but isn't allowed to send one")
)
View Source
var (
	ErrTruncatedTLV    = errors.New("proxyproto: truncated TLV")
	ErrMalformedTLV    = errors.New("proxyproto: malformed TLV Value")
	ErrIncompatibleTLV = errors.New("proxyproto: incompatible TLV type")
)
View Source
var DefaultReadHeaderTimeout = 10 * time.Second

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"

Functions

func JoinTLVs added in v0.2.0

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

JoinTLVs joins multiple Type-Length-Value records.

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 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'
)

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 {
	Validate Validator

	ProxyHeaderPolicy Policy
	// 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

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 is check for the proxy protocol header when doing the initial scan. If there is an error parsing the header, it is returned and the socket is closed.

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 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 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 ReadTimeout

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.

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) IPs added in v0.3.0

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

func (*Header) Ports added in v0.3.0

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

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)

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)

func (*Header) UnixAddrs added in v0.3.0

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

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          net.Listener
	Policy            PolicyFunc
	ValidateHeader    Validator
	ReadHeaderTimeout time.Duration
}

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 200ms will be used. This can be disabled by setting the timeout to < 0.

func (*Listener) Accept

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

Accept waits for and returns the next 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_TYPE_NETNS          PP2Type = 0x30

	// Section 2.2.7, 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
)

func (PP2Type) App

func (p PP2Type) App() bool

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

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.7

func (PP2Type) Future

func (p PP2Type) Future() bool

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

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.7

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: even though the first read on the connection returns an error if
	// a PROXY header is present, subsequent reads do not. It is the task of
	// the code using the connection to handle that case properly.
	REJECT
	// REQUIRE connection to send PROXY header, reject if not present
	// Note: even though the first read on the connection returns an error if
	// a PROXY header is not present, subsequent reads do not. It is the task
	// of the code using the connection to handle that case properly.
	REQUIRE
	// SKIP accepts a connection without requiring the PROXY header
	// Note: an example usage can be found in the SkipProxyHeaderForCIDR
	// function.
	SKIP
)

type PolicyFunc

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.

func LaxWhiteListPolicy

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.

func MustLaxWhiteListPolicy

func MustLaxWhiteListPolicy(allowed []string) PolicyFunc

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

func MustStrictWhiteListPolicy

func MustStrictWhiteListPolicy(allowed []string) PolicyFunc

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

func SkipProxyHeaderForCIDR 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.

func StrictWhiteListPolicy

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 on the first read. Please note: subsequent reads do not error. It is the task of the code using the connection to handle that case properly. If one of the provided IP addresses or IP ranges is invalid it will return an error instead of a PolicyFunc.

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

Jump to

Keyboard shortcuts

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