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);
        }
_______________________________________________
svn-src-head@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/svn-src-head
To unsubscribe, send any mail to "svn-src-head-unsubscr...@freebsd.org"

Reply via email to