Date: Fri, 29 Apr 2016 20:22:01 +0000 (UTC) From: Michael Tuexen <tuexen@FreeBSD.org> To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r298800 - head/sys/netinet Message-ID: <201604292022.u3TKM1vO009188@repo.freebsd.org>
next in thread | raw e-mail | index | archive | help
Author: tuexen Date: Fri Apr 29 20:22:01 2016 New Revision: 298800 URL: https://svnweb.freebsd.org/changeset/base/298800 Log: Add support for handling ICMP and ICMP6 messages sent in response to SCTP/UDP/IP and SCTP/UDP/IPv6 packets. Modified: head/sys/netinet/ip_icmp.h head/sys/netinet/sctputil.c Modified: head/sys/netinet/ip_icmp.h ============================================================================== --- head/sys/netinet/ip_icmp.h Fri Apr 29 20:19:41 2016 (r298799) +++ head/sys/netinet/ip_icmp.h Fri Apr 29 20:22:01 2016 (r298800) @@ -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. Modified: head/sys/netinet/sctputil.c ============================================================================== --- head/sys/netinet/sctputil.c Fri Apr 29 20:19:41 2016 (r298799) +++ head/sys/netinet/sctputil.c Fri Apr 29 20:22:01 2016 (r298800) @@ -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 @@ out: 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); }
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201604292022.u3TKM1vO009188>