[PATCH v9 0/4] Reconstruct incoming ICMP headers for failed UDP connect and forward back
v2: - Added patch breaking out udp header creation from function tap_udp4_send(). - Updated the ICMP creation by using the new function. - Added logics to find correct flow, depending on origin. - All done after feedback from David Gibson. v3: - More changes after feedback from David Gibson. v4: - Even more changes after feedback from D. Gibson v5: - Added corresponding patches for IPv6 v6: - Fixed some small nits after comments from D. Gibson. v7: - Added handling of all rejected ICMP messages - Returning correct user data amount if IPv6 as per RFC 4884. v8: - Added MTU to ICMPv4 ICMP_FRAG_NEEDED messages. - Added ASSERT() validation to message creation functions. v9: - Using real source address of ICMP to complement destination address for originial UDP message when needed. Jon Maloy (4): tap: break out building of udp header from tap_udp4_send function udp: create and send ICMPv4 to local peer when applicable tap: break out building of udp header from tap_udp6_send function udp: create and send ICMPv6 to local peer when applicable tap.c | 86 +++++++++++++++++++++++--------- tap.h | 15 ++++++ udp.c | 132 ++++++++++++++++++++++++++++++++++++++++++++----- udp_internal.h | 2 +- udp_vu.c | 4 +- 5 files changed, 200 insertions(+), 39 deletions(-) -- 2.48.1
We will need to build the UDP header at other locations than in function
tap_udp4_send(), so we break that part out to a separate function.
Reviewed-by: David Gibson
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.
Reviewed-by: David Gibson
We will need to build the UDP header at other locations than in function
tap_udp6_send(), so we break that part out to a separate function.
Reviewed-by: David Gibson
When a local peer sends a UDP message to a non-existing port on an
existing remote host, that host will return an ICMPv6 message containing
the error code ICMP6_DST_UNREACH_NOPORT, plus the IPv6 header, UDP header
and the first 1232 bytes of the original message, if any. 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 IPv6 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 types and codes. We have noticed that at least
ICMP_PROT_UNREACH is propagated as an error event back to the user.
Reviewed-by: David Gibson
On Mon, Mar 03, 2025 at 08:29:11PM -0500, Jon Maloy wrote:
v2: - Added patch breaking out udp header creation from function tap_udp4_send(). - Updated the ICMP creation by using the new function. - Added logics to find correct flow, depending on origin. - All done after feedback from David Gibson. v3: - More changes after feedback from David Gibson. v4: - Even more changes after feedback from D. Gibson v5: - Added corresponding patches for IPv6 v6: - Fixed some small nits after comments from D. Gibson. v7: - Added handling of all rejected ICMP messages - Returning correct user data amount if IPv6 as per RFC 4884. v8: - Added MTU to ICMPv4 ICMP_FRAG_NEEDED messages. - Added ASSERT() validation to message creation functions. v9: - Using real source address of ICMP to complement destination address for originial UDP message when needed.
I think the changes for this are fine as far as they go. It does raise an additional wrinkle for when we try to do this for "listening" sockets: since the ICMP may be coming from somewhere other than the destination of the triggering message, we can't rely on the source address to find the correct flow. I think we'll need to use EE_OFFENDER() to get the information we need, which will also mean extended our cmsg buffers a bit: looks like the kernel puts a sockaddr after the extended_err structure. -- David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson
On Mon, 3 Mar 2025 20:29:11 -0500
Jon Maloy
v2: - Added patch breaking out udp header creation from function tap_udp4_send(). - Updated the ICMP creation by using the new function. - Added logics to find correct flow, depending on origin. - All done after feedback from David Gibson. v3: - More changes after feedback from David Gibson. v4: - Even more changes after feedback from D. Gibson v5: - Added corresponding patches for IPv6 v6: - Fixed some small nits after comments from D. Gibson. v7: - Added handling of all rejected ICMP messages - Returning correct user data amount if IPv6 as per RFC 4884. v8: - Added MTU to ICMPv4 ICMP_FRAG_NEEDED messages. - Added ASSERT() validation to message creation functions. v9: - Using real source address of ICMP to complement destination address for originial UDP message when needed.
Jon Maloy (4): tap: break out building of udp header from tap_udp4_send function udp: create and send ICMPv4 to local peer when applicable tap: break out building of udp header from tap_udp6_send function udp: create and send ICMPv6 to local peer when applicable
I was about to apply those, then I realised that Coverity Scan isn't happy about a few things, listed below. I didn't check if those are false positives (I can have a look later or within a couple of days unless you get to it first). 1. --- /home/sbrivio/passt/udp.c:448:2: Type: Out-of-bounds access (ARRAY_VS_SINGLETON) /home/sbrivio/passt/udp.c:440:2: 1. path: Condition "!(dlen <= 8)", taking false branch. /home/sbrivio/passt/udp.c:444:2: 2. path: Condition "ee->ee_type == 3", taking true branch. /home/sbrivio/passt/udp.c:444:2: 3. path: Condition "ee->ee_code == 4", taking true branch. /home/sbrivio/passt/udp.c:448:2: 4. address_of: Taking address with "&msg.ip4h" yields a singleton pointer. /home/sbrivio/passt/udp.c:448:2: 5. callee_ptr_arith: Passing "&msg.ip4h" to function "tap_push_ip4h" which uses it as an array. This might corrupt or misinterpret adjacent memory locations. /home/sbrivio/passt/tap.c:162:2: 5.1. ptr_arith: Performing pointer arithmetic on "ip4h" in expression "ip4h + 1". --- 2. --- /home/sbrivio/passt/udp.c:493:2: Type: Out-of-bounds access (ARRAY_VS_SINGLETON) /home/sbrivio/passt/udp.c:485:2: 1. path: Condition "!(dlen <= 1232UL /* 1280 - sizeof (struct udphdr) - sizeof (struct ipv6hdr) */)", taking false branch. /home/sbrivio/passt/udp.c:489:2: 2. path: Condition "ee->ee_type == 2", taking true branch. /home/sbrivio/passt/udp.c:493:2: 3. address_of: Taking address with "&msg.ip6h" yields a singleton pointer. /home/sbrivio/passt/udp.c:493:2: 4. callee_ptr_arith: Passing "&msg.ip6h" to function "tap_push_ip6h" which uses it as an array. This might corrupt or misinterpret adjacent memory locations. /home/sbrivio/passt/tap.c:265:2: 4.1. ptr_arith: Performing pointer arithmetic on "ip6h" in expression "ip6h + 1". --- 3. --- /home/sbrivio/passt/udp.c:449:2: Type: Out-of-bounds access (ARRAY_VS_SINGLETON) /home/sbrivio/passt/udp.c:440:2: 1. path: Condition "!(dlen <= 8)", taking false branch. /home/sbrivio/passt/udp.c:444:2: 2. path: Condition "ee->ee_type == 3", taking true branch. /home/sbrivio/passt/udp.c:444:2: 3. path: Condition "ee->ee_code == 4", taking true branch. /home/sbrivio/passt/udp.c:449:2: 4. address_of: Taking address with "&msg.uh" yields a singleton pointer. /home/sbrivio/passt/udp.c:449:2: 5. callee_ptr_arith: Passing "&msg.uh" to function "tap_push_uh4" which uses it as an array. This might corrupt or misinterpret adjacent memory locations. /home/sbrivio/passt/tap.c:190:2: 5.1. ptr_arith: Performing pointer arithmetic on "uh" in expression "uh + 1". --- 4. --- /home/sbrivio/passt/udp.c:494:2: Type: Out-of-bounds access (ARRAY_VS_SINGLETON) /home/sbrivio/passt/udp.c:485:2: 1. path: Condition "!(dlen <= 1232UL /* 1280 - sizeof (struct udphdr) - sizeof (struct ipv6hdr) */)", taking false branch. /home/sbrivio/passt/udp.c:489:2: 2. path: Condition "ee->ee_type == 2", taking true branch. /home/sbrivio/passt/udp.c:494:2: 3. address_of: Taking address with "&msg.uh" yields a singleton pointer. /home/sbrivio/passt/udp.c:494:2: 4. callee_ptr_arith: Passing "&msg.uh" to function "tap_push_uh6" which uses it as an array. This might corrupt or misinterpret adjacent memory locations. /home/sbrivio/passt/tap.c:295:2: 4.1. ptr_arith: Performing pointer arithmetic on "uh" in expression "uh + 1". --- -- Stefano
On 2025-03-04 07:05, Stefano Brivio wrote:
On Mon, 3 Mar 2025 20:29:11 -0500 Jon Maloy
wrote: v2: - Added patch breaking out udp header creation from function tap_udp4_send(). - Updated the ICMP creation by using the new function. - Added logics to find correct flow, depending on origin. - All done after feedback from David Gibson. v3: - More changes after feedback from David Gibson. v4: - Even more changes after feedback from D. Gibson v5: - Added corresponding patches for IPv6 v6: - Fixed some small nits after comments from D. Gibson. v7: - Added handling of all rejected ICMP messages - Returning correct user data amount if IPv6 as per RFC 4884. v8: - Added MTU to ICMPv4 ICMP_FRAG_NEEDED messages. - Added ASSERT() validation to message creation functions. v9: - Using real source address of ICMP to complement destination address for originial UDP message when needed.
Jon Maloy (4): tap: break out building of udp header from tap_udp4_send function udp: create and send ICMPv4 to local peer when applicable tap: break out building of udp header from tap_udp6_send function udp: create and send ICMPv6 to local peer when applicable
I was about to apply those, then I realised that Coverity Scan isn't happy about a few things, listed below. I didn't check if those are false positives (I can have a look later or within a couple of days unless you get to it first).
1. --- /home/sbrivio/passt/udp.c:448:2: Type: Out-of-bounds access (ARRAY_VS_SINGLETON)
/home/sbrivio/passt/udp.c:440:2: 1. path: Condition "!(dlen <= 8)", taking false branch. /home/sbrivio/passt/udp.c:444:2: 2. path: Condition "ee->ee_type == 3", taking true branch. /home/sbrivio/passt/udp.c:444:2: 3. path: Condition "ee->ee_code == 4", taking true branch. /home/sbrivio/passt/udp.c:448:2: 4. address_of: Taking address with "&msg.ip4h" yields a singleton pointer. /home/sbrivio/passt/udp.c:448:2: 5. callee_ptr_arith: Passing "&msg.ip4h" to function "tap_push_ip4h" which uses it as an array. This might corrupt or misinterpret adjacent memory locations. /home/sbrivio/passt/tap.c:162:2: 5.1. ptr_arith: Performing pointer arithmetic on "ip4h" in expression "ip4h + 1". ---
[...]
3. --- /home/sbrivio/passt/udp.c:449:2: Type: Out-of-bounds access (ARRAY_VS_SINGLETON)
/home/sbrivio/passt/udp.c:440:2: 1. path: Condition "!(dlen <= 8)", taking false branch. /home/sbrivio/passt/udp.c:444:2: 2. path: Condition "ee->ee_type == 3", taking true branch. /home/sbrivio/passt/udp.c:444:2: 3. path: Condition "ee->ee_code == 4", taking true branch. /home/sbrivio/passt/udp.c:449:2: 4. address_of: Taking address with "&msg.uh" yields a singleton pointer. /home/sbrivio/passt/udp.c:449:2: 5. callee_ptr_arith: Passing "&msg.uh" to function "tap_push_uh4" which uses it as an array. This might corrupt or misinterpret adjacent memory locations. /home/sbrivio/passt/tap.c:190:2: 5.1. ptr_arith: Performing pointer arithmetic on "uh" in expression "uh + 1". ---
[...] I installed coverity and tried it, of course with the same result. These are clearly false positives, and the first one is already in the upstream code, not added by me. I can probably get rid of them with some pointer gymnastics, but is it really worth it? BTW, I discovered a bug in patch #2 which I just fixed. I will post a v10 of my patches shortly. ///jon
[Dropping Laurent from Cc:, I'm not sure why he's Cc'ed here, and
fixing David's email]
On Tue, 4 Mar 2025 17:44:29 -0500
Jon Maloy
On 2025-03-04 07:05, Stefano Brivio wrote:
On Mon, 3 Mar 2025 20:29:11 -0500 Jon Maloy
wrote: v2: - Added patch breaking out udp header creation from function tap_udp4_send(). - Updated the ICMP creation by using the new function. - Added logics to find correct flow, depending on origin. - All done after feedback from David Gibson. v3: - More changes after feedback from David Gibson. v4: - Even more changes after feedback from D. Gibson v5: - Added corresponding patches for IPv6 v6: - Fixed some small nits after comments from D. Gibson. v7: - Added handling of all rejected ICMP messages - Returning correct user data amount if IPv6 as per RFC 4884. v8: - Added MTU to ICMPv4 ICMP_FRAG_NEEDED messages. - Added ASSERT() validation to message creation functions. v9: - Using real source address of ICMP to complement destination address for originial UDP message when needed.
Jon Maloy (4): tap: break out building of udp header from tap_udp4_send function udp: create and send ICMPv4 to local peer when applicable tap: break out building of udp header from tap_udp6_send function udp: create and send ICMPv6 to local peer when applicable
I was about to apply those, then I realised that Coverity Scan isn't happy about a few things, listed below. I didn't check if those are false positives (I can have a look later or within a couple of days unless you get to it first).
1. --- /home/sbrivio/passt/udp.c:448:2: Type: Out-of-bounds access (ARRAY_VS_SINGLETON)
/home/sbrivio/passt/udp.c:440:2: 1. path: Condition "!(dlen <= 8)", taking false branch. /home/sbrivio/passt/udp.c:444:2: 2. path: Condition "ee->ee_type == 3", taking true branch. /home/sbrivio/passt/udp.c:444:2: 3. path: Condition "ee->ee_code == 4", taking true branch. /home/sbrivio/passt/udp.c:448:2: 4. address_of: Taking address with "&msg.ip4h" yields a singleton pointer. /home/sbrivio/passt/udp.c:448:2: 5. callee_ptr_arith: Passing "&msg.ip4h" to function "tap_push_ip4h" which uses it as an array. This might corrupt or misinterpret adjacent memory locations. /home/sbrivio/passt/tap.c:162:2: 5.1. ptr_arith: Performing pointer arithmetic on "ip4h" in expression "ip4h + 1". ---
[...]
3. --- /home/sbrivio/passt/udp.c:449:2: Type: Out-of-bounds access (ARRAY_VS_SINGLETON)
/home/sbrivio/passt/udp.c:440:2: 1. path: Condition "!(dlen <= 8)", taking false branch. /home/sbrivio/passt/udp.c:444:2: 2. path: Condition "ee->ee_type == 3", taking true branch. /home/sbrivio/passt/udp.c:444:2: 3. path: Condition "ee->ee_code == 4", taking true branch. /home/sbrivio/passt/udp.c:449:2: 4. address_of: Taking address with "&msg.uh" yields a singleton pointer. /home/sbrivio/passt/udp.c:449:2: 5. callee_ptr_arith: Passing "&msg.uh" to function "tap_push_uh4" which uses it as an array. This might corrupt or misinterpret adjacent memory locations. /home/sbrivio/passt/tap.c:190:2: 5.1. ptr_arith: Performing pointer arithmetic on "uh" in expression "uh + 1". ---
[...] I installed coverity and tried it, of course with the same result.
These are clearly false positives, and the first one is already in the upstream code, not added by me.
Not really, that one: /home/sbrivio/passt/udp.c:448:2: 5. callee_ptr_arith: Passing "&msg.ip4h" to function "tap_push_ip4h" which uses it as an array. This might corrupt or misinterpret adjacent memory locations. /home/sbrivio/passt/tap.c:162:2: 5.1. ptr_arith: Performing pointer arithmetic on "ip4h" in expression "ip4h + 1". comes from patch 2/4 in this series: + /* Reconstruct the original headers as returned in the ICMP message */ + tap_push_ip4h(&msg.ip4h, eaddr, oaddr, l4len, IPPROTO_UDP); + tap_push_uh4(&msg.uh, eaddr, eport, oaddr, oport, in, dlen);
I can probably get rid of them with some pointer gymnastics, but is it really worth it?
You didn't mention why they're false positives, so I checked. First off, I noticed that you forgot to update function comments: you don't say what tap_push_uh4() and tap_push_uh6() return now. Hint: look at the function comments for tap_push_ip4h() and tap_push_ip6h(). By the way, in both comments: * @in: UDP payload contents (not including UDP header) needs two tabs to be aligned with the other arguments. Back to the warning: the fact is that "ip4h + 1", "ipv6h + 1", and "uh + 1" are seen, per se, as array usage. It's kind of arbitrary what "uses it as an array" means: one could even say that simply taking what's after an element (header) qualifies as array usage because you have one element and something after it in an ordered set. So yes, the check is overzealous and not helpful here, but strictly speaking I wouldn't call it a false positive. I think it's worth it because as part of my maintainer duties I routinely use that checker, and keeping warnings to a minimum decreases the noise I have to go through, regardless of the fact that warnings make no sense as it's the case here. Static checkers are otherwise very valuable. Besides, the workaround is perhaps a bit annoying but not significantly less readable, and it took me approximately 30 seconds: -- diff --git a/tap.c b/tap.c index 7e4bc00..48cca00 100644 --- a/tap.c +++ b/tap.c @@ -159,7 +159,8 @@ void *tap_push_ip4h(struct iphdr *ip4h, struct in_addr src, ip4h->saddr = src.s_addr; ip4h->daddr = dst.s_addr; ip4h->check = csum_ip4_header(l3len, proto, src, dst); - return ip4h + 1; + + return (char *)ip4h + sizeof(*ip4h); } /** @@ -187,7 +188,8 @@ void *tap_push_uh4(struct udphdr *uh, struct in_addr src, in_port_t sport, uh->dest = htons(dport); uh->len = htons(l4len); csum_udp4(uh, src, dst, &payload); - return uh + 1; + + return (char *)uh + sizeof(*uh); } /** @@ -262,7 +264,8 @@ void *tap_push_ip6h(struct ipv6hdr *ip6h, ip6h->flow_lbl[0] = (flow >> 16) & 0xf; ip6h->flow_lbl[1] = (flow >> 8) & 0xff; ip6h->flow_lbl[2] = (flow >> 0) & 0xff; - return ip6h + 1; + + return (char *)ip6h + sizeof(*ip6h); } /** @@ -292,7 +295,8 @@ void *tap_push_uh6(struct udphdr *uh, uh->dest = htons(dport); uh->len = htons(l4len); csum_udp6(uh, src, dst, &payload); - return uh + 1; + + return (char *)uh + sizeof(*uh); } /** -- -- Stefano
participants (3)
-
David Gibson
-
Jon Maloy
-
Stefano Brivio