netfilter: bridge: use rcu hook to resolve br_netfilter dependency
[cascardo/linux.git] / net / bridge / br_netfilter.c
index ef1fe28..b260a97 100644 (file)
@@ -892,6 +892,41 @@ static unsigned int ip_sabotage_in(const struct nf_hook_ops *ops,
        return NF_ACCEPT;
 }
 
+/* This is called when br_netfilter has called into iptables/netfilter,
+ * and DNAT has taken place on a bridge-forwarded packet.
+ *
+ * neigh->output has created a new MAC header, with local br0 MAC
+ * as saddr.
+ *
+ * This restores the original MAC saddr of the bridged packet
+ * before invoking bridge forward logic to transmit the packet.
+ */
+static void br_nf_pre_routing_finish_bridge_slow(struct sk_buff *skb)
+{
+       struct nf_bridge_info *nf_bridge = skb->nf_bridge;
+
+       skb_pull(skb, ETH_HLEN);
+       nf_bridge->mask &= ~BRNF_BRIDGED_DNAT;
+
+       skb_copy_to_linear_data_offset(skb, -(ETH_HLEN-ETH_ALEN),
+                                      skb->nf_bridge->data, ETH_HLEN-ETH_ALEN);
+       skb->dev = nf_bridge->physindev;
+       br_handle_frame_finish(skb);
+}
+
+static int br_nf_dev_xmit(struct sk_buff *skb)
+{
+       if (skb->nf_bridge && (skb->nf_bridge->mask & BRNF_BRIDGED_DNAT)) {
+               br_nf_pre_routing_finish_bridge_slow(skb);
+               return 1;
+       }
+       return 0;
+}
+
+static const struct nf_br_ops br_ops = {
+       .br_dev_xmit_hook =     br_nf_dev_xmit,
+};
+
 void br_netfilter_enable(void)
 {
 }
@@ -1029,12 +1064,14 @@ static int __init br_netfilter_init(void)
                return -ENOMEM;
        }
 #endif
+       RCU_INIT_POINTER(nf_br_ops, &br_ops);
        printk(KERN_NOTICE "Bridge firewalling registered\n");
        return 0;
 }
 
 static void __exit br_netfilter_fini(void)
 {
+       RCU_INIT_POINTER(nf_br_ops, NULL);
        nf_unregister_hooks(br_nf_ops, ARRAY_SIZE(br_nf_ops));
 #ifdef CONFIG_SYSCTL
        unregister_net_sysctl_table(brnf_sysctl_header);