sctp: allow GSO frags to access the chunk too
[cascardo/linux.git] / net / sctp / socket.c
index 67154b8..52fdd54 100644 (file)
@@ -1914,6 +1914,9 @@ static int sctp_sendmsg(struct sock *sk, struct msghdr *msg, size_t msg_len)
                goto out_free;
        }
 
+       if (sctp_wspace(asoc) < msg_len)
+               sctp_prsctp_prune(asoc, sinfo, msg_len - sctp_wspace(asoc));
+
        timeo = sock_sndtimeo(sk, msg->msg_flags & MSG_DONTWAIT);
        if (!sctp_wspace(asoc)) {
                err = sctp_wait_for_sndbuf(asoc, &timeo, msg_len);
@@ -2063,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;
@@ -2099,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. */
@@ -3661,6 +3668,80 @@ static int sctp_setsockopt_recvnxtinfo(struct sock *sk,
        return 0;
 }
 
+static int sctp_setsockopt_pr_supported(struct sock *sk,
+                                       char __user *optval,
+                                       unsigned int optlen)
+{
+       struct sctp_assoc_value params;
+       struct sctp_association *asoc;
+       int retval = -EINVAL;
+
+       if (optlen != sizeof(params))
+               goto out;
+
+       if (copy_from_user(&params, optval, optlen)) {
+               retval = -EFAULT;
+               goto out;
+       }
+
+       asoc = sctp_id2assoc(sk, params.assoc_id);
+       if (asoc) {
+               asoc->prsctp_enable = !!params.assoc_value;
+       } else if (!params.assoc_id) {
+               struct sctp_sock *sp = sctp_sk(sk);
+
+               sp->ep->prsctp_enable = !!params.assoc_value;
+       } else {
+               goto out;
+       }
+
+       retval = 0;
+
+out:
+       return retval;
+}
+
+static int sctp_setsockopt_default_prinfo(struct sock *sk,
+                                         char __user *optval,
+                                         unsigned int optlen)
+{
+       struct sctp_default_prinfo info;
+       struct sctp_association *asoc;
+       int retval = -EINVAL;
+
+       if (optlen != sizeof(info))
+               goto out;
+
+       if (copy_from_user(&info, optval, sizeof(info))) {
+               retval = -EFAULT;
+               goto out;
+       }
+
+       if (info.pr_policy & ~SCTP_PR_SCTP_MASK)
+               goto out;
+
+       if (info.pr_policy == SCTP_PR_SCTP_NONE)
+               info.pr_value = 0;
+
+       asoc = sctp_id2assoc(sk, info.pr_assoc_id);
+       if (asoc) {
+               SCTP_PR_SET_POLICY(asoc->default_flags, info.pr_policy);
+               asoc->default_timetolive = info.pr_value;
+       } else if (!info.pr_assoc_id) {
+               struct sctp_sock *sp = sctp_sk(sk);
+
+               SCTP_PR_SET_POLICY(sp->default_flags, info.pr_policy);
+               sp->default_timetolive = info.pr_value;
+       } else {
+               goto out;
+       }
+
+       retval = 0;
+
+out:
+       return retval;
+}
+
 /* API 6.2 setsockopt(), getsockopt()
  *
  * Applications use setsockopt() and getsockopt() to set or retrieve
@@ -3821,6 +3902,12 @@ static int sctp_setsockopt(struct sock *sk, int level, int optname,
        case SCTP_RECVNXTINFO:
                retval = sctp_setsockopt_recvnxtinfo(sk, optval, optlen);
                break;
+       case SCTP_PR_SUPPORTED:
+               retval = sctp_setsockopt_pr_supported(sk, optval, optlen);
+               break;
+       case SCTP_DEFAULT_PRINFO:
+               retval = sctp_setsockopt_default_prinfo(sk, optval, optlen);
+               break;
        default:
                retval = -ENOPROTOOPT;
                break;
@@ -4003,6 +4090,8 @@ static int sctp_init_sock(struct sock *sk)
                return -ESOCKTNOSUPPORT;
        }
 
+       sk->sk_gso_type = SKB_GSO_SCTP;
+
        /* Initialize default send parameters. These parameters can be
         * modified with the SCTP_DEFAULT_SEND_PARAM socket option.
         */
@@ -4193,6 +4282,7 @@ static void sctp_shutdown(struct sock *sk, int how)
                return;
 
        if (how & SEND_SHUTDOWN) {
+               sk->sk_state = SCTP_SS_CLOSING;
                ep = sctp_sk(sk)->ep;
                if (!list_empty(&ep->asocs)) {
                        asoc = list_entry(ep->asocs.next,
@@ -6163,6 +6253,148 @@ static int sctp_getsockopt_recvnxtinfo(struct sock *sk, int len,
        return 0;
 }
 
+static int sctp_getsockopt_pr_supported(struct sock *sk, int len,
+                                       char __user *optval,
+                                       int __user *optlen)
+{
+       struct sctp_assoc_value params;
+       struct sctp_association *asoc;
+       int retval = -EFAULT;
+
+       if (len < sizeof(params)) {
+               retval = -EINVAL;
+               goto out;
+       }
+
+       len = sizeof(params);
+       if (copy_from_user(&params, optval, len))
+               goto out;
+
+       asoc = sctp_id2assoc(sk, params.assoc_id);
+       if (asoc) {
+               params.assoc_value = asoc->prsctp_enable;
+       } else if (!params.assoc_id) {
+               struct sctp_sock *sp = sctp_sk(sk);
+
+               params.assoc_value = sp->ep->prsctp_enable;
+       } else {
+               retval = -EINVAL;
+               goto out;
+       }
+
+       if (put_user(len, optlen))
+               goto out;
+
+       if (copy_to_user(optval, &params, len))
+               goto out;
+
+       retval = 0;
+
+out:
+       return retval;
+}
+
+static int sctp_getsockopt_default_prinfo(struct sock *sk, int len,
+                                         char __user *optval,
+                                         int __user *optlen)
+{
+       struct sctp_default_prinfo info;
+       struct sctp_association *asoc;
+       int retval = -EFAULT;
+
+       if (len < sizeof(info)) {
+               retval = -EINVAL;
+               goto out;
+       }
+
+       len = sizeof(info);
+       if (copy_from_user(&info, optval, len))
+               goto out;
+
+       asoc = sctp_id2assoc(sk, info.pr_assoc_id);
+       if (asoc) {
+               info.pr_policy = SCTP_PR_POLICY(asoc->default_flags);
+               info.pr_value = asoc->default_timetolive;
+       } else if (!info.pr_assoc_id) {
+               struct sctp_sock *sp = sctp_sk(sk);
+
+               info.pr_policy = SCTP_PR_POLICY(sp->default_flags);
+               info.pr_value = sp->default_timetolive;
+       } else {
+               retval = -EINVAL;
+               goto out;
+       }
+
+       if (put_user(len, optlen))
+               goto out;
+
+       if (copy_to_user(optval, &info, len))
+               goto out;
+
+       retval = 0;
+
+out:
+       return retval;
+}
+
+static int sctp_getsockopt_pr_assocstatus(struct sock *sk, int len,
+                                         char __user *optval,
+                                         int __user *optlen)
+{
+       struct sctp_prstatus params;
+       struct sctp_association *asoc;
+       int policy;
+       int retval = -EINVAL;
+
+       if (len < sizeof(params))
+               goto out;
+
+       len = sizeof(params);
+       if (copy_from_user(&params, optval, len)) {
+               retval = -EFAULT;
+               goto out;
+       }
+
+       policy = params.sprstat_policy;
+       if (policy & ~SCTP_PR_SCTP_MASK)
+               goto out;
+
+       asoc = sctp_id2assoc(sk, params.sprstat_assoc_id);
+       if (!asoc)
+               goto out;
+
+       if (policy == SCTP_PR_SCTP_NONE) {
+               params.sprstat_abandoned_unsent = 0;
+               params.sprstat_abandoned_sent = 0;
+               for (policy = 0; policy <= SCTP_PR_INDEX(MAX); policy++) {
+                       params.sprstat_abandoned_unsent +=
+                               asoc->abandoned_unsent[policy];
+                       params.sprstat_abandoned_sent +=
+                               asoc->abandoned_sent[policy];
+               }
+       } else {
+               params.sprstat_abandoned_unsent =
+                       asoc->abandoned_unsent[__SCTP_PR_INDEX(policy)];
+               params.sprstat_abandoned_sent =
+                       asoc->abandoned_sent[__SCTP_PR_INDEX(policy)];
+       }
+
+       if (put_user(len, optlen)) {
+               retval = -EFAULT;
+               goto out;
+       }
+
+       if (copy_to_user(optval, &params, len)) {
+               retval = -EFAULT;
+               goto out;
+       }
+
+       retval = 0;
+
+out:
+       return retval;
+}
+
 static int sctp_getsockopt(struct sock *sk, int level, int optname,
                           char __user *optval, int __user *optlen)
 {
@@ -6316,6 +6548,17 @@ static int sctp_getsockopt(struct sock *sk, int level, int optname,
        case SCTP_RECVNXTINFO:
                retval = sctp_getsockopt_recvnxtinfo(sk, len, optval, optlen);
                break;
+       case SCTP_PR_SUPPORTED:
+               retval = sctp_getsockopt_pr_supported(sk, len, optval, optlen);
+               break;
+       case SCTP_DEFAULT_PRINFO:
+               retval = sctp_getsockopt_default_prinfo(sk, len, optval,
+                                                       optlen);
+               break;
+       case SCTP_PR_ASSOC_STATUS:
+               retval = sctp_getsockopt_pr_assocstatus(sk, len, optval,
+                                                       optlen);
+               break;
        default:
                retval = -ENOPROTOOPT;
                break;
@@ -6863,7 +7106,7 @@ static int sctp_msghdr_parse(const struct msghdr *msg, sctp_cmsgs_t *cmsgs)
 
                        if (cmsgs->srinfo->sinfo_flags &
                            ~(SCTP_UNORDERED | SCTP_ADDR_OVER |
-                             SCTP_SACK_IMMEDIATELY |
+                             SCTP_SACK_IMMEDIATELY | SCTP_PR_SCTP_MASK |
                              SCTP_ABORT | SCTP_EOF))
                                return -EINVAL;
                        break;
@@ -6887,7 +7130,7 @@ static int sctp_msghdr_parse(const struct msghdr *msg, sctp_cmsgs_t *cmsgs)
 
                        if (cmsgs->sinfo->snd_flags &
                            ~(SCTP_UNORDERED | SCTP_ADDR_OVER |
-                             SCTP_SACK_IMMEDIATELY |
+                             SCTP_SACK_IMMEDIATELY | SCTP_PR_SCTP_MASK |
                              SCTP_ABORT | SCTP_EOF))
                                return -EINVAL;
                        break;
@@ -7564,10 +7807,13 @@ static void sctp_sock_migrate(struct sock *oldsk, struct sock *newsk,
        /* If the association on the newsk is already closed before accept()
         * is called, set RCV_SHUTDOWN flag.
         */
-       if (sctp_state(assoc, CLOSED) && sctp_style(newsk, TCP))
+       if (sctp_state(assoc, CLOSED) && sctp_style(newsk, TCP)) {
+               newsk->sk_state = SCTP_SS_CLOSED;
                newsk->sk_shutdown |= RCV_SHUTDOWN;
+       } else {
+               newsk->sk_state = SCTP_SS_ESTABLISHED;
+       }
 
-       newsk->sk_state = SCTP_SS_ESTABLISHED;
        release_sock(newsk);
 }