The HAProxy PROXY protocol, versions 1 (text) and 2 (binary), which a proxy or load balancer sends first on a connection to pass on the original client and server addresses.
parse/2 decodes a header at the start of a binary and read/3 reads
one from a socket; encode_v1/1 and encode_v2/1 build headers for
clients. A parsed header is a Sovite.ProxyProtocol.Header.
{:ok, socket} = :gen_tcp.accept(listen_socket)
{:ok, %Header{command: :proxy, source: {client_ip, client_port}}} =
Sovite.ProxyProtocol.read(socket, 10_000)Parsing is strict, as the specification requires: anything that is not exactly a valid header is an error, and the connection should be closed. Only accept headers from trusted proxies: a header is just what its sender claims.
Version 1
A line such as PROXY TCP4 192.0.2.1 198.51.100.1 56324 25\r\n of at
most 107 bytes. Addresses and ports are decimal without leading zeros.
PROXY UNKNOWN is parsed as :unspec, with the rest of its line
ignored. Version 1 has no LOCAL command and no TLVs.
Version 2
A 16-byte prefix (signature, version and command, family and
protocol, length) followed by the addresses and TLVs. A LOCAL header
is parsed without its address block, which the receiver must ignore.
With AF_UNSPEC the whole block is TLVs. Headers longer than
:max_length are rejected. A PP2_TYPE_CRC32C checksum is verified;
the well-known TLVs must be well-formed and appear at most once.
Errors
:invalid_signature- neither a version 1 nor a version 2 header.- Version 1:
:line_too_long,:invalid_line(bad syntax or characters, a lone CR or LF),:invalid_transport(notTCP4,TCP6, orUNKNOWN),:invalid_address,:invalid_port. - Version 2:
:invalid_version,:invalid_command,:invalid_family(an undefined family or protocol),:invalid_length(too short for the family's addresses),:header_too_long,:invalid_tlv(truncated, malformed, or repeated),:crc32c_mismatch.
Summary
Functions
Encodes a version 1 header. A :proxy header over :tcp4 or :tcp6
carries its addresses; anything else becomes PROXY UNKNOWN. TLVs
are not sent. Raises ArgumentError on addresses that do not match
the transport.
Encodes a version 2 header with the TLVs in :tlvs (the decoded TLV
fields are ignored). A PP2_TYPE_CRC32C TLV among them, such as
tlv(:crc32c), gets the header's checksum. A :local header is sent
with no addresses and no TLVs. Raises ArgumentError on addresses
that do not match the transport, or a header over 64 KiB.
Parses a header at the start of data. Returns the header and the
bytes after it, which belong to the application protocol.
Reads a header from socket, a passive (active: false) :gen_tcp
socket in binary mode with packet: :raw. Fails unless the whole
header arrives within timeout milliseconds.
Builds a well-known version 2 TLV for Header :tlvs.
Types
Functions
@spec encode_v1(Sovite.ProxyProtocol.Header.t()) :: binary()
Encodes a version 1 header. A :proxy header over :tcp4 or :tcp6
carries its addresses; anything else becomes PROXY UNKNOWN. TLVs
are not sent. Raises ArgumentError on addresses that do not match
the transport.
iex> Sovite.ProxyProtocol.encode_v1(%Sovite.ProxyProtocol.Header{
...> transport: :tcp4,
...> source: {{192, 0, 2, 1}, 56324},
...> destination: {{198, 51, 100, 1}, 25}
...> })
"PROXY TCP4 192.0.2.1 198.51.100.1 56324 25\r\n"
iex> Sovite.ProxyProtocol.encode_v1(%Sovite.ProxyProtocol.Header{command: :local})
"PROXY UNKNOWN\r\n"
@spec encode_v2(Sovite.ProxyProtocol.Header.t()) :: binary()
Encodes a version 2 header with the TLVs in :tlvs (the decoded TLV
fields are ignored). A PP2_TYPE_CRC32C TLV among them, such as
tlv(:crc32c), gets the header's checksum. A :local header is sent
with no addresses and no TLVs. Raises ArgumentError on addresses
that do not match the transport, or a header over 64 KiB.
@spec parse(binary(), keyword()) :: {:ok, Sovite.ProxyProtocol.Header.t(), rest :: binary()} | {:more, pos_integer() | :unknown} | {:error, error_reason()}
Parses a header at the start of data. Returns the header and the
bytes after it, which belong to the application protocol.
On incomplete input, returns {:more, n} when at least n more bytes
are needed (version 2), or {:more, :unknown} when the end cannot be
known yet (version 1, or too little input to tell the version).
Options:
:max_length- the longest version 2 header accepted, in bytes. Defaults to 4096.
Examples:
iex> {:ok, header, rest} =
...> Sovite.ProxyProtocol.parse("PROXY TCP4 192.0.2.1 198.51.100.1 56324 25\r\nEHLO")
iex> {header.source, header.destination, rest}
{{{192, 0, 2, 1}, 56324}, {{198, 51, 100, 1}, 25}, "EHLO"}
iex> Sovite.ProxyProtocol.parse("PROXY TCP4 192.0.2.1")
{:more, :unknown}
iex> Sovite.ProxyProtocol.parse("EHLO client.example\r\n")
{:error, :invalid_signature}
@spec read(:gen_tcp.socket(), non_neg_integer(), keyword()) :: {:ok, Sovite.ProxyProtocol.Header.t()} | {:error, error_reason() | :timeout | :closed | :inet.posix()}
Reads a header from socket, a passive (active: false) :gen_tcp
socket in binary mode with packet: :raw. Fails unless the whole
header arrives within timeout milliseconds.
Exactly the header is read: what the client sent after it is left on the socket for the application protocol. A version 1 line is read a byte at a time past its first 15 bytes (from the socket's buffer, not a system call each), a version 2 header by its length.
Takes the options of parse/2. Returns {:error, reason} with a
reason from parse/2, :timeout, :closed, or another
:inet.posix() error.
@spec tlv(:crc32c) :: Sovite.ProxyProtocol.Header.tlv()
Returns a PP2_TYPE_CRC32C placeholder, see tlv/2.
@spec tlv(:alpn | :authority | :unique_id | :netns | :noop | :ssl, binary() | map()) :: Sovite.ProxyProtocol.Header.tlv()
Builds a well-known version 2 TLV for Header :tlvs.
:alpn,:authority,:unique_id,:netns,:noop-valueis the binary to send.:ssl-valueis a map likeSovite.ProxyProtocol.Header.ssl/0, every key optional (:clientdefaults to[],:verifyto0,:tlvsis ignored).
tlv(:crc32c) is a placeholder that encode_v2/1 fills in.
iex> Sovite.ProxyProtocol.tlv(:alpn, "h2")
{0x01, "h2"}
iex> Sovite.ProxyProtocol.tlv(:ssl, %{client: [:ssl], version: "TLSv1.3"})
{0x20, <<1, 0, 0, 0, 0, 0x21, 0, 7, "TLSv1.3">>}