1
0
Fork 0
knot-resolver/daemon/proxyv2.c
Daniel Baumann fbc604e215
Adding upstream version 5.7.5.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-21 13:56:17 +02:00

297 lines
7.6 KiB
C

/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "daemon/session.h"
#include "daemon/network.h"
#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;
default:; /* Keep zero from initializer. */
}
/* 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;
default:; /* Ignore others - add more if needed */
}
}
fill_wirebuf:
return session_wirebuf_trim(s, hdr_len);
}