diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h --- a/sys/net/pfvar.h +++ b/sys/net/pfvar.h @@ -70,6 +70,8 @@ #ifdef _KERNEL +#define PF_PFIL_NOREFRAGMENT 0x80000000 + #if defined(__arm__) #define PF_WANT_32_TO_64_COUNTER #endif @@ -2372,6 +2374,7 @@ struct pf_addr *, struct pf_addr *, sa_family_t); void pf_addr_inc(struct pf_addr *, sa_family_t); int pf_max_frag_size(struct mbuf *); +int pf_do_refragment6(struct ifnet *, struct mbuf **, struct m_tag *); int pf_refragment6(struct ifnet *, struct mbuf **, struct m_tag *, bool); #endif /* INET6 */ diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c --- a/sys/netpfil/pf/pf.c +++ b/sys/netpfil/pf/pf.c @@ -7926,7 +7926,8 @@ pf_route6(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp, struct pf_kstate *s, struct pf_pdesc *pd, struct inpcb *inp) { - struct mbuf *m0, *md; + struct mbuf *m0; + struct m_tag *mtag; struct sockaddr_in6 dst; struct ip6_hdr *ip6; struct pfi_kkif *nkif = NULL; @@ -8053,7 +8054,7 @@ } if (pd->dir == PF_IN) { - if (pf_test(AF_INET6, PF_OUT, PFIL_FWD, ifp, &m0, inp, + if (pf_test(AF_INET6, PF_OUT, PFIL_FWD | PF_PFIL_NOREFRAGMENT, ifp, &m0, inp, &pd->act) != PF_PASS) { SDT_PROBE1(pf, ip6, route_to, drop, __LINE__); goto bad; @@ -8081,22 +8082,45 @@ m0->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA_IPV6; } + if (IN6_IS_SCOPE_EMBED(&dst.sin6_addr)) + dst.sin6_addr.s6_addr16[1] = htons(ifp->if_index); + mtag = m_tag_find(m0, PACKET_TAG_PF_REASSEMBLED, NULL); /* * If the packet is too large for the outgoing interface, * send back an icmp6 error. + * Refragment if we reassembled the packet. */ - if (IN6_IS_SCOPE_EMBED(&dst.sin6_addr)) - dst.sin6_addr.s6_addr16[1] = htons(ifp->if_index); - if ((u_long)m0->m_pkthdr.len <= ifp->if_mtu) { - md = m0; - pf_dummynet_route(pd, s, r, ifp, sintosa(&dst), &md); - if (md != NULL) { - int ret __sdt_used; - ret = nd6_output_ifp(ifp, ifp, md, &dst, NULL); - SDT_PROBE2(pf, ip6, route_to, output, ifp, ret); + if (mtag != NULL || ((u_long)m0->m_pkthdr.len <= ifp->if_mtu)) { + struct mbuf *m, *t; + int ret __sdt_used; + if (mtag != NULL) { + ret = pf_do_refragment6(ifp, &m0, mtag); + if (ret != 0) { + SDT_PROBE2(pf, ip6, route_to, output, ifp, ret); + return; + } + /* The first mbuf contains the unfragmented packet. */ + m = m0->m_nextpkt; + m0->m_nextpkt = NULL; + m_freem(m0); + m0 = m; } - } - else { + for (m = m0; m; m = t) { + struct pf_pdesc pd; + + t = m->m_nextpkt; + m->m_nextpkt = NULL; + m->m_flags |= M_SKIP_FIREWALL; + memset(&pd, 0, sizeof(pd)); + pd.pf_mtag = pf_find_mtag(m); + + pf_dummynet_route(&pd, s, r, ifp, sintosa(&dst), &m); + if (m != NULL) { + ret = nd6_output_ifp(ifp, ifp, m, &dst, NULL); + SDT_PROBE2(pf, ip6, route_to, output, ifp, ret); + } + } + } else { in6_ifstat_inc(ifp, ifs6_in_toobig); if (r_rt != PF_DUPTO) { if (s && s->nat_rule != NULL) @@ -9472,14 +9496,15 @@ if (s) PF_STATE_UNLOCK(s); +out: #ifdef INET6 /* If reassembled packet passed, create new fragments. */ if (af == AF_INET6 && action == PF_PASS && *m0 && dir == PF_OUT && + (! (pflags & PF_PFIL_NOREFRAGMENT)) && (mtag = m_tag_find(pd.m, PACKET_TAG_PF_REASSEMBLED, NULL)) != NULL) action = pf_refragment6(ifp, m0, mtag, pflags & PFIL_FWD); #endif -out: pf_sctp_multihome_delayed(&pd, kif, s, action); return (action); diff --git a/sys/netpfil/pf/pf_norm.c b/sys/netpfil/pf/pf_norm.c --- a/sys/netpfil/pf/pf_norm.c +++ b/sys/netpfil/pf/pf_norm.c @@ -957,17 +957,14 @@ } int -pf_refragment6(struct ifnet *ifp, struct mbuf **m0, struct m_tag *mtag, - bool forward) +pf_do_refragment6(struct ifnet *ifp, struct mbuf **m0, struct m_tag *mtag) { - struct mbuf *m = *m0, *t; + struct mbuf *m = *m0; struct ip6_hdr *hdr; struct pf_fragment_tag *ftag = (struct pf_fragment_tag *)(mtag + 1); - struct pf_pdesc pd; uint32_t frag_id; uint16_t hdrlen, extoff, maxlen; uint8_t proto; - int error, action; hdrlen = ftag->ft_hdrlen; extoff = ftag->ft_extoff; @@ -1010,7 +1007,18 @@ * is less than 8, ip6_fragment() will return EMSGSIZE and * we drop the packet. */ - error = ip6_fragment(ifp, m, hdrlen, proto, maxlen, frag_id); + return (ip6_fragment(ifp, m, hdrlen, proto, maxlen, frag_id)); +} + +int +pf_refragment6(struct ifnet *ifp, struct mbuf **m0, struct m_tag *mtag, + bool forward) +{ + struct pf_pdesc pd; + struct mbuf *m, *t; + int error, action; + + error = pf_do_refragment6(ifp, m0, mtag); m = (*m0)->m_nextpkt; (*m0)->m_nextpkt = NULL; if (error == 0) { diff --git a/tests/sys/netpfil/pf/fragmentation_pass.sh b/tests/sys/netpfil/pf/fragmentation_pass.sh --- a/tests/sys/netpfil/pf/fragmentation_pass.sh +++ b/tests/sys/netpfil/pf/fragmentation_pass.sh @@ -155,6 +155,75 @@ pft_cleanup } +atf_test_case "v6_route_to" "cleanup" +v6_route_to_head() +{ + atf_set descr 'Test IPv6 reassembly combined with route-to' + atf_set require.user root +} + +v6_route_to_body() +{ + pft_init +} + +v6_route_to_cleanup() +{ + pft_cleanup + + epair_send=$(vnet_mkepair) + epair_link=$(vnet_mkepair) + + vnet_mkjail alcatraz ${epair_send}b ${epair_link}a + vnet_mkjail singsing ${epair_link}b + + ifconfig ${epair_send}a inet6 2001:db8:42::1/64 no_dad up + + jexec alcatraz ifconfig ${epair_send}b inet6 2001:db8:42::2/64 no_dad up + jexec alcatraz ifconfig ${epair_link}a inet6 2001:db8:43::2/64 no_dad up + jexec alcatraz sysctl net.inet6.ip6.forwarding=1 + + jexec singsing ifconfig ${epair_link}b inet6 2001:db8:43::3/64 no_dad up + jexec singsing route add -6 2001:db8:42::/64 2001:db8:43::2 + route add -6 2001:db8:43::/64 2001:db8:42::2 + + jexec alcatraz ifconfig ${epair_send}b inet6 -ifdisabled + jexec alcatraz ifconfig ${epair_link}a inet6 -ifdisabled + jexec singsing ifconfig ${epair_link}b inet6 -ifdisabled + ifconfig ${epair_send}a inet6 -ifdisabled + + jexec alcatraz pfctl -e + pft_set_rules alcatraz \ + "set reassemble yes" \ + "pass" \ + "pass in route-to (${epair_link}a 2001:db8:43::3) inet6 proto icmp6 from any to 2001:db8:43::3 keep state" + + # Forwarding test + atf_check -s exit:0 -o ignore \ + ping -6 -c 1 2001:db8:43::3 + + atf_check -s exit:0 -o ignore \ + ping -6 -c 1 -s 4500 2001:db8:43::3 + + atf_check -s exit:0 -o ignore\ + ping -6 -c 1 -b 70000 -s 65000 2001:db8:43::3 + + # Now test this without fragmentation + pft_set_rules alcatraz \ + "set reassemble no" \ + "pass" \ + "pass in route-to (${epair_link}a 2001:db8:43::3) inet6 proto icmp6 from any to 2001:db8:43::3 keep state" + + atf_check -s exit:0 -o ignore \ + ping -6 -c 1 2001:db8:43::3 + + atf_check -s exit:0 -o ignore \ + ping -6 -c 1 -s 4500 2001:db8:43::3 + + atf_check -s exit:0 -o ignore\ + ping -6 -c 1 -b 70000 -s 65000 2001:db8:43::3 +} + atf_test_case "mtu_diff" "cleanup" mtu_diff_head() { @@ -544,6 +613,7 @@ { atf_add_test_case "too_many_fragments" atf_add_test_case "v6" + atf_add_test_case "v6_route_to" atf_add_test_case "mtu_diff" atf_add_test_case "overreplace" atf_add_test_case "overindex"