From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: passt.top; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: passt.top; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=QS7Tbks1; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by passt.top (Postfix) with ESMTPS id 5D5BF5A0272 for ; Wed, 19 Feb 2025 20:30:17 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1739993416; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=LcA37BoS58Taa2oBVLl7Fsj0PbehORyTZJ9U4DIkT9g=; b=QS7Tbks1le9vZ8n4awUJwvq9j+jBvQin4l7/F/A949SyQz2wlw6YlaMFSHhyEpP+P4jqlR TlLJ7Qe/nkyghW1CTClcr8yW4q1k91RfWTZ5E6gIS9i+HVMil6+ogSYy6/87bhuSG7fwXp K4os0EttP/QIVDmDXn3D8bhxiNU5m6Q= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-615-QrhKQSK8MrKGBPMKO1MJ9w-1; Wed, 19 Feb 2025 14:30:14 -0500 X-MC-Unique: QrhKQSK8MrKGBPMKO1MJ9w-1 X-Mimecast-MFC-AGG-ID: QrhKQSK8MrKGBPMKO1MJ9w_1739993414 Received: from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.93]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 055AA18E6952 for ; Wed, 19 Feb 2025 19:30:14 +0000 (UTC) Received: from jmaloy-thinkpadp16vgen1.rmtcaqc.csb (unknown [10.22.65.244]) by mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 70A391800D9D; Wed, 19 Feb 2025 19:30:12 +0000 (UTC) From: Jon Maloy To: passt-dev@passt.top, sbrivio@redhat.com, lvivier@redhat.com, dgibson@redhat.com, jmaloy@redhat.com Subject: [PATCH v3 2/2] udp: create and send ICMPv4 to local peer when applicable Date: Wed, 19 Feb 2025 14:30:07 -0500 Message-ID: <20250219193007.2336670-3-jmaloy@redhat.com> In-Reply-To: <20250219193007.2336670-1-jmaloy@redhat.com> References: <20250219193007.2336670-1-jmaloy@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.93 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: Cc2B-VFlwwUW4m-zEVAkYw2BQyIglpPBjqP8w4rWUrI_1739993414 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit content-type: text/plain; charset="US-ASCII"; x-default=true Message-ID-Hash: KGSQZ3XDO5FXSMWIAKP3QSED4XHJWVIN X-Message-ID-Hash: KGSQZ3XDO5FXSMWIAKP3QSED4XHJWVIN X-MailFrom: jmaloy@redhat.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.8 Precedence: list List-Id: Development discussion and patches for passt Archived-At: Archived-At: List-Archive: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: When a local peer sends a UDP message to a non-existing port on an existing remote host, that host will return an ICMP message containing the error code ICMP_PORT_UNREACH, plus the header and the first eight bytes of the original message. If the sender socket has been connected, it uses this message to issue a "Connection Refused" event to the user. Until now, we have only read such events from the externally facing socket, but we don't forward them back to the local sender because we cannot read the ICMP message directly to user space. Because of this, the local peer will hang and wait for a response that never arrives. We now fix this for IPv4 by recreating and forwarding a correct ICMP message back to the internal sender. We synthesize the message based on the information in the extended error structure, plus the returned part of the original message body. Note that for the sake of completeness, we even produce ICMP messages for other error codes. We have noticed that at least ICMP_PROT_UNREACH is propagated as an error event back to the user. Signed-off-by: Jon Maloy --- v2: - Updated the ICMP creation to use the new function tap_push_uh4(). - Added logics to find correct flow, depending on origin. - All done after feedback from David Gibson. v3: - Passing parameter 'now' along with call to udp_sock_errs() call. - Corrected lookup of flow from listener socket - All done after feedback from David Gibson. --- tap.c | 4 +-- tap.h | 3 ++ udp.c | 96 +++++++++++++++++++++++++++++++++++++++++++------- udp_internal.h | 3 +- udp_vu.c | 4 +-- 5 files changed, 92 insertions(+), 18 deletions(-) diff --git a/tap.c b/tap.c index 95d64bf..902f076 100644 --- a/tap.c +++ b/tap.c @@ -142,8 +142,8 @@ static void *tap_push_l2h(const struct ctx *c, void *buf, uint16_t proto) * * Return: pointer at which to write the packet's payload */ -static void *tap_push_ip4h(struct iphdr *ip4h, struct in_addr src, - struct in_addr dst, size_t l4len, uint8_t proto) +void *tap_push_ip4h(struct iphdr *ip4h, struct in_addr src, + struct in_addr dst, size_t l4len, uint8_t proto) { uint16_t l3len = l4len + sizeof(*ip4h); diff --git a/tap.h b/tap.h index 3451343..5b326f5 100644 --- a/tap.h +++ b/tap.h @@ -45,9 +45,12 @@ static inline void tap_hdr_update(struct tap_hdr *thdr, size_t l2len) if (thdr) thdr->vnet_len = htonl(l2len); } + void *tap_push_uh4(struct udphdr *uh, struct in_addr src, in_port_t sport, struct in_addr dst, in_port_t dport, const void *in, size_t dlen); +void *tap_push_ip4h(struct iphdr *ip4h, struct in_addr src, + struct in_addr dst, size_t l4len, uint8_t proto); void tap_udp4_send(const struct ctx *c, struct in_addr src, in_port_t sport, struct in_addr dst, in_port_t dport, const void *in, size_t dlen); diff --git a/udp.c b/udp.c index 923cc38..b9c53eb 100644 --- a/udp.c +++ b/udp.c @@ -87,6 +87,7 @@ #include #include #include +#include #include #include #include @@ -402,25 +403,72 @@ static void udp_tap_prepare(const struct mmsghdr *mmh, (*tap_iov)[UDP_IOV_PAYLOAD].iov_len = l4len; } +/** + * udp_send_conn_fail_icmp4() - Construct and send ICMP to local peer + * @c: Execution context + * @ee: Extended error descriptor + * @ref: epoll reference + * @in: First bytes (max 8) of original UDP message body + * @dlen: Length of the read part of original UDP message body + */ +static void udp_send_conn_fail_icmp4(const struct ctx *c, + const struct sock_extended_err *ee, + const struct flowside *toside, + void *in, size_t dlen) +{ + struct in_addr oaddr = toside->oaddr.v4mapped.a4; + struct in_addr eaddr = toside->eaddr.v4mapped.a4; + in_port_t eport = toside->eport; + in_port_t oport = toside->oport; + struct { + struct icmphdr icmp4h; + struct iphdr ip4h; + struct udphdr uh; + char data[8]; + } __attribute__((packed, aligned(__alignof__(max_align_t)))) msg; + size_t msglen = sizeof(msg) - sizeof(msg.data) + dlen; + + memset(&msg, 0, sizeof(msg)); + msg.icmp4h.type = ee->ee_type; + msg.icmp4h.code = ee->ee_code; + + /* Reconstruct the original headers as returned in the ICMP message */ + tap_push_ip4h(&msg.ip4h, eaddr, oaddr, dlen, IPPROTO_UDP); + tap_push_uh4(&msg.uh, eaddr, eport, oaddr, oport, in, dlen); + memcpy(&msg.data, in, dlen); + + tap_icmp4_send(c, oaddr, eaddr, &msg, msglen); +} + /** * udp_sock_recverr() - Receive and clear an error from a socket - * @s: Socket to receive from + * @c: Execution context + * @ref: epoll reference + * @now: Current timestamp * * Return: 1 if error received and processed, 0 if no more errors in queue, < 0 * if there was an error reading the queue * * #syscalls recvmsg */ -static int udp_sock_recverr(int s) +static int udp_sock_recverr(const struct ctx *c, union epoll_ref ref, + const struct timespec *now) { const struct sock_extended_err *ee; const struct cmsghdr *hdr; + union sockaddr_inany saddr; char buf[CMSG_SPACE(sizeof(*ee))]; + char udp_data[8]; + int s = ref.fd; + struct iovec iov = { + .iov_base = udp_data, + .iov_len = sizeof(udp_data) + }; struct msghdr mh = { - .msg_name = NULL, - .msg_namelen = 0, - .msg_iov = NULL, - .msg_iovlen = 0, + .msg_name = &saddr, + .msg_namelen = sizeof(saddr), + .msg_iov = &iov, + .msg_iovlen = 1, .msg_control = buf, .msg_controllen = sizeof(buf), }; @@ -450,8 +498,27 @@ static int udp_sock_recverr(int s) } ee = (const struct sock_extended_err *)CMSG_DATA(hdr); - - /* TODO: When possible propagate and otherwise handle errors */ + if (ee->ee_type == ICMP_DEST_UNREACH) { + flow_sidx_t sidx; + struct udp_flow *flow; + const struct flowside *toside; + + if (ref.type == EPOLL_TYPE_UDP_LISTEN) { + sidx = flow_lookup_sa(c, IPPROTO_UDP, ref.udp.pif, + &saddr, ref.udp.port); + flow = udp_at_sidx(sidx); + if (!flow) { + err("Unexpected cmsg reading error queue"); + return -1; + } + flow->ts = now->tv_sec; + sidx = flow_sidx_opposite(sidx); + } else { + sidx = flow_sidx_opposite(ref.flowside); + } + toside = flowside_at_sidx(sidx); + udp_send_conn_fail_icmp4(c, ee, toside, udp_data, rc); + } debug("%s error on UDP socket %i: %s", str_ee_origin(ee), s, strerror_(ee->ee_errno)); @@ -461,15 +528,18 @@ static int udp_sock_recverr(int s) /** * udp_sock_errs() - Process errors on a socket * @c: Execution context - * @s: Socket to receive from + * @ref: epoll reference * @events: epoll events bitmap + * @now: Current timestamp * * Return: Number of errors handled, or < 0 if we have an unrecoverable error */ -int udp_sock_errs(const struct ctx *c, int s, uint32_t events) +int udp_sock_errs(const struct ctx *c, union epoll_ref ref, uint32_t events, + const struct timespec *now) { unsigned n_err = 0; socklen_t errlen; + int s = ref.fd; int rc, err; ASSERT(!c->no_udp); @@ -478,7 +548,7 @@ int udp_sock_errs(const struct ctx *c, int s, uint32_t events) return 0; /* Nothing to do */ /* Empty the error queue */ - while ((rc = udp_sock_recverr(s)) > 0) + while ((rc = udp_sock_recverr(c, ref, now)) > 0) n_err += rc; if (rc < 0) @@ -558,7 +628,7 @@ static void udp_buf_listen_sock_handler(const struct ctx *c, const socklen_t sasize = sizeof(udp_meta[0].s_in); int n, i; - if (udp_sock_errs(c, ref.fd, events) < 0) { + if (udp_sock_errs(c, ref, events, now) < 0) { err("UDP: Unrecoverable error on listening socket:" " (%s port %hu)", pif_name(ref.udp.pif), ref.udp.port); /* FIXME: what now? close/re-open socket? */ @@ -661,7 +731,7 @@ static void udp_buf_reply_sock_handler(const struct ctx *c, union epoll_ref ref, from_s = uflow->s[ref.flowside.sidei]; - if (udp_sock_errs(c, from_s, events) < 0) { + if (udp_sock_errs(c, ref, events, now) < 0) { flow_err(uflow, "Unrecoverable error on reply socket"); flow_err_details(uflow); udp_flow_close(c, uflow); diff --git a/udp_internal.h b/udp_internal.h index cc80e30..c5f8304 100644 --- a/udp_internal.h +++ b/udp_internal.h @@ -30,5 +30,6 @@ size_t udp_update_hdr4(struct iphdr *ip4h, struct udp_payload_t *bp, size_t udp_update_hdr6(struct ipv6hdr *ip6h, struct udp_payload_t *bp, const struct flowside *toside, size_t dlen, bool no_udp_csum); -int udp_sock_errs(const struct ctx *c, int s, uint32_t events); +int udp_sock_errs(const struct ctx *c, union epoll_ref ref, uint32_t events, + const struct timespec *now); #endif /* UDP_INTERNAL_H */ diff --git a/udp_vu.c b/udp_vu.c index 4123510..0b1d3c6 100644 --- a/udp_vu.c +++ b/udp_vu.c @@ -227,7 +227,7 @@ void udp_vu_listen_sock_handler(const struct ctx *c, union epoll_ref ref, struct vu_virtq *vq = &vdev->vq[VHOST_USER_RX_QUEUE]; int i; - if (udp_sock_errs(c, ref.fd, events) < 0) { + if (udp_sock_errs(c, ref, events, now) < 0) { err("UDP: Unrecoverable error on listening socket:" " (%s port %hu)", pif_name(ref.udp.pif), ref.udp.port); return; @@ -302,7 +302,7 @@ void udp_vu_reply_sock_handler(const struct ctx *c, union epoll_ref ref, ASSERT(!c->no_udp); - if (udp_sock_errs(c, from_s, events) < 0) { + if (udp_sock_errs(c, ref, events, now) < 0) { flow_err(uflow, "Unrecoverable error on reply socket"); flow_err_details(uflow); udp_flow_close(c, uflow); -- 2.48.1