sctp: Add GSO support
[cascardo/linux.git] / net / sctp / input.c
index 00b8445..6f8e676 100644 (file)
@@ -84,7 +84,7 @@ static inline int sctp_rcv_checksum(struct net *net, struct sk_buff *skb)
 
        if (val != cmp) {
                /* CRC failure, dump it. */
-               SCTP_INC_STATS_BH(net, SCTP_MIB_CHECKSUMERRORS);
+               __SCTP_INC_STATS(net, SCTP_MIB_CHECKSUMERRORS);
                return -1;
        }
        return 0;
@@ -112,7 +112,6 @@ int sctp_rcv(struct sk_buff *skb)
        struct sctp_ep_common *rcvr;
        struct sctp_transport *transport = NULL;
        struct sctp_chunk *chunk;
-       struct sctphdr *sh;
        union sctp_addr src;
        union sctp_addr dest;
        int family;
@@ -122,30 +121,31 @@ int sctp_rcv(struct sk_buff *skb)
        if (skb->pkt_type != PACKET_HOST)
                goto discard_it;
 
-       SCTP_INC_STATS_BH(net, SCTP_MIB_INSCTPPACKS);
+       __SCTP_INC_STATS(net, SCTP_MIB_INSCTPPACKS);
 
-       if (skb_linearize(skb))
+       /* If packet is too small to contain a single chunk, let's not
+        * waste time on it anymore.
+        */
+       if (skb->len < sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr) +
+                      skb_transport_offset(skb))
                goto discard_it;
 
-       sh = sctp_hdr(skb);
+       if (!pskb_may_pull(skb, sizeof(struct sctphdr)))
+               goto discard_it;
 
-       /* Pull up the IP and SCTP headers. */
+       /* Pull up the IP header. */
        __skb_pull(skb, skb_transport_offset(skb));
-       if (skb->len < sizeof(struct sctphdr))
-               goto discard_it;
 
        skb->csum_valid = 0; /* Previous value not applicable */
        if (skb_csum_unnecessary(skb))
                __skb_decr_checksum_unnecessary(skb);
-       else if (!sctp_checksum_disable && sctp_rcv_checksum(net, skb) < 0)
+       else if (!sctp_checksum_disable &&
+                !(skb_shinfo(skb)->gso_type & SKB_GSO_SCTP) &&
+                sctp_rcv_checksum(net, skb) < 0)
                goto discard_it;
        skb->csum_valid = 1;
 
-       skb_pull(skb, sizeof(struct sctphdr));
-
-       /* Make sure we at least have chunk headers worth of data left. */
-       if (skb->len < sizeof(struct sctp_chunkhdr))
-               goto discard_it;
+       __skb_pull(skb, sizeof(struct sctphdr));
 
        family = ipver2af(ip_hdr(skb)->version);
        af = sctp_get_af_specific(family);
@@ -208,7 +208,7 @@ int sctp_rcv(struct sk_buff *skb)
         */
        if (!asoc) {
                if (sctp_rcv_ootb(skb)) {
-                       SCTP_INC_STATS_BH(net, SCTP_MIB_OUTOFBLUES);
+                       __SCTP_INC_STATS(net, SCTP_MIB_OUTOFBLUES);
                        goto discard_release;
                }
        }
@@ -230,7 +230,7 @@ int sctp_rcv(struct sk_buff *skb)
        chunk->rcvr = rcvr;
 
        /* Remember the SCTP header. */
-       chunk->sctp_hdr = sh;
+       chunk->sctp_hdr = sctp_hdr(skb);
 
        /* Set the source and destination addresses of the incoming chunk.  */
        sctp_init_addrs(chunk, &src, &dest);
@@ -264,9 +264,9 @@ int sctp_rcv(struct sk_buff *skb)
                        skb = NULL; /* sctp_chunk_free already freed the skb */
                        goto discard_release;
                }
-               SCTP_INC_STATS_BH(net, SCTP_MIB_IN_PKT_BACKLOG);
+               __SCTP_INC_STATS(net, SCTP_MIB_IN_PKT_BACKLOG);
        } else {
-               SCTP_INC_STATS_BH(net, SCTP_MIB_IN_PKT_SOFTIRQ);
+               __SCTP_INC_STATS(net, SCTP_MIB_IN_PKT_SOFTIRQ);
                sctp_inq_push(&chunk->rcvr->inqueue, chunk);
        }
 
@@ -281,7 +281,7 @@ int sctp_rcv(struct sk_buff *skb)
        return 0;
 
 discard_it:
-       SCTP_INC_STATS_BH(net, SCTP_MIB_IN_PKT_DISCARDS);
+       __SCTP_INC_STATS(net, SCTP_MIB_IN_PKT_DISCARDS);
        kfree_skb(skb);
        return 0;
 
@@ -532,7 +532,7 @@ struct sock *sctp_err_lookup(struct net *net, int family, struct sk_buff *skb,
         * servers this needs to be solved differently.
         */
        if (sock_owned_by_user(sk))
-               NET_INC_STATS_BH(net, LINUX_MIB_LOCKDROPPEDICMPS);
+               __NET_INC_STATS(net, LINUX_MIB_LOCKDROPPEDICMPS);
 
        *app = asoc;
        *tpp = transport;
@@ -589,7 +589,7 @@ void sctp_v4_err(struct sk_buff *skb, __u32 info)
        skb->network_header = saveip;
        skb->transport_header = savesctp;
        if (!sk) {
-               ICMP_INC_STATS_BH(net, ICMP_MIB_INERRORS);
+               __ICMP_INC_STATS(net, ICMP_MIB_INERRORS);
                return;
        }
        /* Warning:  The sock lock is held.  Remember to call
@@ -660,19 +660,23 @@ out_unlock:
  */
 static int sctp_rcv_ootb(struct sk_buff *skb)
 {
-       sctp_chunkhdr_t *ch;
-       __u8 *ch_end;
-
-       ch = (sctp_chunkhdr_t *) skb->data;
+       sctp_chunkhdr_t *ch, _ch;
+       int ch_end, offset = 0;
 
        /* Scan through all the chunks in the packet.  */
        do {
+               /* Make sure we have at least the header there */
+               if (offset + sizeof(sctp_chunkhdr_t) > skb->len)
+                       break;
+
+               ch = skb_header_pointer(skb, offset, sizeof(*ch), &_ch);
+
                /* Break out if chunk length is less then minimal. */
                if (ntohs(ch->length) < sizeof(sctp_chunkhdr_t))
                        break;
 
-               ch_end = ((__u8 *)ch) + WORD_ROUND(ntohs(ch->length));
-               if (ch_end > skb_tail_pointer(skb))
+               ch_end = offset + WORD_ROUND(ntohs(ch->length));
+               if (ch_end > skb->len)
                        break;
 
                /* RFC 8.4, 2) If the OOTB packet contains an ABORT chunk, the
@@ -697,8 +701,8 @@ static int sctp_rcv_ootb(struct sk_buff *skb)
                if (SCTP_CID_INIT == ch->type && (void *)ch != skb->data)
                        goto discard;
 
-               ch = (sctp_chunkhdr_t *) ch_end;
-       } while (ch_end < skb_tail_pointer(skb));
+               offset = ch_end;
+       } while (ch_end < skb->len);
 
        return 0;
 
@@ -1173,6 +1177,17 @@ static struct sctp_association *__sctp_rcv_lookup_harder(struct net *net,
 {
        sctp_chunkhdr_t *ch;
 
+       /* We do not allow GSO frames here as we need to linearize and
+        * then cannot guarantee frame boundaries. This shouldn't be an
+        * issue as packets hitting this are mostly INIT or INIT-ACK and
+        * those cannot be on GSO-style anyway.
+        */
+       if ((skb_shinfo(skb)->gso_type & SKB_GSO_SCTP) == SKB_GSO_SCTP)
+               return NULL;
+
+       if (skb_linearize(skb))
+               return NULL;
+
        ch = (sctp_chunkhdr_t *) skb->data;
 
        /* The code below will attempt to walk the chunk and extract