--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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, ¤t_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, ®ion);
+
+ 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, ®ion);
+
+ return (result);
+}