diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:26:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:26:00 +0000 |
commit | 830407e88f9d40d954356c3754f2647f91d5c06a (patch) | |
tree | d6a0ece6feea91f3c656166dbaa884ef8a29740e /daemon/proxyv2.c | |
parent | Initial commit. (diff) | |
download | knot-resolver-830407e88f9d40d954356c3754f2647f91d5c06a.tar.xz knot-resolver-830407e88f9d40d954356c3754f2647f91d5c06a.zip |
Adding upstream version 5.6.0.upstream/5.6.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'daemon/proxyv2.c')
-rw-r--r-- | daemon/proxyv2.c | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/daemon/proxyv2.c b/daemon/proxyv2.c new file mode 100644 index 0000000..f977ccb --- /dev/null +++ b/daemon/proxyv2.c @@ -0,0 +1,290 @@ +/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz> + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "daemon/proxyv2.h" + +#include "lib/generic/trie.h" + +const char PROXY2_SIGNATURE[12] = { + 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A +}; + +#define PROXY2_IP6_ADDR_SIZE 16 +#define PROXY2_UNIX_ADDR_SIZE 108 + +#define TLV_TYPE_SSL 0x20 + +enum proxy2_family { + PROXY2_AF_UNSPEC = 0x0, + PROXY2_AF_INET = 0x1, + PROXY2_AF_INET6 = 0x2, + PROXY2_AF_UNIX = 0x3 +}; + +enum proxy2_protocol { + PROXY2_PROTOCOL_UNSPEC = 0x0, + PROXY2_PROTOCOL_STREAM = 0x1, + PROXY2_PROTOCOL_DGRAM = 0x2 +}; + +/** PROXYv2 protocol header section */ +struct proxy2_header { + uint8_t signature[sizeof(PROXY2_SIGNATURE)]; + uint8_t version_command; + uint8_t family_protocol; + uint16_t length; /**< Length of the address section */ +}; + +/** PROXYv2 additional information in Type-Length-Value (TLV) format. */ +struct proxy2_tlv { + uint8_t type; + uint8_t length_hi; + uint8_t length_lo; + uint8_t value[]; +}; + +/** PROXYv2 protocol address section */ +union proxy2_address { + struct { + uint32_t src_addr; + uint32_t dst_addr; + uint16_t src_port; + uint16_t dst_port; + } ipv4_addr; + struct { + uint8_t src_addr[PROXY2_IP6_ADDR_SIZE]; + uint8_t dst_addr[PROXY2_IP6_ADDR_SIZE]; + uint16_t src_port; + uint16_t dst_port; + } ipv6_addr; + struct { + uint8_t src_addr[PROXY2_UNIX_ADDR_SIZE]; + uint8_t dst_addr[PROXY2_UNIX_ADDR_SIZE]; + } unix_addr; +}; + + +/** Gets protocol version from the specified PROXYv2 header. */ +static inline unsigned char proxy2_header_version(const struct proxy2_header* h) +{ + return (h->version_command & 0xF0) >> 4; +} + +/** Gets command from the specified PROXYv2 header. */ +static inline enum proxy2_command proxy2_header_command(const struct proxy2_header *h) +{ + return h->version_command & 0x0F; +} + +/** Gets address family from the specified PROXYv2 header. */ +static inline enum proxy2_family proxy2_header_family(const struct proxy2_header *h) +{ + return (h->family_protocol & 0xF0) >> 4; +} + +/** Gets transport protocol from the specified PROXYv2 header. */ +static inline enum proxy2_family proxy2_header_protocol(const struct proxy2_header *h) +{ + return h->family_protocol & 0x0F; +} + +static inline union proxy2_address *proxy2_get_address(const struct proxy2_header *h) +{ + return (union proxy2_address *) ((uint8_t *) h + sizeof(struct proxy2_header)); +} + +static inline struct proxy2_tlv *get_tlvs(const struct proxy2_header *h, size_t addr_len) +{ + return (struct proxy2_tlv *) ((uint8_t *) proxy2_get_address(h) + addr_len); +} + +/** Gets the length of the TLV's `value` attribute. */ +static inline uint16_t proxy2_tlv_length(const struct proxy2_tlv *tlv) +{ + return ((uint16_t) tlv->length_hi << 16) | tlv->length_lo; +} + +static inline bool has_tlv(const struct proxy2_header *h, + const struct proxy2_tlv *tlv) +{ + uint64_t addr_length = ntohs(h->length); + ptrdiff_t hdr_len = sizeof(struct proxy2_header) + addr_length; + + uint8_t *tlv_hdr_end = (uint8_t *) tlv + sizeof(struct proxy2_tlv); + ptrdiff_t distance = tlv_hdr_end - (uint8_t *) h; + if (hdr_len < distance) + return false; + + uint8_t *tlv_end = tlv_hdr_end + proxy2_tlv_length(tlv); + distance = tlv_end - (uint8_t *) h; + return hdr_len >= distance; +} + +static inline void next_tlv(struct proxy2_tlv **tlv) +{ + uint8_t *next = ((uint8_t *) *tlv + sizeof(struct proxy2_tlv) + proxy2_tlv_length(*tlv)); + *tlv = (struct proxy2_tlv *) next; +} + + +bool proxy_allowed(const struct network *net, const struct sockaddr *saddr) +{ + union kr_in_addr addr; + trie_t *trie; + size_t addr_size; + switch (saddr->sa_family) { + case AF_INET: + if (net->proxy_all4) + return true; + + trie = net->proxy_addrs4; + addr_size = sizeof(addr.ip4); + addr.ip4 = ((struct sockaddr_in *) saddr)->sin_addr; + break; + case AF_INET6: + if (net->proxy_all6) + return true; + + trie = net->proxy_addrs6; + addr_size = sizeof(addr.ip6); + addr.ip6 = ((struct sockaddr_in6 *) saddr)->sin6_addr; + break; + default: + kr_assert(false); // Only IPv4 and IPv6 proxy addresses supported + return false; + } + + trie_val_t *val; + int ret = trie_get_leq(trie, (char *) &addr, addr_size, &val); + if (ret != kr_ok() && ret != 1) + return false; + + kr_assert(val); + const struct net_proxy_data *found = *val; + kr_assert(found); + return kr_bitcmp((char *) &addr, (char *) &found->addr, found->netmask) == 0; +} + +ssize_t proxy_process_header(struct proxy_result *out, struct session *s, + const void *buf, const ssize_t nread) +{ + if (!buf) + return kr_error(EINVAL); + + const struct proxy2_header *hdr = (struct proxy2_header *) buf; + + uint64_t content_length = ntohs(hdr->length); + ssize_t hdr_len = sizeof(struct proxy2_header) + content_length; + + /* PROXYv2 requires the header to be received all at once */ + if (nread < hdr_len) { + return kr_error(KNOT_EMALF); + } + + unsigned char version = proxy2_header_version(hdr); + if (version != 2) { + /* Version MUST be 2 for PROXYv2 protocol */ + return kr_error(KNOT_EMALF); + } + + enum proxy2_command command = proxy2_header_command(hdr); + if (command == PROXY2_CMD_LOCAL) { + /* Addresses for LOCAL are to be discarded */ + *out = (struct proxy_result) { .command = PROXY2_CMD_LOCAL }; + goto fill_wirebuf; + } + + if (command != PROXY2_CMD_PROXY) { + /* PROXYv2 prohibits values other than LOCAL and PROXY */ + return kr_error(KNOT_EMALF); + } + + *out = (struct proxy_result) { .command = PROXY2_CMD_PROXY }; + + /* Parse flags */ + enum proxy2_family family = proxy2_header_family(hdr); + switch(family) { + case PROXY2_AF_UNSPEC: + case PROXY2_AF_UNIX: /* UNIX is unsupported, fall back to UNSPEC */ + out->family = AF_UNSPEC; + break; + case PROXY2_AF_INET: + out->family = AF_INET; + break; + case PROXY2_AF_INET6: + out->family = AF_INET6; + break; + default: /* PROXYv2 prohibits other values */ + return kr_error(KNOT_EMALF); + } + + enum proxy2_family protocol = proxy2_header_protocol(hdr); + switch (protocol) { + case PROXY2_PROTOCOL_DGRAM: + out->protocol = SOCK_DGRAM; + break; + case PROXY2_PROTOCOL_STREAM: + out->protocol = SOCK_STREAM; + break; + default: /* PROXYv2 prohibits other values */ + return kr_error(KNOT_EMALF); + } + + /* Parse addresses */ + union proxy2_address* addr = proxy2_get_address(hdr); + size_t addr_length = 0; + switch(out->family) { + case AF_INET: + addr_length = sizeof(addr->ipv4_addr); + if (content_length < addr_length) + return kr_error(KNOT_EMALF); + + out->src_addr.ip4 = (struct sockaddr_in) { + .sin_family = AF_INET, + .sin_addr = { .s_addr = addr->ipv4_addr.src_addr }, + .sin_port = addr->ipv4_addr.src_port, + }; + out->dst_addr.ip4 = (struct sockaddr_in) { + .sin_family = AF_INET, + .sin_addr = { .s_addr = addr->ipv4_addr.dst_addr }, + .sin_port = addr->ipv4_addr.dst_port, + }; + break; + case AF_INET6: + addr_length = sizeof(addr->ipv6_addr); + if (content_length < addr_length) + return kr_error(KNOT_EMALF); + + out->src_addr.ip6 = (struct sockaddr_in6) { + .sin6_family = AF_INET6, + .sin6_port = addr->ipv6_addr.src_port + }; + memcpy( + &out->src_addr.ip6.sin6_addr.s6_addr, + &addr->ipv6_addr.src_addr, + sizeof(out->src_addr.ip6.sin6_addr.s6_addr)); + out->dst_addr.ip6 = (struct sockaddr_in6) { + .sin6_family = AF_INET6, + .sin6_port = addr->ipv6_addr.dst_port + }; + memcpy( + &out->dst_addr.ip6.sin6_addr.s6_addr, + &addr->ipv6_addr.dst_addr, + sizeof(out->dst_addr.ip6.sin6_addr.s6_addr)); + break; + } + + /* Process additional information */ + for (struct proxy2_tlv *tlv = get_tlvs(hdr, addr_length); has_tlv(hdr, tlv); next_tlv(&tlv)) { + switch (tlv->type) { + case TLV_TYPE_SSL: + out->has_tls = true; + break; + /* TODO: add more TLV types if needed */ + } + } + +fill_wirebuf: + return session_wirebuf_trim(s, hdr_len); +} |