diff --git a/sys/kern/uipc_mbuf.c b/sys/kern/uipc_mbuf.c index c3e8856f5c45..3dfee6954a67 100644 --- a/sys/kern/uipc_mbuf.c +++ b/sys/kern/uipc_mbuf.c @@ -883,3 +883,87 @@ m_defrag(struct mbuf *m0, int how) m_freem(m_final); return (NULL); } + +#ifdef MBUF_STRESS_TEST + +/* + * Fragment an mbuf chain. There's no reason you'd ever want to do + * this in normal usage, but it's great for stress testing various + * mbuf consumers. + * + * If fragmentation is not possible, the original chain will be + * returned. + * + * Possible length values: + * 0 no fragmentation will occur + * > 0 each fragment will be of the specified length + * -1 each fragment will be the same random value in length + * -2 each fragment's length will be entirely random + * (Random values range from 1 to 256) + */ +struct mbuf * +m_fragment(struct mbuf *m0, int how, int length) +{ + struct mbuf *m_new = NULL, *m_final = NULL; + int progress = 0; + + if (!(m0->m_flags & M_PKTHDR)) + return (m0); + + if ((length == 0) || (length < -2)) + return (m0); + + m_fixhdr(m0); /* Needed sanity check */ + + m_final = m_getcl(how, MT_DATA, M_PKTHDR); + + if (m_final == NULL) + goto nospace; + + if (m_dup_pkthdr(m_final, m0, how) == NULL) + goto nospace; + + m_new = m_final; + + if (length == -1) + length = 1 + (arc4random() & 255); + + while (progress < m0->m_pkthdr.len) { + int fraglen; + + if (length > 0) + fraglen = length; + else + fraglen = 1 + (arc4random() & 255); + if (fraglen > m0->m_pkthdr.len - progress) + fraglen = m0->m_pkthdr.len - progress; + + if (fraglen > MCLBYTES) + fraglen = MCLBYTES; + + if (m_new == NULL) { + m_new = m_getcl(how, MT_DATA, 0); + if (m_new == NULL) + goto nospace; + } + + m_copydata(m0, progress, fraglen, mtod(m_new, caddr_t)); + progress += fraglen; + m_new->m_len = fraglen; + if (m_new != m_final) + m_cat(m_final, m_new); + m_new = NULL; + } + m_freem(m0); + m0 = m_final; + return (m0); +nospace: + if (m_new) + m_free(m_new); + if (m_final) + m_freem(m_final); + /* Return the original chain on failure */ + return (m0); +} + +#endif diff --git a/sys/netinet/ip_output.c b/sys/netinet/ip_output.c index 4a507cf8224b..f6a70efc9e5a 100644 --- a/sys/netinet/ip_output.c +++ b/sys/netinet/ip_output.c @@ -1030,24 +1030,8 @@ ip_output(struct mbuf *m0, struct mbuf *opt, struct route *ro, #endif #ifdef MBUF_STRESS_TEST - if (mbuf_frag_size && m->m_pkthdr.len > mbuf_frag_size) { - struct mbuf *m1, *m2; - int length, tmp; - - tmp = length = m->m_pkthdr.len; - - while ((length -= mbuf_frag_size) >= 1) { - m1 = m_split(m, length, M_DONTWAIT); - if (m1 == NULL) - break; - m1->m_flags &= ~M_PKTHDR; - m2 = m; - while (m2->m_next != NULL) - m2 = m2->m_next; - m2->m_next = m1; - m->m_pkthdr.len = tmp; - } - } + if (mbuf_frag_size && m->m_pkthdr.len > mbuf_frag_size) + m = m_fragment(m, M_DONTWAIT, mbuf_frag_size); #endif error = (*ifp->if_output)(ifp, m, (struct sockaddr *)dst, ro->ro_rt); diff --git a/sys/sys/mbuf.h b/sys/sys/mbuf.h index a0e4b47b982a..abdd6625f06b 100644 --- a/sys/sys/mbuf.h +++ b/sys/sys/mbuf.h @@ -443,6 +443,7 @@ struct mbuf *m_devget(char *, int, int, struct ifnet *, struct mbuf *m_dup(struct mbuf *, int); int m_dup_pkthdr(struct mbuf *, struct mbuf *, int); u_int m_fixhdr(struct mbuf *); +struct mbuf *m_fragment(struct mbuf *, int, int); struct mbuf *m_free(struct mbuf *); void m_freem(struct mbuf *); struct mbuf *m_get(int, short);