From: Oto Šťáva Date: Mon, 7 Feb 2022 13:56:33 +0000 (+0100) Subject: daemon: add PROXYv2 SSL TLV handling + minor refactoring X-Git-Tag: v5.5.0~11^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=efb26841923ab29fff5e7bfdfe7699469e775c41;p=thirdparty%2Fknot-resolver.git daemon: add PROXYv2 SSL TLV handling + minor refactoring --- diff --git a/daemon/proxyv2.c b/daemon/proxyv2.c index 7a6dc49af..a541541f7 100644 --- a/daemon/proxyv2.c +++ b/daemon/proxyv2.c @@ -10,6 +10,60 @@ 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) @@ -40,6 +94,39 @@ static inline union proxy2_address *proxy2_get_address(const struct proxy2_heade 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) { @@ -87,8 +174,8 @@ ssize_t proxy_process_header(struct proxy_result *out, struct session *s, const struct proxy2_header *hdr = (struct proxy2_header *) buf; - uint64_t addr_length = ntohs(hdr->length); - ssize_t hdr_len = sizeof(struct proxy2_header) + addr_length; + 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) { @@ -146,9 +233,11 @@ ssize_t proxy_process_header(struct proxy_result *out, struct session *s, /* Parse addresses */ union proxy2_address* addr = proxy2_get_address(hdr); + size_t addr_length = 0; switch(out->family) { case AF_INET: - if (addr_length < sizeof(addr->ipv4_addr)) + addr_length = sizeof(addr->ipv4_addr); + if (content_length < addr_length) return kr_error(KNOT_EMALF); out->src_addr.ip4 = (struct sockaddr_in) { @@ -163,7 +252,8 @@ ssize_t proxy_process_header(struct proxy_result *out, struct session *s, }; break; case AF_INET6: - if (addr_length < sizeof(addr->ipv6_addr)) + addr_length = sizeof(addr->ipv6_addr); + if (content_length < addr_length) return kr_error(KNOT_EMALF); out->src_addr.ip6 = (struct sockaddr_in6) { @@ -185,6 +275,16 @@ ssize_t proxy_process_header(struct proxy_result *out, struct session *s, 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); } diff --git a/daemon/proxyv2.h b/daemon/proxyv2.h index f608ecb37..0167e6226 100644 --- a/daemon/proxyv2.h +++ b/daemon/proxyv2.h @@ -13,55 +13,12 @@ extern const char PROXY2_SIGNATURE[12]; #define PROXY2_MIN_SIZE 16 -#define PROXY2_IP6_ADDR_SIZE 16 -#define PROXY2_UNIX_ADDR_SIZE 108 enum proxy2_command { PROXY2_CMD_LOCAL = 0x0, PROXY2_CMD_PROXY = 0x1 }; -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 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; -}; - /** Parsed result of the PROXY protocol */ struct proxy_result { enum proxy2_command command; /**< Proxy command - PROXY or LOCAL. */ @@ -69,6 +26,9 @@ struct proxy_result { int protocol; /**< Protocol type from socket library (e.g. SOCK_STREAM). */ union kr_sockaddr src_addr; /**< Parsed source address and port. */ union kr_sockaddr dst_addr; /**< Parsed destination address and port. */ + bool has_tls : 1; /**< `true` = client has used TLS with the proxy. + If TLS padding is enabled, it will be used even if + the proxy did not use TLS with kresd. */ }; /** Checks for a PROXY protocol version 2 signature in the specified buffer. */ @@ -87,4 +47,4 @@ bool proxy_allowed(const struct network *net, const struct sockaddr *saddr); * wire buffer. The function assumes that the PROXYv2 signature is present * and has been already checked by the caller (like `udp_recv` or `tcp_recv`). */ ssize_t proxy_process_header(struct proxy_result *out, struct session *s, - const void *buf, ssize_t nread); + const void *buf, ssize_t nread); diff --git a/daemon/session.c b/daemon/session.c index fc606e385..2269ea434 100644 --- a/daemon/session.c +++ b/daemon/session.c @@ -86,9 +86,7 @@ void session_clear(struct session *session) if (session->handle && session->handle->type == UV_TCP) { free(session->wire_buf); } - if (session->proxy) { - free(session->proxy); - } + free(session->proxy); #if ENABLE_DOH2 http_free(session->http_ctx); #endif diff --git a/daemon/worker.c b/daemon/worker.c index 0a27547d1..baa615e25 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -408,16 +408,12 @@ static struct request_ctx *request_create(struct worker_ctx *worker, kr_request_set_extended_error(req, KNOT_EDNS_EDE_NONE, NULL); array_init(req->qsource.headers); if (session) { - const struct sockaddr *src_addr = NULL; - const struct sockaddr *comm_addr = NULL; - const struct sockaddr *dst_addr = NULL; - const struct proxy_result *proxy = NULL; - if (comm) { - src_addr = comm->src_addr; - comm_addr = comm->comm_addr; - dst_addr = comm->dst_addr; - proxy = comm->proxy; - } + kr_require(comm); + + const struct sockaddr *src_addr = comm->src_addr; + const struct sockaddr *comm_addr = comm->comm_addr; + const struct sockaddr *dst_addr = comm->dst_addr; + const struct proxy_result *proxy = comm->proxy; req->qsource.comm_flags.tcp = session_get_handle(session)->type == UV_TCP; req->qsource.comm_flags.tls = session_flags(session)->has_tls; @@ -426,11 +422,12 @@ static struct request_ctx *request_create(struct worker_ctx *worker, req->qsource.flags = req->qsource.comm_flags; if (proxy) { req->qsource.flags.tcp = proxy->protocol == SOCK_STREAM; + req->qsource.flags.tls = proxy->has_tls; } req->qsource.stream_id = -1; #if ENABLE_DOH2 - if (req->qsource.flags.http) { + if (req->qsource.comm_flags.http) { struct http_ctx *http_ctx = session_http_get_server_ctx(session); struct http_stream stream = queue_head(http_ctx->streams); req->qsource.stream_id = stream.id; @@ -442,30 +439,18 @@ static struct request_ctx *request_create(struct worker_ctx *worker, } #endif /* We need to store a copy of peer address. */ - if (src_addr) { - memcpy(&ctx->source.addr.ip, src_addr, kr_sockaddr_len(src_addr)); - req->qsource.addr = &ctx->source.addr.ip; - } else { - req->qsource.addr = NULL; - } + memcpy(&ctx->source.addr.ip, src_addr, kr_sockaddr_len(src_addr)); + req->qsource.addr = &ctx->source.addr.ip; if (!comm_addr) comm_addr = src_addr; - if (comm_addr) { - memcpy(&ctx->source.comm_addr.ip, comm_addr, kr_sockaddr_len(comm_addr)); - req->qsource.comm_addr = &ctx->source.comm_addr.ip; - } else { - req->qsource.comm_addr = NULL; - } + memcpy(&ctx->source.comm_addr.ip, comm_addr, kr_sockaddr_len(comm_addr)); + req->qsource.comm_addr = &ctx->source.comm_addr.ip; if (!dst_addr) /* We wouldn't have to copy in this case, but for consistency. */ dst_addr = session_get_sockname(session); - if (dst_addr) { - memcpy(&ctx->source.dst_addr.ip, dst_addr, kr_sockaddr_len(dst_addr)); - req->qsource.dst_addr = &ctx->source.dst_addr.ip; - } else { - req->qsource.dst_addr = NULL; - } + memcpy(&ctx->source.dst_addr.ip, dst_addr, kr_sockaddr_len(dst_addr)); + req->qsource.dst_addr = &ctx->source.dst_addr.ip; } req->selection_context.is_tls_capable = is_tls_capable; diff --git a/lib/resolve.c b/lib/resolve.c index 2a4e672ec..e61574969 100644 --- a/lib/resolve.c +++ b/lib/resolve.c @@ -369,7 +369,7 @@ static int edns_create(knot_pkt_t *pkt, const struct kr_request *req) wire_size += KR_COOKIE_OPT_MAX_LEN; } #endif /* ENABLE_COOKIES */ - if (req->qsource.flags.tls) { + if (req->qsource.flags.tls || req->qsource.comm_flags.tls) { wire_size += edns_padding_option_size(req->ctx->tls_padding); } return knot_pkt_reserve(pkt, wire_size); @@ -456,7 +456,7 @@ static int answer_padding(struct kr_request *request) { if (kr_fails_assert(request && request->answer && request->ctx)) return kr_error(EINVAL); - if (!request->qsource.flags.tls) { + if (!request->qsource.flags.tls && !request->qsource.comm_flags.tls) { /* Not meaningful to pad without encryption. */ return kr_ok(); } @@ -741,9 +741,10 @@ knot_pkt_t *kr_request_ensure_answer(struct kr_request *request) // Find answer_max: limit on DNS wire length. uint16_t answer_max; const struct kr_request_qsource_flags *qs_flags = &request->qsource.flags; - if (kr_fails_assert((qs_flags->tls || qs_flags->http) ? qs_flags->tcp : true)) + const struct kr_request_qsource_flags *qs_cflags = &request->qsource.comm_flags; + if (kr_fails_assert(!(qs_flags->tls || qs_cflags->tls || qs_cflags->http) || qs_flags->tcp)) goto fail; - if (!request->qsource.addr || qs_flags->tcp) { + if (!request->qsource.addr || qs_flags->tcp || qs_cflags->tcp) { // not on UDP answer_max = KNOT_WIRE_MAX_PKTSIZE; } else if (knot_pkt_has_edns(qs_pkt)) {