From: Oto Šťáva Date: Tue, 21 May 2024 17:04:38 +0000 (+0200) Subject: daemon/proxyv2: move PROXY protocol into its own layer X-Git-Tag: v6.0.8~14^2~5 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b475d004170fd4abe8b967b9f08f5c9472dbdbbb;p=thirdparty%2Fknot-resolver.git daemon/proxyv2: move PROXY protocol into its own layer Previously, PROXYv2 handling was partially implemented in the `io.c` unit in the `_TCP` and `_UDP` protocol layers, which technically made very little sense. This commit moves this handling into separate `_PROXYV2_DGRAM` and `_PROXYV2_STREAM` protocol layers, basically encapsulating the handling of proxies in the `proxyv2.c` unit. This commit also makes the PROXYv2 stream layer only support `PROTOLAYER_PAYLOAD_WIRE_BUF` on its input, as other payload types were unused and untested in this context. --- diff --git a/daemon/io.c b/daemon/io.c index 8333577de..d19da0ebd 100644 --- a/daemon/io.c +++ b/daemon/io.c @@ -17,7 +17,6 @@ #endif #include "daemon/network.h" -#include "daemon/proxyv2.h" #include "daemon/worker.h" #include "daemon/tls.h" #include "daemon/http.h" @@ -137,73 +136,6 @@ static int family_to_freebind_option(sa_family_t sa_family, int *level, int *nam } -struct pl_udp_iter_data { - struct protolayer_data h; - struct proxy_result proxy; - bool has_proxy; -}; - -static enum protolayer_iter_cb_result pl_udp_unwrap( - void *sess_data, void *iter_data, struct protolayer_iter_ctx *ctx) -{ - ctx->payload = protolayer_payload_as_buffer(&ctx->payload); - if (kr_fails_assert(ctx->payload.type == PROTOLAYER_PAYLOAD_BUFFER)) { - /* unsupported payload */ - return protolayer_break(ctx, kr_error(EINVAL)); - } - - struct session2 *s = ctx->manager->session; - struct pl_udp_iter_data *udp = iter_data; - - char *data = ctx->payload.buffer.buf; - ssize_t data_len = ctx->payload.buffer.len; - struct comm_info *comm = &ctx->comm; - if (!s->outgoing && proxy_header_present(data, data_len)) { - if (!proxy_allowed(comm->comm_addr)) { - kr_log_debug(IO, "<= ignoring PROXYv2 UDP from disallowed address '%s'\n", - kr_straddr(comm->comm_addr)); - return protolayer_break(ctx, kr_error(EPERM)); - } - - ssize_t trimmed = proxy_process_header(&udp->proxy, data, data_len); - if (trimmed == KNOT_EMALF) { - if (kr_log_is_debug(IO, NULL)) { - kr_log_debug(IO, "<= ignoring malformed PROXYv2 UDP " - "from address '%s'\n", - kr_straddr(comm->comm_addr)); - } - return protolayer_break(ctx, kr_error(EINVAL)); - } else if (trimmed < 0) { - if (kr_log_is_debug(IO, NULL)) { - kr_log_debug(IO, "<= error processing PROXYv2 UDP " - "from address '%s', ignoring\n", - kr_straddr(comm->comm_addr)); - } - return protolayer_break(ctx, kr_error(EINVAL)); - } - - if (udp->proxy.command == PROXY2_CMD_PROXY && udp->proxy.family != AF_UNSPEC) { - udp->has_proxy = true; - - comm->src_addr = &udp->proxy.src_addr.ip; - comm->dst_addr = &udp->proxy.dst_addr.ip; - comm->proxy = &udp->proxy; - - if (kr_log_is_debug(IO, NULL)) { - kr_log_debug(IO, "<= UDP query from '%s'\n", - kr_straddr(comm->src_addr)); - kr_log_debug(IO, "<= proxied through '%s'\n", - kr_straddr(comm->comm_addr)); - } - } - - ctx->payload = protolayer_payload_buffer( - data + trimmed, data_len - trimmed, false); - } - - return protolayer_continue(ctx); -} - static enum protolayer_event_cb_result pl_udp_event_wrap( enum protolayer_event_type event, void **baton, struct protolayer_manager *manager, void *sess_data) @@ -219,127 +151,6 @@ static enum protolayer_event_cb_result pl_udp_event_wrap( return PROTOLAYER_EVENT_PROPAGATE; } - -struct pl_tcp_sess_data { - struct protolayer_data h; - struct proxy_result proxy; - struct wire_buf wire_buf; - bool had_data : 1; - bool has_proxy : 1; -}; - -static int pl_tcp_sess_init(struct protolayer_manager *manager, - void *data, - void *param) -{ - struct sockaddr *peer = session2_get_peer(manager->session); - manager->session->comm = (struct comm_info) { - .comm_addr = peer, - .src_addr = peer - }; - return 0; -} - -static int pl_tcp_sess_deinit(struct protolayer_manager *manager, void *sess_data) -{ - struct pl_tcp_sess_data *tcp = sess_data; - wire_buf_deinit(&tcp->wire_buf); - return 0; -} - -static enum protolayer_iter_cb_result pl_tcp_unwrap( - void *sess_data, void *iter_data, struct protolayer_iter_ctx *ctx) -{ - struct session2 *s = ctx->manager->session; - struct pl_tcp_sess_data *tcp = sess_data; - struct sockaddr *peer = session2_get_peer(s); - - if (ctx->payload.type == PROTOLAYER_PAYLOAD_BUFFER) { - const char *buf = ctx->payload.buffer.buf; - const size_t len = ctx->payload.buffer.len; - - /* Copy a simple buffer into internal wirebuffer. */ - if (len > KNOT_WIRE_MAX_PKTSIZE) - return protolayer_break(ctx, kr_error(EMSGSIZE)); - - if (!tcp->wire_buf.buf) { - int ret = wire_buf_reserve(&tcp->wire_buf, - KNOT_WIRE_MAX_PKTSIZE); - if (ret) - return protolayer_break(ctx, ret); - } - - /* Check if space can be made */ - if (len > wire_buf_free_space_length(&tcp->wire_buf)) { - if (len > tcp->wire_buf.size - wire_buf_data_length(&tcp->wire_buf)) - return protolayer_break(ctx, kr_error(EMSGSIZE)); - wire_buf_movestart(&tcp->wire_buf); - } - - memcpy(wire_buf_free_space(&tcp->wire_buf), buf, len); - wire_buf_consume(&tcp->wire_buf, ctx->payload.buffer.len); - ctx->payload = protolayer_payload_wire_buf(&tcp->wire_buf, false); - } - - if (kr_fails_assert(ctx->payload.type == PROTOLAYER_PAYLOAD_WIRE_BUF)) { - /* TODO: iovec support unimplemented */ - return protolayer_break(ctx, kr_error(EINVAL)); - } - - char *data = wire_buf_data(ctx->payload.wire_buf); /* layer's or session's wirebuf */ - ssize_t data_len = wire_buf_data_length(ctx->payload.wire_buf); - struct comm_info *comm = &ctx->manager->session->comm; - if (!s->outgoing && !tcp->had_data && proxy_header_present(data, data_len)) { - if (!proxy_allowed(comm->src_addr)) { - if (kr_log_is_debug(IO, NULL)) { - kr_log_debug(IO, "<= connection to '%s': PROXYv2 not allowed " - "for this peer, close\n", - kr_straddr(peer)); - } - worker_end_tcp(s); - return protolayer_break(ctx, kr_error(ECONNRESET)); - } - - ssize_t trimmed = proxy_process_header(&tcp->proxy, data, data_len); - if (trimmed < 0) { - if (kr_log_is_debug(IO, NULL)) { - if (trimmed == KNOT_EMALF) { - kr_log_debug(IO, "<= connection to '%s': " - "malformed PROXYv2 header, close\n", - kr_straddr(comm->src_addr)); - } else { - kr_log_debug(IO, "<= connection to '%s': " - "error processing PROXYv2 header, close\n", - kr_straddr(comm->src_addr)); - } - } - worker_end_tcp(s); - return protolayer_break(ctx, kr_error(ECONNRESET)); - } else if (trimmed == 0) { - session2_close(s); - return protolayer_break(ctx, kr_error(ECONNRESET)); - } - - if (tcp->proxy.command != PROXY2_CMD_LOCAL && tcp->proxy.family != AF_UNSPEC) { - comm->src_addr = &tcp->proxy.src_addr.ip; - comm->dst_addr = &tcp->proxy.dst_addr.ip; - - if (kr_log_is_debug(IO, NULL)) { - kr_log_debug(IO, "<= TCP stream from '%s'\n", - kr_straddr(comm->src_addr)); - kr_log_debug(IO, "<= proxied through '%s'\n", - kr_straddr(comm->comm_addr)); - } - } - - wire_buf_trim(ctx->payload.wire_buf, trimmed); - } - - tcp->had_data = true; - ctx->comm = ctx->manager->session->comm; - return protolayer_continue(ctx); -} - static enum protolayer_event_cb_result pl_tcp_event_wrap( enum protolayer_event_type event, void **baton, struct protolayer_manager *manager, void *sess_data) @@ -358,16 +169,10 @@ static enum protolayer_event_cb_result pl_tcp_event_wrap( void io_protolayers_init(void) { protolayer_globals[PROTOLAYER_TYPE_UDP] = (struct protolayer_globals){ - .iter_size = sizeof(struct pl_udp_iter_data), - .unwrap = pl_udp_unwrap, .event_wrap = pl_udp_event_wrap, }; protolayer_globals[PROTOLAYER_TYPE_TCP] = (struct protolayer_globals){ - .sess_size = sizeof(struct pl_tcp_sess_data), - .sess_init = pl_tcp_sess_init, - .sess_deinit = pl_tcp_sess_deinit, - .unwrap = pl_tcp_unwrap, .event_wrap = pl_tcp_event_wrap, }; } diff --git a/daemon/main.c b/daemon/main.c index 8185c1c67..63cdd7f24 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -7,6 +7,7 @@ #include "contrib/ucw/mempool.h" #include "daemon/engine.h" #include "daemon/io.h" +#include "daemon/proxyv2.h" #include "daemon/network.h" #include "daemon/udp_queue.h" #include "daemon/worker.h" @@ -585,6 +586,7 @@ int main(int argc, char **argv) io_protolayers_init(); tls_protolayers_init(); + proxy_protolayers_init(); #ifdef ENABLE_DOH2 http_protolayers_init(); #endif diff --git a/daemon/proxyv2.c b/daemon/proxyv2.c index ce0ea0a68..1b61f20f4 100644 --- a/daemon/proxyv2.c +++ b/daemon/proxyv2.c @@ -3,14 +3,17 @@ */ #include "daemon/network.h" +#include "daemon/session2.h" +#include "daemon/worker.h" #include "lib/generic/trie.h" #include "daemon/proxyv2.h" -const char PROXY2_SIGNATURE[12] = { +static const char PROXY2_SIGNATURE[12] = { 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A }; +#define PROXY2_MIN_SIZE 16 #define PROXY2_IP6_ADDR_SIZE 16 #define PROXY2_UNIX_ADDR_SIZE 108 @@ -129,7 +132,9 @@ static inline void next_tlv(struct proxy2_tlv **tlv) } -bool proxy_allowed(const struct sockaddr *saddr) +/** Checks whether the use of PROXYv2 protocol is allowed for the specified + * address. */ +static bool proxy_allowed(const struct sockaddr *saddr) { union kr_in_addr addr; trie_t *trie; @@ -167,7 +172,10 @@ bool proxy_allowed(const struct sockaddr *saddr) return kr_bitcmp((char *)&addr, (char *)&found->addr, found->netmask) == 0; } -ssize_t proxy_process_header(struct proxy_result *out, +/** Parses the PROXYv2 header from buf of size nread and writes the result into + * out. The function assumes that the PROXYv2 signature is present + * and has been already checked by the caller (like `udp_recv` or `tcp_recv`). */ +static ssize_t proxy_process_header(struct proxy_result *out, const void *buf, const ssize_t nread) { if (!buf) @@ -293,3 +301,179 @@ ssize_t proxy_process_header(struct proxy_result *out, fill_wirebuf: return hdr_len; } + +/** Checks for a PROXY protocol version 2 signature in the specified buffer. */ +static inline bool proxy_header_present(const void* buf, const ssize_t nread) +{ + return nread >= PROXY2_MIN_SIZE && + memcmp(buf, PROXY2_SIGNATURE, sizeof(PROXY2_SIGNATURE)) == 0; +} + + +struct pl_proxyv2_dgram_iter_data { + struct protolayer_data h; + struct proxy_result proxy; + bool has_proxy; +}; + +static enum protolayer_iter_cb_result pl_proxyv2_dgram_unwrap( + void *sess_data, void *iter_data, struct protolayer_iter_ctx *ctx) +{ + ctx->payload = protolayer_payload_as_buffer(&ctx->payload); + if (kr_fails_assert(ctx->payload.type == PROTOLAYER_PAYLOAD_BUFFER)) { + /* unsupported payload */ + return protolayer_break(ctx, kr_error(EINVAL)); + } + + struct session2 *s = ctx->manager->session; + struct pl_proxyv2_dgram_iter_data *udp = iter_data; + + char *data = ctx->payload.buffer.buf; + ssize_t data_len = ctx->payload.buffer.len; + struct comm_info *comm = &ctx->comm; + if (!s->outgoing && proxy_header_present(data, data_len)) { + if (!proxy_allowed(comm->comm_addr)) { + kr_log_debug(IO, "<= ignoring PROXYv2 UDP from disallowed address '%s'\n", + kr_straddr(comm->comm_addr)); + return protolayer_break(ctx, kr_error(EPERM)); + } + + ssize_t trimmed = proxy_process_header(&udp->proxy, data, data_len); + if (trimmed == KNOT_EMALF) { + if (kr_log_is_debug(IO, NULL)) { + kr_log_debug(IO, "<= ignoring malformed PROXYv2 UDP " + "from address '%s'\n", + kr_straddr(comm->comm_addr)); + } + return protolayer_break(ctx, kr_error(EINVAL)); + } else if (trimmed < 0) { + if (kr_log_is_debug(IO, NULL)) { + kr_log_debug(IO, "<= error processing PROXYv2 UDP " + "from address '%s', ignoring\n", + kr_straddr(comm->comm_addr)); + } + return protolayer_break(ctx, kr_error(EINVAL)); + } + + if (udp->proxy.command == PROXY2_CMD_PROXY && udp->proxy.family != AF_UNSPEC) { + udp->has_proxy = true; + + comm->src_addr = &udp->proxy.src_addr.ip; + comm->dst_addr = &udp->proxy.dst_addr.ip; + comm->proxy = &udp->proxy; + + if (kr_log_is_debug(IO, NULL)) { + kr_log_debug(IO, "<= UDP query from '%s'\n", + kr_straddr(comm->src_addr)); + kr_log_debug(IO, "<= proxied through '%s'\n", + kr_straddr(comm->comm_addr)); + } + } + + ctx->payload = protolayer_payload_buffer( + data + trimmed, data_len - trimmed, false); + } + + return protolayer_continue(ctx); +} + + +struct pl_proxyv2_stream_sess_data { + struct protolayer_data h; + struct proxy_result proxy; + bool had_data : 1; + bool has_proxy : 1; +}; + +static int pl_proxyv2_stream_sess_init(struct protolayer_manager *manager, + void *data, + void *param) +{ + struct sockaddr *peer = session2_get_peer(manager->session); + manager->session->comm = (struct comm_info) { + .comm_addr = peer, + .src_addr = peer + }; + return 0; +} + +static enum protolayer_iter_cb_result pl_proxyv2_stream_unwrap( + void *sess_data, void *iter_data, struct protolayer_iter_ctx *ctx) +{ + struct session2 *s = ctx->manager->session; + struct pl_proxyv2_stream_sess_data *tcp = sess_data; + struct sockaddr *peer = session2_get_peer(s); + + if (kr_fails_assert(ctx->payload.type == PROTOLAYER_PAYLOAD_WIRE_BUF)) { + /* Only wire buffer is supported */ + return protolayer_break(ctx, kr_error(EINVAL)); + } + + char *data = wire_buf_data(ctx->payload.wire_buf); /* layer's or session's wirebuf */ + ssize_t data_len = wire_buf_data_length(ctx->payload.wire_buf); + struct comm_info *comm = &ctx->manager->session->comm; + if (!s->outgoing && !tcp->had_data && proxy_header_present(data, data_len)) { + if (!proxy_allowed(comm->src_addr)) { + if (kr_log_is_debug(IO, NULL)) { + kr_log_debug(IO, "<= connection to '%s': PROXYv2 not allowed " + "for this peer, close\n", + kr_straddr(peer)); + } + worker_end_tcp(s); + return protolayer_break(ctx, kr_error(ECONNRESET)); + } + + ssize_t trimmed = proxy_process_header(&tcp->proxy, data, data_len); + if (trimmed < 0) { + if (kr_log_is_debug(IO, NULL)) { + if (trimmed == KNOT_EMALF) { + kr_log_debug(IO, "<= connection to '%s': " + "malformed PROXYv2 header, close\n", + kr_straddr(comm->src_addr)); + } else { + kr_log_debug(IO, "<= connection to '%s': " + "error processing PROXYv2 header, close\n", + kr_straddr(comm->src_addr)); + } + } + worker_end_tcp(s); + return protolayer_break(ctx, kr_error(ECONNRESET)); + } else if (trimmed == 0) { + session2_close(s); + return protolayer_break(ctx, kr_error(ECONNRESET)); + } + + if (tcp->proxy.command != PROXY2_CMD_LOCAL && tcp->proxy.family != AF_UNSPEC) { + comm->src_addr = &tcp->proxy.src_addr.ip; + comm->dst_addr = &tcp->proxy.dst_addr.ip; + + if (kr_log_is_debug(IO, NULL)) { + kr_log_debug(IO, "<= TCP stream from '%s'\n", + kr_straddr(comm->src_addr)); + kr_log_debug(IO, "<= proxied through '%s'\n", + kr_straddr(comm->comm_addr)); + } + } + + wire_buf_trim(ctx->payload.wire_buf, trimmed); + } + + tcp->had_data = true; + ctx->comm = ctx->manager->session->comm; + return protolayer_continue(ctx); +} + + +void proxy_protolayers_init(void) +{ + protolayer_globals[PROTOLAYER_TYPE_PROXYV2_DGRAM] = (struct protolayer_globals){ + .iter_size = sizeof(struct pl_proxyv2_dgram_iter_data), + .unwrap = pl_proxyv2_dgram_unwrap, + }; + + protolayer_globals[PROTOLAYER_TYPE_PROXYV2_STREAM] = (struct protolayer_globals){ + .sess_size = sizeof(struct pl_proxyv2_stream_sess_data), + .sess_init = pl_proxyv2_stream_sess_init, + .unwrap = pl_proxyv2_stream_unwrap, + }; +} diff --git a/daemon/proxyv2.h b/daemon/proxyv2.h index a21f14b1e..6a6bc1794 100644 --- a/daemon/proxyv2.h +++ b/daemon/proxyv2.h @@ -8,10 +8,6 @@ #include "lib/utils.h" -extern const char PROXY2_SIGNATURE[12]; - -#define PROXY2_MIN_SIZE 16 - enum proxy2_command { PROXY2_CMD_LOCAL = 0x0, PROXY2_CMD_PROXY = 0x1 @@ -35,19 +31,5 @@ struct proxy_result { bool has_tls : 1; }; -/** Checks for a PROXY protocol version 2 signature in the specified buffer. */ -static inline bool proxy_header_present(const void* buf, const ssize_t nread) -{ - return nread >= PROXY2_MIN_SIZE && - memcmp(buf, PROXY2_SIGNATURE, sizeof(PROXY2_SIGNATURE)) == 0; -} - -/** Checks whether the use of PROXYv2 protocol is allowed for the specified - * address. */ -bool proxy_allowed(const struct sockaddr *saddr); - -/** Parses the PROXYv2 header from buf of size nread and writes the result into - * out. 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, - const void *buf, ssize_t nread); +/** Initializes the protocol layers managed by the PROXYv2 "module". */ +void proxy_protolayers_init(void); diff --git a/daemon/session2.c b/daemon/session2.c index fa3feb9c4..818300d86 100644 --- a/daemon/session2.c +++ b/daemon/session2.c @@ -35,22 +35,26 @@ struct protolayer_globals protolayer_globals[PROTOLAYER_TYPE_COUNT] = {{0}}; static const enum protolayer_type protolayer_grp_udp53[] = { PROTOLAYER_TYPE_UDP, + PROTOLAYER_TYPE_PROXYV2_DGRAM, PROTOLAYER_TYPE_DNS_DGRAM, }; static const enum protolayer_type protolayer_grp_tcp53[] = { PROTOLAYER_TYPE_TCP, + PROTOLAYER_TYPE_PROXYV2_STREAM, PROTOLAYER_TYPE_DNS_MULTI_STREAM, }; static const enum protolayer_type protolayer_grp_dot[] = { PROTOLAYER_TYPE_TCP, + PROTOLAYER_TYPE_PROXYV2_STREAM, PROTOLAYER_TYPE_TLS, PROTOLAYER_TYPE_DNS_MULTI_STREAM, }; static const enum protolayer_type protolayer_grp_doh[] = { PROTOLAYER_TYPE_TCP, + PROTOLAYER_TYPE_PROXYV2_STREAM, PROTOLAYER_TYPE_TLS, PROTOLAYER_TYPE_HTTP, PROTOLAYER_TYPE_DNS_UNSIZED_STREAM, diff --git a/daemon/session2.h b/daemon/session2.h index f65cf5d6b..ac83d8dec 100644 --- a/daemon/session2.h +++ b/daemon/session2.h @@ -212,6 +212,10 @@ static inline size_t wire_buf_free_space_length(const struct wire_buf *wb) XX(TLS)\ XX(HTTP)\ \ + /* PROXYv2 */\ + XX(PROXYV2_DGRAM)\ + XX(PROXYV2_STREAM)\ + \ /* DNS (`worker`) */\ XX(DNS_DGRAM) /**< Packets WITHOUT prepended size, one per (un)wrap, * limited to UDP sizes, multiple sources (single