IPv6 forward copies small packet content on the stack.
authorbluhm <bluhm@openbsd.org>
Tue, 9 Jul 2024 09:33:13 +0000 (09:33 +0000)
committerbluhm <bluhm@openbsd.org>
Tue, 9 Jul 2024 09:33:13 +0000 (09:33 +0000)
Unfortunately RFC 4443 demands that the ICMP6 error packet containing
the orignal packet is up to 1280 bytes long.  That means for every
forwarded packet forward() creates a mbuf copy, just in case delivery
fails.

For small packets we can copy the content on the stack like IPv4
forward does.  This saves us some mbuf allocations if the content
is shorter than the mbuf data size.

OK mvs@

sys/netinet6/ip6_forward.c

index c706c32..14c2956 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ip6_forward.c,v 1.120 2024/07/04 12:50:08 bluhm Exp $ */
+/*     $OpenBSD: ip6_forward.c,v 1.121 2024/07/09 09:33:13 bluhm Exp $ */
 /*     $KAME: ip6_forward.c,v 1.75 2001/06/29 12:42:13 jinmei Exp $    */
 
 /*
@@ -89,8 +89,16 @@ ip6_forward(struct mbuf *m, struct route *ro, int flags)
        struct rtentry *rt;
        struct sockaddr *dst;
        struct ifnet *ifp = NULL;
-       int error = 0, type = 0, code = 0, destmtu = 0;
+       u_int rtableid = m->m_pkthdr.ph_rtableid;
+       u_int ifidx = m->m_pkthdr.ph_ifidx;
+       u_int8_t loopcnt = m->m_pkthdr.ph_loopcnt;
+       u_int icmp_len;
+       char icmp_buf[MHLEN];
+       CTASSERT(sizeof(struct ip6_hdr) + sizeof(struct tcphdr) +
+           MAX_TCPOPTLEN <= sizeof(icmp_buf));
+       u_short mflags, pfflags;
        struct mbuf *mcopy;
+       int error = 0, type = 0, code = 0, destmtu = 0;
 #ifdef IPSEC
        struct tdb *tdb = NULL;
 #endif /* IPSEC */
@@ -117,9 +125,7 @@ ip6_forward(struct mbuf *m, struct route *ro, int flags)
                        log(LOG_DEBUG,
                            "cannot forward "
                            "from %s to %s nxt %d received on interface %u\n",
-                           src6, dst6,
-                           ip6->ip6_nxt,
-                           m->m_pkthdr.ph_ifidx);
+                           src6, dst6, ip6->ip6_nxt, ifidx);
                }
                m_freem(m);
                goto done;
@@ -137,12 +143,21 @@ ip6_forward(struct mbuf *m, struct route *ro, int flags)
         * size of IPv6 + ICMPv6 headers) bytes of the packet in case
         * we need to generate an ICMP6 message to the src.
         * Thanks to M_EXT, in most cases copy will not occur.
+        * For small packets copy original onto stack instead of mbuf.
         *
         * It is important to save it before IPsec processing as IPsec
         * processing may modify the mbuf.
         */
-       mcopy = m_copym(m, 0, imin(m->m_pkthdr.len, ICMPV6_PLD_MAXLEN),
-           M_NOWAIT);
+       icmp_len = min(m->m_pkthdr.len, ICMPV6_PLD_MAXLEN);
+       if (icmp_len <= sizeof(icmp_buf)) {
+               mflags = m->m_flags;
+               pfflags = m->m_pkthdr.pf.flags;
+               m_copydata(m, 0, icmp_len, icmp_buf);
+               mcopy = NULL;
+       } else {
+               mcopy = m_copym(m, 0, icmp_len, M_NOWAIT);
+               icmp_len = 0;
+       }
 
 #if NPF > 0
 reroute:
@@ -174,12 +189,10 @@ reroute:
            m->m_pkthdr.ph_rtableid);
        if (rt == NULL) {
                ip6stat_inc(ip6s_noroute);
-               if (mcopy != NULL) {
-                       icmp6_error(mcopy, ICMP6_DST_UNREACH,
-                                   ICMP6_DST_UNREACH_NOROUTE, 0);
-               }
+               type = ICMP6_DST_UNREACH;
+               code = ICMP6_DST_UNREACH_NOROUTE;
                m_freem(m);
-               goto done;
+               goto icmperror;
        }
        dst = &ro->ro_dstsa;
 
@@ -190,7 +203,7 @@ reroute:
         * unreachable error with Code 2 (beyond scope of source address).
         * [draft-ietf-ipngwg-icmp-v3-00.txt, Section 3.1]
         */
-       if (in6_addr2scopeid(m->m_pkthdr.ph_ifidx, &ip6->ip6_src) !=
+       if (in6_addr2scopeid(ifidx, &ip6->ip6_src) !=
            in6_addr2scopeid(rt->rt_ifidx, &ip6->ip6_src)) {
                time_t uptime;
 
@@ -205,15 +218,12 @@ reroute:
                        log(LOG_DEBUG,
                            "cannot forward "
                            "src %s, dst %s, nxt %d, rcvif %u, outif %u\n",
-                           src6, dst6,
-                           ip6->ip6_nxt,
-                           m->m_pkthdr.ph_ifidx, rt->rt_ifidx);
+                           src6, dst6, ip6->ip6_nxt, ifidx, rt->rt_ifidx);
                }
-               if (mcopy != NULL)
-                       icmp6_error(mcopy, ICMP6_DST_UNREACH,
-                                   ICMP6_DST_UNREACH_BEYONDSCOPE, 0);
+               type = ICMP6_DST_UNREACH;
+               code = ICMP6_DST_UNREACH_BEYONDSCOPE;
                m_freem(m);
-               goto done;
+               goto icmperror;
        }
 
 #ifdef IPSEC
@@ -248,7 +258,7 @@ reroute:
                m_freem(m);
                goto freecopy;
        }
-       if (rt->rt_ifidx == m->m_pkthdr.ph_ifidx &&
+       if (rt->rt_ifidx == ifidx &&
            ip6_sendredirects && !ISSET(flags, IPV6_REDIRECT) &&
            (rt->rt_flags & (RTF_DYNAMIC|RTF_MODIFIED)) == 0) {
                if ((ifp->if_flags & IFF_POINTOPOINT) &&
@@ -268,11 +278,10 @@ reroute:
                         * type/code is based on suggestion by Rich Draves.
                         * not sure if it is the best pick.
                         */
-                       if (mcopy != NULL)
-                               icmp6_error(mcopy, ICMP6_DST_UNREACH,
-                                   ICMP6_DST_UNREACH_ADDR, 0);
+                       type = ICMP6_DST_UNREACH;
+                       code = ICMP6_DST_UNREACH_ADDR;
                        m_freem(m);
-                       goto done;
+                       goto icmperror;
                }
                type = ND_REDIRECT;
        }
@@ -332,27 +341,41 @@ reroute:
        if (error || m == NULL)
                goto senderr;
 
-       if (mcopy != NULL)
-               icmp6_error(mcopy, ICMP6_PACKET_TOO_BIG, 0, ifp->if_mtu);
+       type = ICMP6_PACKET_TOO_BIG;
+       destmtu = ifp->if_mtu;
        m_freem(m);
-       goto done;
+       goto icmperror;
 
 senderr:
-       if (mcopy == NULL)
+       if (mcopy == NULL && icmp_len == 0)
                goto done;
 
        switch (error) {
        case 0:
                if (type == ND_REDIRECT) {
-                       icmp6_redirect_output(mcopy, rt);
-                       ip6stat_inc(ip6s_redirectsent);
+                       if (icmp_len != 0) {
+                               mcopy = m_gethdr(M_DONTWAIT, MT_DATA);
+                               if (mcopy == NULL)
+                                       goto done;
+                               mcopy->m_len = mcopy->m_pkthdr.len = icmp_len;
+                               mcopy->m_flags |= (mflags & M_COPYFLAGS);
+                               mcopy->m_pkthdr.ph_rtableid = rtableid;
+                               mcopy->m_pkthdr.ph_ifidx = ifidx;
+                               mcopy->m_pkthdr.ph_loopcnt = loopcnt;
+                               mcopy->m_pkthdr.pf.flags |=
+                                   (pfflags & PF_TAG_GENERATED);
+                               memcpy(mcopy->m_data, icmp_buf, icmp_len);
+                       }
+                       if (mcopy != NULL) {
+                               icmp6_redirect_output(mcopy, rt);
+                               ip6stat_inc(ip6s_redirectsent);
+                       }
                        goto done;
                }
                goto freecopy;
 
        case EMSGSIZE:
                type = ICMP6_PACKET_TOO_BIG;
-               code = 0;
                if (rt != NULL) {
                        if (rt->rt_mtu) {
                                destmtu = rt->rt_mtu;
@@ -390,7 +413,21 @@ senderr:
                code = ICMP6_DST_UNREACH_ADDR;
                break;
        }
-       icmp6_error(mcopy, type, code, destmtu);
+ icmperror:
+       if (icmp_len != 0) {
+               mcopy = m_gethdr(M_DONTWAIT, MT_DATA);
+               if (mcopy == NULL)
+                       goto done;
+               mcopy->m_len = mcopy->m_pkthdr.len = icmp_len;
+               mcopy->m_flags |= (mflags & M_COPYFLAGS);
+               mcopy->m_pkthdr.ph_rtableid = rtableid;
+               mcopy->m_pkthdr.ph_ifidx = ifidx;
+               mcopy->m_pkthdr.ph_loopcnt = loopcnt;
+               mcopy->m_pkthdr.pf.flags |= (pfflags & PF_TAG_GENERATED);
+               memcpy(mcopy->m_data, icmp_buf, icmp_len);
+       }
+       if (mcopy != NULL)
+               icmp6_error(mcopy, type, code, destmtu);
        goto done;
 
  freecopy: