ipv6: Fix BUG when disabled ipv6 module is unloaded
[safe/jmp/linux-2.6] / net / ipv6 / af_inet6.c
index e091391..9c8309e 100644 (file)
@@ -72,6 +72,10 @@ MODULE_LICENSE("GPL");
 static struct list_head inetsw6[SOCK_MAX];
 static DEFINE_SPINLOCK(inetsw6_lock);
 
+static int disable_ipv6 = 0;
+module_param_named(disable, disable_ipv6, int, 0);
+MODULE_PARM_DESC(disable, "Disable IPv6 such that it is non-functional");
+
 static __inline__ struct ipv6_pinfo *inet6_sk_generic(struct sock *sk)
 {
        const int offset = sk->sk_prot->obj_size - sizeof(struct ipv6_pinfo);
@@ -637,7 +641,7 @@ int inet6_sk_rebuild_header(struct sock *sk)
                if (final_p)
                        ipv6_addr_copy(&fl.fl6_dst, final_p);
 
-               if ((err = xfrm_lookup(&dst, &fl, sk, 0)) < 0) {
+               if ((err = xfrm_lookup(sock_net(sk), &dst, &fl, sk, 0)) < 0) {
                        sk->sk_err_soft = -err;
                        return err;
                }
@@ -672,8 +676,7 @@ int ipv6_opt_accepted(struct sock *sk, struct sk_buff *skb)
 
 EXPORT_SYMBOL_GPL(ipv6_opt_accepted);
 
-static struct inet6_protocol *ipv6_gso_pull_exthdrs(struct sk_buff *skb,
-                                                   int proto)
+static int ipv6_gso_pull_exthdrs(struct sk_buff *skb, int proto)
 {
        struct inet6_protocol *ops = NULL;
 
@@ -704,7 +707,7 @@ static struct inet6_protocol *ipv6_gso_pull_exthdrs(struct sk_buff *skb,
                __skb_pull(skb, len);
        }
 
-       return ops;
+       return proto;
 }
 
 static int ipv6_gso_send_check(struct sk_buff *skb)
@@ -721,7 +724,9 @@ static int ipv6_gso_send_check(struct sk_buff *skb)
        err = -EPROTONOSUPPORT;
 
        rcu_read_lock();
-       ops = ipv6_gso_pull_exthdrs(skb, ipv6h->nexthdr);
+       ops = rcu_dereference(inet6_protos[
+               ipv6_gso_pull_exthdrs(skb, ipv6h->nexthdr)]);
+
        if (likely(ops && ops->gso_send_check)) {
                skb_reset_transport_header(skb);
                err = ops->gso_send_check(skb);
@@ -757,7 +762,9 @@ static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb, int features)
        segs = ERR_PTR(-EPROTONOSUPPORT);
 
        rcu_read_lock();
-       ops = ipv6_gso_pull_exthdrs(skb, ipv6h->nexthdr);
+       ops = rcu_dereference(inet6_protos[
+               ipv6_gso_pull_exthdrs(skb, ipv6h->nexthdr)]);
+
        if (likely(ops && ops->gso_segment)) {
                skb_reset_transport_header(skb);
                segs = ops->gso_segment(skb, features);
@@ -777,11 +784,112 @@ out:
        return segs;
 }
 
+struct ipv6_gro_cb {
+       struct napi_gro_cb napi;
+       int proto;
+};
+
+#define IPV6_GRO_CB(skb) ((struct ipv6_gro_cb *)(skb)->cb)
+
+static struct sk_buff **ipv6_gro_receive(struct sk_buff **head,
+                                        struct sk_buff *skb)
+{
+       struct inet6_protocol *ops;
+       struct sk_buff **pp = NULL;
+       struct sk_buff *p;
+       struct ipv6hdr *iph;
+       unsigned int nlen;
+       int flush = 1;
+       int proto;
+       __wsum csum;
+
+       if (unlikely(!pskb_may_pull(skb, sizeof(*iph))))
+               goto out;
+
+       iph = ipv6_hdr(skb);
+       __skb_pull(skb, sizeof(*iph));
+
+       flush += ntohs(iph->payload_len) != skb->len;
+
+       rcu_read_lock();
+       proto = ipv6_gso_pull_exthdrs(skb, iph->nexthdr);
+       iph = ipv6_hdr(skb);
+       IPV6_GRO_CB(skb)->proto = proto;
+       ops = rcu_dereference(inet6_protos[proto]);
+       if (!ops || !ops->gro_receive)
+               goto out_unlock;
+
+       flush--;
+       skb_reset_transport_header(skb);
+       nlen = skb_network_header_len(skb);
+
+       for (p = *head; p; p = p->next) {
+               struct ipv6hdr *iph2;
+
+               if (!NAPI_GRO_CB(p)->same_flow)
+                       continue;
+
+               iph2 = ipv6_hdr(p);
+
+               /* All fields must match except length. */
+               if (nlen != skb_network_header_len(p) ||
+                   memcmp(iph, iph2, offsetof(struct ipv6hdr, payload_len)) ||
+                   memcmp(&iph->nexthdr, &iph2->nexthdr,
+                          nlen - offsetof(struct ipv6hdr, nexthdr))) {
+                       NAPI_GRO_CB(p)->same_flow = 0;
+                       continue;
+               }
+
+               NAPI_GRO_CB(p)->flush |= flush;
+       }
+
+       NAPI_GRO_CB(skb)->flush |= flush;
+
+       csum = skb->csum;
+       skb_postpull_rcsum(skb, iph, skb_network_header_len(skb));
+
+       pp = ops->gro_receive(head, skb);
+
+       skb->csum = csum;
+
+out_unlock:
+       rcu_read_unlock();
+
+out:
+       NAPI_GRO_CB(skb)->flush |= flush;
+
+       return pp;
+}
+
+static int ipv6_gro_complete(struct sk_buff *skb)
+{
+       struct inet6_protocol *ops;
+       struct ipv6hdr *iph = ipv6_hdr(skb);
+       int err = -ENOSYS;
+
+       iph->payload_len = htons(skb->len - skb_network_offset(skb) -
+                                sizeof(*iph));
+
+       rcu_read_lock();
+       ops = rcu_dereference(inet6_protos[IPV6_GRO_CB(skb)->proto]);
+       if (WARN_ON(!ops || !ops->gro_complete))
+               goto out_unlock;
+
+       err = ops->gro_complete(skb);
+
+out_unlock:
+       rcu_read_unlock();
+
+       return err;
+}
+
 static struct packet_type ipv6_packet_type = {
        .type = __constant_htons(ETH_P_IPV6),
        .func = ipv6_rcv,
        .gso_send_check = ipv6_gso_send_check,
        .gso_segment = ipv6_gso_segment,
+       .gro_receive = ipv6_gro_receive,
+       .gro_complete = ipv6_gro_complete,
 };
 
 static int __init ipv6_packet_init(void)
@@ -795,55 +903,46 @@ static void ipv6_packet_cleanup(void)
        dev_remove_pack(&ipv6_packet_type);
 }
 
-static int __init init_ipv6_mibs(void)
+static int __net_init ipv6_init_mibs(struct net *net)
 {
-       if (snmp_mib_init((void **)ipv6_statistics,
+       if (snmp_mib_init((void **)net->mib.udp_stats_in6,
+                         sizeof (struct udp_mib)) < 0)
+               return -ENOMEM;
+       if (snmp_mib_init((void **)net->mib.udplite_stats_in6,
+                         sizeof (struct udp_mib)) < 0)
+               goto err_udplite_mib;
+       if (snmp_mib_init((void **)net->mib.ipv6_statistics,
                          sizeof(struct ipstats_mib)) < 0)
                goto err_ip_mib;
-       if (snmp_mib_init((void **)icmpv6_statistics,
+       if (snmp_mib_init((void **)net->mib.icmpv6_statistics,
                          sizeof(struct icmpv6_mib)) < 0)
                goto err_icmp_mib;
-       if (snmp_mib_init((void **)icmpv6msg_statistics,
+       if (snmp_mib_init((void **)net->mib.icmpv6msg_statistics,
                          sizeof(struct icmpv6msg_mib)) < 0)
                goto err_icmpmsg_mib;
-       if (snmp_mib_init((void **)udplite_stats_in6,
-                         sizeof (struct udp_mib)) < 0)
-               goto err_udplite_mib;
        return 0;
 
-err_udplite_mib:
-       snmp_mib_free((void **)icmpv6msg_statistics);
 err_icmpmsg_mib:
-       snmp_mib_free((void **)icmpv6_statistics);
+       snmp_mib_free((void **)net->mib.icmpv6_statistics);
 err_icmp_mib:
-       snmp_mib_free((void **)ipv6_statistics);
+       snmp_mib_free((void **)net->mib.ipv6_statistics);
 err_ip_mib:
+       snmp_mib_free((void **)net->mib.udplite_stats_in6);
+err_udplite_mib:
+       snmp_mib_free((void **)net->mib.udp_stats_in6);
        return -ENOMEM;
-
-}
-
-static void cleanup_ipv6_mibs(void)
-{
-       snmp_mib_free((void **)ipv6_statistics);
-       snmp_mib_free((void **)icmpv6_statistics);
-       snmp_mib_free((void **)icmpv6msg_statistics);
-       snmp_mib_free((void **)udplite_stats_in6);
-}
-
-static int __net_init ipv6_init_mibs(struct net *net)
-{
-       if (snmp_mib_init((void **)net->mib.udp_stats_in6,
-                         sizeof (struct udp_mib)) < 0)
-               return -ENOMEM;
-       return 0;
 }
 
 static void __net_exit ipv6_cleanup_mibs(struct net *net)
 {
        snmp_mib_free((void **)net->mib.udp_stats_in6);
+       snmp_mib_free((void **)net->mib.udplite_stats_in6);
+       snmp_mib_free((void **)net->mib.ipv6_statistics);
+       snmp_mib_free((void **)net->mib.icmpv6_statistics);
+       snmp_mib_free((void **)net->mib.icmpv6msg_statistics);
 }
 
-static int inet6_net_init(struct net *net)
+static int __net_init inet6_net_init(struct net *net)
 {
        int err = 0;
 
@@ -896,10 +995,21 @@ static int __init inet6_init(void)
 {
        struct sk_buff *dummy_skb;
        struct list_head *r;
-       int err;
+       int err = 0;
 
        BUILD_BUG_ON(sizeof(struct inet6_skb_parm) > sizeof(dummy_skb->cb));
 
+       /* Register the socket-side information for inet6_create.  */
+       for(r = &inetsw6[0]; r < &inetsw6[SOCK_MAX]; ++r)
+               INIT_LIST_HEAD(r);
+
+       if (disable_ipv6) {
+               printk(KERN_INFO
+                      "IPv6: Loaded, but administratively disabled, "
+                      "reboot required to enable\n");
+               goto out;
+       }
+
        err = proto_register(&tcpv6_prot, 1);
        if (err)
                goto out;
@@ -917,10 +1027,6 @@ static int __init inet6_init(void)
                goto out_unregister_udplite_proto;
 
 
-       /* Register the socket-side information for inet6_create.  */
-       for(r = &inetsw6[0]; r < &inetsw6[SOCK_MAX]; ++r)
-               INIT_LIST_HEAD(r);
-
        /* We MUST register RAW sockets before we create the ICMP6,
         * IGMP6, or NDISC control sockets.
         */
@@ -935,11 +1041,6 @@ static int __init inet6_init(void)
        if (err)
                goto out_sock_register_fail;
 
-       /* Initialise ipv6 mibs */
-       err = init_ipv6_mibs();
-       if (err)
-               goto out_unregister_sock;
-
 #ifdef CONFIG_SYSCTL
        err = ipv6_static_sysctl_register();
        if (err)
@@ -1073,8 +1174,6 @@ register_pernet_fail:
        ipv6_static_sysctl_unregister();
 static_sysctl_fail:
 #endif
-       cleanup_ipv6_mibs();
-out_unregister_sock:
        sock_unregister(PF_INET6);
        rtnl_unregister_all(PF_INET6);
 out_sock_register_fail:
@@ -1093,6 +1192,9 @@ module_init(inet6_init);
 
 static void __exit inet6_exit(void)
 {
+       if (disable_ipv6)
+               return;
+
        /* First of all disallow new sockets creation. */
        sock_unregister(PF_INET6);
        /* Disallow any further netlink messages */
@@ -1131,7 +1233,6 @@ static void __exit inet6_exit(void)
 #ifdef CONFIG_SYSCTL
        ipv6_static_sysctl_unregister();
 #endif
-       cleanup_ipv6_mibs();
        proto_unregister(&rawv6_prot);
        proto_unregister(&udplitev6_prot);
        proto_unregister(&udpv6_prot);