Add support for handling ICMP and ICMP6 messages sent in response

to SCTP/UDP/IP and SCTP/UDP/IPv6 packets.
This commit is contained in:
Michael Tuexen 2016-04-29 20:22:01 +00:00
parent 7ae2ff0dba
commit fd7af143e2
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=298800
2 changed files with 267 additions and 4 deletions

View File

@ -140,9 +140,10 @@ struct icmp {
/*
* ICMP_ADVLENPREF is the preferred number of bytes which should be contiguous.
* SCTP needs additional 12 bytes to be able to access the initiate tag
* in packets containing an INIT chunk.
* in packets containing an INIT chunk. For also supporting SCTP/UDP,
* additional 8 bytes are needed.
*/
#define ICMP_ADVLENPREF(p) (8 + ((p)->icmp_ip.ip_hl << 2) + 8 + 12)
#define ICMP_ADVLENPREF(p) (8 + ((p)->icmp_ip.ip_hl << 2) + 8 + 8 + 12)
/*
* Definition of type and code field values.

View File

@ -52,6 +52,9 @@ __FBSDID("$FreeBSD$");
#include <netinet/udp.h>
#include <netinet/udp_var.h>
#include <sys/proc.h>
#ifdef INET6
#include <netinet/icmp6.h>
#endif
#ifndef KTR_SCTP
@ -6880,6 +6883,261 @@ sctp_recv_udp_tunneled_packet(struct mbuf *m, int off, struct inpcb *inp,
m_freem(m);
}
#ifdef INET
static void
sctp_recv_icmp_tunneled_packet(int cmd, struct sockaddr *sa, void *vip, void *ctx SCTP_UNUSED)
{
struct ip *outer_ip, *inner_ip;
struct sctphdr *sh;
struct icmp *icmp;
struct udphdr *udp;
struct sctp_inpcb *inp;
struct sctp_tcb *stcb;
struct sctp_nets *net;
struct sctp_init_chunk *ch;
struct sockaddr_in src, dst;
uint8_t type, code;
inner_ip = (struct ip *)vip;
icmp = (struct icmp *)((caddr_t)inner_ip -
(sizeof(struct icmp) - sizeof(struct ip)));
outer_ip = (struct ip *)((caddr_t)icmp - sizeof(struct ip));
if (ntohs(outer_ip->ip_len) <
sizeof(struct ip) + 8 + (inner_ip->ip_hl << 2) + sizeof(struct udphdr) + 8) {
return;
}
udp = (struct udphdr *)((caddr_t)inner_ip + (inner_ip->ip_hl << 2));
sh = (struct sctphdr *)(udp + 1);
memset(&src, 0, sizeof(struct sockaddr_in));
src.sin_family = AF_INET;
src.sin_len = sizeof(struct sockaddr_in);
src.sin_port = sh->src_port;
src.sin_addr = inner_ip->ip_src;
memset(&dst, 0, sizeof(struct sockaddr_in));
dst.sin_family = AF_INET;
dst.sin_len = sizeof(struct sockaddr_in);
dst.sin_port = sh->dest_port;
dst.sin_addr = inner_ip->ip_dst;
/*
* 'dst' holds the dest of the packet that failed to be sent. 'src'
* holds our local endpoint address. Thus we reverse the dst and the
* src in the lookup.
*/
inp = NULL;
net = NULL;
stcb = sctp_findassociation_addr_sa((struct sockaddr *)&dst,
(struct sockaddr *)&src,
&inp, &net, 1,
SCTP_DEFAULT_VRFID);
if ((stcb != NULL) &&
(net != NULL) &&
(inp != NULL) &&
(inp->sctp_socket != NULL)) {
/* Check the UDP port numbers */
if ((udp->uh_dport != net->port) ||
(udp->uh_sport != htons(SCTP_BASE_SYSCTL(sctp_udp_tunneling_port)))) {
SCTP_TCB_UNLOCK(stcb);
return;
}
/* Check the verification tag */
if (ntohl(sh->v_tag) != 0) {
/*
* This must be the verification tag used for
* sending out packets. We don't consider packets
* reflecting the verification tag.
*/
if (ntohl(sh->v_tag) != stcb->asoc.peer_vtag) {
SCTP_TCB_UNLOCK(stcb);
return;
}
} else {
if (ntohs(outer_ip->ip_len) >=
sizeof(struct ip) +
8 + (inner_ip->ip_hl << 2) + 8 + 20) {
/*
* In this case we can check if we got an
* INIT chunk and if the initiate tag
* matches.
*/
ch = (struct sctp_init_chunk *)(sh + 1);
if ((ch->ch.chunk_type != SCTP_INITIATION) ||
(ntohl(ch->init.initiate_tag) != stcb->asoc.my_vtag)) {
SCTP_TCB_UNLOCK(stcb);
return;
}
} else {
SCTP_TCB_UNLOCK(stcb);
return;
}
}
type = icmp->icmp_type;
code = icmp->icmp_code;
if ((type = ICMP_UNREACH) &&
(code = ICMP_UNREACH_PORT)) {
code = ICMP_UNREACH_PROTOCOL;
}
sctp_notify(inp, stcb, net, type, code,
ntohs(inner_ip->ip_len),
ntohs(icmp->icmp_nextmtu));
} else {
if ((stcb == NULL) && (inp != NULL)) {
/* reduce ref-count */
SCTP_INP_WLOCK(inp);
SCTP_INP_DECR_REF(inp);
SCTP_INP_WUNLOCK(inp);
}
if (stcb) {
SCTP_TCB_UNLOCK(stcb);
}
}
return;
}
#endif
#ifdef INET6
static void
sctp_recv_icmp6_tunneled_packet(int cmd, struct sockaddr *sa, void *d, void *ctx SCTP_UNUSED)
{
struct ip6ctlparam *ip6cp;
struct sctp_inpcb *inp;
struct sctp_tcb *stcb;
struct sctp_nets *net;
struct sctphdr sh;
struct udphdr udp;
struct sockaddr_in6 src, dst;
uint8_t type, code;
ip6cp = (struct ip6ctlparam *)d;
/*
* XXX: We assume that when IPV6 is non NULL, M and OFF are valid.
*/
if (ip6cp->ip6c_m == NULL) {
return;
}
/*
* Check if we can safely examine the ports and the verification tag
* of the SCTP common header.
*/
if (ip6cp->ip6c_m->m_pkthdr.len <
ip6cp->ip6c_off + sizeof(struct udphdr) + offsetof(struct sctphdr, checksum)) {
return;
}
/* Copy out the UDP header. */
memset(&udp, 0, sizeof(struct udphdr));
m_copydata(ip6cp->ip6c_m,
ip6cp->ip6c_off,
sizeof(struct udphdr),
(caddr_t)&udp);
/* Copy out the port numbers and the verification tag. */
memset(&sh, 0, sizeof(struct sctphdr));
m_copydata(ip6cp->ip6c_m,
ip6cp->ip6c_off + sizeof(struct udphdr),
sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t),
(caddr_t)&sh);
memset(&src, 0, sizeof(struct sockaddr_in6));
src.sin6_family = AF_INET6;
src.sin6_len = sizeof(struct sockaddr_in6);
src.sin6_port = sh.src_port;
src.sin6_addr = ip6cp->ip6c_ip6->ip6_src;
if (in6_setscope(&src.sin6_addr, ip6cp->ip6c_m->m_pkthdr.rcvif, NULL) != 0) {
return;
}
memset(&dst, 0, sizeof(struct sockaddr_in6));
dst.sin6_family = AF_INET6;
dst.sin6_len = sizeof(struct sockaddr_in6);
dst.sin6_port = sh.dest_port;
dst.sin6_addr = ip6cp->ip6c_ip6->ip6_dst;
if (in6_setscope(&dst.sin6_addr, ip6cp->ip6c_m->m_pkthdr.rcvif, NULL) != 0) {
return;
}
inp = NULL;
net = NULL;
stcb = sctp_findassociation_addr_sa((struct sockaddr *)&dst,
(struct sockaddr *)&src,
&inp, &net, 1, SCTP_DEFAULT_VRFID);
if ((stcb != NULL) &&
(net != NULL) &&
(inp != NULL) &&
(inp->sctp_socket != NULL)) {
/* Check the UDP port numbers */
if ((udp.uh_dport != net->port) ||
(udp.uh_sport != htons(SCTP_BASE_SYSCTL(sctp_udp_tunneling_port)))) {
SCTP_TCB_UNLOCK(stcb);
return;
}
/* Check the verification tag */
if (ntohl(sh.v_tag) != 0) {
/*
* This must be the verification tag used for
* sending out packets. We don't consider packets
* reflecting the verification tag.
*/
if (ntohl(sh.v_tag) != stcb->asoc.peer_vtag) {
SCTP_TCB_UNLOCK(stcb);
return;
}
} else {
if (ip6cp->ip6c_m->m_pkthdr.len >=
ip6cp->ip6c_off + sizeof(struct udphdr) +
sizeof(struct sctphdr) +
sizeof(struct sctp_chunkhdr) +
offsetof(struct sctp_init, a_rwnd)) {
/*
* In this case we can check if we got an
* INIT chunk and if the initiate tag
* matches.
*/
uint32_t initiate_tag;
uint8_t chunk_type;
m_copydata(ip6cp->ip6c_m,
ip6cp->ip6c_off +
sizeof(struct udphdr) +
sizeof(struct sctphdr),
sizeof(uint8_t),
(caddr_t)&chunk_type);
m_copydata(ip6cp->ip6c_m,
ip6cp->ip6c_off +
sizeof(struct udphdr) +
sizeof(struct sctphdr) +
sizeof(struct sctp_chunkhdr),
sizeof(uint32_t),
(caddr_t)&initiate_tag);
if ((chunk_type != SCTP_INITIATION) ||
(ntohl(initiate_tag) != stcb->asoc.my_vtag)) {
SCTP_TCB_UNLOCK(stcb);
return;
}
} else {
SCTP_TCB_UNLOCK(stcb);
return;
}
}
type = ip6cp->ip6c_icmp6->icmp6_type;
code = ip6cp->ip6c_icmp6->icmp6_code;
if ((type == ICMP6_DST_UNREACH) &&
(code == ICMP6_DST_UNREACH_NOPORT)) {
type = ICMP6_PARAM_PROB;
code = ICMP6_PARAMPROB_NEXTHEADER;
}
sctp6_notify(inp, stcb, net, type, code,
(uint16_t) ntohl(ip6cp->ip6c_icmp6->icmp6_mtu));
} else {
if ((stcb == NULL) && (inp != NULL)) {
/* reduce inp's ref-count */
SCTP_INP_WLOCK(inp);
SCTP_INP_DECR_REF(inp);
SCTP_INP_WUNLOCK(inp);
}
if (stcb) {
SCTP_TCB_UNLOCK(stcb);
}
}
}
#endif
void
sctp_over_udp_stop(void)
{
@ -6945,7 +7203,9 @@ sctp_over_udp_start(void)
}
/* Call the special UDP hook. */
if ((ret = udp_set_kernel_tunneling(SCTP_BASE_INFO(udp4_tun_socket),
sctp_recv_udp_tunneled_packet, NULL, NULL))) {
sctp_recv_udp_tunneled_packet,
sctp_recv_icmp_tunneled_packet,
NULL))) {
sctp_over_udp_stop();
return (ret);
}
@ -6969,7 +7229,9 @@ sctp_over_udp_start(void)
}
/* Call the special UDP hook. */
if ((ret = udp_set_kernel_tunneling(SCTP_BASE_INFO(udp6_tun_socket),
sctp_recv_udp_tunneled_packet, NULL, NULL))) {
sctp_recv_udp_tunneled_packet,
sctp_recv_icmp6_tunneled_packet,
NULL))) {
sctp_over_udp_stop();
return (ret);
}