]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
daemon: add PROXYv2 SSL TLV handling + minor refactoring
authorOto Šťáva <oto.stava@nic.cz>
Mon, 7 Feb 2022 13:56:33 +0000 (14:56 +0100)
committerVladimír Čunát <vladimir.cunat@nic.cz>
Tue, 22 Feb 2022 10:52:11 +0000 (10:52 +0000)
daemon/proxyv2.c
daemon/proxyv2.h
daemon/session.c
daemon/worker.c
lib/resolve.c

index 7a6dc49af5074b6cda6715cef6b9dbed6a45a33d..a541541f70d8a292ffe4a10e9c2b78505ae1721c 100644 (file)
@@ -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);
 }
index f608ecb374713ca921d016529bae3a633fce38d7..0167e6226a23b4aceb17329101cff8b84de1c796 100644 (file)
 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);
index fc606e385c31fbcc94ae15ac46e3be4f129597d7..2269ea43475a3610c6e066ef056a9af4a5cba7f9 100644 (file)
@@ -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
index 0a27547d167a17226bd8f5b8ded5456c5c49402d..baa615e2536b594a0e394d06f2bcefdc9d031708 100644 (file)
@@ -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;
index 2a4e672ecc6d84a0be54d7a3335bb833ff28b983..e615749693bc45eebc2d8ac4cf9b4442970cebb0 100644 (file)
@@ -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)) {