]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Add PROXYv2 header utilities
authorArtem Boldariev <artem@boldariev.com>
Fri, 3 Mar 2023 14:42:47 +0000 (16:42 +0200)
committerArtem Boldariev <artem@boldariev.com>
Wed, 6 Dec 2023 13:15:24 +0000 (15:15 +0200)
This commit adds a set of utilities for dealing with PROXYv2 headers,
both parsing and generating them. The code has no dependencies from
the networking code and is (for the most part) a "separate library".

The part responsible for handling incoming PROXYv2 headers is
structured as a state machine which accepts data as input and calls a
callback to notify the upper-level code about the data processing
status.

Such a design, among other things, makes it easy to write a thorough
unit test suite for that, as there are fewer dependencies as well as
will not stand in the way of any changes in the networking code.

lib/isc/Makefile.am
lib/isc/include/isc/proxy2.h [new file with mode: 0644]
lib/isc/proxy2.c [new file with mode: 0644]

index c6b7aa6c9bfe231926a9638e38181a7466d2e895..033b0f4435e9486d573eb4c4dac9cc554e42d5b9 100644 (file)
@@ -64,6 +64,7 @@ libisc_la_HEADERS =                   \
        include/isc/parseint.h          \
        include/isc/pause.h             \
        include/isc/portset.h           \
+       include/isc/proxy2.h            \
        include/isc/quota.h             \
        include/isc/radix.h             \
        include/isc/random.h            \
@@ -170,6 +171,7 @@ libisc_la_SOURCES =         \
        picohttpparser.h        \
        portset.c               \
        probes.d                \
+       proxy2.c                \
        quota.c                 \
        radix.c                 \
        random.c                \
diff --git a/lib/isc/include/isc/proxy2.h b/lib/isc/include/isc/proxy2.h
new file mode 100644 (file)
index 0000000..b831ee7
--- /dev/null
@@ -0,0 +1,855 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+#pragma once
+
+#include <isc/buffer.h>
+#include <isc/mem.h>
+#include <isc/sockaddr.h>
+
+/* Definitions taken or derived from the specification */
+
+#define ISC_PROXY2_HEADER_SIGNATURE \
+       ("\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A")
+
+#define ISC_PROXY2_HEADER_SIGNATURE_SIZE (12)
+
+#define ISC_PROXY2_HEADER_SIZE                                            \
+       (ISC_PROXY2_HEADER_SIGNATURE_SIZE + 1 /* version and command */ + \
+        1 /* protocol and family */ + 2 /* data size */)
+
+#define ISC_PROXY2_MAX_SIZE (ISC_PROXY2_HEADER_SIZE + UINT16_MAX)
+
+#define ISC_PROXY2_MIN_AF_INET_SIZE                                     \
+       (ISC_PROXY2_HEADER_SIZE + 4 /* src_addr */ + 4 /* dst_addr */ + \
+        2 /* src port */ + 2 /* dst_port */)
+
+#define ISC_PROXY2_MIN_AF_INET6_SIZE                                      \
+       (ISC_PROXY2_HEADER_SIZE + 16 /* src_addr */ + 16 /* dst_addr */ + \
+        2 /* src port */ + 2 /* dst_port */)
+
+#define ISC_PROXY2_AF_UNIX_MAX_PATH_LEN (108)
+
+#define ISC_PROXY2_MIN_AF_UNIX_SIZE                       \
+       (ISC_PROXY2_HEADER_SIZE +                         \
+        ISC_PROXY2_AF_UNIX_MAX_PATH_LEN /* src_addr */ + \
+        ISC_PROXY2_AF_UNIX_MAX_PATH_LEN /* dst_addr */)
+
+#define ISC_PROXY2_TLV_HEADER_SIZE \
+       (1 /* type */ + 1 /* length_hi */ + 1 /* length_lo */)
+
+#define ISC_PROXY2_TLS_SUBHEADER_MIN_SIZE \
+       (1 /* client_flags */ + 4 /* verify */)
+
+ISC_LANG_BEGINDECLS
+
+typedef enum isc_proxy2_command {
+       ISC_PROXY2_CMD_ILLEGAL = -1,
+       /*
+        * PROXYv2 header does not contain any addresses and is supposedly
+        * created on behalf of locally running software.
+        */
+       ISC_PROXY2_CMD_LOCAL = 0,
+       /*
+        * PROXYv2 header contains address-related information and is
+        * created on a behalf of the client.
+        */
+       ISC_PROXY2_CMD_PROXY = 1
+} isc_proxy2_command_t;
+
+typedef enum isc_proxy2_addrfamily {
+       ISC_PROXY2_AF_UNSPEC = 0,
+       ISC_PROXY2_AF_INET = 1,
+       ISC_PROXY2_AF_INET6 = 2,
+       ISC_PROXY2_AF_UNIX = 3
+} isc_proxy2_addrfamily_t;
+
+typedef enum isc_proxy2_socktype {
+       ISC_PROXY2_SOCK_ILLEGAL = -1,
+       ISC_PROXY2_SOCK_UNSPEC = 0,
+       ISC_PROXY2_SOCK_STREAM = 1,
+       ISC_PROXY2_SOCK_DGRAM = 2
+} isc_proxy2_socktype_t;
+
+typedef enum isc_proxy2_tlv_type {
+       /*
+        * Application-Layer Protocol Negotiation (ALPN). It is a byte
+        * sequence defining the upper layer protocol in use over the
+        * connection.
+        */
+       ISC_PROXY2_TLV_TYPE_ALPN = 0x01,
+       /*
+        * Contains the host name value passed by the client, as an
+        * UTF8-encoded string.
+        */
+       ISC_PROXY2_TLV_TYPE_AUTHORITY = 0x02,
+       /*
+        * The value is a 32-bit number storing the CRC32c checksum of the
+        * PROXY protocol header.
+        */
+       ISC_PROXY2_TLV_TYPE_CRC32C = 0x03,
+       /*
+        * The TLV of this type should be ignored when parsed. The value
+        * is zero or more bytes. Can be used for data padding or
+        * alignment.
+        */
+       ISC_PROXY2_TLV_TYPE_NOOP = 0x04,
+       /*
+        * The value is an opaque byte sequence of up to 128 bytes
+        * generated by the upstream proxy that uniquely identifies the
+        * connection.
+        */
+       ISC_PROXY2_TLV_TYPE_UNIQUE_ID = 0x05,
+       /*
+        * SSL type contains subfields of the given subtypes (see
+        * isc_proxy2_tlv_subtype_tls_t). The header contains:
+        *
+        * - uint8_t client is a bit-field made of
+        *   isc_proxy2_tls_client_flags_t;
+        * - uint32_t verify (0 for a successfully verified certificate);
+        */
+       ISC_PROXY2_TLV_TYPE_TLS = 0x20,
+       /*
+        * The type PP2_TYPE_NETNS defines the value as the US-ASCII
+        * string representation of the namespace's name.
+        */
+       ISC_PROXY2_TLV_TYPE_NETNS = 0x30,
+       /*
+        * The following range of 16 type values is reserved for
+        * application-specific data and will be never used by the PROXY
+        * Protocol.
+        */
+       ISC_PROXY2_TLV_TYPE_MIN_CUSTOM = 0xE0,
+       ISC_PROXY2_TLV_TYPE_MAX_CUSTOM = 0xEF,
+       /*
+        * This range of 8 values is reserved for temporary experimental
+        * use by application developers and protocol designers.
+        */
+       ISC_PROXY2_TLV_TYPE_MIN_EXPERIMENT = 0xF0,
+       ISC_PROXY2_TLV_TYPE_MAX_EXPERIMENT = 0xF7,
+       /*
+        * The following range of 8 values is reserved for future use,
+        * potentially to extend the protocol with multibyte type values.
+        */
+       ISC_PROXY2_TLV_TYPE_MIN_FUTURE = 0xF8,
+       ISC_PROXY2_TLV_TYPE_MAX_FUTURE = 0xFF
+} isc_proxy2_tlv_type_t;
+
+typedef enum isc_proxy2_tls_client_flags {
+       /* The flag indicates that the client connected over SSL/TLS. */
+       ISC_PROXY2_CLIENT_TLS = 0x01,
+       /* The client provided a certificate over the current connection. */
+       ISC_PROXY2_CLIENT_CERT_CONN = 0x02,
+       /*
+        * The client provided a certificate at least once over the TLS
+        * session this connection belongs to.
+        */
+       ISC_PROXY2_CLIENT_CERT_SESS = 0x04
+} isc_proxy2_tls_client_flags_t;
+
+typedef enum isc_proxy2_tlv_subtype_tls {
+       /*
+        * The US-ASCII string representation of the TLS version the TLV
+        * format.
+        */
+       ISC_PROXY2_TLV_SUBTYPE_TLS_VERSION = 0x21,
+       /*
+        * The Common Name field of the client certificate's Distinguished
+        * Name in the TLV format.
+        */
+       ISC_PROXY2_TLV_SUBTYPE_TLS_CN = 0x22,
+       /*
+        * The US-ASCII string name of the used cipher, for
+        * example "ECDHE-RSA-AES128-GCM-SHA256".
+        */
+       ISC_PROXY2_TLV_SUBTYPE_TLS_CIPHER = 0x23,
+       /*
+        * The US-ASCII string name of the algorithm used to sign the
+        * certificate presented by the frontend when the incoming
+        * connection was made over an SSL/TLS transport layer, for
+        * example "SHA256".
+        */
+       ISC_PROXY2_TLV_SUBTYPE_TLS_SIG_ALG = 0x24,
+       /*
+        * The US-ASCII string name of the algorithm used to generate the
+        * key of the certificate presented by the frontend when the
+        * incoming connection was made over an SSL/TLS transport layer,
+        * for example "RSA2048".
+        */
+       ISC_PROXY2_TLV_SUBTYPE_TLS_KEY_ALG = 0x25
+} isc_proxy2_tlv_subtype_tls_t;
+
+/*
+ * Definitions related to processing and verification of existing PROXYv2
+ * headers
+ */
+
+typedef struct isc_proxy2_handler isc_proxy2_handler_t;
+/*!<
+ * 'isc_proxy2_handler_t' is an entity designed for processing of the
+ * PROXYv2 data received from a network. Despite its purpose, it is
+ * designed as a state machine which, in fact, has no direct connection
+ * to the networking code. Interaction with the networking code is done
+ * via the provided API only.
+ *
+ * The entity is designed as a state machine which accepts data on
+ * input and calls a user-provided data processing callback to notify
+ * about data processing status and, in the case of successful
+ * processing, provide the upper level code with the data obtained
+ * from the PROXYv2 header and associated payload.
+ *
+ * The reason for the state machine-based approach is
+ * many-fold. Firstly, the protocol itself is well suited for
+ * processing by a state machine with well-defined steps. Secondly,
+ * such a design allows iterative data processing combined with
+ * verification, which is more secure than trying to read seemingly
+ * enough data, process it, and then retrospectively verify
+ * it. Thirdly, such an approach aligns well with how stream-based
+ * transports work - PROXYv2 headers might arrive torn into multiple
+ * parts which need to be assembled (theoretically, it is fine to send
+ * data over TCP bite-by-bite), and we should handle such
+ * cases. Additionally to that, we should stop reading data as soon as
+ * we detect that it is ill-formed. This design allows that and also
+ * can be used easily with datagram-based networking code.
+ *
+ * Another important characteristic of the state machine-based code is
+ * that it can be unit-tested separately from the rest of the code
+ * base. Of course, we use that to our advantage.
+ *
+ * The implementations closely follows the PROXYv2 protocol
+ * specification from 2020/03/05
+ * (https://www.haproxy.org/download/2.9/doc/proxy-protocol.txt),
+ * however, enough functionality is provided to handle future
+ * extensions, too. To be fair, our needs are quite modest and we are
+ * not interested in all the information PROXYv2 protocol could carry
+ * - we are mostly interested in the basics. However, our protocol
+ * handling code is fairly complete at the time of writing leaving a
+ * good foundation for further extensions, as the PROXYv2 protocol is
+ * itself extensible. The only missing thing is header checksum
+ * verification - but that functionality is optional. That being said, it is
+ * easy to add that, should we ever need to.
+ */
+
+typedef void (*isc_proxy2_handler_cb_t)(const isc_result_t        result,
+                                       const isc_proxy2_command_t cmd,
+                                       const int                  socktype,
+                                       const isc_sockaddr_t *restrict src_addr,
+                                       const isc_sockaddr_t *restrict dst_addr,
+                                       const isc_region_t *restrict tlv_data,
+                                       const isc_region_t *restrict extra,
+                                       void *cbarg);
+/*!<
+ * PROXYv2 data processing callback.
+ *
+ * Arguments:
+ *\li  'result' - error status code;
+ *\li  'cmd' - PROXYv2 command;
+ *\li  'socktype' - PROXYv2 addresses socket type
+ *(SOCK_STREAM, SOCK_DGRAM, or '0' for "unspecified" (SOCK_UNSPEC)).
+ *\li  'src_addr' - original source address extracted from the PROXYv2 header;
+ *\li  'dst_addr' - original destination address extracted from the PROXYv2
+ *header;
+ *\li  'tlv_data' - TLV-data extracted from the header;
+ *\li  'extra_data' - extra unprocessed data past the PROXYv2 header. It is
+ *not a part of the header, but it is fine to receive this when reading data
+ *over TCP-based transports. In general, needs to be passed to the upper
+ *level as is;
+ *\li  'cbarg' - opaque pointer to user supplied data.
+ *
+ * The user-provided data processing callback function can get the
+ * following error status codes:
+ * \li 'ISC_R_SUCCESS' - the header has been processed, and data has been
+ * extracted from the received header and its payload;
+ * \li 'ISC_R_NOMORE' - the data passed was processed, and we need more to
+ * continue processing (=resume reading from the network as we have no more
+ * data to process);
+ * \li 'ISC_R_UNEXPECTED' - an unexpected value has been detected;
+ * \li 'ISC_R_RANGE' - an expected value is not within an expected range.
+ *
+ * When processing error status within the callback, in general, we
+ * are interested in dispatching on the first two values, as anything
+ * else can be treated as hard-stop errors: their purpose is to give a
+ * little insight into what has happened without going into gory
+ * details (as we are not interested in them most of the time anyway).
+ *
+ * Any of the argument pointers can be 'NULL', identifying that the
+ * corresponding data is not present in the PROXYv2 header. Also,
+ * 'socktype' can be '-1' in a case of processing error.
+ *
+ */
+
+struct isc_proxy2_handler {
+       isc_buffer_t hdrbuf; /*!< Internal buffer for assembling PROXYv2 header
+                             */
+       uint8_t buf[256];    /*!< Internal buffer static storage */
+
+       int      state;       /*!< Current state machine state */
+       uint16_t expect_data; /*!< How much data do we need to switch to the
+                                next state */
+       uint16_t max_size; /*!< Max PROXYv2 header size including its payload */
+
+       isc_proxy2_handler_cb_t cb;    /*!< Data processing callback. */
+       void                   *cbarg; /*!< Callback argument. */
+       bool calling_cb; /*<! Callback calling marker. Used to detect recursive
+                   object uses (changing the data state from within
+                   the callback). */
+       isc_result_t result; /*<! The last passed to the callback processing
+                               status value. */
+       isc_mem_t *mctx;
+
+       uint16_t header_size;   /*!< Total PROXYv2 header size (including the
+                                  payload. */
+       uint16_t tlv_data_size; /*!< The size of TLVs payload size */
+
+       isc_proxy2_command_t    cmd; /*!< The decoded PROXYv2 command */
+       isc_proxy2_addrfamily_t proxy_addr_family; /*!< The decoded PROXYv2
+                                                     address family */
+       isc_proxy2_socktype_t proxy_socktype; /*!< The decoded PROXYv2 socket
+                                                type */
+
+       isc_region_t tlv_data;   /*!< TLV data region within the handled PROXYv2
+                                   header */
+       isc_region_t extra_data; /*!< Data past the PROXYv2 header (not
+                                   belonging to it) */
+};
+
+void
+isc_proxy2_handler_init(isc_proxy2_handler_t *restrict handler, isc_mem_t *mctx,
+                       const uint16_t max_size, isc_proxy2_handler_cb_t cb,
+                       void *cbarg);
+/*!<
+ * \brief Initialise the given 'isc_proxy2_handler_t' object, attach
+ * to the memory context.
+ *
+ * Arguments:
+ *\li  'mctx' -  memory context;
+ *\li  'max_size' - the upper limit for the PROXYv2 header and its payload (0 -
+ *unlimited);
+ *\li  'cb' - data processing callback;
+ *\li  'cbarg' - data processing callback argument.
+ *
+ * Requires:
+ *\li  'handler' is not NULL;
+ *\li  'mctx' is not NULL;
+ *\li  'max_size' is >= `ISC_PROXY2_HEADER_SIZE` or is 0;
+ *\li  'cb' is not NULL.
+ */
+
+void
+isc_proxy2_handler_uninit(isc_proxy2_handler_t *restrict handler);
+/*!<
+ * \brief Un-initialise the given 'isc_proxy2_handler_t' object, detach
+ * from the attached memory context. Invalidate any internal unprocessed data.
+ *
+ * Requires:
+ *\li  'handler' is not NULL.
+ */
+
+void
+isc_proxy2_handler_clear(isc_proxy2_handler_t *restrict handler);
+/*!<
+ * \brief Clear the given 'isc_proxy2_handler_t' object from
+ * any unprocessed data, clear the last data processing status (set it to
+ * 'ISC_R_UNSET'). Effectively, the function returns the object to its initial
+ * state.
+ *
+ * Requires:
+ *\li  'handler' is not NULL.
+ */
+
+isc_proxy2_handler_t *
+isc_proxy2_handler_new(isc_mem_t *mctx, const uint16_t max_size,
+                      isc_proxy2_handler_cb_t cb, void *cbarg);
+/*!<
+ * \brief Allocate and initialise a new 'isc_proxy2_handler_t'
+ * object, attach to the memory context.
+ *
+ * Arguments:
+ *\li  'mctx' -  memory context;
+ *\li  'max_size' - the upper limit for the PROXYv2 header and its payload (0 -
+ *unlimited);
+ *\li  'cb' - data processing callback;
+ *\li  'cbarg' - data
+ *processing callback argument.
+ *
+ * Requires:
+ *\li  'mctx' is not NULL;
+ *\li  'max_size' is >= `ISC_PROXY2_HEADER_SIZE` or is 0;
+ *\li  'cb' is not NULL.
+ */
+
+void
+isc_proxy2_handler_free(isc_proxy2_handler_t **restrict handler);
+/*!<
+ * \brief Un-initialise the given 'isc_proxy2_handler_t' object, detach
+ * from the attached memory context, free the memory consumed by the object.
+ *
+ * Requires:
+ *\li  'handler' is not NULL;
+ *\li  'handler' is not pointing to NULL.
+ */
+
+void
+isc_proxy2_handler_setcb(isc_proxy2_handler_t *restrict handler,
+                        isc_proxy2_handler_cb_t cb, void *cbarg);
+/*!<
+ * \brief Change the data processing callback and its argument within the
+ * given 'isc_proxy2_handler_t' object.
+ *
+ * Arguments:
+ *\li  'handler' - PROXYv2 handler object;
+ *\li  'cb' - new data processing callback;
+ *\li  'cbarg' - new data processing callback argument.
+ *
+ * Requires:
+ *\li  'handler' is not NULL;
+ *\li  'cb' is not NULL.
+ */
+
+isc_result_t
+isc_proxy2_handler_push_data(isc_proxy2_handler_t *restrict handler,
+                            const void *restrict buf,
+                            const unsigned int buf_size);
+/*!<
+ * \brief Push new data to the given 'isc_proxy2_handler_t'
+ * object. Call the callback passing a status and a result of data
+ * processing to it.
+ *
+ * To avoid erroneously recursive usage of the object, it is forbidden to call
+ * this function from within the callback. Doing so will abort the program.
+ *
+ * Requires:
+ *\li  'handler' is not NULL;
+ *\li  'buf' is not NULL;
+ *\li  'buf_size' is not 0.
+ */
+
+isc_result_t
+isc_proxy2_handler_push(isc_proxy2_handler_t *restrict handler,
+                       const isc_region_t *restrict region);
+/*!<
+ * \brief The same as 'isc_proxy2_handler_push_data()' but pushes that
+ * data for processing via an 'isc_region_t' object.
+ *
+ * Requires:
+ *\li  'handler' is not NULL;
+ *\li  'region' is not NULL.
+ */
+
+isc_result_t
+isc_proxy2_handler_result(const isc_proxy2_handler_t *restrict handler);
+/*!<
+ * \brief Return the last data processing status passed to the
+ * callback.
+ *
+ * Requires:
+ *\li  'handler' is not NULL.
+ *
+ * Return values:
+ * \li 'ISC_R_SUCCESS' - the header has been processed, and data has been
+ * extracted from the received header and its payload;
+ * \li 'ISC_R_NOMORE' - the data passed was processed, and we need more to
+ * continue processing (=resume reading from the network as we have no more
+ * data to process);
+ * \li 'ISC_R_UNEXPECTED' - an unexpected value has been detected;
+ * \li 'ISC_R_RANGE' - an expected value is not within an expected range.
+ */
+
+size_t
+isc_proxy2_handler_header(const isc_proxy2_handler_t *restrict handler,
+                         isc_region_t *restrict region);
+/*!<
+ * \brief Get the complete processed PROXYv2 header as is
+ * (e.g. for forwarding).
+ *
+ * Requires:
+ *\li  'handler' is not NULL;
+ *\li  'region' is NULL or points to a zeroed 'isc_region_t' object.
+ *
+ * Return the size of the header or 0 on error (if it has not been
+ * processed yet).
+ */
+
+size_t
+isc_proxy2_handler_tlvs(const isc_proxy2_handler_t *restrict handler,
+                       isc_region_t *restrict region);
+/*!<
+ * \brief Get the TLV-data within the processed PROXYv2 header.
+ *
+ * Requires:
+ *\li  'handler' is not NULL;
+ *\li  'region' is NULL or points to a zeroed 'isc_region_t' object.
+ *
+ * Return the size of the header or 0 on error (if it has not been
+ * processed yet).
+ */
+
+size_t
+isc_proxy2_handler_extra(const isc_proxy2_handler_t *restrict handler,
+                        isc_region_t *restrict region);
+/*!<
+ * \brief Get the data past the processed PROXYv2 header. The data
+ * is not the part of the PROXYv2 header itself. That can happen (and
+ * does happen) when data is being sent over TCP.
+ *
+ * Requires:
+ *\li  'handler' is not NULL;
+ *\li  'region' is NULL or points to a zeroed 'isc_region_t' object.
+ *
+ * Return the size of the header or 0 on error (if it has not been
+ * processed yet).
+ */
+
+isc_result_t
+isc_proxy2_handler_addresses(const isc_proxy2_handler_t *restrict handler,
+                            int *restrict psocktype,
+                            isc_sockaddr_t *restrict psrc_addr,
+                            isc_sockaddr_t *restrict pdst_addr);
+/*!<
+ * \brief Get the addresses directly from the processed PROXYv2
+ * header. If you are not interested in particular data, you can pass
+ * NULL as the argument to ignore it.
+ *
+ * Requires:
+ *\li  'handler' is not NULL.
+ */
+
+isc_result_t
+isc_proxy2_header_handle_directly(const isc_region_t *restrict header_data,
+                                 const isc_proxy2_handler_cb_t cb,
+                                 void                         *cbarg);
+/*!<
+ * \brief Process PROXYv2 header in one go directly without memory
+ * allocation and copying. Specifically designed to work when a
+ * complete header and associated follow-up data is expected (for
+ * example, when datagram transports are used, like UDP).
+ *
+ * Requires:
+ *\li  'header_data' is not NULL;
+ *\li  'cb' is not NULL.
+ *
+ * Return values are the same that get passed to the processing
+ * callback. Given that processing should complete in one go, getting
+ * anything except `ISC_R_SUCCESS` indicates failure.
+ */
+
+typedef bool (*isc_proxy2_tlv_cb_t)(const isc_proxy2_tlv_type_t tlv_type,
+                                   const isc_region_t *restrict data,
+                                   void *cbarg);
+/*!<
+ * \brief Callback used for iterating over TLV data extracted from
+ * PROXYv2 headers.
+ *
+ * Arguments:
+ *\li  'tlv_type' - type value (see the 'isc_proxy2_tlv_type_t' enumeration);
+ *\li  'data' - pointer to 'isc_region_t' object referring to the data;
+ *\li  'cbarg' - opaque pointer to user supplied data.
+ *
+ * Return values:
+ *\li  'true' - continue processing the next TLV entry (if any);
+ *\li  'false' - stop processing TLV-entries.
+ */
+
+isc_result_t
+isc_proxy2_tlv_iterate(const isc_region_t *restrict tlv_data,
+                      const isc_proxy2_tlv_cb_t cb, void *cbarg);
+/*!<
+ * \brief Iterate over the TLV data extracted from PROXYv2 headers.
+ *
+ * Arguments:
+ *\li  'tlv_data' - TLV data extracted from a PROXYv2 header;
+ *\li  'cb' - user provided iteration callback;
+ *\li  'cbarg' - user provided iteration callback argument.
+ *
+ * Requires:
+ *\li  'tlv_data' is not NULL;
+ *\li  'cb' is not NULL.
+ *
+ * Return values:
+ *\li  'ISC_R_SUCCESS' - iteration over the data was successful;
+ *\li  'ISC_R_RANGE' - malformed TLV data was detected.
+ */
+
+typedef bool (*isc_proxy2_tls_subtlv_cb_t)(
+       const uint8_t client_flags, const bool client_cert_verified,
+       const isc_proxy2_tlv_subtype_tls_t tls_subtlv_type,
+       const isc_region_t *restrict data, void *cbarg);
+/*!<
+ * \brief Callback used for iterating over TLS sub-TLV data extracted from
+ * PROXYv2 headers.
+ *
+ * Arguments:
+ *\li  'client_flags' - TLS client flags extracted from TLS TLV data
+ * (see 'isc_proxy2_tls_client_flags_t' enumeration);
+ *\li  'client_cert_verified' - flag which indicates if the supplied
+ *TLS client certificate was verified- (if provided by the client);
+ *\li  'tls_subtlv_type' - TLS sub-TLV type (see the
+ *'isc_proxy2_tlv_subtype_tls_t' enumeration);
+ *\li  'cbarg' - opaque pointer to user supplied data.
+ *
+ * Return values:
+ *\li  'true' - continue processing the next TLV entry (if any);
+ *\li  'false' - stop processing TLV-entries.
+ */
+
+isc_result_t
+isc_proxy2_subtlv_tls_header_data(const isc_region_t *restrict tls_tlv_data,
+                                 uint8_t *restrict pclient_flags,
+                                 bool *restrict pclient_cert_verified);
+/*!<
+ * \brief Get data from a TLS ('ISC_PROXY2_TLV_TYPE_TLS') TLV value.
+ *
+ * Arguments:
+ *\li  'pclient_flags' - a pointer to the variable to receive the TLS client
+ *flags (see 'isc_proxy2_tls_client_flags_t' enumeration for more details);
+ *\li  'pclient_cert_verified' - a pointer the value to receive TLS client
+ *certificate verification status ('true' - verified).
+ *
+ * Requires:
+ *\li  'tls_tlv_data' is not NULL;
+ *\li  'pclient_flags' is either NULL or a pointer pointing to a
+ *zeroed variable;
+ *\li  'pclient_cert_verified' is either NULL or a pointer pointing to a
+ *zeroed variable.
+ *
+ * Return values:
+ *\li  'ISC_R_SUCCESS' - iteration over the data was successful;
+ *\li  'ISC_R_RANGE' - malformed TLV data was detected.
+ */
+
+isc_result_t
+isc_proxy2_subtlv_tls_iterate(const isc_region_t *restrict tls_tlv_data,
+                             const isc_proxy2_tls_subtlv_cb_t cb, void *cbarg);
+/*!<
+ * \brief Iterate over the sub-TLV data extracted from TLS
+ * ('ISC_PROXY2_TLV_TYPE_TLS') TLV value of a PROXYv2 header.
+ *
+ * Arguments:
+ *\li  'tls_tlv_data' - TLS-realted sub-TLV data extracted from
+ *a PROXYv2 header;
+ *\li  'cb' - user provided iteration callback;
+ *\li  'cbarg' - user provided iteration callback argument.
+ *
+ * Requires:
+ *\li  'tls_tlv_data' is not NULL;
+ *\li  'cb' is not NULL.
+ *
+ * Return values:
+ *\li  'ISC_R_SUCCESS' - iteration over the data was successful;
+ *\li  'ISC_R_RANGE' - malformed TLV data was detected.
+ */
+
+isc_result_t
+isc_proxy2_tlv_data_verify(const isc_region_t *restrict tlv_data);
+/*!<
+ * \brief Verify TLV-data structure extracted from a PROXYv2 header.
+ * The function loops over the data verifying that TLVs are structured
+ * in a correct way.
+ *
+ * NOTE: If you are using an 'isc_proxy2_handler_t' object then there
+ * is no need for you to call this function as it is called during
+ * the normal operation. It is exposed mostly for unit testing
+ * purposes or for verifying outgoing data, should it be required.
+ *
+ * Arguments:
+ *\li  'tlv_data' - TLV data extracted from a PROXYv2 header.
+ *
+ * Requires:
+ *\li  'tlv_data' is not NULL.
+ *
+ * Return values:
+ *\li  'ISC_R_SUCCESS' - iteration over the data was successful;
+ *\li  'ISC_R_RANGE' - malformed TLV data was detected.
+ */
+
+/*
+ * Definitions related to generation of PROXYv2 headers
+ */
+
+isc_result_t
+isc_proxy2_make_header(isc_buffer_t *restrict outbuf,
+                      const isc_proxy2_command_t cmd, const int socktype,
+                      const isc_sockaddr_t *restrict src_addr,
+                      const isc_sockaddr_t *restrict dst_addr,
+                      const isc_region_t *restrict tlv_data);
+/*!<
+ * \brief Create a PROXYv2 header.
+ *
+ * Arguments:
+ *\li  'outbuf' - the output buffer;
+ *\li  'cmd' - PROXYv2 command;
+ *\li  'socktype' - PROXYv2 socket type (possible values are 'SOCK_STREAM',
+ *'SOCK_DGRAM', or '0' for "unspecified");
+ *\li  'src_addr' - source address, if any;
+ *\li  'dst_addr' - destination address, if any;
+ *\li  'tlv_data' - TLV data, if any.
+ *
+ * Requires:
+ *\li  'outbuf' is not NULL;
+ *\li  'cmd' is 'ISC_PROXY2_CMD_PROXY' or 'socktype' is equal to '0';
+ *\li  either both of 'src_addr' and 'dst_addr' are NULL or both are not;
+ *\li  both of 'src_addr' and 'dst_addr' are of the same type when specified.
+ *
+ * Notes:
+ *
+ * When 'cmd' equals 'ISC_PROXY2_CMD_LOCAL', then 'socktype' must equal '0'
+ * (unspecified) and both 'src_addr' and 'dst_addr' must be 'NULL'.
+ * When 'cmd' equals 'ISC_PROXY2_CMD_PROXY', then having 'socktype' being equal
+ * to '0' will instruct the function to create PROXYv2 header marked as bearing
+ * address of "unspecified" ('0') opaque type. In this case both 'src_addr' and
+ * 'dst_addr' must be 'NULL'. In other cases the address type is determined
+ * from the 'src_addr' and 'dst_addr' arguments (and might 'AF_INET',
+ * 'AF_INET6', and 'AF_UNIX' per the protocol spec).
+ * The socket type, when applicable, is determined from the 'socktype' argument
+ * and must be any of 'SOCK_STREAM', 'SOCK_DGRAM', when applicable, or '0'
+ * (unspecified).
+ *
+ * Return values:
+ *\li  'ISC_R_SUCCESS' - iteration over the data was successful;
+ *\li  'ISC_R_UNEXPECTED' - an unexpected value has been detected in the input
+ *data (function arguments);
+ *\li  'ISC_R_NOSPACE' - not enough data in the output buffer;
+ *\li  'ISC_R_RANGE' - too much data to fit PROXYv2 header.
+ */
+
+isc_result_t
+isc_proxy2_header_append(isc_buffer_t *restrict outbuf,
+                        const isc_region_t *restrict data);
+/*!<
+ * \brief Append arbitrary data to PROXYv2 header and update the
+ * length field within the header accordingly. It is used as foundation
+ * for TLV appending functionality. Also, it can be used to add address
+ * information in the case when "unspecified" opaque format is used.
+ *
+ * Arguments:
+ *\li  'outbuf' - the output buffer containing a valid PROXYv2 header;
+ *\li  'data' - use provided arbitrary data.
+ *
+ * Requires:
+ *\li  'outbuf' is not NULL;
+ *\li  used region within 'outbuf' is more or equal
+ *to 'ISC_PROXY2_HEADER_SIZE';
+ *\li  'data' is not NULL.
+ *
+ * Return values:
+ *\li  'ISC_R_SUCCESS' - iteration over the data was successful;
+ *\li  'ISC_R_NOSPACE' - not enough data in the output buffer;
+ *\li  'ISC_R_RANGE' - too much data for PROXYv2 header.
+ */
+
+isc_result_t
+isc_proxy2_header_append_tlv(isc_buffer_t *restrict outbuf,
+                            const isc_proxy2_tlv_type_t tlv_type,
+                            const isc_region_t *restrict data);
+/*!<
+ * \brief Append TLV data to PROXYv2 header and update the
+ * length field within the header accordingly.
+ *
+ * Requires:
+ *\li  'outbuf' is not NULL;
+ *\li  used region within 'outbuf' is more or equal
+ *to 'ISC_PROXY2_HEADER_SIZE';
+ *\li  'data' is not NULL.
+ *
+ * Return values:
+ *\li  'ISC_R_SUCCESS' - iteration over the data was successful;
+ *\li  'ISC_R_NOSPACE' - not enough data in the output buffer
+ *\li  'ISC_R_RANGE' - too much data for PROXYv2 header.
+ */
+
+isc_result_t
+isc_proxy2_header_append_tlv_string(isc_buffer_t *restrict outbuf,
+                                   const isc_proxy2_tlv_type_t tlv_type,
+                                   const char *restrict str);
+/*!<
+ * \brief Append the string as TLV data to PROXYv2 header and update the
+ * length field within the header accordingly.
+ *
+ * Requires:
+ *\li  'outbuf' is not NULL;
+ *\li  used region within 'outbuf' is more or equal
+ *to 'ISC_PROXY2_HEADER_SIZE';
+ *\li  'data' is not NULL.
+ *
+ * Return values:
+ *\li  'ISC_R_SUCCESS' - iteration over the data was successful;
+ *\li  'ISC_R_NOSPACE' - not enough data in the output buffer.
+ *\li  'ISC_R_RANGE' - too much data for PROXYv2 header.
+ */
+
+isc_result_t
+isc_proxy2_make_tls_subheader(isc_buffer_t *restrict outbuf,
+                             const uint8_t client_flags,
+                             const bool    client_cert_verified,
+                             const isc_region_t *restrict tls_subtlvs_data);
+/*!<
+ * \brief Create TLS (ISC_PROXY2_TLV_TYPE_TLS) TLV subheader which
+ * can later be added to the PROXYv2 header TLV data.
+ *
+ * Arguments:
+ *\li  'client_flags' - TLS client flags (see
+ 'isc_proxy2_tls_client_flags_t' enumeration for more details);
+ *\li  'client_cert_verified' - TLS client certificate verification
+ *status ('true' - verified).
+ *\li  'tls_subtlvs_data' - TLS subtlvs data, if any (see
+ 'isc_proxy2_tlv_subtype_tls_t' for more details).
+ *
+ * Requires:
+ *\li  'outbuf' is not NULL;
+ *
+ * Return values:
+ *\li  'ISC_R_SUCCESS' - iteration over the data was successful;
+ *\li  'ISC_R_NOSPACE' - not enough data in the output buffer;
+  *\li 'ISC_R_RANGE' - too much data for a TLV value.
+ */
+
+isc_result_t
+isc_proxy2_append_tlv(isc_buffer_t *restrict outbuf, const uint8_t type,
+                     const isc_region_t *restrict data);
+/*!<
+ * \brief Append TLV data to the end of the buffer. Compared to
+ * 'isc_proxy2_header_append_tlv()' it does not try to look for a
+ * correct PROXYv2 header at the beginning of the buffer and update
+ * its length field. The main purpose of this function is to work with
+ * sub-TLVs.
+ *
+ * Requires:
+ *\li  'outbuf' is not NULL;
+ *\li  'data' is not NULL.
+ *
+ * Return values:
+ *\li  'ISC_R_SUCCESS' - iteration over the data was successful;
+ *\li  'ISC_R_NOSPACE' - not enough data in the output buffer
+ *\li  'ISC_R_RANGE' - too much data for PROXYv2 header.
+ */
+
+isc_result_t
+isc_proxy2_append_tlv_string(isc_buffer_t *restrict outbuf, const uint8_t type,
+                            const char *restrict str);
+/*!<
+ * \brief Append the string as TLV data to the end of the
+ * buffer. Compared to 'isc_proxy2_header_append_tlv_string()' it does not
+ * try to look for a correct PROXYv2 header at the beginning of the
+ * buffer and update its length field. The main purpose of this
+ * function is to work with sub-TLVs.
+ *
+ * Requires:
+ *\li  'outbuf' is not NULL;
+ *\li  'data' is not NULL.
+ *
+ * Return values:
+ *\li  'ISC_R_SUCCESS' - iteration over the data was successful;
+ *\li  'ISC_R_NOSPACE' - not enough data in the output buffer
+ *\li  'ISC_R_RANGE' - too much data for PROXYv2 header.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/proxy2.c b/lib/isc/proxy2.c
new file mode 100644 (file)
index 0000000..0e3b99a
--- /dev/null
@@ -0,0 +1,1440 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <isc/proxy2.h>
+
+enum isc_proxy2_states {
+       ISC_PROXY2_STATE_WAITING_SIGNATURE,
+       ISC_PROXY2_STATE_WAITING_HEADER,
+       ISC_PROXY2_STATE_WAITING_PAYLOAD, /* Addresses and TLVs */
+       ISC_PROXY2_STATE_END
+};
+
+static inline void
+isc__proxy2_handler_init_direct(isc_proxy2_handler_t *restrict handler,
+                               const uint16_t max_size,
+                               const isc_region_t *restrict data,
+                               isc_proxy2_handler_cb_t cb, void *cbarg) {
+       *handler = (isc_proxy2_handler_t){ .result = ISC_R_UNSET,
+                                          .max_size = max_size };
+       isc_proxy2_handler_setcb(handler, cb, cbarg);
+
+       if (data == NULL) {
+               isc_buffer_init(&handler->hdrbuf, handler->buf,
+                               sizeof(handler->buf));
+       } else {
+               isc_buffer_init(&handler->hdrbuf, data->base, data->length);
+               isc_buffer_add(&handler->hdrbuf, data->length);
+       }
+}
+
+void
+isc_proxy2_handler_init(isc_proxy2_handler_t *restrict handler, isc_mem_t *mctx,
+                       const uint16_t max_size, isc_proxy2_handler_cb_t cb,
+                       void *cbarg) {
+       REQUIRE(handler != NULL);
+       REQUIRE(mctx != NULL);
+       REQUIRE(max_size == 0 || max_size >= ISC_PROXY2_HEADER_SIZE);
+       REQUIRE(cb != NULL);
+
+       isc__proxy2_handler_init_direct(handler, max_size, NULL, cb, cbarg);
+
+       isc_mem_attach(mctx, &handler->mctx);
+       isc_buffer_setmctx(&handler->hdrbuf, handler->mctx);
+}
+
+void
+isc_proxy2_handler_uninit(isc_proxy2_handler_t *restrict handler) {
+       REQUIRE(handler != NULL);
+
+       /*
+        * Uninitialising the object from withing the callback does not
+        * make any sense.
+        */
+       INSIST(handler->calling_cb == false);
+       if (handler->mctx != NULL) {
+               isc_buffer_clearmctx(&handler->hdrbuf);
+               isc_mem_detach(&handler->mctx);
+       }
+       isc_buffer_invalidate(&handler->hdrbuf);
+}
+
+void
+isc_proxy2_handler_clear(isc_proxy2_handler_t *restrict handler) {
+       REQUIRE(handler != NULL);
+
+       *handler = (isc_proxy2_handler_t){ .result = ISC_R_UNSET,
+                                          .mctx = handler->mctx,
+                                          .cb = handler->cb,
+                                          .cbarg = handler->cbarg,
+                                          .hdrbuf = handler->hdrbuf,
+                                          .max_size = handler->max_size };
+
+       isc_buffer_clear(&handler->hdrbuf);
+       isc_buffer_trycompact(&handler->hdrbuf);
+}
+
+isc_proxy2_handler_t *
+isc_proxy2_handler_new(isc_mem_t *mctx, const uint16_t max_size,
+                      isc_proxy2_handler_cb_t cb, void *cbarg) {
+       isc_proxy2_handler_t *newhandler;
+
+       REQUIRE(mctx != NULL);
+       REQUIRE(cb != NULL);
+
+       newhandler = isc_mem_get(mctx, sizeof(*newhandler));
+       isc_proxy2_handler_init(newhandler, mctx, max_size, cb, cbarg);
+
+       return (newhandler);
+}
+
+void
+isc_proxy2_handler_free(isc_proxy2_handler_t **restrict phandler) {
+       isc_proxy2_handler_t *restrict handler = NULL;
+       isc_mem_t *mctx = NULL;
+       REQUIRE(phandler != NULL && *phandler != NULL);
+
+       handler = *phandler;
+
+       isc_mem_attach(handler->mctx, &mctx);
+       isc_proxy2_handler_uninit(handler);
+       isc_mem_putanddetach(&mctx, handler, sizeof(*handler));
+
+       *phandler = NULL;
+}
+
+void
+isc_proxy2_handler_setcb(isc_proxy2_handler_t *restrict handler,
+                        isc_proxy2_handler_cb_t cb, void *cbarg) {
+       REQUIRE(handler != NULL);
+       REQUIRE(cb != NULL);
+       handler->cb = cb;
+       handler->cbarg = cbarg;
+}
+
+static inline int
+proxy2_socktype_to_socktype(const isc_proxy2_socktype_t proxy_socktype) {
+       int socktype = 0;
+
+       switch (proxy_socktype) {
+       case ISC_PROXY2_SOCK_UNSPEC:
+               socktype = 0;
+               break;
+       case ISC_PROXY2_SOCK_STREAM:
+               socktype = SOCK_STREAM;
+               break;
+       case ISC_PROXY2_SOCK_DGRAM:
+               socktype = SOCK_DGRAM;
+               break;
+       default:
+               ISC_UNREACHABLE();
+       };
+
+       return (socktype);
+}
+
+static inline void
+isc__proxy2_handler_callcb(isc_proxy2_handler_t *restrict handler,
+                          const isc_result_t result,
+                          const isc_proxy2_command_t cmd,
+                          const isc_proxy2_socktype_t proxy_socktype,
+                          const isc_sockaddr_t *src_addr,
+                          const isc_sockaddr_t *dst_addr,
+                          const isc_region_t *restrict tlv_data,
+                          const isc_region_t *restrict extra_data) {
+       int socktype = 0;
+
+       handler->result = result;
+       handler->calling_cb = true;
+
+       if (result != ISC_R_SUCCESS) {
+               handler->cb(result, cmd, -1, NULL, NULL, NULL, NULL,
+                           handler->cbarg);
+       } else {
+               socktype = proxy2_socktype_to_socktype(proxy_socktype);
+               handler->cb(result, cmd, socktype,
+                           proxy_socktype == ISC_PROXY2_SOCK_UNSPEC ? NULL
+                                                                    : src_addr,
+                           proxy_socktype == ISC_PROXY2_SOCK_UNSPEC ? NULL
+                                                                    : dst_addr,
+                           tlv_data->length == 0 ? NULL : tlv_data,
+                           extra_data->length == 0 ? NULL : extra_data,
+                           handler->cbarg);
+       }
+
+       handler->calling_cb = false;
+}
+
+static inline void
+isc__proxy2_handler_error(isc_proxy2_handler_t *restrict handler,
+                         const isc_result_t result) {
+       INSIST(result != ISC_R_SUCCESS);
+       isc__proxy2_handler_callcb(handler, result, ISC_PROXY2_CMD_ILLEGAL,
+                                  ISC_PROXY2_SOCK_ILLEGAL, NULL, NULL, NULL,
+                                  NULL);
+       if (result != ISC_R_NOMORE) {
+               handler->state = ISC_PROXY2_STATE_END;
+       }
+}
+
+static inline bool
+isc__proxy2_handler_handle_signature(isc_proxy2_handler_t *restrict handler) {
+       isc_region_t remaining = { 0, 0 };
+       size_t len;
+
+       isc_buffer_remainingregion(&handler->hdrbuf, &remaining);
+       len = ISC_MIN(remaining.length, ISC_PROXY2_HEADER_SIGNATURE_SIZE);
+
+       if (memcmp(ISC_PROXY2_HEADER_SIGNATURE, remaining.base, len) != 0) {
+               isc__proxy2_handler_error(handler, ISC_R_UNEXPECTED);
+               return (false);
+       } else if (len == ISC_PROXY2_HEADER_SIGNATURE_SIZE) {
+               isc_buffer_forward(&handler->hdrbuf,
+                                  ISC_PROXY2_HEADER_SIGNATURE_SIZE);
+               handler->expect_data = ISC_PROXY2_HEADER_SIZE -
+                                      ISC_PROXY2_HEADER_SIGNATURE_SIZE;
+               handler->state++;
+       } else {
+               INSIST(len < ISC_PROXY2_HEADER_SIGNATURE_SIZE);
+               isc__proxy2_handler_error(handler, ISC_R_NOMORE);
+               return (false);
+       }
+       return (true);
+}
+
+static inline bool
+isc__proxy2_handler_handle_header(isc_proxy2_handler_t *restrict handler) {
+       /*
+        * The PROXYv2 header can be described as (signature 'sig' has been
+        * processed and verified already as a separate step):
+        *
+        *  struct proxy_hdr_v2 {
+        *     uint8_t sig[12];  // hex 0D 0A 0D 0A 00 0D 0A 51 55 49 54 0A
+        *     uint8_t ver_cmd;  // protocol version and command
+        *     uint8_t fam;      // protocol family and address
+        *     uint16_t len;     // number of following bytes part of the header
+        *  };
+        */
+       uint8_t ver_cmd = 0;
+       uint8_t cmd = 0;
+       uint8_t fam = 0;
+       uint16_t len = 0;
+       int addrfamily = 0;
+       int socktype = 0;
+       size_t min_addr_payload_size = 0;
+
+       ver_cmd = isc_buffer_getuint8(&handler->hdrbuf);
+
+       /* extract version and check it */
+       if ((ver_cmd & 0xF0U) >> 4 != 2) {
+               /* only support for version 2 is implemented */
+               isc__proxy2_handler_error(handler, ISC_R_NOTIMPLEMENTED);
+               return (false);
+       }
+
+       /* extract command */
+       cmd = ver_cmd & 0xFU;
+
+       fam = isc_buffer_getuint8(&handler->hdrbuf);
+       len = isc_buffer_getuint16(&handler->hdrbuf);
+
+       if (handler->max_size > 0 &&
+           (len + ISC_PROXY2_HEADER_SIZE) > handler->max_size)
+       {
+               goto error_range;
+       }
+
+       handler->expect_data = len;
+
+       /* extract address family and socket type */
+       addrfamily = (fam & 0xF0U) >> 4;
+       socktype = fam & 0xFU;
+
+       /* dispatch on the command value */
+       switch (cmd) {
+       case ISC_PROXY2_CMD_LOCAL:
+               /* LOCAL implies "unspec" mode */
+               handler->cmd = ISC_PROXY2_CMD_LOCAL;
+               if (addrfamily != ISC_PROXY2_AF_UNSPEC ||
+                   socktype != ISC_PROXY2_SOCK_UNSPEC)
+               {
+                       goto error_unexpected;
+               }
+               handler->proxy_addr_family = ISC_PROXY2_AF_UNSPEC;
+               handler->proxy_socktype = ISC_PROXY2_SOCK_UNSPEC;
+               break;
+       case ISC_PROXY2_CMD_PROXY:
+               handler->cmd = ISC_PROXY2_CMD_PROXY;
+               switch (addrfamily) {
+               case ISC_PROXY2_AF_UNSPEC:
+                       if (socktype != ISC_PROXY2_SOCK_UNSPEC) {
+                               goto error_unexpected;
+                       }
+                       handler->proxy_addr_family = ISC_PROXY2_AF_UNSPEC;
+                       handler->proxy_socktype = ISC_PROXY2_SOCK_UNSPEC;
+                       break;
+               case ISC_PROXY2_AF_INET:
+               case ISC_PROXY2_AF_INET6:
+               case ISC_PROXY2_AF_UNIX:
+                       handler->proxy_addr_family =
+                               (isc_proxy2_addrfamily_t)addrfamily;
+                       switch (socktype) {
+                       case ISC_PROXY2_SOCK_DGRAM:
+                       case ISC_PROXY2_SOCK_STREAM:
+                               handler->proxy_socktype =
+                                       (isc_proxy2_socktype_t)socktype;
+                               break;
+                       default:
+                               goto error_unexpected;
+                       }
+                       break;
+               default:
+                       goto error_unexpected;
+               }
+               break;
+       default:
+               goto error_unexpected;
+       };
+
+       /* verify if enough data will be available in the payload */
+       switch (handler->proxy_addr_family) {
+       case ISC_PROXY2_AF_INET:
+               min_addr_payload_size = ISC_PROXY2_MIN_AF_INET_SIZE -
+                                       ISC_PROXY2_HEADER_SIZE;
+               break;
+       case ISC_PROXY2_AF_INET6:
+               min_addr_payload_size = ISC_PROXY2_MIN_AF_INET6_SIZE -
+                                       ISC_PROXY2_HEADER_SIZE;
+               break;
+       case ISC_PROXY2_AF_UNIX:
+               min_addr_payload_size = ISC_PROXY2_MIN_AF_UNIX_SIZE -
+                                       ISC_PROXY2_HEADER_SIZE;
+               break;
+       default:
+               break;
+       }
+
+       if (min_addr_payload_size > 0) {
+               if (len < min_addr_payload_size) {
+                       goto error_range;
+               }
+               handler->tlv_data_size = len - min_addr_payload_size;
+       }
+
+       if (handler->tlv_data_size > 0 &&
+           handler->tlv_data_size < ISC_PROXY2_TLV_HEADER_SIZE)
+       {
+               goto error_range;
+       }
+
+       handler->header_size = ISC_PROXY2_HEADER_SIZE + len;
+
+       handler->state++;
+
+       return (true);
+
+error_unexpected:
+       isc__proxy2_handler_error(handler, ISC_R_UNEXPECTED);
+       return (false);
+error_range:
+       isc__proxy2_handler_error(handler, ISC_R_RANGE);
+       return (false);
+}
+
+static inline isc_result_t
+isc__proxy2_handler_get_addresses(isc_proxy2_handler_t *restrict handler,
+                                 isc_buffer_t *restrict hdrbuf,
+                                 isc_sockaddr_t *restrict src_addr,
+                                 isc_sockaddr_t *restrict dst_addr) {
+       size_t addr_size = 0;
+       void *psrc_addr = NULL, *pdst_addr = NULL;
+       uint16_t src_port = 0, dst_port = 0;
+
+       switch (handler->proxy_addr_family) {
+       case ISC_PROXY2_AF_UNSPEC:
+               /* in this case we are instructed to skip over the data */
+               INSIST(handler->tlv_data_size == 0);
+               isc_buffer_forward(hdrbuf, handler->expect_data);
+               break;
+       case ISC_PROXY2_AF_INET:
+               addr_size = sizeof(src_addr->type.sin.sin_addr.s_addr);
+               /*
+                * IPv4 source and destination endpoint addresses can be
+                * described as follows:
+                *
+                * struct {        // for TCP/UDP over IPv4, len = 12
+                *   uint32_t src_addr;
+                *   uint32_t dst_addr;
+                *   uint16_t src_port;
+                *   uint16_t dst_port;
+                * } ipv4_addr;
+                */
+               psrc_addr = isc_buffer_current(hdrbuf);
+               isc_buffer_forward(hdrbuf, addr_size);
+
+               pdst_addr = isc_buffer_current(hdrbuf);
+               isc_buffer_forward(hdrbuf, addr_size);
+
+               src_port = isc_buffer_getuint16(hdrbuf);
+               dst_port = isc_buffer_getuint16(hdrbuf);
+
+               if (src_addr != NULL) {
+                       isc_sockaddr_fromin(src_addr, psrc_addr, src_port);
+               }
+               if (dst_addr != NULL) {
+                       isc_sockaddr_fromin(dst_addr, pdst_addr, dst_port);
+               }
+               break;
+       case ISC_PROXY2_AF_INET6:
+               addr_size = sizeof(src_addr->type.sin6.sin6_addr);
+               /*
+                * IPv4 source and destination endpoint addresses can be
+                * described as follows:
+                *
+                * struct {        // for TCP/UDP over IPv6, len = 36
+                *    uint8_t  src_addr[16];
+                *    uint8_t  dst_addr[16];
+                *    uint16_t src_port;
+                *    uint16_t dst_port;
+                * } ipv6_addr;
+                */
+               psrc_addr = isc_buffer_current(hdrbuf);
+               isc_buffer_forward(hdrbuf, addr_size);
+
+               pdst_addr = isc_buffer_current(hdrbuf);
+               isc_buffer_forward(hdrbuf, addr_size);
+
+               src_port = isc_buffer_getuint16(hdrbuf);
+               dst_port = isc_buffer_getuint16(hdrbuf);
+
+               if (src_addr != NULL) {
+                       isc_sockaddr_fromin6(src_addr, psrc_addr, src_port);
+               }
+
+               if (dst_addr != NULL) {
+                       isc_sockaddr_fromin6(dst_addr, pdst_addr, dst_port);
+               }
+               break;
+       case ISC_PROXY2_AF_UNIX: {
+               /*
+                * UNIX domain sockets source and destination endpoint
+                * addresses can be described as follows:
+                *
+                * struct {        // for AF_UNIX sockets, len = 216
+                *    uint8_t src_addr[108];
+                *    uint8_t dst_addr[108];
+                * } unix_addr;
+                *
+                * We currently have no use for this address type, but we can
+                * validate the data.
+                */
+               unsigned char *ret = NULL;
+
+               addr_size = ISC_PROXY2_AF_UNIX_MAX_PATH_LEN;
+
+               ret = memchr(isc_buffer_current(hdrbuf), '\0', addr_size);
+               if (ret == NULL) {
+                       /*
+                        * Someone has attempted to send us a path string
+                        * without a terminating '\0' byte - not a friend
+                        * knocking at the door.
+                        */
+                       return (ISC_R_RANGE);
+               }
+               isc_buffer_forward(hdrbuf, addr_size);
+
+               ret = memchr(isc_buffer_current(hdrbuf), '\0', addr_size);
+               if (ret == NULL) {
+                       return (ISC_R_RANGE);
+               }
+               isc_buffer_forward(hdrbuf, addr_size);
+       } break;
+       default:
+               UNREACHABLE();
+       }
+
+       return (ISC_R_SUCCESS);
+}
+
+static inline void
+isc__proxy2_handler_handle_payload(isc_proxy2_handler_t *restrict handler) {
+       isc_result_t result;
+       isc_sockaddr_t src_addr = { 0 }, dst_addr = { 0 };
+
+       result = isc__proxy2_handler_get_addresses(handler, &handler->hdrbuf,
+                                                  &src_addr, &dst_addr);
+
+       if (result != ISC_R_SUCCESS) {
+               isc__proxy2_handler_error(handler, result);
+               return;
+       }
+
+       if (handler->tlv_data_size > 0) {
+               isc_buffer_remainingregion(&handler->hdrbuf,
+                                          &handler->tlv_data);
+               handler->tlv_data.length = handler->tlv_data_size;
+               isc_buffer_forward(&handler->hdrbuf, handler->tlv_data_size);
+               result = isc_proxy2_tlv_data_verify(&handler->tlv_data);
+               if (result != ISC_R_SUCCESS) {
+                       isc__proxy2_handler_error(handler, result);
+                       return;
+               }
+       }
+
+       isc_buffer_remainingregion(&handler->hdrbuf, &handler->extra_data);
+       handler->expect_data = 0;
+
+       handler->state++;
+
+       /*
+        * Treat AF_UNIX as AF_UNSPEC as we have no use for it, although
+        * at this point we have fully verified the header.
+        */
+       if (handler->proxy_addr_family == ISC_PROXY2_AF_UNIX) {
+               handler->proxy_addr_family = ISC_PROXY2_AF_UNSPEC;
+               handler->proxy_socktype = ISC_PROXY2_SOCK_UNSPEC;
+               handler->tlv_data = (isc_region_t){ 0 };
+       }
+
+       isc__proxy2_handler_callcb(
+               handler, ISC_R_SUCCESS, handler->cmd, handler->proxy_socktype,
+               &src_addr, &dst_addr, &handler->tlv_data, &handler->extra_data);
+
+       return;
+}
+
+static inline bool
+isc__proxy2_handler_handle_data(isc_proxy2_handler_t *restrict handler) {
+       if (isc_buffer_remaininglength(&handler->hdrbuf) < handler->expect_data)
+       {
+               isc__proxy2_handler_error(handler, ISC_R_NOMORE);
+               return (false);
+       }
+
+       switch (handler->state) {
+       case ISC_PROXY2_STATE_WAITING_SIGNATURE:
+               /*
+                * We check for signature no matter how many bytes of it we
+                * have received. The idea is to not wait for the whole
+                * signature to verify it at once, but to detect, e.g. port
+                * scanners as early as possible. Should we receive data byte
+                * by byte, we would detect the problem when processing the
+                * first unexpected byte.
+                */
+               return (isc__proxy2_handler_handle_signature(handler));
+       case ISC_PROXY2_STATE_WAITING_HEADER:
+               /*
+                * Handle the rest of the header (except signature which we
+                * heave verified by now).
+                */
+               return (isc__proxy2_handler_handle_header(handler));
+       case ISC_PROXY2_STATE_WAITING_PAYLOAD:
+               /*
+                * Handle the PROXYv2 header payload - addresses and TLVs.
+                */
+               isc__proxy2_handler_handle_payload(handler);
+               break;
+       default:
+               UNREACHABLE();
+               break;
+       };
+
+       return (false);
+}
+
+static inline isc_result_t
+isc__proxy2_handler_process_data(isc_proxy2_handler_t *restrict handler) {
+       while (isc__proxy2_handler_handle_data(handler)) {
+               if (handler->state == ISC_PROXY2_STATE_END) {
+                       break;
+               }
+       }
+
+       return (handler->result);
+}
+
+isc_result_t
+isc_proxy2_handler_push_data(isc_proxy2_handler_t *restrict handler,
+                            const void *restrict buf,
+                            const unsigned int buf_size) {
+       isc_result_t result;
+
+       REQUIRE(handler != NULL);
+       REQUIRE(buf != NULL && buf_size != 0);
+
+       INSIST(!handler->calling_cb);
+
+       if (handler->state == ISC_PROXY2_STATE_END) {
+               isc_proxy2_handler_clear(handler);
+       }
+
+       isc_buffer_putmem(&handler->hdrbuf, buf, buf_size);
+
+       result = isc__proxy2_handler_process_data(handler);
+
+       return (result);
+}
+
+isc_result_t
+isc_proxy2_handler_push(isc_proxy2_handler_t *restrict handler,
+                       const isc_region_t *restrict region) {
+       isc_result_t result;
+
+       REQUIRE(handler != NULL);
+       REQUIRE(region != NULL);
+
+       result = isc_proxy2_handler_push_data(handler, region->base,
+                                             region->length);
+
+       return (result);
+}
+
+static inline bool
+proxy2_payload_is_processed(const isc_proxy2_handler_t *restrict handler) {
+       if (handler->state < ISC_PROXY2_STATE_END ||
+           handler->result != ISC_R_SUCCESS)
+       {
+               return (false);
+       }
+
+       return (true);
+}
+
+size_t
+isc_proxy2_handler_header(const isc_proxy2_handler_t *restrict handler,
+                         isc_region_t *restrict region) {
+       REQUIRE(handler != NULL);
+       REQUIRE(region == NULL ||
+               (region->base == NULL && region->length == 0));
+
+       if (!proxy2_payload_is_processed(handler)) {
+               return (0);
+       }
+
+       if (region != NULL) {
+               region->base = isc_buffer_base(&handler->hdrbuf);
+               region->length = handler->header_size;
+       }
+
+       return (handler->header_size);
+}
+
+size_t
+isc_proxy2_handler_tlvs(const isc_proxy2_handler_t *restrict handler,
+                       isc_region_t *restrict region) {
+       REQUIRE(handler != NULL);
+       REQUIRE(region == NULL ||
+               (region->base == NULL && region->length == 0));
+
+       if (!proxy2_payload_is_processed(handler)) {
+               return (0);
+       }
+
+       SET_IF_NOT_NULL(region, handler->tlv_data);
+
+       return (handler->tlv_data.length);
+}
+
+size_t
+isc_proxy2_handler_extra(const isc_proxy2_handler_t *restrict handler,
+                        isc_region_t *restrict region) {
+       REQUIRE(handler != NULL);
+       REQUIRE(region == NULL ||
+               (region->base == NULL && region->length == 0));
+
+       if (!proxy2_payload_is_processed(handler)) {
+               return (0);
+       }
+
+       SET_IF_NOT_NULL(region, handler->extra_data);
+
+       return (handler->extra_data.length);
+}
+
+isc_result_t
+isc_proxy2_handler_result(const isc_proxy2_handler_t *restrict handler) {
+       REQUIRE(handler != NULL);
+
+       return (handler->result);
+}
+
+isc_result_t
+isc_proxy2_handler_addresses(const isc_proxy2_handler_t *restrict handler,
+                            int *restrict psocktype,
+                            isc_sockaddr_t *restrict psrc_addr,
+                            isc_sockaddr_t *restrict pdst_addr) {
+       isc_result_t result;
+       size_t ret;
+       isc_region_t header_region = { 0 };
+       isc_buffer_t buf = { 0 };
+
+       REQUIRE(handler != NULL);
+
+       if (!proxy2_payload_is_processed(handler)) {
+               return (ISC_R_UNEXPECTED);
+       }
+
+       ret = isc_proxy2_handler_header(handler, &header_region);
+       RUNTIME_CHECK(ret > 0);
+
+       isc_buffer_init(&buf, header_region.base, header_region.length);
+       isc_buffer_add(&buf, header_region.length);
+       isc_buffer_forward(&buf, ISC_PROXY2_HEADER_SIZE);
+
+       INSIST(handler->expect_data == 0);
+
+       result = isc__proxy2_handler_get_addresses(
+               (isc_proxy2_handler_t *)handler, &buf, psrc_addr, pdst_addr);
+
+       if (result != ISC_R_SUCCESS) {
+               return (result);
+       }
+
+       SET_IF_NOT_NULL(psocktype,
+                       proxy2_socktype_to_socktype(handler->proxy_socktype));
+
+       return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_proxy2_tlv_iterate(const isc_region_t *restrict tlv_data,
+                      const isc_proxy2_tlv_cb_t cb, void *cbarg) {
+       isc_result_t result = ISC_R_SUCCESS;
+       isc_buffer_t tlvs = { 0 };
+       size_t remaining;
+
+       /*
+        * TLV header can be described as follows:
+        *
+        *   struct {
+        *       uint8_t type;
+        *       uint8_t length_hi;
+        *       uint8_t length_lo;
+        *   };
+        *
+        */
+
+       REQUIRE(tlv_data != NULL);
+       REQUIRE(cb != NULL);
+
+       isc_buffer_init(&tlvs, tlv_data->base, tlv_data->length);
+       isc_buffer_add(&tlvs, tlv_data->length);
+
+       while ((remaining = isc_buffer_remaininglength(&tlvs)) > 0) {
+               uint8_t type = 0;
+               uint16_t len = 0;
+               isc_region_t current_tlv_data = { 0 };
+               bool ret = false;
+
+               /* not enough data for a TLV header */
+               if (remaining < ISC_PROXY2_TLV_HEADER_SIZE) {
+                       result = ISC_R_RANGE;
+                       break;
+               }
+
+               type = isc_buffer_getuint8(&tlvs);
+               len = isc_buffer_getuint16(&tlvs);
+
+               if ((remaining - ISC_PROXY2_TLV_HEADER_SIZE) < len) {
+                       result = ISC_R_RANGE;
+                       break;
+               }
+
+               current_tlv_data.base = isc_buffer_current(&tlvs);
+               current_tlv_data.length = len;
+               isc_buffer_forward(&tlvs, len);
+
+               ret = cb((isc_proxy2_tlv_type_t)type, &current_tlv_data, cbarg);
+               if (!ret) {
+                       break;
+               }
+       }
+
+       return (result);
+}
+
+typedef struct proxy2_tls_cbarg {
+       uint8_t client;
+       bool client_cert_verified;
+       isc_proxy2_tls_subtlv_cb_t cb;
+       void *cbarg;
+} tls_cbarg_t;
+
+static bool
+proxy2_tls_iter_cb(const isc_proxy2_tlv_type_t tlv_type,
+                  const isc_region_t *restrict data, void *cbarg) {
+       bool ret = false;
+       tls_cbarg_t *tls_cbarg = (tls_cbarg_t *)cbarg;
+
+       ret = tls_cbarg->cb(tls_cbarg->client, tls_cbarg->client_cert_verified,
+                           (isc_proxy2_tlv_subtype_tls_t)tlv_type, data,
+                           tls_cbarg->cbarg);
+
+       return (ret);
+}
+
+isc_result_t
+isc_proxy2_subtlv_tls_header_data(const isc_region_t *restrict tls_tlv_data,
+                                 uint8_t *restrict pclient_flags,
+                                 bool *restrict pclient_cert_verified) {
+       /*
+        * SSL/TLS TLV header can be described as follows:
+        *
+        *   struct {
+        *       uint8_t  client_flags;
+        *       uint32_t client_cert_not_verified;
+        *   }
+        */
+       uint8_t *p = NULL;
+       uint8_t client_flags = 0;
+       bool client_cert_verified = false;
+       uint32_t client_cert_verified_data = 0;
+
+       REQUIRE(tls_tlv_data != NULL);
+       REQUIRE(pclient_flags == NULL || *pclient_flags == 0);
+       REQUIRE(pclient_cert_verified == NULL ||
+               *pclient_cert_verified == false);
+
+       if (tls_tlv_data->length < ISC_PROXY2_TLS_SUBHEADER_MIN_SIZE) {
+               return (ISC_R_RANGE);
+       }
+
+       p = tls_tlv_data->base;
+
+       client_flags = *p;
+       p++;
+       /* We need this to avoid ASAN complain about unaligned access */
+       memmove(&client_cert_verified_data, p, sizeof(uint32_t));
+       client_cert_verified = ntohl(client_cert_verified_data) == 0;
+
+       SET_IF_NOT_NULL(pclient_flags, client_flags);
+       SET_IF_NOT_NULL(pclient_cert_verified, client_cert_verified);
+
+       return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_proxy2_subtlv_tls_iterate(const isc_region_t *restrict tls_tlv_data,
+                             const isc_proxy2_tls_subtlv_cb_t cb,
+                             void *cbarg) {
+       tls_cbarg_t tls_cbarg;
+       isc_result_t result = ISC_R_SUCCESS;
+       uint8_t *p = NULL;
+       uint8_t client_flags = 0;
+       bool client_cert_verified = false;
+
+       REQUIRE(tls_tlv_data != NULL);
+       REQUIRE(cb != NULL);
+
+       if (tls_tlv_data->length < ISC_PROXY2_TLS_SUBHEADER_MIN_SIZE) {
+               return (ISC_R_RANGE);
+       }
+
+       result = isc_proxy2_subtlv_tls_header_data(tls_tlv_data, &client_flags,
+                                                  &client_cert_verified);
+
+       if (result != ISC_R_SUCCESS) {
+               return (result);
+       }
+
+       p = tls_tlv_data->base;
+       p += ISC_PROXY2_TLS_SUBHEADER_MIN_SIZE;
+
+       if (cb != NULL) {
+               isc_region_t data = {
+                       .base = p,
+                       .length = tls_tlv_data->length -
+                                 ISC_PROXY2_TLS_SUBHEADER_MIN_SIZE
+               };
+               tls_cbarg = (tls_cbarg_t){ .client = client_flags,
+                                          .client_cert_verified =
+                                                  client_cert_verified,
+                                          .cb = cb,
+                                          .cbarg = cbarg };
+               result = isc_proxy2_tlv_iterate(&data, proxy2_tls_iter_cb,
+                                               &tls_cbarg);
+       }
+
+       return (result);
+}
+
+typedef struct tls_subtlv_verify_cbarg {
+       uint16_t *count;
+       isc_result_t verif_result;
+} tls_subtlv_verify_cbarg_t;
+
+static bool
+proxy2_subtlv_verify_iter_cb(const uint8_t client,
+                            const bool client_cert_verified,
+                            const isc_proxy2_tlv_subtype_tls_t tls_subtlv_type,
+                            const isc_region_t *restrict data, void *cbarg) {
+       bool verify_count = false;
+       tls_subtlv_verify_cbarg_t *restrict arg =
+               (tls_subtlv_verify_cbarg_t *)cbarg;
+       uint8_t type = tls_subtlv_type;
+
+       UNUSED(client);
+       UNUSED(client_cert_verified);
+
+       if (type <= ISC_PROXY2_TLV_TYPE_TLS ||
+           type == ISC_PROXY2_TLV_TYPE_NETNS)
+       {
+               arg->verif_result = ISC_R_UNEXPECTED;
+               return (false);
+       }
+
+       switch (tls_subtlv_type) {
+       case ISC_PROXY2_TLV_SUBTYPE_TLS_VERSION:
+       case ISC_PROXY2_TLV_SUBTYPE_TLS_CN:
+       case ISC_PROXY2_TLV_SUBTYPE_TLS_SIG_ALG:
+       case ISC_PROXY2_TLV_SUBTYPE_TLS_KEY_ALG:
+               if (data->length == 0) {
+                       arg->verif_result = ISC_R_RANGE;
+                       return (false);
+               }
+               arg->count[tls_subtlv_type]++;
+               verify_count = true;
+               break;
+       default:
+               break;
+       };
+
+       if (verify_count && arg->count[tls_subtlv_type] > 1) {
+               arg->verif_result = ISC_R_UNEXPECTED;
+               return (false);
+       }
+
+       return (true);
+}
+
+typedef struct tlv_verify_cbarg {
+       uint16_t count[256];
+       isc_result_t verify_result;
+} tlv_verify_cbarg_t;
+
+static bool
+isc_proxy2_tlv_verify_cb(const isc_proxy2_tlv_type_t tlv_type,
+                        const isc_region_t *restrict data, void *cbarg) {
+       bool verify_count = false;
+       uint8_t client = 0;
+       tlv_verify_cbarg_t *arg = (tlv_verify_cbarg_t *)cbarg;
+
+       if (tlv_type == 0) {
+               /* the TLV values start from 1 */
+               goto error_unexpected;
+       }
+
+       switch (tlv_type) {
+       case ISC_PROXY2_TLV_TYPE_ALPN:
+       case ISC_PROXY2_TLV_TYPE_AUTHORITY:
+       case ISC_PROXY2_TLV_TYPE_NETNS:
+               /* these values need to be more than 0 bytes long */
+               if (data->length == 0) {
+                       goto error_range;
+               }
+               arg->count[tlv_type]++;
+               verify_count = true;
+               break;
+       case ISC_PROXY2_TLV_TYPE_CRC32C:
+               if (data->length != sizeof(uint32_t)) {
+                       goto error_range;
+               }
+               arg->count[tlv_type]++;
+               verify_count = true;
+               break;
+       case ISC_PROXY2_TLV_TYPE_UNIQUE_ID:
+               if (data->length > 128) {
+                       goto error_range;
+               }
+               arg->count[tlv_type]++;
+               verify_count = true;
+               break;
+       case ISC_PROXY2_TLV_TYPE_TLS: {
+               tls_subtlv_verify_cbarg_t tls_cbarg = {
+                       .verif_result = ISC_R_SUCCESS, .count = arg->count
+               };
+               size_t tls_version_count, tls_cn_count;
+
+               arg->verify_result =
+                       isc_proxy2_subtlv_tls_header_data(data, &client, NULL);
+
+               if (arg->verify_result != ISC_R_SUCCESS) {
+                       return (false);
+               }
+
+               arg->verify_result = isc_proxy2_subtlv_tls_iterate(
+                       data, proxy2_subtlv_verify_iter_cb, &tls_cbarg);
+
+               if (arg->verify_result != ISC_R_SUCCESS) {
+                       return (false);
+               } else if (tls_cbarg.verif_result != ISC_R_SUCCESS) {
+                       arg->verify_result = tls_cbarg.verif_result;
+                       return (false);
+               }
+
+               /*
+                * if CLIENT_TLS flag is set - TLS version TLV must be present
+                */
+               tls_version_count =
+                       arg->count[ISC_PROXY2_TLV_SUBTYPE_TLS_VERSION];
+
+               if ((client & ISC_PROXY2_CLIENT_TLS) != 0) {
+                       if (tls_version_count != 1) {
+                               goto error_unexpected;
+                       }
+               } else if (tls_version_count > 0) {
+                       /* unexpected TLS version TLV */
+                       goto error_unexpected;
+               }
+
+               /*
+                * If client cert was submitted, CLIENT_CERT_CONN or
+                * CLIENT_CERT_SESS flags must be present alongside the
+                * CLIENT_TLS flag.
+                */
+               tls_cn_count = arg->count[ISC_PROXY2_TLV_SUBTYPE_TLS_CN];
+
+               if ((client & (ISC_PROXY2_CLIENT_CERT_CONN |
+                              ISC_PROXY2_CLIENT_CERT_SESS)) != 0)
+               {
+                       if (tls_cn_count != 1 ||
+                           (client & ISC_PROXY2_CLIENT_TLS) == 0)
+                       {
+                               goto error_unexpected;
+                       }
+               } else if (tls_cn_count > 0) {
+                       /* unexpected Common Name TLV */
+                       goto error_unexpected;
+               }
+
+               arg->count[tlv_type]++;
+               verify_count = true;
+       } break;
+       default:
+               break;
+       };
+
+       if (verify_count && arg->count[tlv_type] > 1) {
+               goto error_unexpected;
+       }
+
+       return (true);
+
+error_unexpected:
+       arg->verify_result = ISC_R_UNEXPECTED;
+       return (false);
+
+error_range:
+       arg->verify_result = ISC_R_RANGE;
+       return (false);
+}
+
+isc_result_t
+isc_proxy2_tlv_data_verify(const isc_region_t *restrict tlv_data) {
+       isc_result_t result;
+       tlv_verify_cbarg_t cbarg = { .verify_result = ISC_R_SUCCESS };
+
+       result = isc_proxy2_tlv_iterate(tlv_data, isc_proxy2_tlv_verify_cb,
+                                       &cbarg);
+       if (result != ISC_R_SUCCESS) {
+               return (result);
+       }
+
+       return (cbarg.verify_result);
+}
+
+isc_result_t
+isc_proxy2_header_handle_directly(const isc_region_t *restrict header_data,
+                                 const isc_proxy2_handler_cb_t cb,
+                                 void *cbarg) {
+       isc_result_t result;
+       isc_proxy2_handler_t handler = { 0 };
+
+       REQUIRE(header_data != NULL);
+       REQUIRE(cb != NULL);
+
+       isc__proxy2_handler_init_direct(&handler, 0, header_data, cb, cbarg);
+
+       result = isc__proxy2_handler_process_data(&handler);
+
+       return (result);
+}
+
+isc_result_t
+isc_proxy2_make_header(isc_buffer_t *restrict outbuf,
+                      const isc_proxy2_command_t cmd, const int socktype,
+                      const isc_sockaddr_t *restrict src_addr,
+                      const isc_sockaddr_t *restrict dst_addr,
+                      const isc_region_t *restrict tlv_data) {
+       size_t total_size = ISC_PROXY2_HEADER_SIZE;
+       uint8_t family = ISC_PROXY2_AF_UNSPEC;
+       isc_proxy2_socktype_t proxy_socktype = ISC_PROXY2_SOCK_UNSPEC;
+
+       uint8_t ver_cmd = 0;
+       uint8_t fam_socktype = 0;
+       uint16_t len = 0;
+
+       size_t addr_size = 0;
+       void *psrc_addr = NULL, *pdst_addr = NULL;
+       /*
+        * The complete PROXYv2 header can be described as follows:
+        *
+        * 1. Header:
+        *
+        * struct proxy_hdr_v2 {
+        *   uint8_t sig[12];      // hex 0D 0A 0D 0A 00 0D 0A 51 55 49 54 0A
+        *   uint8_t ver_cmd;      // protocol version and command
+        *   uint8_t fam_socktype; // protocol family and socket type
+        *   uint16_t len;         // number of following bytes
+        * };
+        *
+        * 2. Addresses:
+        *
+        * union proxy_addr {
+        *   struct {        // for TCP/UDP over IPv4, len = 12
+        *       uint32_t src_addr;
+        *       uint32_t dst_addr;
+        *       uint16_t src_port;
+        *       uint16_t dst_port;
+        *   } ipv4_addr;
+        *   struct {        // for TCP/UDP over IPv6, len = 36
+        *        uint8_t  src_addr[16];
+        *        uint8_t  dst_addr[16];
+        *        uint16_t src_port;
+        *        uint16_t dst_port;
+        *   } ipv6_addr;
+        *   struct {        // for AF_UNIX sockets, len = 216
+        *        uint8_t src_addr[108];
+        *        uint8_t dst_addr[108];
+        *   } unix_addr;
+        * };
+        *
+        * 3. TLVs (optional)
+        */
+
+       REQUIRE(outbuf != NULL);
+       REQUIRE(cmd == ISC_PROXY2_CMD_PROXY || socktype == 0);
+       REQUIRE((src_addr == NULL && dst_addr == NULL) ||
+               (src_addr != NULL && dst_addr != NULL));
+       REQUIRE(src_addr == NULL ||
+               (isc_sockaddr_pf(src_addr) == isc_sockaddr_pf(dst_addr)));
+
+       switch (cmd) {
+       case ISC_PROXY2_CMD_LOCAL:
+               family = ISC_PROXY2_AF_UNSPEC;
+               break;
+       case ISC_PROXY2_CMD_PROXY:
+               if (socktype == 0) {
+                       family = ISC_PROXY2_AF_UNSPEC;
+               } else {
+                       switch (isc_sockaddr_pf(src_addr)) {
+                       case AF_INET:
+                               family = ISC_PROXY2_AF_INET;
+                               addr_size = sizeof(src_addr->type.sin.sin_addr);
+                               total_size += addr_size * 2 +
+                                             sizeof(uint16_t) * 2;
+                               psrc_addr = (void *)&src_addr->type.sin.sin_addr
+                                                   .s_addr;
+                               pdst_addr = (void *)&dst_addr->type.sin.sin_addr
+                                                   .s_addr;
+                               break;
+                       case AF_INET6:
+                               family = ISC_PROXY2_AF_INET6;
+                               addr_size =
+                                       sizeof(src_addr->type.sin6.sin6_addr);
+                               total_size += addr_size * 2 +
+                                             sizeof(uint16_t) * 2;
+                               psrc_addr =
+                                       (void *)&src_addr->type.sin6.sin6_addr;
+                               pdst_addr =
+                                       (void *)&dst_addr->type.sin6.sin6_addr;
+                               break;
+                       default:
+                               return (ISC_R_UNEXPECTED);
+                       }
+               }
+               break;
+       default:
+               return (ISC_R_UNEXPECTED);
+       }
+
+       switch (socktype) {
+       case 0:
+               proxy_socktype = ISC_PROXY2_SOCK_UNSPEC;
+               break;
+       case SOCK_STREAM:
+               proxy_socktype = ISC_PROXY2_SOCK_STREAM;
+               break;
+       case SOCK_DGRAM:
+               proxy_socktype = ISC_PROXY2_SOCK_DGRAM;
+               break;
+       default:
+               return (ISC_R_UNEXPECTED);
+       }
+
+       if (tlv_data != NULL) {
+               if (tlv_data->length > UINT16_MAX) {
+                       return (ISC_R_RANGE);
+               }
+               total_size += tlv_data->length;
+       }
+
+       if (isc_buffer_availablelength(outbuf) < total_size) {
+               return (ISC_R_NOSPACE);
+       } else if (total_size > UINT16_MAX) {
+               return (ISC_R_RANGE);
+       }
+
+       /*
+        * Combine version 2 (highest four bits) and command (lowest four
+        * bits).
+        */
+       ver_cmd = (((2 << 4) & 0xF0U) | cmd);
+
+       /*
+        * Combine address family (highest four bits) and socket type
+        * (lowest four bits).
+        */
+       fam_socktype = (((family << 4) & 0xF0U) | proxy_socktype);
+
+       len = (uint16_t)(total_size - ISC_PROXY2_HEADER_SIZE);
+
+       /* Write signature */
+       isc_buffer_putmem(outbuf, (uint8_t *)ISC_PROXY2_HEADER_SIGNATURE,
+                         ISC_PROXY2_HEADER_SIGNATURE_SIZE);
+       /* Write version and command */
+       isc_buffer_putuint8(outbuf, ver_cmd);
+       /* Write address family and socket type */
+       isc_buffer_putuint8(outbuf, fam_socktype);
+       /* Write header payload size (addresses + TLVs) */
+       isc_buffer_putuint16(outbuf, len);
+
+       /* Write source and destination addresses (if we should) */
+       if (psrc_addr != NULL) {
+               isc_buffer_putmem(outbuf, psrc_addr, addr_size);
+       }
+
+       if (pdst_addr != NULL) {
+               isc_buffer_putmem(outbuf, pdst_addr, addr_size);
+       }
+
+       /* Write source and destination ports (if we should) */
+       if (family == ISC_PROXY2_AF_INET || family == ISC_PROXY2_AF_INET6) {
+               isc_buffer_putuint16(outbuf, isc_sockaddr_getport(src_addr));
+               isc_buffer_putuint16(outbuf, isc_sockaddr_getport(dst_addr));
+       }
+
+       if (tlv_data != NULL) {
+               isc_buffer_putmem(outbuf, tlv_data->base, tlv_data->length);
+       }
+
+       return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_proxy2_header_append(isc_buffer_t *restrict outbuf,
+                        const isc_region_t *restrict data) {
+       const size_t len_offset = ISC_PROXY2_HEADER_SIZE - sizeof(uint16_t);
+       isc_region_t header_data = { 0 };
+       uint16_t new_len = 0;
+
+       REQUIRE(outbuf != NULL);
+
+       isc_buffer_usedregion(outbuf, &header_data);
+
+       REQUIRE(header_data.length >= ISC_PROXY2_HEADER_SIZE);
+       REQUIRE(data != NULL);
+
+       if (isc_buffer_availablelength(outbuf) < data->length) {
+               return (ISC_R_NOSPACE);
+       } else if ((data->length + header_data.length) > UINT16_MAX) {
+               return (ISC_R_RANGE);
+       }
+
+       INSIST(memcmp(header_data.base, ISC_PROXY2_HEADER_SIGNATURE,
+                     ISC_PROXY2_HEADER_SIGNATURE_SIZE) == 0);
+
+       /* fixup length of the header payload */
+       /* load */
+       memmove(&new_len, &header_data.base[len_offset], sizeof(new_len));
+       new_len = ntohs(new_len);
+       /* check */
+       if ((data->length + new_len) > UINT16_MAX) {
+               return (ISC_R_RANGE);
+       }
+       /* update */
+       new_len += (uint16_t)data->length;
+       /* store */
+       new_len = htons(new_len);
+       memmove(&header_data.base[len_offset], &new_len, sizeof(new_len));
+
+       isc_buffer_putmem(outbuf, data->base, data->length);
+
+       return (ISC_R_SUCCESS);
+}
+
+static inline void
+append_type_and_length(isc_buffer_t *restrict outbuf, const uint8_t type,
+                      const uint16_t tlv_length, const bool update_header) {
+       uint16_t length;
+       isc_region_t type_region = { 0 }, length_region = { 0 };
+
+       type_region = (isc_region_t){ .base = (uint8_t *)&type,
+                                     .length = sizeof(type) };
+       length = htons(tlv_length);
+       length_region = (isc_region_t){ .base = (uint8_t *)&length,
+                                       .length = sizeof(length) };
+
+       if (update_header) {
+               isc_result_t result = isc_proxy2_header_append(outbuf,
+                                                              &type_region);
+               RUNTIME_CHECK(result == ISC_R_SUCCESS);
+               result = isc_proxy2_header_append(outbuf, &length_region);
+               RUNTIME_CHECK(result == ISC_R_SUCCESS);
+       } else {
+               isc_buffer_putmem(outbuf, type_region.base, type_region.length);
+               isc_buffer_putmem(outbuf, length_region.base,
+                                 length_region.length);
+       }
+}
+
+isc_result_t
+isc_proxy2_header_append_tlv(isc_buffer_t *restrict outbuf,
+                            const isc_proxy2_tlv_type_t tlv_type,
+                            const isc_region_t *restrict tlv_data) {
+       size_t new_data_len = 0;
+       REQUIRE(outbuf != NULL);
+       REQUIRE(tlv_data != NULL);
+
+       /*
+        * TLV header can be described as follows:
+        *
+        *   struct {
+        *       uint8_t type;
+        *       uint8_t length_hi;
+        *       uint8_t length_lo;
+        *   };
+        *
+        */
+       new_data_len = tlv_data->length + 3;
+
+       if (isc_buffer_availablelength(outbuf) < (new_data_len)) {
+               return (ISC_R_NOSPACE);
+       } else if ((isc_buffer_usedlength(outbuf) + new_data_len) > UINT16_MAX)
+       {
+               return (ISC_R_RANGE);
+       }
+
+       append_type_and_length(outbuf, (uint8_t)tlv_type,
+                              ((uint16_t)tlv_data->length), true);
+
+       if (tlv_data->length > 0) {
+               isc_result_t result = isc_proxy2_header_append(outbuf,
+                                                              tlv_data);
+               RUNTIME_CHECK(result == ISC_R_SUCCESS);
+       }
+
+       return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_proxy2_header_append_tlv_string(isc_buffer_t *restrict outbuf,
+                                   const isc_proxy2_tlv_type_t tlv_type,
+                                   const char *restrict str) {
+       isc_result_t result;
+       isc_region_t region = { 0 };
+
+       REQUIRE(str != NULL && *str != '\0');
+
+       region.base = (uint8_t *)str;
+       region.length = strlen(str);
+
+       result = isc_proxy2_header_append_tlv(outbuf, tlv_type, &region);
+
+       return (result);
+}
+
+isc_result_t
+isc_proxy2_make_tls_subheader(isc_buffer_t *restrict outbuf,
+                             const uint8_t client_flags,
+                             const bool client_cert_verified,
+                             const isc_region_t *restrict tls_subtlvs_data) {
+       size_t total_size = ISC_PROXY2_TLS_SUBHEADER_MIN_SIZE;
+       uint32_t client_cert_not_verified = 1;
+       REQUIRE(outbuf != NULL);
+
+       if (tls_subtlvs_data != NULL) {
+               total_size += tls_subtlvs_data->length;
+       }
+
+       if (isc_buffer_availablelength(outbuf) < total_size) {
+               return (ISC_R_NOSPACE);
+       } else if (total_size > UINT16_MAX) {
+               return (ISC_R_RANGE);
+       }
+
+       isc_buffer_putuint8(outbuf, client_flags);
+       client_cert_not_verified = htonl(!client_cert_verified);
+       isc_buffer_putmem(outbuf, (uint8_t *)&client_cert_not_verified,
+                         sizeof(client_cert_not_verified));
+
+       if (tls_subtlvs_data != NULL) {
+               isc_buffer_putmem(outbuf, tls_subtlvs_data->base,
+                                 tls_subtlvs_data->length);
+       }
+
+       return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_proxy2_append_tlv(isc_buffer_t *restrict outbuf, const uint8_t type,
+                     const isc_region_t *restrict data) {
+       size_t new_data_len = 0;
+       REQUIRE(outbuf != NULL);
+       REQUIRE(data != NULL);
+
+       new_data_len = (data->length + 3);
+
+       if (isc_buffer_availablelength(outbuf) < new_data_len) {
+               return (ISC_R_NOSPACE);
+       } else if ((isc_buffer_usedlength(outbuf) + (data->length + 3)) >
+                  UINT16_MAX)
+       {
+               return (ISC_R_RANGE);
+       }
+
+       append_type_and_length(outbuf, (uint8_t)type, ((uint16_t)data->length),
+                              false);
+
+       if (data->length > 0) {
+               isc_buffer_putmem(outbuf, data->base, data->length);
+       }
+
+       return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_proxy2_append_tlv_string(isc_buffer_t *restrict outbuf, const uint8_t type,
+                            const char *restrict str) {
+       isc_result_t result;
+       isc_region_t region = { 0 };
+
+       REQUIRE(str != NULL && *str != '\0');
+
+       region.base = (uint8_t *)str;
+       region.length = strlen(str);
+
+       result = isc_proxy2_append_tlv(outbuf, type, &region);
+
+       return (result);
+}