sctp: allow GSO frags to access the chunk too
authorMarcelo Ricardo Leitner <marcelo.leitner@gmail.com>
Wed, 13 Jul 2016 18:08:57 +0000 (15:08 -0300)
committerDavid S. Miller <davem@davemloft.net>
Thu, 14 Jul 2016 01:10:14 +0000 (18:10 -0700)
SCTP will try to access original IP headers on sctp_recvmsg in order to
copy the addresses used. There are also other places that do similar access
to IP or even SCTP headers. But after 90017accff61 ("sctp: Add GSO
support") they aren't always there because they are only present in the
header skb.

SCTP handles the queueing of incoming data by cloning the incoming skb
and limiting to only the relevant payload. This clone has its cb updated
to something different and it's then queued on socket rx queue. Thus we
need to fix this in two moments.

For rx path, not related to socket queue yet, this patch uses a
partially copied sctp_input_cb to such GSO frags. This restores the
ability to access the headers for this part of the code.

Regarding the socket rx queue, it removes iif member from sctp_event and
also add a chunk pointer on it.

With these changes we're always able to reach the headers again.

The biggest change here is that now the sctp_chunk struct and the
original skb are only freed after the application consumed the buffer.
Note however that the original payload was already like this due to the
skb cloning.

For iif, SCTP's IPv4 code doesn't use it, so no change is necessary.
IPv6 now can fetch it directly from original's IPv6 CB as the original
skb is still accessible.

In the future we probably can simplify sctp_v*_skb_iif() stuff, as
sctp_v4_skb_iif() was called but it's return value not used, and now
it's not even called, but such cleanup is out of scope for this change.

Fixes: 90017accff61 ("sctp: Add GSO support")
Signed-off-by: Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/net/sctp/structs.h
include/net/sctp/ulpevent.h
net/sctp/inqueue.c
net/sctp/ipv6.c
net/sctp/protocol.c
net/sctp/sm_statefuns.c
net/sctp/socket.c
net/sctp/ulpevent.c

index 966c3a40039c12a7c525612594a51312d5de1d2a..f6f201de6fa46b3ca203c00f4970ca408edb6930 100644 (file)
@@ -1107,6 +1107,13 @@ struct sctp_input_cb {
 };
 #define SCTP_INPUT_CB(__skb)   ((struct sctp_input_cb *)&((__skb)->cb[0]))
 
+static inline const struct sk_buff *sctp_gso_headskb(const struct sk_buff *skb)
+{
+       const struct sctp_chunk *chunk = SCTP_INPUT_CB(skb)->chunk;
+
+       return chunk->head_skb ? : skb;
+}
+
 /* These bind address data fields common between endpoints and associations */
 struct sctp_bind_addr {
 
index aa342645dbce446186d55151c3f507cf0e165b44..2c098cd7e7e202b6fa96e97ccb56471df27cec91 100644 (file)
  */
 struct sctp_ulpevent {
        struct sctp_association *asoc;
+       struct sctp_chunk *chunk;
        unsigned int rmem_len;
        __u32 ppid;
        __u32 tsn;
        __u32 cumtsn;
-       int iif;
        __u16 stream;
        __u16 ssn;
        __u16 flags;
index edabbbdfca541b830526a7a52aee18c20680c19c..147d975b04559f7858b040b1f04dbc559ef2ec78 100644 (file)
@@ -218,6 +218,13 @@ new_skb:
                chunk->has_asconf = 0;
                chunk->end_of_packet = 0;
                chunk->ecn_ce_done = 0;
+               if (chunk->head_skb) {
+                       struct sctp_input_cb
+                               *cb = SCTP_INPUT_CB(chunk->skb),
+                               *head_cb = SCTP_INPUT_CB(chunk->head_skb);
+
+                       cb->chunk = head_cb->chunk;
+               }
        }
 
        chunk->chunk_hdr = ch;
index 0657d18a85bf7aa751a0456d0cc9adae3ff95e42..ae6f1a2178bab81fa14562bd1c37d1e55557b1e3 100644 (file)
@@ -420,6 +420,7 @@ static void sctp_v6_from_skb(union sctp_addr *addr, struct sk_buff *skb,
        addr->v6.sin6_flowinfo = 0; /* FIXME */
        addr->v6.sin6_scope_id = ((struct inet6_skb_parm *)skb->cb)->iif;
 
+       /* Always called on head skb, so this is safe */
        sh = sctp_hdr(skb);
        if (is_saddr) {
                *port  = sh->source;
@@ -710,8 +711,7 @@ static int sctp_v6_addr_to_user(struct sctp_sock *sp, union sctp_addr *addr)
 /* Where did this skb come from?  */
 static int sctp_v6_skb_iif(const struct sk_buff *skb)
 {
-       struct inet6_skb_parm *opt = (struct inet6_skb_parm *) skb->cb;
-       return opt->iif;
+       return IP6CB(skb)->iif;
 }
 
 /* Was this packet marked by Explicit Congestion Notification? */
@@ -780,15 +780,14 @@ static void sctp_inet6_skb_msgname(struct sk_buff *skb, char *msgname,
        if (ip_hdr(skb)->version == 4) {
                addr->v4.sin_family = AF_INET;
                addr->v4.sin_port = sh->source;
-               addr->v4.sin_addr.s_addr =  ip_hdr(skb)->saddr;
+               addr->v4.sin_addr.s_addr = ip_hdr(skb)->saddr;
        } else {
                addr->v6.sin6_family = AF_INET6;
                addr->v6.sin6_flowinfo = 0;
                addr->v6.sin6_port = sh->source;
                addr->v6.sin6_addr = ipv6_hdr(skb)->saddr;
                if (ipv6_addr_type(&addr->v6.sin6_addr) & IPV6_ADDR_LINKLOCAL) {
-                       struct sctp_ulpevent *ev = sctp_skb2event(skb);
-                       addr->v6.sin6_scope_id = ev->iif;
+                       addr->v6.sin6_scope_id = sctp_v6_skb_iif(skb);
                }
        }
 
index 3b56ae55aba36ec6e57bc2deb31fae507c5e4b54..1adb9270e31786c3a8131f0c24e58fe1b41ccc9b 100644 (file)
@@ -240,6 +240,7 @@ static void sctp_v4_from_skb(union sctp_addr *addr, struct sk_buff *skb,
        port = &addr->v4.sin_port;
        addr->v4.sin_family = AF_INET;
 
+       /* Always called on head skb, so this is safe */
        sh = sctp_hdr(skb);
        if (is_saddr) {
                *port  = sh->source;
index f1f08c8f277bd8719299d1ed21eb23e36d55f7e2..5aabf42065e2fba9388350996310b77c58369395 100644 (file)
@@ -6125,7 +6125,8 @@ static int sctp_eat_data(const struct sctp_association *asoc,
                af = sctp_get_af_specific(
                        ipver2af(ip_hdr(chunk->skb)->version));
 
-               if (af && af->is_ce(chunk->skb) && asoc->peer.ecn_capable) {
+               if (af && af->is_ce(sctp_gso_headskb(chunk->skb)) &&
+                   asoc->peer.ecn_capable) {
                        /* Do real work as sideffect. */
                        sctp_add_cmd_sf(commands, SCTP_CMD_ECN_CE,
                                        SCTP_U32(tsn));
index 71c7dc5ea62e162a95d7d318334ef9403d138b69..52fdd540a9ef153336e0c6df725ce47c9ebab11b 100644 (file)
@@ -2066,7 +2066,7 @@ static int sctp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
 {
        struct sctp_ulpevent *event = NULL;
        struct sctp_sock *sp = sctp_sk(sk);
-       struct sk_buff *skb;
+       struct sk_buff *skb, *head_skb;
        int copied;
        int err = 0;
        int skb_len;
@@ -2102,12 +2102,16 @@ static int sctp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
        if (err)
                goto out_free;
 
-       sock_recv_ts_and_drops(msg, sk, skb);
+       if (event->chunk && event->chunk->head_skb)
+               head_skb = event->chunk->head_skb;
+       else
+               head_skb = skb;
+       sock_recv_ts_and_drops(msg, sk, head_skb);
        if (sctp_ulpevent_is_notification(event)) {
                msg->msg_flags |= MSG_NOTIFICATION;
                sp->pf->event_msgname(event, msg->msg_name, addr_len);
        } else {
-               sp->pf->skb_msgname(skb, msg->msg_name, addr_len);
+               sp->pf->skb_msgname(head_skb, msg->msg_name, addr_len);
        }
 
        /* Check if we allow SCTP_NXTINFO. */
index 706f5bc9f0c3083ab455ec78b963cd609a3a95b5..f6219b164b42c821c3e6f626731e97dd712c957c 100644 (file)
@@ -701,6 +701,12 @@ struct sctp_ulpevent *sctp_ulpevent_make_rcvmsg(struct sctp_association *asoc,
 
        sctp_ulpevent_receive_data(event, asoc);
 
+       /* And hold the chunk as we need it for getting the IP headers
+        * later in recvmsg
+        */
+       sctp_chunk_hold(chunk);
+       event->chunk = chunk;
+
        event->stream = ntohs(chunk->subh.data_hdr->stream);
        event->ssn = ntohs(chunk->subh.data_hdr->ssn);
        event->ppid = chunk->subh.data_hdr->ppid;
@@ -710,11 +716,11 @@ struct sctp_ulpevent *sctp_ulpevent_make_rcvmsg(struct sctp_association *asoc,
        }
        event->tsn = ntohl(chunk->subh.data_hdr->tsn);
        event->msg_flags |= chunk->chunk_hdr->flags;
-       event->iif = sctp_chunk_iif(chunk);
 
        return event;
 
 fail_mark:
+       sctp_chunk_put(chunk);
        kfree_skb(skb);
 fail:
        return NULL;
@@ -1007,6 +1013,7 @@ static void sctp_ulpevent_release_data(struct sctp_ulpevent *event)
 
 done:
        sctp_assoc_rwnd_increase(event->asoc, len);
+       sctp_chunk_put(event->chunk);
        sctp_ulpevent_release_owner(event);
 }
 
@@ -1029,6 +1036,7 @@ static void sctp_ulpevent_release_frag_data(struct sctp_ulpevent *event)
        }
 
 done:
+       sctp_chunk_put(event->chunk);
        sctp_ulpevent_release_owner(event);
 }