udp: properly support MSG_PEEK with truncated buffers
[pandora-kernel.git] / net / ipv6 / udp.c
index 846f475..03a7ed1 100644 (file)
@@ -340,21 +340,19 @@ int udpv6_recvmsg(struct kiocb *iocb, struct sock *sk,
        struct ipv6_pinfo *np = inet6_sk(sk);
        struct inet_sock *inet = inet_sk(sk);
        struct sk_buff *skb;
-       unsigned int ulen;
+       unsigned int ulen, copied;
        int peeked;
        int err;
        int is_udplite = IS_UDPLITE(sk);
+       bool checksum_valid = false;
        int is_udp4;
        bool slow;
 
-       if (addr_len)
-               *addr_len=sizeof(struct sockaddr_in6);
-
        if (flags & MSG_ERRQUEUE)
-               return ipv6_recv_error(sk, msg, len);
+               return ipv6_recv_error(sk, msg, len, addr_len);
 
        if (np->rxpmtu && np->rxopt.bits.rxpmtu)
-               return ipv6_recv_rxpmtu(sk, msg, len);
+               return ipv6_recv_rxpmtu(sk, msg, len, addr_len);
 
 try_again:
        skb = __skb_recv_datagram(sk, flags | (noblock ? MSG_DONTWAIT : 0),
@@ -363,9 +361,10 @@ try_again:
                goto out;
 
        ulen = skb->len - sizeof(struct udphdr);
-       if (len > ulen)
-               len = ulen;
-       else if (len < ulen)
+       copied = len;
+       if (copied > ulen)
+               copied = ulen;
+       else if (copied < ulen)
                msg->msg_flags |= MSG_TRUNC;
 
        is_udp4 = (skb->protocol == htons(ETH_P_IP));
@@ -376,14 +375,15 @@ try_again:
         * coverage checksum (UDP-Lite), do it before the copy.
         */
 
-       if (len < ulen || UDP_SKB_CB(skb)->partial_cov) {
-               if (udp_lib_checksum_complete(skb))
+       if (copied < ulen || UDP_SKB_CB(skb)->partial_cov) {
+               checksum_valid = !udp_lib_checksum_complete(skb);
+               if (!checksum_valid)
                        goto csum_copy_err;
        }
 
-       if (skb_csum_unnecessary(skb))
+       if (checksum_valid || skb_csum_unnecessary(skb))
                err = skb_copy_datagram_iovec(skb, sizeof(struct udphdr),
-                                             msg->msg_iov,len);
+                                             msg->msg_iov, copied       );
        else {
                err = skb_copy_and_csum_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov);
                if (err == -EINVAL)
@@ -422,7 +422,7 @@ try_again:
                        if (ipv6_addr_type(&sin6->sin6_addr) & IPV6_ADDR_LINKLOCAL)
                                sin6->sin6_scope_id = IP6CB(skb)->iif;
                }
-
+               *addr_len = sizeof(*sin6);
        }
        if (is_udp4) {
                if (inet->cmsg_flags)
@@ -432,7 +432,7 @@ try_again:
                        datagram_recv_ctl(sk, msg, skb);
        }
 
-       err = len;
+       err = copied;
        if (flags & MSG_TRUNC)
                err = ulen;
 
@@ -453,10 +453,8 @@ csum_copy_err:
        }
        unlock_sock_fast(sk, slow);
 
-       if (noblock)
-               return -EAGAIN;
-
-       /* starting over for a new packet */
+       /* starting over for a new packet, but check if we need to yield */
+       cond_resched();
        msg->msg_flags &= ~MSG_TRUNC;
        goto try_again;
 }
@@ -892,11 +890,16 @@ static int udp_v6_push_pending_frames(struct sock *sk)
        struct udphdr *uh;
        struct udp_sock  *up = udp_sk(sk);
        struct inet_sock *inet = inet_sk(sk);
-       struct flowi6 *fl6 = &inet->cork.fl.u.ip6;
+       struct flowi6 *fl6;
        int err = 0;
        int is_udplite = IS_UDPLITE(sk);
        __wsum csum = 0;
 
+       if (up->pending == AF_INET)
+               return udp_push_pending_frames(sk);
+
+       fl6 = &inet->cork.fl.u.ip6;
+
        /* Grab the skbuff where UDP header space exists. */
        if ((skb = skb_peek(&sk->sk_write_queue)) == NULL)
                goto out;
@@ -952,6 +955,7 @@ int udpv6_sendmsg(struct kiocb *iocb, struct sock *sk,
        struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) msg->msg_name;
        struct in6_addr *daddr, *final_p, final;
        struct ipv6_txoptions *opt = NULL;
+       struct ipv6_txoptions *opt_to_free = NULL;
        struct ip6_flowlabel *flowlabel = NULL;
        struct flowi6 fl6;
        struct dst_entry *dst;
@@ -1105,8 +1109,10 @@ do_udp_sendmsg:
                        opt = NULL;
                connected = 0;
        }
-       if (opt == NULL)
-               opt = np->opt;
+       if (!opt) {
+               opt = txopt_get(np);
+               opt_to_free = opt;
+       }
        if (flowlabel)
                opt = fl6_merge_options(&opt_space, flowlabel, opt);
        opt = ipv6_fixup_options(&opt_space, opt);
@@ -1206,6 +1212,7 @@ do_append_data:
 out:
        dst_release(dst);
        fl6_sock_release(flowlabel);
+       txopt_put(opt_to_free);
        if (!err)
                return len;
        /*
@@ -1359,7 +1366,7 @@ static struct sk_buff *udp6_ufo_fragment(struct sk_buff *skb, u32 features)
        fptr = (struct frag_hdr *)(skb_network_header(skb) + unfrag_ip6hlen);
        fptr->nexthdr = nexthdr;
        fptr->reserved = 0;
-       ipv6_select_ident(fptr, (struct rt6_info *)skb_dst(skb));
+       fptr->identification = skb_shinfo(skb)->ip6_frag_id;
 
        /* Fragment the skb. ipv6 header and the remaining fields of the
         * fragment header are updated in ipv6_gso_segment()
@@ -1452,6 +1459,17 @@ void udp6_proc_exit(struct net *net) {
 }
 #endif /* CONFIG_PROC_FS */
 
+void udp_v6_clear_sk(struct sock *sk, int size)
+{
+       struct inet_sock *inet = inet_sk(sk);
+
+       /* we do not want to clear pinet6 field, because of RCU lookups */
+       sk_prot_clear_portaddr_nulls(sk, offsetof(struct inet_sock, pinet6));
+
+       size -= offsetof(struct inet_sock, pinet6) + sizeof(inet->pinet6);
+       memset(&inet->pinet6 + 1, 0, size);
+}
+
 /* ------------------------------------------------------------------------ */
 
 struct proto udpv6_prot = {
@@ -1482,7 +1500,7 @@ struct proto udpv6_prot = {
        .compat_setsockopt = compat_udpv6_setsockopt,
        .compat_getsockopt = compat_udpv6_getsockopt,
 #endif
-       .clear_sk          = sk_prot_clear_portaddr_nulls,
+       .clear_sk          = udp_v6_clear_sk,
 };
 
 static struct inet_protosw udpv6_protosw = {