@@ -18,6 +18,11 @@
#include <linux/netfilter_bridge.h>
#include <linux/export.h>
#include <linux/rculist.h>
+
+#ifdef CONFIG_BRIDGE_NETFILTER
+#include <linux/jump_label.h>
+#endif
+
#include "br_private.h"
/* Hook for brouter */
@@ -153,6 +158,39 @@ static int br_handle_local_finish(struct sk_buff *skb)
return 0; /* process further */
}
+#ifdef CONFIG_BRIDGE_NETFILTER
+extern struct static_key brnf_hooks_active;
+
+static inline int br_nf_check_call_iptables(const struct net_bridge *br)
+{
+ bool ip, ip6, arp;
+ int i;
+
+ if (static_key_true(&brnf_hooks_active))
+ return 0;
+
+ ip = brnf_call_iptables || br->nf_call_iptables;
+ ip6 = brnf_call_ip6tables || br->nf_call_ip6tables;
+ arp = brnf_call_arptables || br->nf_call_arptables;
+
+ for (i=0; i < NF_MAX_HOOKS; i++) {
+ if (nf_hooks_active(NFPROTO_IPV4, i) && ip)
+ break;
+ if (nf_hooks_active(NFPROTO_IPV6, i) && ip6)
+ break;
+ if (nf_hooks_active(NFPROTO_ARP, i) && arp)
+ break;
+ }
+
+ if (i < NF_MAX_HOOKS)
+ return br_netfiler_hooks_init();
+
+ return 0;
+}
+#else
+static inline int br_nf_check_call_iptables(const struct net_bridge *br){}
+#endif
+
/*
* Return NULL if skb is handled
* note: already called with rcu_read_lock
@@ -176,6 +214,9 @@ rx_handler_result_t br_handle_frame(struct sk_buff **pskb)
p = br_port_get_rcu(skb->dev);
+ if (br_nf_check_call_iptables(p->br))
+ goto drop;
+
if (unlikely(is_link_local_ether_addr(dest))) {
u16 fwd_mask = p->br->group_fwd_mask_required;
@@ -31,6 +31,8 @@
#include <linux/netfilter_arp.h>
#include <linux/in_route.h>
#include <linux/inetdevice.h>
+#include <linux/workqueue.h>
+#include <linux/jump_label.h>
#include <net/ip.h>
#include <net/ipv6.h>
@@ -47,18 +49,19 @@
#define store_orig_dstaddr(skb) (skb_origaddr(skb) = ip_hdr(skb)->daddr)
#define dnat_took_place(skb) (skb_origaddr(skb) != ip_hdr(skb)->daddr)
+struct static_key brnf_hooks_active __read_mostly;
+static DEFINE_MUTEX(brnf_hook_mutex);
+
#ifdef CONFIG_SYSCTL
static struct ctl_table_header *brnf_sysctl_header;
-static int brnf_call_iptables __read_mostly = 1;
-static int brnf_call_ip6tables __read_mostly = 1;
-static int brnf_call_arptables __read_mostly = 1;
static int brnf_filter_vlan_tagged __read_mostly = 0;
static int brnf_filter_pppoe_tagged __read_mostly = 0;
static int brnf_pass_vlan_indev __read_mostly = 0;
+
+int brnf_call_iptables __read_mostly = 1;
+int brnf_call_ip6tables __read_mostly = 1;
+int brnf_call_arptables __read_mostly = 1;
#else
-#define brnf_call_iptables 1
-#define brnf_call_ip6tables 1
-#define brnf_call_arptables 1
#define brnf_filter_vlan_tagged 0
#define brnf_filter_pppoe_tagged 0
#define brnf_pass_vlan_indev 0
@@ -1059,6 +1062,45 @@ static struct ctl_table brnf_table[] = {
};
#endif
+static void __br_register_hooks_init(struct work_struct *w)
+{
+ int ret = nf_register_hooks(br_nf_ops, ARRAY_SIZE(br_nf_ops));
+ if (ret == 0)
+ static_key_slow_inc(&brnf_hooks_active);
+
+ mutex_unlock(&brnf_hook_mutex);
+ kfree(w);
+
+ WARN_ONCE(ret, "could not register bridge netfilter hook (error %d)",
+ ret);
+}
+
+int br_netfiler_hooks_init(void)
+{
+ struct work_struct *w;
+
+ /* called from bh context, cannot sleep */
+ if (!mutex_trylock(&brnf_hook_mutex))
+ return -EBUSY;
+
+ if (static_key_true(&brnf_hooks_active)) {
+ mutex_unlock(&brnf_hook_mutex);
+ return 0;
+ }
+
+ w = kzalloc(sizeof(*w), GFP_ATOMIC);
+ if (!w) {
+ mutex_unlock(&brnf_hook_mutex);
+ return -ENOMEM;
+ }
+
+ INIT_WORK(w, __br_register_hooks_init);
+ schedule_work(w);
+ /* worker will unlock mutex */
+
+ return 0;
+}
+
int __init br_netfilter_init(void)
{
int ret;
@@ -1067,17 +1109,11 @@ int __init br_netfilter_init(void)
if (ret < 0)
return ret;
- ret = nf_register_hooks(br_nf_ops, ARRAY_SIZE(br_nf_ops));
- if (ret < 0) {
- dst_entries_destroy(&fake_dst_ops);
- return ret;
- }
#ifdef CONFIG_SYSCTL
brnf_sysctl_header = register_net_sysctl(&init_net, "net/bridge", brnf_table);
if (brnf_sysctl_header == NULL) {
printk(KERN_WARNING
"br_netfilter: can't register to sysctl.\n");
- nf_unregister_hooks(br_nf_ops, ARRAY_SIZE(br_nf_ops));
dst_entries_destroy(&fake_dst_ops);
return -ENOMEM;
}
@@ -1088,7 +1124,12 @@ int __init br_netfilter_init(void)
void br_netfilter_fini(void)
{
- nf_unregister_hooks(br_nf_ops, ARRAY_SIZE(br_nf_ops));
+ /* sync with possibly running __br_register_hooks_init() */
+ mutex_lock(&brnf_hook_mutex);
+
+ if (static_key_enabled(&brnf_hooks_active))
+ nf_unregister_hooks(br_nf_ops, ARRAY_SIZE(br_nf_ops));
+ mutex_unlock(&brnf_hook_mutex);
#ifdef CONFIG_SYSCTL
unregister_net_sysctl_table(brnf_sysctl_header);
#endif
@@ -755,6 +755,17 @@ static inline int br_vlan_enabled(struct net_bridge *br)
int br_netfilter_init(void);
void br_netfilter_fini(void);
void br_netfilter_rtable_init(struct net_bridge *);
+int br_netfiler_hooks_init(void);
+
+#ifdef CONFIG_SYSCTL
+extern int brnf_call_iptables;
+extern int brnf_call_ip6tables;
+extern int brnf_call_arptables;
+#else
+#define brnf_call_iptables 1
+#define brnf_call_ip6tables 1
+#define brnf_call_arptables 1
+#endif
#else
#define br_netfilter_init() (0)
#define br_netfilter_fini() do { } while (0)