Merge branches 'pm-cpuidle' and 'pm-em'
[linux-2.6-microblaze.git] / net / ipv6 / ipv6_sockglue.c
index 76f9e41..43a894b 100644 (file)
@@ -136,13 +136,42 @@ static bool setsockopt_needs_rtnl(int optname)
        return false;
 }
 
+static int copy_group_source_from_sockptr(struct group_source_req *greqs,
+               sockptr_t optval, int optlen)
+{
+       if (in_compat_syscall()) {
+               struct compat_group_source_req gr32;
+
+               if (optlen < sizeof(gr32))
+                       return -EINVAL;
+               if (copy_from_sockptr(&gr32, optval, sizeof(gr32)))
+                       return -EFAULT;
+               greqs->gsr_interface = gr32.gsr_interface;
+               greqs->gsr_group = gr32.gsr_group;
+               greqs->gsr_source = gr32.gsr_source;
+       } else {
+               if (optlen < sizeof(*greqs))
+                       return -EINVAL;
+               if (copy_from_sockptr(greqs, optval, sizeof(*greqs)))
+                       return -EFAULT;
+       }
+
+       return 0;
+}
+
 static int do_ipv6_mcast_group_source(struct sock *sk, int optname,
-                                     struct group_source_req *greqs)
+               sockptr_t optval, int optlen)
 {
+       struct group_source_req greqs;
        int omode, add;
+       int ret;
+
+       ret = copy_group_source_from_sockptr(&greqs, optval, optlen);
+       if (ret)
+               return ret;
 
-       if (greqs->gsr_group.ss_family != AF_INET6 ||
-           greqs->gsr_source.ss_family != AF_INET6)
+       if (greqs.gsr_group.ss_family != AF_INET6 ||
+           greqs.gsr_source.ss_family != AF_INET6)
                return -EADDRNOTAVAIL;
 
        if (optname == MCAST_BLOCK_SOURCE) {
@@ -155,8 +184,8 @@ static int do_ipv6_mcast_group_source(struct sock *sk, int optname,
                struct sockaddr_in6 *psin6;
                int retv;
 
-               psin6 = (struct sockaddr_in6 *)&greqs->gsr_group;
-               retv = ipv6_sock_mc_join_ssm(sk, greqs->gsr_interface,
+               psin6 = (struct sockaddr_in6 *)&greqs.gsr_group;
+               retv = ipv6_sock_mc_join_ssm(sk, greqs.gsr_interface,
                                             &psin6->sin6_addr,
                                             MCAST_INCLUDE);
                /* prior join w/ different source is ok */
@@ -168,11 +197,200 @@ static int do_ipv6_mcast_group_source(struct sock *sk, int optname,
                omode = MCAST_INCLUDE;
                add = 0;
        }
-       return ip6_mc_source(add, omode, sk, greqs);
+       return ip6_mc_source(add, omode, sk, &greqs);
+}
+
+static int ipv6_set_mcast_msfilter(struct sock *sk, sockptr_t optval,
+               int optlen)
+{
+       struct group_filter *gsf;
+       int ret;
+
+       if (optlen < GROUP_FILTER_SIZE(0))
+               return -EINVAL;
+       if (optlen > sysctl_optmem_max)
+               return -ENOBUFS;
+
+       gsf = memdup_sockptr(optval, optlen);
+       if (IS_ERR(gsf))
+               return PTR_ERR(gsf);
+
+       /* numsrc >= (4G-140)/128 overflow in 32 bits */
+       ret = -ENOBUFS;
+       if (gsf->gf_numsrc >= 0x1ffffffU ||
+           gsf->gf_numsrc > sysctl_mld_max_msf)
+               goto out_free_gsf;
+
+       ret = -EINVAL;
+       if (GROUP_FILTER_SIZE(gsf->gf_numsrc) > optlen)
+               goto out_free_gsf;
+
+       ret = ip6_mc_msfilter(sk, gsf, gsf->gf_slist);
+out_free_gsf:
+       kfree(gsf);
+       return ret;
+}
+
+static int compat_ipv6_set_mcast_msfilter(struct sock *sk, sockptr_t optval,
+               int optlen)
+{
+       const int size0 = offsetof(struct compat_group_filter, gf_slist);
+       struct compat_group_filter *gf32;
+       void *p;
+       int ret;
+       int n;
+
+       if (optlen < size0)
+               return -EINVAL;
+       if (optlen > sysctl_optmem_max - 4)
+               return -ENOBUFS;
+
+       p = kmalloc(optlen + 4, GFP_KERNEL);
+       if (!p)
+               return -ENOMEM;
+
+       gf32 = p + 4; /* we want ->gf_group and ->gf_slist aligned */
+       ret = -EFAULT;
+       if (copy_from_sockptr(gf32, optval, optlen))
+               goto out_free_p;
+
+       /* numsrc >= (4G-140)/128 overflow in 32 bits */
+       ret = -ENOBUFS;
+       n = gf32->gf_numsrc;
+       if (n >= 0x1ffffffU || n > sysctl_mld_max_msf)
+               goto out_free_p;
+
+       ret = -EINVAL;
+       if (offsetof(struct compat_group_filter, gf_slist[n]) > optlen)
+               goto out_free_p;
+
+       ret = ip6_mc_msfilter(sk, &(struct group_filter){
+                       .gf_interface = gf32->gf_interface,
+                       .gf_group = gf32->gf_group,
+                       .gf_fmode = gf32->gf_fmode,
+                       .gf_numsrc = gf32->gf_numsrc}, gf32->gf_slist);
+
+out_free_p:
+       kfree(p);
+       return ret;
+}
+
+static int ipv6_mcast_join_leave(struct sock *sk, int optname,
+               sockptr_t optval, int optlen)
+{
+       struct sockaddr_in6 *psin6;
+       struct group_req greq;
+
+       if (optlen < sizeof(greq))
+               return -EINVAL;
+       if (copy_from_sockptr(&greq, optval, sizeof(greq)))
+               return -EFAULT;
+
+       if (greq.gr_group.ss_family != AF_INET6)
+               return -EADDRNOTAVAIL;
+       psin6 = (struct sockaddr_in6 *)&greq.gr_group;
+       if (optname == MCAST_JOIN_GROUP)
+               return ipv6_sock_mc_join(sk, greq.gr_interface,
+                                        &psin6->sin6_addr);
+       return ipv6_sock_mc_drop(sk, greq.gr_interface, &psin6->sin6_addr);
+}
+
+static int compat_ipv6_mcast_join_leave(struct sock *sk, int optname,
+               sockptr_t optval, int optlen)
+{
+       struct compat_group_req gr32;
+       struct sockaddr_in6 *psin6;
+
+       if (optlen < sizeof(gr32))
+               return -EINVAL;
+       if (copy_from_sockptr(&gr32, optval, sizeof(gr32)))
+               return -EFAULT;
+
+       if (gr32.gr_group.ss_family != AF_INET6)
+               return -EADDRNOTAVAIL;
+       psin6 = (struct sockaddr_in6 *)&gr32.gr_group;
+       if (optname == MCAST_JOIN_GROUP)
+               return ipv6_sock_mc_join(sk, gr32.gr_interface,
+                                       &psin6->sin6_addr);
+       return ipv6_sock_mc_drop(sk, gr32.gr_interface, &psin6->sin6_addr);
+}
+
+static int ipv6_set_opt_hdr(struct sock *sk, int optname, sockptr_t optval,
+               int optlen)
+{
+       struct ipv6_pinfo *np = inet6_sk(sk);
+       struct ipv6_opt_hdr *new = NULL;
+       struct net *net = sock_net(sk);
+       struct ipv6_txoptions *opt;
+       int err;
+
+       /* hop-by-hop / destination options are privileged option */
+       if (optname != IPV6_RTHDR && !ns_capable(net->user_ns, CAP_NET_RAW))
+               return -EPERM;
+
+       /* remove any sticky options header with a zero option
+        * length, per RFC3542.
+        */
+       if (optlen > 0) {
+               if (sockptr_is_null(optval))
+                       return -EINVAL;
+               if (optlen < sizeof(struct ipv6_opt_hdr) ||
+                   optlen & 0x7 ||
+                   optlen > 8 * 255)
+                       return -EINVAL;
+
+               new = memdup_sockptr(optval, optlen);
+               if (IS_ERR(new))
+                       return PTR_ERR(new);
+               if (unlikely(ipv6_optlen(new) > optlen)) {
+                       kfree(new);
+                       return -EINVAL;
+               }
+       }
+
+       opt = rcu_dereference_protected(np->opt, lockdep_sock_is_held(sk));
+       opt = ipv6_renew_options(sk, opt, optname, new);
+       kfree(new);
+       if (IS_ERR(opt))
+               return PTR_ERR(opt);
+
+       /* routing header option needs extra check */
+       err = -EINVAL;
+       if (optname == IPV6_RTHDR && opt && opt->srcrt) {
+               struct ipv6_rt_hdr *rthdr = opt->srcrt;
+               switch (rthdr->type) {
+#if IS_ENABLED(CONFIG_IPV6_MIP6)
+               case IPV6_SRCRT_TYPE_2:
+                       if (rthdr->hdrlen != 2 || rthdr->segments_left != 1)
+                               goto sticky_done;
+                       break;
+#endif
+               case IPV6_SRCRT_TYPE_4:
+               {
+                       struct ipv6_sr_hdr *srh =
+                               (struct ipv6_sr_hdr *)opt->srcrt;
+
+                       if (!seg6_validate_srh(srh, optlen, false))
+                               goto sticky_done;
+                       break;
+               }
+               default:
+                       goto sticky_done;
+               }
+       }
+
+       err = 0;
+       opt = ipv6_update_options(sk, opt);
+sticky_done:
+       if (opt) {
+               atomic_sub(opt->tot_len, &sk->sk_omem_alloc);
+               txopt_put(opt);
+       }
+       return err;
 }
 
 static int do_ipv6_setsockopt(struct sock *sk, int level, int optname,
-                   char __user *optval, unsigned int optlen)
+                  sockptr_t optval, unsigned int optlen)
 {
        struct ipv6_pinfo *np = inet6_sk(sk);
        struct net *net = sock_net(sk);
@@ -180,11 +398,11 @@ static int do_ipv6_setsockopt(struct sock *sk, int level, int optname,
        int retv = -ENOPROTOOPT;
        bool needs_rtnl = setsockopt_needs_rtnl(optname);
 
-       if (!optval)
+       if (sockptr_is_null(optval))
                val = 0;
        else {
                if (optlen >= sizeof(int)) {
-                       if (get_user(val, (int __user *) optval))
+                       if (copy_from_sockptr(&val, optval, sizeof(val)))
                                return -EFAULT;
                } else
                        val = 0;
@@ -436,82 +654,8 @@ static int do_ipv6_setsockopt(struct sock *sk, int level, int optname,
        case IPV6_RTHDRDSTOPTS:
        case IPV6_RTHDR:
        case IPV6_DSTOPTS:
-       {
-               struct ipv6_txoptions *opt;
-               struct ipv6_opt_hdr *new = NULL;
-
-               /* hop-by-hop / destination options are privileged option */
-               retv = -EPERM;
-               if (optname != IPV6_RTHDR && !ns_capable(net->user_ns, CAP_NET_RAW))
-                       break;
-
-               /* remove any sticky options header with a zero option
-                * length, per RFC3542.
-                */
-               if (optlen == 0)
-                       optval = NULL;
-               else if (!optval)
-                       goto e_inval;
-               else if (optlen < sizeof(struct ipv6_opt_hdr) ||
-                        optlen & 0x7 || optlen > 8 * 255)
-                       goto e_inval;
-               else {
-                       new = memdup_user(optval, optlen);
-                       if (IS_ERR(new)) {
-                               retv = PTR_ERR(new);
-                               break;
-                       }
-                       if (unlikely(ipv6_optlen(new) > optlen)) {
-                               kfree(new);
-                               goto e_inval;
-                       }
-               }
-
-               opt = rcu_dereference_protected(np->opt,
-                                               lockdep_sock_is_held(sk));
-               opt = ipv6_renew_options(sk, opt, optname, new);
-               kfree(new);
-               if (IS_ERR(opt)) {
-                       retv = PTR_ERR(opt);
-                       break;
-               }
-
-               /* routing header option needs extra check */
-               retv = -EINVAL;
-               if (optname == IPV6_RTHDR && opt && opt->srcrt) {
-                       struct ipv6_rt_hdr *rthdr = opt->srcrt;
-                       switch (rthdr->type) {
-#if IS_ENABLED(CONFIG_IPV6_MIP6)
-                       case IPV6_SRCRT_TYPE_2:
-                               if (rthdr->hdrlen != 2 ||
-                                   rthdr->segments_left != 1)
-                                       goto sticky_done;
-
-                               break;
-#endif
-                       case IPV6_SRCRT_TYPE_4:
-                       {
-                               struct ipv6_sr_hdr *srh = (struct ipv6_sr_hdr *)
-                                                         opt->srcrt;
-
-                               if (!seg6_validate_srh(srh, optlen, false))
-                                       goto sticky_done;
-                               break;
-                       }
-                       default:
-                               goto sticky_done;
-                       }
-               }
-
-               retv = 0;
-               opt = ipv6_update_options(sk, opt);
-sticky_done:
-               if (opt) {
-                       atomic_sub(opt->tot_len, &sk->sk_omem_alloc);
-                       txopt_put(opt);
-               }
+               retv = ipv6_set_opt_hdr(sk, optname, optval, optlen);
                break;
-       }
 
        case IPV6_PKTINFO:
        {
@@ -519,12 +663,13 @@ sticky_done:
 
                if (optlen == 0)
                        goto e_inval;
-               else if (optlen < sizeof(struct in6_pktinfo) || !optval)
+               else if (optlen < sizeof(struct in6_pktinfo) ||
+                        sockptr_is_null(optval))
                        goto e_inval;
 
-               if (copy_from_user(&pkt, optval, sizeof(struct in6_pktinfo))) {
-                               retv = -EFAULT;
-                               break;
+               if (copy_from_sockptr(&pkt, optval, sizeof(pkt))) {
+                       retv = -EFAULT;
+                       break;
                }
                if (!sk_dev_equal_l3scope(sk, pkt.ipi6_ifindex))
                        goto e_inval;
@@ -565,7 +710,7 @@ sticky_done:
                refcount_set(&opt->refcnt, 1);
                opt->tot_len = sizeof(*opt) + optlen;
                retv = -EFAULT;
-               if (copy_from_user(opt+1, optval, optlen))
+               if (copy_from_sockptr(opt + 1, optval, optlen))
                        goto done;
 
                msg.msg_controllen = optlen;
@@ -687,7 +832,7 @@ done:
                        break;
 
                retv = -EFAULT;
-               if (copy_from_user(&mreq, optval, sizeof(struct ipv6_mreq)))
+               if (copy_from_sockptr(&mreq, optval, sizeof(struct ipv6_mreq)))
                        break;
 
                if (optname == IPV6_ADD_MEMBERSHIP)
@@ -705,7 +850,7 @@ done:
                        goto e_inval;
 
                retv = -EFAULT;
-               if (copy_from_user(&mreq, optval, sizeof(struct ipv6_mreq)))
+               if (copy_from_sockptr(&mreq, optval, sizeof(struct ipv6_mreq)))
                        break;
 
                if (optname == IPV6_JOIN_ANYCAST)
@@ -723,77 +868,26 @@ done:
 
        case MCAST_JOIN_GROUP:
        case MCAST_LEAVE_GROUP:
-       {
-               struct group_req greq;
-               struct sockaddr_in6 *psin6;
-
-               if (optlen < sizeof(struct group_req))
-                       goto e_inval;
-
-               retv = -EFAULT;
-               if (copy_from_user(&greq, optval, sizeof(struct group_req)))
-                       break;
-               if (greq.gr_group.ss_family != AF_INET6) {
-                       retv = -EADDRNOTAVAIL;
-                       break;
-               }
-               psin6 = (struct sockaddr_in6 *)&greq.gr_group;
-               if (optname == MCAST_JOIN_GROUP)
-                       retv = ipv6_sock_mc_join(sk, greq.gr_interface,
-                                                &psin6->sin6_addr);
+               if (in_compat_syscall())
+                       retv = compat_ipv6_mcast_join_leave(sk, optname, optval,
+                                                           optlen);
                else
-                       retv = ipv6_sock_mc_drop(sk, greq.gr_interface,
-                                                &psin6->sin6_addr);
+                       retv = ipv6_mcast_join_leave(sk, optname, optval,
+                                                    optlen);
                break;
-       }
        case MCAST_JOIN_SOURCE_GROUP:
        case MCAST_LEAVE_SOURCE_GROUP:
        case MCAST_BLOCK_SOURCE:
        case MCAST_UNBLOCK_SOURCE:
-       {
-               struct group_source_req greqs;
-
-               if (optlen < sizeof(struct group_source_req))
-                       goto e_inval;
-               if (copy_from_user(&greqs, optval, sizeof(greqs))) {
-                       retv = -EFAULT;
-                       break;
-               }
-               retv = do_ipv6_mcast_group_source(sk, optname, &greqs);
+               retv = do_ipv6_mcast_group_source(sk, optname, optval, optlen);
                break;
-       }
        case MCAST_MSFILTER:
-       {
-               struct group_filter *gsf;
-
-               if (optlen < GROUP_FILTER_SIZE(0))
-                       goto e_inval;
-               if (optlen > sysctl_optmem_max) {
-                       retv = -ENOBUFS;
-                       break;
-               }
-               gsf = memdup_user(optval, optlen);
-               if (IS_ERR(gsf)) {
-                       retv = PTR_ERR(gsf);
-                       break;
-               }
-               /* numsrc >= (4G-140)/128 overflow in 32 bits */
-               if (gsf->gf_numsrc >= 0x1ffffffU ||
-                   gsf->gf_numsrc > sysctl_mld_max_msf) {
-                       kfree(gsf);
-                       retv = -ENOBUFS;
-                       break;
-               }
-               if (GROUP_FILTER_SIZE(gsf->gf_numsrc) > optlen) {
-                       kfree(gsf);
-                       retv = -EINVAL;
-                       break;
-               }
-               retv = ip6_mc_msfilter(sk, gsf, gsf->gf_slist);
-               kfree(gsf);
-
+               if (in_compat_syscall())
+                       retv = compat_ipv6_set_mcast_msfilter(sk, optval,
+                                                             optlen);
+               else
+                       retv = ipv6_set_mcast_msfilter(sk, optval, optlen);
                break;
-       }
        case IPV6_ROUTER_ALERT:
                if (optlen < sizeof(int))
                        goto e_inval;
@@ -872,6 +966,14 @@ done:
                np->rxopt.bits.recvfragsize = valbool;
                retv = 0;
                break;
+       case IPV6_RECVERR_RFC4884:
+               if (optlen < sizeof(int))
+                       goto e_inval;
+               if (val < 0 || val > 1)
+                       goto e_inval;
+               np->recverr_rfc4884 = valbool;
+               retv = 0;
+               break;
        }
 
        release_sock(sk);
@@ -887,8 +989,8 @@ e_inval:
        return -EINVAL;
 }
 
-int ipv6_setsockopt(struct sock *sk, int level, int optname,
-                   char __user *optval, unsigned int optlen)
+int ipv6_setsockopt(struct sock *sk, int level, int optname, sockptr_t optval,
+                   unsigned int optlen)
 {
        int err;
 
@@ -909,140 +1011,6 @@ int ipv6_setsockopt(struct sock *sk, int level, int optname,
 }
 EXPORT_SYMBOL(ipv6_setsockopt);
 
-#ifdef CONFIG_COMPAT
-int compat_ipv6_setsockopt(struct sock *sk, int level, int optname,
-                          char __user *optval, unsigned int optlen)
-{
-       int err;
-
-       if (level == SOL_IP && sk->sk_type != SOCK_RAW) {
-               if (udp_prot.compat_setsockopt != NULL)
-                       return udp_prot.compat_setsockopt(sk, level, optname,
-                                                         optval, optlen);
-               return udp_prot.setsockopt(sk, level, optname, optval, optlen);
-       }
-
-       if (level != SOL_IPV6)
-               return -ENOPROTOOPT;
-
-       switch (optname) {
-       case MCAST_JOIN_GROUP:
-       case MCAST_LEAVE_GROUP:
-       {
-               struct compat_group_req __user *gr32 = (void __user *)optval;
-               struct group_req greq;
-               struct sockaddr_in6 *psin6 = (struct sockaddr_in6 *)&greq.gr_group;
-
-               if (optlen < sizeof(struct compat_group_req))
-                       return -EINVAL;
-
-               if (get_user(greq.gr_interface, &gr32->gr_interface) ||
-                   copy_from_user(&greq.gr_group, &gr32->gr_group,
-                               sizeof(greq.gr_group)))
-                       return -EFAULT;
-
-               if (greq.gr_group.ss_family != AF_INET6)
-                       return -EADDRNOTAVAIL;
-
-               rtnl_lock();
-               lock_sock(sk);
-               if (optname == MCAST_JOIN_GROUP)
-                       err = ipv6_sock_mc_join(sk, greq.gr_interface,
-                                                &psin6->sin6_addr);
-               else
-                       err = ipv6_sock_mc_drop(sk, greq.gr_interface,
-                                                &psin6->sin6_addr);
-               release_sock(sk);
-               rtnl_unlock();
-               return err;
-       }
-       case MCAST_JOIN_SOURCE_GROUP:
-       case MCAST_LEAVE_SOURCE_GROUP:
-       case MCAST_BLOCK_SOURCE:
-       case MCAST_UNBLOCK_SOURCE:
-       {
-               struct compat_group_source_req __user *gsr32 = (void __user *)optval;
-               struct group_source_req greqs;
-
-               if (optlen < sizeof(struct compat_group_source_req))
-                       return -EINVAL;
-
-               if (get_user(greqs.gsr_interface, &gsr32->gsr_interface) ||
-                   copy_from_user(&greqs.gsr_group, &gsr32->gsr_group,
-                               sizeof(greqs.gsr_group)) ||
-                   copy_from_user(&greqs.gsr_source, &gsr32->gsr_source,
-                               sizeof(greqs.gsr_source)))
-                       return -EFAULT;
-
-               rtnl_lock();
-               lock_sock(sk);
-               err = do_ipv6_mcast_group_source(sk, optname, &greqs);
-               release_sock(sk);
-               rtnl_unlock();
-               return err;
-       }
-       case MCAST_MSFILTER:
-       {
-               const int size0 = offsetof(struct compat_group_filter, gf_slist);
-               struct compat_group_filter *gf32;
-               void *p;
-               int n;
-
-               if (optlen < size0)
-                       return -EINVAL;
-               if (optlen > sysctl_optmem_max - 4)
-                       return -ENOBUFS;
-
-               p = kmalloc(optlen + 4, GFP_KERNEL);
-               if (!p)
-                       return -ENOMEM;
-
-               gf32 = p + 4; /* we want ->gf_group and ->gf_slist aligned */
-               if (copy_from_user(gf32, optval, optlen)) {
-                       err = -EFAULT;
-                       goto mc_msf_out;
-               }
-
-               n = gf32->gf_numsrc;
-               /* numsrc >= (4G-140)/128 overflow in 32 bits */
-               if (n >= 0x1ffffffU ||
-                   n > sysctl_mld_max_msf) {
-                       err = -ENOBUFS;
-                       goto mc_msf_out;
-               }
-               if (offsetof(struct compat_group_filter, gf_slist[n]) > optlen) {
-                       err = -EINVAL;
-                       goto mc_msf_out;
-               }
-
-               rtnl_lock();
-               lock_sock(sk);
-               err = ip6_mc_msfilter(sk, &(struct group_filter){
-                               .gf_interface = gf32->gf_interface,
-                               .gf_group = gf32->gf_group,
-                               .gf_fmode = gf32->gf_fmode,
-                               .gf_numsrc = gf32->gf_numsrc}, gf32->gf_slist);
-               release_sock(sk);
-               rtnl_unlock();
-mc_msf_out:
-               kfree(p);
-               return err;
-       }
-       }
-
-       err = do_ipv6_setsockopt(sk, level, optname, optval, optlen);
-#ifdef CONFIG_NETFILTER
-       /* we need to exclude all possible ENOPROTOOPTs except default case */
-       if (err == -ENOPROTOOPT && optname != IPV6_IPSEC_POLICY &&
-           optname != IPV6_XFRM_POLICY)
-               err = compat_nf_setsockopt(sk, PF_INET6, optname, optval,
-                                          optlen);
-#endif
-       return err;
-}
-EXPORT_SYMBOL(compat_ipv6_setsockopt);
-#endif
-
 static int ipv6_getsockopt_sticky(struct sock *sk, struct ipv6_txoptions *opt,
                                  int optname, char __user *optval, int len)
 {
@@ -1077,6 +1045,75 @@ static int ipv6_getsockopt_sticky(struct sock *sk, struct ipv6_txoptions *opt,
        return len;
 }
 
+static int ipv6_get_msfilter(struct sock *sk, void __user *optval,
+               int __user *optlen, int len)
+{
+       const int size0 = offsetof(struct group_filter, gf_slist);
+       struct group_filter __user *p = optval;
+       struct group_filter gsf;
+       int num;
+       int err;
+
+       if (len < size0)
+               return -EINVAL;
+       if (copy_from_user(&gsf, p, size0))
+               return -EFAULT;
+       if (gsf.gf_group.ss_family != AF_INET6)
+               return -EADDRNOTAVAIL;
+       num = gsf.gf_numsrc;
+       lock_sock(sk);
+       err = ip6_mc_msfget(sk, &gsf, p->gf_slist);
+       if (!err) {
+               if (num > gsf.gf_numsrc)
+                       num = gsf.gf_numsrc;
+               if (put_user(GROUP_FILTER_SIZE(num), optlen) ||
+                   copy_to_user(p, &gsf, size0))
+                       err = -EFAULT;
+       }
+       release_sock(sk);
+       return err;
+}
+
+static int compat_ipv6_get_msfilter(struct sock *sk, void __user *optval,
+               int __user *optlen)
+{
+       const int size0 = offsetof(struct compat_group_filter, gf_slist);
+       struct compat_group_filter __user *p = optval;
+       struct compat_group_filter gf32;
+       struct group_filter gf;
+       int len, err;
+       int num;
+
+       if (get_user(len, optlen))
+               return -EFAULT;
+       if (len < size0)
+               return -EINVAL;
+
+       if (copy_from_user(&gf32, p, size0))
+               return -EFAULT;
+       gf.gf_interface = gf32.gf_interface;
+       gf.gf_fmode = gf32.gf_fmode;
+       num = gf.gf_numsrc = gf32.gf_numsrc;
+       gf.gf_group = gf32.gf_group;
+
+       if (gf.gf_group.ss_family != AF_INET6)
+               return -EADDRNOTAVAIL;
+
+       lock_sock(sk);
+       err = ip6_mc_msfget(sk, &gf, p->gf_slist);
+       release_sock(sk);
+       if (err)
+               return err;
+       if (num > gf.gf_numsrc)
+               num = gf.gf_numsrc;
+       len = GROUP_FILTER_SIZE(num) - (sizeof(gf)-sizeof(gf32));
+       if (put_user(len, optlen) ||
+           put_user(gf.gf_fmode, &p->gf_fmode) ||
+           put_user(gf.gf_numsrc, &p->gf_numsrc))
+               return -EFAULT;
+       return 0;
+}
+
 static int do_ipv6_getsockopt(struct sock *sk, int level, int optname,
                    char __user *optval, int __user *optlen, unsigned int flags)
 {
@@ -1100,33 +1137,9 @@ static int do_ipv6_getsockopt(struct sock *sk, int level, int optname,
                val = sk->sk_family;
                break;
        case MCAST_MSFILTER:
-       {
-               struct group_filter __user *p = (void __user *)optval;
-               struct group_filter gsf;
-               const int size0 = offsetof(struct group_filter, gf_slist);
-               int num;
-               int err;
-
-               if (len < size0)
-                       return -EINVAL;
-               if (copy_from_user(&gsf, p, size0))
-                       return -EFAULT;
-               if (gsf.gf_group.ss_family != AF_INET6)
-                       return -EADDRNOTAVAIL;
-               num = gsf.gf_numsrc;
-               lock_sock(sk);
-               err = ip6_mc_msfget(sk, &gsf, p->gf_slist);
-               if (!err) {
-                       if (num > gsf.gf_numsrc)
-                               num = gsf.gf_numsrc;
-                       if (put_user(GROUP_FILTER_SIZE(num), optlen) ||
-                           copy_to_user(p, &gsf, size0))
-                               err = -EFAULT;
-               }
-               release_sock(sk);
-               return err;
-       }
-
+               if (in_compat_syscall())
+                       return compat_ipv6_get_msfilter(sk, optval, optlen);
+               return ipv6_get_msfilter(sk, optval, optlen, len);
        case IPV6_2292PKTOPTIONS:
        {
                struct msghdr msg;
@@ -1435,6 +1448,10 @@ static int do_ipv6_getsockopt(struct sock *sk, int level, int optname,
                val = np->rtalert_isolate;
                break;
 
+       case IPV6_RECVERR_RFC4884:
+               val = np->recverr_rfc4884;
+               break;
+
        default:
                return -ENOPROTOOPT;
        }
@@ -1474,78 +1491,3 @@ int ipv6_getsockopt(struct sock *sk, int level, int optname,
        return err;
 }
 EXPORT_SYMBOL(ipv6_getsockopt);
-
-#ifdef CONFIG_COMPAT
-int compat_ipv6_getsockopt(struct sock *sk, int level, int optname,
-                          char __user *optval, int __user *optlen)
-{
-       int err;
-
-       if (level == SOL_IP && sk->sk_type != SOCK_RAW) {
-               if (udp_prot.compat_getsockopt != NULL)
-                       return udp_prot.compat_getsockopt(sk, level, optname,
-                                                         optval, optlen);
-               return udp_prot.getsockopt(sk, level, optname, optval, optlen);
-       }
-
-       if (level != SOL_IPV6)
-               return -ENOPROTOOPT;
-
-       if (optname == MCAST_MSFILTER) {
-               const int size0 = offsetof(struct compat_group_filter, gf_slist);
-               struct compat_group_filter __user *p = (void __user *)optval;
-               struct compat_group_filter gf32;
-               struct group_filter gf;
-               int ulen, err;
-               int num;
-
-               if (get_user(ulen, optlen))
-                       return -EFAULT;
-
-               if (ulen < size0)
-                       return -EINVAL;
-
-               if (copy_from_user(&gf32, p, size0))
-                       return -EFAULT;
-
-               gf.gf_interface = gf32.gf_interface;
-               gf.gf_fmode = gf32.gf_fmode;
-               num = gf.gf_numsrc = gf32.gf_numsrc;
-               gf.gf_group = gf32.gf_group;
-
-               if (gf.gf_group.ss_family != AF_INET6)
-                       return -EADDRNOTAVAIL;
-               lock_sock(sk);
-               err = ip6_mc_msfget(sk, &gf, p->gf_slist);
-               release_sock(sk);
-               if (err)
-                       return err;
-               if (num > gf.gf_numsrc)
-                       num = gf.gf_numsrc;
-               ulen = GROUP_FILTER_SIZE(num) - (sizeof(gf)-sizeof(gf32));
-               if (put_user(ulen, optlen) ||
-                   put_user(gf.gf_fmode, &p->gf_fmode) ||
-                   put_user(gf.gf_numsrc, &p->gf_numsrc))
-                       return -EFAULT;
-               return 0;
-       }
-
-       err = do_ipv6_getsockopt(sk, level, optname, optval, optlen,
-                                MSG_CMSG_COMPAT);
-#ifdef CONFIG_NETFILTER
-       /* we need to exclude all possible ENOPROTOOPTs except default case */
-       if (err == -ENOPROTOOPT && optname != IPV6_2292PKTOPTIONS) {
-               int len;
-
-               if (get_user(len, optlen))
-                       return -EFAULT;
-
-               err = compat_nf_getsockopt(sk, PF_INET6, optname, optval, &len);
-               if (err >= 0)
-                       err = put_user(len, optlen);
-       }
-#endif
-       return err;
-}
-EXPORT_SYMBOL(compat_ipv6_getsockopt);
-#endif