diff --git a/lib/isc/Makefile.am b/lib/isc/Makefile.am index c6b7aa6c9b..033b0f4435 100644 --- a/lib/isc/Makefile.am +++ b/lib/isc/Makefile.am @@ -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 index 0000000000..b831ee74ca --- /dev/null +++ b/lib/isc/include/isc/proxy2.h @@ -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 +#include +#include + +/* 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; /*= `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 index 0000000000..0e3b99a146 --- /dev/null +++ b/lib/isc/proxy2.c @@ -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 + +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); +}