Staging: sep: return -EFAULT on copy_to_user errors
[safe/jmp/linux-2.6] / net / ipv6 / datagram.c
index f7b535d..7126846 100644 (file)
@@ -21,6 +21,7 @@
 #include <linux/in6.h>
 #include <linux/ipv6.h>
 #include <linux/route.h>
+#include <linux/slab.h>
 
 #include <net/ipv6.h>
 #include <net/ndisc.h>
@@ -98,17 +99,15 @@ ipv4_connected:
                if (err)
                        goto out;
 
-               ipv6_addr_set(&np->daddr, 0, 0, htonl(0x0000ffff), inet->daddr);
+               ipv6_addr_set_v4mapped(inet->inet_daddr, &np->daddr);
 
-               if (ipv6_addr_any(&np->saddr)) {
-                       ipv6_addr_set(&np->saddr, 0, 0, htonl(0x0000ffff),
-                                     inet->saddr);
-               }
+               if (ipv6_addr_any(&np->saddr))
+                       ipv6_addr_set_v4mapped(inet->inet_saddr, &np->saddr);
+
+               if (ipv6_addr_any(&np->rcv_saddr))
+                       ipv6_addr_set_v4mapped(inet->inet_rcv_saddr,
+                                              &np->rcv_saddr);
 
-               if (ipv6_addr_any(&np->rcv_saddr)) {
-                       ipv6_addr_set(&np->rcv_saddr, 0, 0, htonl(0x0000ffff),
-                                     inet->rcv_saddr);
-               }
                goto out;
        }
 
@@ -136,7 +135,7 @@ ipv4_connected:
        ipv6_addr_copy(&np->daddr, daddr);
        np->flow_label = fl.fl6_flowlabel;
 
-       inet->dport = usin->sin6_port;
+       inet->inet_dport = usin->sin6_port;
 
        /*
         *      Check for a route to destination an obtain the
@@ -147,8 +146,9 @@ ipv4_connected:
        ipv6_addr_copy(&fl.fl6_dst, &np->daddr);
        ipv6_addr_copy(&fl.fl6_src, &np->saddr);
        fl.oif = sk->sk_bound_dev_if;
-       fl.fl_ip_dport = inet->dport;
-       fl.fl_ip_sport = inet->sport;
+       fl.mark = sk->sk_mark;
+       fl.fl_ip_dport = inet->inet_dport;
+       fl.fl_ip_sport = inet->inet_sport;
 
        if (!fl.oif && (addr_type&IPV6_ADDR_MULTICAST))
                fl.oif = np->mcast_oif;
@@ -175,7 +175,8 @@ ipv4_connected:
        if (final_p)
                ipv6_addr_copy(&fl.fl6_dst, final_p);
 
-       if ((err = __xfrm_lookup(&dst, &fl, sk, XFRM_LOOKUP_WAIT)) < 0) {
+       err = __xfrm_lookup(sock_net(sk), &dst, &fl, sk, XFRM_LOOKUP_WAIT);
+       if (err < 0) {
                if (err == -EREMOTE)
                        err = ip6_dst_blackhole(sk, &dst, &fl);
                if (err < 0)
@@ -189,7 +190,7 @@ ipv4_connected:
 
        if (ipv6_addr_any(&np->rcv_saddr)) {
                ipv6_addr_copy(&np->rcv_saddr, &fl.fl6_src);
-               inet->rcv_saddr = LOOPBACK4_IPV6;
+               inet->inet_rcv_saddr = LOOPBACK4_IPV6;
        }
 
        ip6_dst_store(sk, dst,
@@ -221,6 +222,8 @@ void ipv6_icmp_error(struct sock *sk, struct sk_buff *skb, int err,
        if (!skb)
                return;
 
+       skb->protocol = htons(ETH_P_IPV6);
+
        serr = SKB_EXT_ERR(skb);
        serr->ee.ee_errno = err;
        serr->ee.ee_origin = SO_EE_ORIGIN_ICMP6;
@@ -254,6 +257,8 @@ void ipv6_local_error(struct sock *sk, int err, struct flowi *fl, u32 info)
        if (!skb)
                return;
 
+       skb->protocol = htons(ETH_P_IPV6);
+
        skb_put(skb, sizeof(struct ipv6hdr));
        skb_reset_network_header(skb);
        iph = ipv6_hdr(skb);
@@ -277,6 +282,45 @@ void ipv6_local_error(struct sock *sk, int err, struct flowi *fl, u32 info)
                kfree_skb(skb);
 }
 
+void ipv6_local_rxpmtu(struct sock *sk, struct flowi *fl, u32 mtu)
+{
+       struct ipv6_pinfo *np = inet6_sk(sk);
+       struct ipv6hdr *iph;
+       struct sk_buff *skb;
+       struct ip6_mtuinfo *mtu_info;
+
+       if (!np->rxopt.bits.rxpmtu)
+               return;
+
+       skb = alloc_skb(sizeof(struct ipv6hdr), GFP_ATOMIC);
+       if (!skb)
+               return;
+
+       skb_put(skb, sizeof(struct ipv6hdr));
+       skb_reset_network_header(skb);
+       iph = ipv6_hdr(skb);
+       ipv6_addr_copy(&iph->daddr, &fl->fl6_dst);
+
+       mtu_info = IP6CBMTU(skb);
+       if (!mtu_info) {
+               kfree_skb(skb);
+               return;
+       }
+
+       mtu_info->ip6m_mtu = mtu;
+       mtu_info->ip6m_addr.sin6_family = AF_INET6;
+       mtu_info->ip6m_addr.sin6_port = 0;
+       mtu_info->ip6m_addr.sin6_flowinfo = 0;
+       mtu_info->ip6m_addr.sin6_scope_id = fl->oif;
+       ipv6_addr_copy(&mtu_info->ip6m_addr.sin6_addr, &ipv6_hdr(skb)->daddr);
+
+       __skb_pull(skb, skb_tail_pointer(skb) - skb->data);
+       skb_reset_transport_header(skb);
+
+       skb = xchg(&np->rxpmtu, skb);
+       kfree_skb(skb);
+}
+
 /*
  *     Handle MSG_ERRQUEUE
  */
@@ -318,7 +362,7 @@ int ipv6_recv_error(struct sock *sk, struct msghdr *msg, int len)
                sin->sin6_flowinfo = 0;
                sin->sin6_port = serr->port;
                sin->sin6_scope_id = 0;
-               if (serr->ee.ee_origin == SO_EE_ORIGIN_ICMP6) {
+               if (skb->protocol == htons(ETH_P_IPV6)) {
                        ipv6_addr_copy(&sin->sin6_addr,
                                  (struct in6_addr *)(nh + serr->addr_offset));
                        if (np->sndflow)
@@ -328,9 +372,8 @@ int ipv6_recv_error(struct sock *sk, struct msghdr *msg, int len)
                        if (ipv6_addr_type(&sin->sin6_addr) & IPV6_ADDR_LINKLOCAL)
                                sin->sin6_scope_id = IP6CB(skb)->iif;
                } else {
-                       ipv6_addr_set(&sin->sin6_addr, 0, 0,
-                                     htonl(0xffff),
-                                     *(__be32 *)(nh + serr->addr_offset));
+                       ipv6_addr_set_v4mapped(*(__be32 *)(nh + serr->addr_offset),
+                                              &sin->sin6_addr);
                }
        }
 
@@ -341,7 +384,7 @@ int ipv6_recv_error(struct sock *sk, struct msghdr *msg, int len)
                sin->sin6_family = AF_INET6;
                sin->sin6_flowinfo = 0;
                sin->sin6_scope_id = 0;
-               if (serr->ee.ee_origin == SO_EE_ORIGIN_ICMP6) {
+               if (skb->protocol == htons(ETH_P_IPV6)) {
                        ipv6_addr_copy(&sin->sin6_addr, &ipv6_hdr(skb)->saddr);
                        if (np->rxopt.all)
                                datagram_recv_ctl(sk, msg, skb);
@@ -350,8 +393,8 @@ int ipv6_recv_error(struct sock *sk, struct msghdr *msg, int len)
                } else {
                        struct inet_sock *inet = inet_sk(sk);
 
-                       ipv6_addr_set(&sin->sin6_addr, 0, 0,
-                                     htonl(0xffff), ip_hdr(skb)->saddr);
+                       ipv6_addr_set_v4mapped(ip_hdr(skb)->saddr,
+                                              &sin->sin6_addr);
                        if (inet->cmsg_flags)
                                ip_cmsg_recv(msg, skb);
                }
@@ -381,6 +424,54 @@ out:
        return err;
 }
 
+/*
+ *     Handle IPV6_RECVPATHMTU
+ */
+int ipv6_recv_rxpmtu(struct sock *sk, struct msghdr *msg, int len)
+{
+       struct ipv6_pinfo *np = inet6_sk(sk);
+       struct sk_buff *skb;
+       struct sockaddr_in6 *sin;
+       struct ip6_mtuinfo mtu_info;
+       int err;
+       int copied;
+
+       err = -EAGAIN;
+       skb = xchg(&np->rxpmtu, NULL);
+       if (skb == NULL)
+               goto out;
+
+       copied = skb->len;
+       if (copied > len) {
+               msg->msg_flags |= MSG_TRUNC;
+               copied = len;
+       }
+       err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
+       if (err)
+               goto out_free_skb;
+
+       sock_recv_timestamp(msg, sk, skb);
+
+       memcpy(&mtu_info, IP6CBMTU(skb), sizeof(mtu_info));
+
+       sin = (struct sockaddr_in6 *)msg->msg_name;
+       if (sin) {
+               sin->sin6_family = AF_INET6;
+               sin->sin6_flowinfo = 0;
+               sin->sin6_port = 0;
+               sin->sin6_scope_id = mtu_info.ip6m_addr.sin6_scope_id;
+               ipv6_addr_copy(&sin->sin6_addr, &mtu_info.ip6m_addr.sin6_addr);
+       }
+
+       put_cmsg(msg, SOL_IPV6, IPV6_PATHMTU, sizeof(mtu_info), &mtu_info);
+
+       err = copied;
+
+out_free_skb:
+       kfree_skb(skb);
+out:
+       return err;
+}
 
 
 int datagram_recv_ctl(struct sock *sk, struct msghdr *msg, struct sk_buff *skb)
@@ -497,7 +588,7 @@ int datagram_recv_ctl(struct sock *sk, struct msghdr *msg, struct sk_buff *skb)
 int datagram_send_ctl(struct net *net,
                      struct msghdr *msg, struct flowi *fl,
                      struct ipv6_txoptions *opt,
-                     int *hlimit, int *tclass)
+                     int *hlimit, int *tclass, int *dontfrag)
 {
        struct in6_pktinfo *src_info;
        struct cmsghdr *cmsg;
@@ -538,12 +629,17 @@ int datagram_send_ctl(struct net *net,
 
                        addr_type = __ipv6_addr_type(&src_info->ipi6_addr);
 
+                       rcu_read_lock();
                        if (fl->oif) {
-                               dev = dev_get_by_index(net, fl->oif);
-                               if (!dev)
+                               dev = dev_get_by_index_rcu(net, fl->oif);
+                               if (!dev) {
+                                       rcu_read_unlock();
                                        return -ENODEV;
-                       } else if (addr_type & IPV6_ADDR_LINKLOCAL)
+                               }
+                       } else if (addr_type & IPV6_ADDR_LINKLOCAL) {
+                               rcu_read_unlock();
                                return -EINVAL;
+                       }
 
                        if (addr_type != IPV6_ADDR_ANY) {
                                int strict = __ipv6_addr_src_scope(addr_type) <= IPV6_ADDR_SCOPE_LINKLOCAL;
@@ -554,8 +650,7 @@ int datagram_send_ctl(struct net *net,
                                        ipv6_addr_copy(&fl->fl6_src, &src_info->ipi6_addr);
                        }
 
-                       if (dev)
-                               dev_put(dev);
+                       rcu_read_unlock();
 
                        if (err)
                                goto exit_f;
@@ -661,6 +756,11 @@ int datagram_send_ctl(struct net *net,
                        switch (rthdr->type) {
 #if defined(CONFIG_IPV6_MIP6) || defined(CONFIG_IPV6_MIP6_MODULE)
                        case IPV6_SRCRT_TYPE_2:
+                               if (rthdr->hdrlen != 2 ||
+                                   rthdr->segments_left != 1) {
+                                       err = -EINVAL;
+                                       goto exit_f;
+                               }
                                break;
 #endif
                        default:
@@ -728,11 +828,30 @@ int datagram_send_ctl(struct net *net,
 
                        break;
                    }
+
+               case IPV6_DONTFRAG:
+                   {
+                       int df;
+
+                       err = -EINVAL;
+                       if (cmsg->cmsg_len != CMSG_LEN(sizeof(int))) {
+                               goto exit_f;
+                       }
+
+                       df = *(int *)CMSG_DATA(cmsg);
+                       if (df < 0 || df > 1)
+                               goto exit_f;
+
+                       err = 0;
+                       *dontfrag = df;
+
+                       break;
+                   }
                default:
                        LIMIT_NETDEBUG(KERN_DEBUG "invalid cmsg type: %d\n",
                                       cmsg->cmsg_type);
                        err = -EINVAL;
-                       break;
+                       goto exit_f;
                }
        }