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 @@ -7738,8 +7738,8 @@ if (pf_icmp_mapping(pd, icmptype, &icmp_dir, &multi, &virtual_id, &virtual_type) == 0) { /* - * ICMP query/reply message not related to a TCP/UDP packet. - * Search for an ICMP state. + * ICMP query/reply message not related to a TCP/UDP/SCTP + * packet. Search for an ICMP state. */ ret = pf_icmp_state_lookup(&key, pd, state, pd->dir, virtual_id, virtual_type, icmp_dir, &iidx, @@ -8244,6 +8244,139 @@ break; } #ifdef INET + case IPPROTO_SCTP: { + struct sctphdr sh; + struct pf_state_peer *src; + int copyback = 0; + + if (! pf_pull_hdr(pd->m, pd2.off, &sh, sizeof(sh), NULL, reason, + pd2.af)) { + DPFPRINTF(PF_DEBUG_MISC, + ("pf: ICMP error message too short " + "(sctp)\n")); + return (PF_DROP); + } + + key.af = pd2.af; + key.proto = IPPROTO_SCTP; + PF_ACPY(&key.addr[pd2.sidx], pd2.src, key.af); + PF_ACPY(&key.addr[pd2.didx], pd2.dst, key.af); + key.port[pd2.sidx] = sh.src_port; + key.port[pd2.didx] = sh.dest_port; + + STATE_LOOKUP(&key, *state, pd); + + if (pd->dir == (*state)->direction) { + if (PF_REVERSED_KEY((*state)->key, pd->af)) + src = &(*state)->src; + else + src = &(*state)->dst; + } else { + if (PF_REVERSED_KEY((*state)->key, pd->af)) + src = &(*state)->dst; + else + src = &(*state)->src; + } + + if (src->scrub->pfss_v_tag != sh.v_tag) { + DPFPRINTF(PF_DEBUG_MISC, + ("pf: ICMP error message has incorrect " + "SCTP v_tag\n")); + return (PF_DROP); + } + + /* translate source/destination address, if necessary */ + if ((*state)->key[PF_SK_WIRE] != + (*state)->key[PF_SK_STACK]) { + + struct pf_state_key *nk; + + if (PF_REVERSED_KEY((*state)->key, pd->af)) + nk = (*state)->key[pd->sidx]; + else + nk = (*state)->key[pd->didx]; + +#if defined(INET) && defined(INET6) + int afto, sidx, didx; + + afto = pd->af != nk->af; + sidx = afto ? pd2.didx : pd2.sidx; + didx = afto ? pd2.sidx : pd2.didx; + + if (afto) { + if (pf_translate_icmp_af(nk->af, + &pd->hdr.icmp)) + return (PF_DROP); + m_copyback(pd->m, pd->off, + sizeof(struct icmp6_hdr), + (c_caddr_t)&pd->hdr.icmp6); + if (pf_change_icmp_af(pd->m, ipoff2, pd, + &pd2, &nk->addr[sidx], + &nk->addr[didx], pd->af, + nk->af)) + return (PF_DROP); + if (nk->af == AF_INET) + pd->proto = IPPROTO_ICMP; + else + pd->proto = IPPROTO_ICMPV6; + sh.src_port = nk->port[sidx]; + sh.dest_port = nk->port[didx]; + m_copyback(pd2.m, pd2.off, sizeof(sh), (c_caddr_t)&sh); + PF_ACPY(pd->src, + &nk->addr[pd2.sidx], nk->af); + PF_ACPY(pd->dst, + &nk->addr[pd2.didx], nk->af); + pd->naf = nk->af; + return (PF_AFRT); + } +#endif + + if (PF_ANEQ(pd2.src, + &nk->addr[pd2.sidx], pd2.af) || + nk->port[pd2.sidx] != sh.src_port) + pf_change_icmp(pd2.src, &sh.src_port, + daddr, &nk->addr[pd2.sidx], + nk->port[pd2.sidx], NULL, + pd2.ip_sum, icmpsum, + pd->ip_sum, 0, pd2.af); + + if (PF_ANEQ(pd2.dst, + &nk->addr[pd2.didx], pd2.af) || + nk->port[pd2.didx] != sh.dest_port) + pf_change_icmp(pd2.dst, &sh.dest_port, + saddr, &nk->addr[pd2.didx], + nk->port[pd2.didx], NULL, + pd2.ip_sum, icmpsum, + pd->ip_sum, 0, pd2.af); + copyback = 1; + } + + if (copyback) { + switch (pd2.af) { +#ifdef INET + case AF_INET: + m_copyback(pd->m, pd->off, ICMP_MINLEN, + (caddr_t )&pd->hdr.icmp); + m_copyback(pd->m, ipoff2, sizeof(h2), + (caddr_t )&h2); + break; +#endif /* INET */ +#ifdef INET6 + case AF_INET6: + m_copyback(pd->m, pd->off, + sizeof(struct icmp6_hdr), + (caddr_t )&pd->hdr.icmp6); + m_copyback(pd->m, ipoff2, sizeof(h2_6), + (caddr_t )&h2_6); + break; +#endif /* INET6 */ + } + m_copyback(pd->m, pd2.off, sizeof(sh), (caddr_t)&sh); + } + + return (PF_PASS); + break; + } case IPPROTO_ICMP: { struct icmp *iih = &pd2.hdr.icmp; diff --git a/tests/sys/netpfil/pf/sctp.sh b/tests/sys/netpfil/pf/sctp.sh --- a/tests/sys/netpfil/pf/sctp.sh +++ b/tests/sys/netpfil/pf/sctp.sh @@ -745,6 +745,91 @@ pft_cleanup } +atf_test_case "related_icmp" "cleanup" +related_icmp_head() +{ + atf_set descr 'Verify that ICMP messages related to an SCTP connection are allowed' + atf_set require.user root +} + +related_icmp_body() +{ + sctp_init + + epair_cl=$(vnet_mkepair) + epair_rtr=$(vnet_mkepair) + epair_srv=$(vnet_mkepair) + + ifconfig ${epair_cl}a 192.0.2.1/24 up + route add default 192.0.2.2 + + vnet_mkjail rtr ${epair_cl}b ${epair_rtr}a + jexec rtr ifconfig ${epair_cl}b 192.0.2.2/24 up + jexec rtr ifconfig ${epair_rtr}a 198.51.100.1/24 up + jexec rtr sysctl net.inet.ip.forwarding=1 + jexec rtr route add default 198.51.100.2 + + vnet_mkjail rtr2 ${epair_rtr}b ${epair_srv}a + jexec rtr2 ifconfig ${epair_rtr}b 198.51.100.2/24 up + jexec rtr2 ifconfig ${epair_srv}a 203.0.113.1/24 up + jexec rtr2 ifconfig ${epair_srv}a mtu 1300 + jexec rtr2 sysctl net.inet.ip.forwarding=1 + jexec rtr2 route add default 198.51.100.1 + + vnet_mkjail srv ${epair_srv}b + jexec srv ifconfig ${epair_srv}b 203.0.113.2/24 up + jexec srv ifconfig ${epair_srv}b mtu 1300 + jexec srv route add default 203.0.113.1 + + # Sanity checks + atf_check -s exit:0 -o ignore \ + ping -c 1 192.0.2.2 + atf_check -s exit:0 -o ignore \ + ping -c 1 198.51.100.1 + atf_check -s exit:0 -o ignore \ + ping -c 1 198.51.100.2 + atf_check -s exit:0 -o ignore \ + ping -c 1 203.0.113.1 + atf_check -s exit:0 -o ignore \ + ping -c 1 203.0.113.2 + + jexec rtr pfctl -e + pft_set_rules rtr \ + "block proto icmp" \ + "pass proto sctp" + + # Make sure SCTP traffic passes + echo "foo" | jexec srv nc --sctp -N -l 1234 & + sleep 1 + + out=$(nc --sctp -N -w 3 203.0.113.2 1234) + if [ "$out" != "foo" ]; then + jexec rtr pfctl -ss -vv + jexec rtr pfctl -sr -vv + atf_fail "SCTP connection failed" + fi + + # Do we see ICMP traffic if we send overly large traffic? + echo "foo" | jexec srv nc --sctp -N -l 1234 >/dev/null & + sleep 1 + + atf_check -s exit:0 -o not-match:".*destination unreachable:.*" \ + netstat -s -p icmp + + # Generate traffic that will be fragmented by rtr2, and will provoke an + # ICMP unreachable - need to frag (mtu 1300) message + dd if=/dev/random bs=1600 count=1 | nc --sctp -N -w 3 203.0.113.2 1234 + + # We'd expect to see an ICMP message + atf_check -s exit:0 -o match:".*destination unreachable: 1" \ + netstat -s -p icmp +} + +related_icmp_cleanup() +{ + pft_cleanup +} + atf_init_test_cases() { atf_add_test_case "basic_v4" @@ -757,4 +842,5 @@ atf_add_test_case "rdr_v4" atf_add_test_case "pfsync" atf_add_test_case "timeout" + atf_add_test_case "related_icmp" }