Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net
[linux-2.6-block.git] / net / ipv6 / ip6_input.c
index c1d85830c906f68bdd2310e411b44dca98e3db72..c7ed2b6d5a1dd192cc776231ede06cef8f78065d 100644 (file)
@@ -319,28 +319,26 @@ void ipv6_list_rcv(struct list_head *head, struct packet_type *pt,
 /*
  *     Deliver the packet to the host
  */
-
-
-static int ip6_input_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
+void ip6_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int nexthdr,
+                             bool have_final)
 {
        const struct inet6_protocol *ipprot;
        struct inet6_dev *idev;
        unsigned int nhoff;
-       int nexthdr;
        bool raw;
-       bool have_final = false;
 
        /*
         *      Parse extension headers
         */
 
-       rcu_read_lock();
 resubmit:
        idev = ip6_dst_idev(skb_dst(skb));
-       if (!pskb_pull(skb, skb_transport_offset(skb)))
-               goto discard;
        nhoff = IP6CB(skb)->nhoff;
-       nexthdr = skb_network_header(skb)[nhoff];
+       if (!have_final) {
+               if (!pskb_pull(skb, skb_transport_offset(skb)))
+                       goto discard;
+               nexthdr = skb_network_header(skb)[nhoff];
+       }
 
 resubmit_final:
        raw = raw6_local_deliver(skb, nexthdr);
@@ -359,6 +357,8 @@ resubmit_final:
                        }
                } else if (ipprot->flags & INET6_PROTO_FINAL) {
                        const struct ipv6hdr *hdr;
+                       int sdif = inet6_sdif(skb);
+                       struct net_device *dev;
 
                        /* Only do this once for first final protocol */
                        have_final = true;
@@ -371,9 +371,19 @@ resubmit_final:
                        skb_postpull_rcsum(skb, skb_network_header(skb),
                                           skb_network_header_len(skb));
                        hdr = ipv6_hdr(skb);
+
+                       /* skb->dev passed may be master dev for vrfs. */
+                       if (sdif) {
+                               dev = dev_get_by_index_rcu(net, sdif);
+                               if (!dev)
+                                       goto discard;
+                       } else {
+                               dev = skb->dev;
+                       }
+
                        if (ipv6_addr_is_multicast(&hdr->daddr) &&
-                           !ipv6_chk_mcast_addr(skb->dev, &hdr->daddr,
-                           &hdr->saddr) &&
+                           !ipv6_chk_mcast_addr(dev, &hdr->daddr,
+                                                &hdr->saddr) &&
                            !ipv6_is_mld(skb, nexthdr, skb_network_header_len(skb)))
                                goto discard;
                }
@@ -411,13 +421,19 @@ resubmit_final:
                        consume_skb(skb);
                }
        }
-       rcu_read_unlock();
-       return 0;
+       return;
 
 discard:
        __IP6_INC_STATS(net, idev, IPSTATS_MIB_INDISCARDS);
-       rcu_read_unlock();
        kfree_skb(skb);
+}
+
+static int ip6_input_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
+{
+       rcu_read_lock();
+       ip6_protocol_deliver_rcu(net, skb, 0, false);
+       rcu_read_unlock();
+
        return 0;
 }
 
@@ -432,15 +448,32 @@ EXPORT_SYMBOL_GPL(ip6_input);
 
 int ip6_mc_input(struct sk_buff *skb)
 {
+       int sdif = inet6_sdif(skb);
        const struct ipv6hdr *hdr;
+       struct net_device *dev;
        bool deliver;
 
        __IP6_UPD_PO_STATS(dev_net(skb_dst(skb)->dev),
                         __in6_dev_get_safely(skb->dev), IPSTATS_MIB_INMCAST,
                         skb->len);
 
+       /* skb->dev passed may be master dev for vrfs. */
+       if (sdif) {
+               rcu_read_lock();
+               dev = dev_get_by_index_rcu(dev_net(skb->dev), sdif);
+               if (!dev) {
+                       rcu_read_unlock();
+                       kfree_skb(skb);
+                       return -ENODEV;
+               }
+       } else {
+               dev = skb->dev;
+       }
+
        hdr = ipv6_hdr(skb);
-       deliver = ipv6_chk_mcast_addr(skb->dev, &hdr->daddr, NULL);
+       deliver = ipv6_chk_mcast_addr(dev, &hdr->daddr, NULL);
+       if (sdif)
+               rcu_read_unlock();
 
 #ifdef CONFIG_IPV6_MROUTE
        /*