Do not store full IPv6 packet in common forwarding case.
authorbluhm <bluhm@openbsd.org>
Sat, 13 Jul 2024 09:34:26 +0000 (09:34 +0000)
committerbluhm <bluhm@openbsd.org>
Sat, 13 Jul 2024 09:34:26 +0000 (09:34 +0000)
Forwarding IPv6 packets is slower than IPv4.  Reason is that m_copym()
is done for every packet.  Just in case we may have to send an ICMP6
packet, ip6_forward() creates a mbuf copy.  After that mbuf cluster
is read only, so for the ethernet header another mbuf is allocated.
pf NAT and RDR ignores readonly clusters, so it also modifies the
potential ICMP6 packet.
IPv4 ip_forward() avoids all these problems by copying the leading
68 bytes of the original packets onto the stack.  More is not need
for ICMP.  IPv6 RFC 4443 2.4. (c) requires up to 1232 bytes in the
ICMP6 packet.  This cannot be copied to the stack.
The reason for the difference in the standard seems to be that the
ICMP6 packet has to contain the full header chain.  If we have a
simple TCP, UDP or ESP packet without chain, do a shortcut and just
preserve the header for the ICMP6 packet.
Small packets already use stack memory, large packets need extra
mbuf allocation.  Now truncate ICMP6 packet to a reasonable length
if the original packets has a final protocol header directly after
the IPv6 header.  List of suitable protocols contains TCP, UDP, ESP
as they cover the common cases and anything behind the header should
not be needed for path MTU discovery.

OK deraadt@ florian@ mvs@

sys/netinet6/ip6_forward.c

index 14c2956..790fb84 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ip6_forward.c,v 1.121 2024/07/09 09:33:13 bluhm Exp $ */
+/*     $OpenBSD: ip6_forward.c,v 1.122 2024/07/13 09:34:26 bluhm Exp $ */
 /*     $KAME: ip6_forward.c,v 1.75 2001/06/29 12:42:13 jinmei Exp $    */
 
 /*
@@ -145,10 +145,33 @@ ip6_forward(struct mbuf *m, struct route *ro, int flags)
         * Thanks to M_EXT, in most cases copy will not occur.
         * For small packets copy original onto stack instead of mbuf.
         *
+        * For final protocol header like TCP or UDP, full header chain in
+        * ICMP6 packet is not necessary.  In this case only copy small
+        * part of original packet and save it on stack instead of mbuf.
+        * Although this violates RFC 4443 2.4. (c), it avoids additional
+        * mbuf allocations.  Also pf nat and rdr do not affect the shared
+        * mbuf cluster.
+        *
         * It is important to save it before IPsec processing as IPsec
         * processing may modify the mbuf.
         */
-       icmp_len = min(m->m_pkthdr.len, ICMPV6_PLD_MAXLEN);
+       switch (ip6->ip6_nxt) {
+       case IPPROTO_TCP:
+               icmp_len = sizeof(struct ip6_hdr) + sizeof(struct tcphdr) +
+                   MAX_TCPOPTLEN;
+               break;
+       case IPPROTO_UDP:
+               icmp_len = sizeof(struct ip6_hdr) + sizeof(struct udphdr);
+               break;
+       case IPPROTO_ESP:
+               icmp_len = sizeof(struct ip6_hdr) + 2 * sizeof(u_int32_t);
+               break;
+       default:
+               icmp_len = ICMPV6_PLD_MAXLEN;
+               break;
+       }
+       if (icmp_len > m->m_pkthdr.len)
+               icmp_len = m->m_pkthdr.len;
        if (icmp_len <= sizeof(icmp_buf)) {
                mflags = m->m_flags;
                pfflags = m->m_pkthdr.pf.flags;