Index: sys/netinet/ip_fastfwd.c =================================================================== --- sys/netinet/ip_fastfwd.c +++ sys/netinet/ip_fastfwd.c @@ -109,7 +109,7 @@ #include static struct sockaddr_in * -ip_findroute(struct route *ro, struct in_addr dest, struct mbuf *m) +ip_findroute(struct route *ro, struct in_addr dest, struct mbuf *m, struct mbuf *mcopy) { struct sockaddr_in *dst; struct rtentry *rt; @@ -138,7 +138,8 @@ IPSTAT_INC(ips_cantforward); if (rt) RTFREE(rt); - icmp_error(m, ICMP_UNREACH, ICMP_UNREACH_HOST, 0, 0); + icmp_error(mcopy, ICMP_UNREACH, ICMP_UNREACH_HOST, 0, 0); + m_freem(m); return NULL; } return dst; @@ -165,6 +166,8 @@ int mtu; struct m_tag *fwd_tag = NULL; + struct mbuf *mcopy = NULL; + /* * Are we active and forwarding packets? */ @@ -230,6 +233,38 @@ IPSTAT_INC(ips_total); /* + * Save the IP header and at most 8 bytes of the payload, + * in case we need to generate an ICMP message to the src. + * + * XXX this can be optimized a lot by saving the data in a local + * buffer on the stack (72 bytes at most), and only allocating the + * mbuf if really necessary. The vast majority of the packets + * are forwarded without having to send an ICMP back (either + * because unnecessary, or because rate limited), so we are + * really we are wasting a lot of work here. + * + * We don't use m_copy() because it might return a reference + * to a shared cluster. Both this function and ip_output() + * assume exclusive access to the IP header in `m', so any + * data in a cluster may change before we reach icmp_error(). + */ + mcopy = m_gethdr(M_NOWAIT, m->m_type); + if (mcopy != NULL && !m_dup_pkthdr(mcopy, m, M_NOWAIT)) { + /* + * It's probably ok if the pkthdr dup fails (because + * the deep copy of the tag chain failed), but for now + * be conservative and just discard the copy since + * code below may some day want the tags. + */ + m_free(mcopy); + mcopy = NULL; + } + if (mcopy != NULL) { + mcopy->m_len = min(ntohs(ip->ip_len), M_TRAILINGSPACE(mcopy)); + mcopy->m_pkthdr.len = mcopy->m_len; + m_copydata(m, 0, mcopy->m_len, mtod(mcopy, caddr_t)); + } + /* * Step 3: incoming packet firewall processing */ @@ -285,8 +320,9 @@ if (!V_ipstealth) { #endif if (ip->ip_ttl <= IPTTLDEC) { - icmp_error(m, ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS, 0, 0); - return NULL; /* mbuf already free'd */ + icmp_error(mcopy, ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS, 0, 0); + m_freem(m); + return NULL; } /* @@ -306,8 +342,9 @@ /* * Find route to destination. */ - if ((dst = ip_findroute(&ro, dest, m)) == NULL) + if ((dst = ip_findroute(&ro, dest, m, mcopy)) == NULL) return NULL; /* icmp unreach already sent */ + ifp = ro.ro_rt->rt_ifp; /* @@ -355,6 +392,8 @@ m->m_flags |= M_FASTFWD_OURS; if (ro.ro_rt) RTFREE(ro.ro_rt); + if (mcopy) + m_freem(mcopy); return m; } /* @@ -367,8 +406,9 @@ m->m_flags &= ~M_IP_NEXTHOP; } RTFREE(ro.ro_rt); - if ((dst = ip_findroute(&ro, dest, m)) == NULL) + if ((dst = ip_findroute(&ro, dest, m, mcopy)) == NULL) return NULL; /* icmp unreach already sent */ + ifp = ro.ro_rt->rt_ifp; } @@ -384,7 +424,8 @@ */ if ((ro.ro_rt->rt_flags & RTF_REJECT) && (ro.ro_rt->rt_expire == 0 || time_uptime < ro.ro_rt->rt_expire)) { - icmp_error(m, ICMP_UNREACH, ICMP_UNREACH_HOST, 0, 0); + icmp_error(mcopy, ICMP_UNREACH, ICMP_UNREACH_HOST, 0, 0); + m_freem(m); goto consumed; } @@ -392,7 +433,8 @@ * Check if media link state of interface is not down */ if (ifp->if_link_state == LINK_STATE_DOWN) { - icmp_error(m, ICMP_UNREACH, ICMP_UNREACH_HOST, 0, 0); + icmp_error(mcopy, ICMP_UNREACH, ICMP_UNREACH_HOST, 0, 0); + m_freem(m); goto consumed; } @@ -421,8 +463,9 @@ */ if (ip_off & IP_DF) { IPSTAT_INC(ips_cantfrag); - icmp_error(m, ICMP_UNREACH, ICMP_UNREACH_NEEDFRAG, + icmp_error(mcopy, ICMP_UNREACH, ICMP_UNREACH_NEEDFRAG, 0, mtu); + m_freem(m); goto consumed; } else { /* @@ -469,11 +512,15 @@ IPSTAT_INC(ips_fastforward); } consumed: + if (mcopy) + m_freem(mcopy); RTFREE(ro.ro_rt); return NULL; drop: if (m) m_freem(m); + if (mcopy) + m_freem(mcopy); if (ro.ro_rt) RTFREE(ro.ro_rt); return NULL;