[IPV6]: Added GSO support for TCPv6
[linux-2.6.git] / net / ipv6 / ipv6_sockglue.c
index 988eac5..25f8bf8 100644 (file)
 
 DEFINE_SNMP_STAT(struct ipstats_mib, ipv6_statistics) __read_mostly;
 
+static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb, int features)
+{
+       struct sk_buff *segs = ERR_PTR(-EINVAL);
+       struct ipv6hdr *ipv6h;
+       struct inet6_protocol *ops;
+       int proto;
+
+       if (unlikely(!pskb_may_pull(skb, sizeof(*ipv6h))))
+               goto out;
+
+       ipv6h = skb->nh.ipv6h;
+       proto = ipv6h->nexthdr;
+       __skb_pull(skb, sizeof(*ipv6h));
+
+       rcu_read_lock();
+       for (;;) {
+               struct ipv6_opt_hdr *opth;
+               int len;
+
+               if (proto != NEXTHDR_HOP) {
+                       ops = rcu_dereference(inet6_protos[proto]);
+
+                       if (unlikely(!ops))
+                               goto unlock;
+
+                       if (!(ops->flags & INET6_PROTO_GSO_EXTHDR))
+                               break;
+               }
+
+               if (unlikely(!pskb_may_pull(skb, 8)))
+                       goto unlock;
+
+               opth = (void *)skb->data;
+               len = opth->hdrlen * 8 + 8;
+
+               if (unlikely(!pskb_may_pull(skb, len)))
+                       goto unlock;
+
+               proto = opth->nexthdr;
+               __skb_pull(skb, len);
+       }
+
+       skb->h.raw = skb->data;
+       if (likely(ops->gso_segment))
+               segs = ops->gso_segment(skb, features);
+
+unlock:
+       rcu_read_unlock();
+
+       if (unlikely(IS_ERR(segs)))
+               goto out;
+
+       for (skb = segs; skb; skb = skb->next) {
+               ipv6h = skb->nh.ipv6h;
+               ipv6h->payload_len = htons(skb->len - skb->mac_len);
+       }
+
+out:
+       return segs;
+}
+
 static struct packet_type ipv6_packet_type = {
        .type = __constant_htons(ETH_P_IPV6), 
        .func = ipv6_rcv,
+       .gso_segment = ipv6_gso_segment,
 };
 
 struct ip6_ra_chain *ip6_ra_chain;
@@ -645,17 +707,15 @@ int ipv6_setsockopt(struct sock *sk, int level, int optname,
 
 #ifdef CONFIG_COMPAT
 int compat_ipv6_setsockopt(struct sock *sk, int level, int optname,
-                   char __user *optval, int optlen)
+                          char __user *optval, int optlen)
 {
        int err;
 
        if (level == SOL_IP && sk->sk_type != SOCK_RAW) {
-               if (udp_prot.compat_setsockopt)
-                       return udp_prot.compat_setsockopt(sk, level,
-                                       optname, optval, optlen);
-               else
-                       return udp_prot.setsockopt(sk, level,
-                                       optname, optval, optlen);
+               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)
@@ -665,15 +725,17 @@ int compat_ipv6_setsockopt(struct sock *sk, int level, int optname,
 #ifdef CONFIG_NETFILTER
        /* we need to exclude all possible ENOPROTOOPTs except default case */
        if (err == -ENOPROTOOPT && optname != IPV6_IPSEC_POLICY &&
-                       optname != IPV6_XFRM_POLICY) {
+           optname != IPV6_XFRM_POLICY) {
                lock_sock(sk);
-               err = compat_nf_setsockopt(sk, PF_INET6, optname, optval,
-                               optlen);
+               err = compat_nf_setsockopt(sk, PF_INET6, optname,
+                                          optval, optlen);
                release_sock(sk);
        }
 #endif
        return err;
 }
+
+EXPORT_SYMBOL(compat_ipv6_setsockopt);
 #endif
 
 static int ipv6_getsockopt_sticky(struct sock *sk, struct ipv6_opt_hdr *hdr,
@@ -907,7 +969,7 @@ int ipv6_getsockopt(struct sock *sk, int level, int optname,
        err = do_ipv6_getsockopt(sk, level, optname, optval, optlen);
 #ifdef CONFIG_NETFILTER
        /* we need to exclude all possible EINVALs except default case */
-       if (err == -ENOPROTOOPT && optname != IPV6_ADDRFORM &&
+       if (err == -EINVAL && optname != IPV6_ADDRFORM &&
                        optname != MCAST_MSFILTER) {
                int len;
 
@@ -927,26 +989,24 @@ int ipv6_getsockopt(struct sock *sk, int level, int optname,
 
 #ifdef CONFIG_COMPAT
 int compat_ipv6_getsockopt(struct sock *sk, int level, int optname,
-                   char __user *optval, int __user *optlen)
+                          char __user *optval, int __user *optlen)
 {
        int err;
 
        if (level == SOL_IP && sk->sk_type != SOCK_RAW) {
-               if (udp_prot.compat_getsockopt)
-                       return udp_prot.compat_getsockopt(sk, level,
-                                       optname, optval, optlen);
-               else
-                       return udp_prot.getsockopt(sk, level,
-                                       optname, optval, optlen);
+               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)
+       if (level != SOL_IPV6)
                return -ENOPROTOOPT;
 
        err = do_ipv6_getsockopt(sk, level, optname, optval, optlen);
 #ifdef CONFIG_NETFILTER
        /* we need to exclude all possible EINVALs except default case */
-       if (err == -ENOPROTOOPT && optname != IPV6_ADDRFORM &&
+       if (err == -EINVAL && optname != IPV6_ADDRFORM &&
                        optname != MCAST_MSFILTER) {
                int len;
 
@@ -954,8 +1014,8 @@ int compat_ipv6_getsockopt(struct sock *sk, int level, int optname,
                        return -EFAULT;
 
                lock_sock(sk);
-               err = compat_nf_getsockopt(sk, PF_INET6, optname, optval,
-                               &len);
+               err = compat_nf_getsockopt(sk, PF_INET6,
+                                          optname, optval, &len);
                release_sock(sk);
                if (err >= 0)
                        err = put_user(len, optlen);
@@ -963,6 +1023,8 @@ int compat_ipv6_getsockopt(struct sock *sk, int level, int optname,
 #endif
        return err;
 }
+
+EXPORT_SYMBOL(compat_ipv6_getsockopt);
 #endif
 
 void __init ipv6_packet_init(void)