diff mbox

[RFC,3/3] mac80211: support bpf monitor filter

Message ID 20170412110726.9689-3-johannes@sipsolutions.net
State RFC, archived
Delegated to: David Miller
Headers show

Commit Message

Johannes Berg April 12, 2017, 11:07 a.m. UTC
From: Johannes Berg <johannes.berg@intel.com>

Add the necessary hooks for running monitor filter programs
in mac80211's RX path, before a frame is handed off to a
monitor interface. If the frame isn't accepted then this
will save the overhead of creating a new SKB and building
the radiotap header.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
 net/mac80211/Kconfig       |  1 +
 net/mac80211/cfg.c         | 13 +++++++++++++
 net/mac80211/ieee80211_i.h |  6 ++++++
 net/mac80211/iface.c       |  9 ++++++++-
 net/mac80211/main.c        |  3 +++
 net/mac80211/rx.c          | 45 +++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 76 insertions(+), 1 deletion(-)

Comments

Johannes Berg April 12, 2017, 2:29 p.m. UTC | #1
On Wed, 2017-04-12 at 13:07 +0200, Johannes Berg wrote:
> 
>  struct ieee80211_if_mntr {
>  	u32 flags;
> 
[...]
+	bool deliver;

That's ... broken for multi-queue RX. I haven't really found a good
other way to do it. The best way will likely be to copy the SKB the
first time it's needed, build the radiotap header, and then keep a
reference to it to be able to clone it later if it's needed again.

johannes
David Miller April 12, 2017, 3:22 p.m. UTC | #2
From: Johannes Berg <johannes@sipsolutions.net>
Date: Wed, 12 Apr 2017 16:29:07 +0200

> On Wed, 2017-04-12 at 13:07 +0200, Johannes Berg wrote:
>> 
>>  struct ieee80211_if_mntr {
>>  	u32 flags;
>> 
> [...]
> +	bool deliver;
> 
> That's ... broken for multi-queue RX. I haven't really found a good
> other way to do it. The best way will likely be to copy the SKB the
> first time it's needed, build the radiotap header, and then keep a
> reference to it to be able to clone it later if it's needed again.

If you don't recurse into the receive path for different devices before
you are done with this boolean, simply make a global per-cpu boolean
and use that.
Johannes Berg April 12, 2017, 3:25 p.m. UTC | #3
On Wed, 2017-04-12 at 11:22 -0400, David Miller wrote:
> From: Johannes Berg <johannes@sipsolutions.net>
> Date: Wed, 12 Apr 2017 16:29:07 +0200
> 
> > On Wed, 2017-04-12 at 13:07 +0200, Johannes Berg wrote:
> >> 
> >>  struct ieee80211_if_mntr {
> >>      u32 flags;
> >> 
> > [...]
> > +     bool deliver;
> > 
> > That's ... broken for multi-queue RX. I haven't really found a good
> > other way to do it. The best way will likely be to copy the SKB the
> > first time it's needed, build the radiotap header, and then keep a
> > reference to it to be able to clone it later if it's needed again.
> 
> If you don't recurse into the receive path for different devices
> before you are done with this boolean, simply make a global per-cpu
> boolean and use that.

No, that won't work. We don't recurse, but this is a per-interface
bool, as we can have multiple monitor interfaces (possibly with
different filters).

The problem comes from the fact that I did

for_each_interface()
	iface.deliver = run_bpf_program();

if (nobody_wanted_it)
	return;

skb = build_monitor_skb()

for_each_interface()
	if (iface.monitor)
		deliver(skb);


What I should be doing is something like this:

for_each_interface() {
	if (run_bpf_program()) {
		if (!skb)
			skb = build_monitor_skb();
		deliver(skb);
	}
}

where deliver() does skb_clone() internally or so.

johannes
Johannes Berg April 13, 2017, 6 a.m. UTC | #4
> @@ -551,6 +551,9 @@ struct ieee80211_hw *ieee80211_alloc_hw_nm(size_t
> priv_data_len,
>  			   NL80211_FEATURE_FULL_AP_CLIENT_STATE;
>  	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_FILS_STA);
>  
> +	if (IS_ENABLED(CONFIG_BPF_WIFIMON))
> +		wiphy_ext_feature_isset(wiphy,
> NL80211_EXT_FEATURE_WIFIMON_BPF);
> 
That obviously needs to be _set(), not _isset().

johannes
diff mbox

Patch

diff --git a/net/mac80211/Kconfig b/net/mac80211/Kconfig
index 76e30f4797fb..080e0c705c72 100644
--- a/net/mac80211/Kconfig
+++ b/net/mac80211/Kconfig
@@ -8,6 +8,7 @@  config MAC80211
 	select CRYPTO_GCM
 	select CRYPTO_CMAC
 	select CRC32
+	select WANT_BPF_WIFIMON
 	---help---
 	  This option enables the hardware independent IEEE 802.11
 	  networking stack.
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 4ff03c88022e..c394c08ed0e0 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -106,6 +106,19 @@  static int ieee80211_set_mon_options(struct ieee80211_sub_if_data *sdata,
 		}
 	}
 
+	if (params->filter) {
+		struct bpf_prog *old = rtnl_dereference(sdata->u.mntr.filter);
+
+		if (IS_ERR(params->filter))
+			RCU_INIT_POINTER(sdata->u.mntr.filter, NULL);
+		else
+			rcu_assign_pointer(sdata->u.mntr.filter,
+					   params->filter);
+
+		if (old)
+			bpf_prog_put(old);
+	}
+
 	return 0;
 }
 
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 0e718437d080..06a2e2cdde83 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -27,6 +27,8 @@ 
 #include <linux/leds.h>
 #include <linux/idr.h>
 #include <linux/rhashtable.h>
+#include <linux/filter.h>
+#include <linux/bpf.h>
 #include <net/ieee80211_radiotap.h>
 #include <net/cfg80211.h>
 #include <net/mac80211.h>
@@ -839,6 +841,10 @@  struct txq_info {
 struct ieee80211_if_mntr {
 	u32 flags;
 	u8 mu_follow_addr[ETH_ALEN] __aligned(2);
+#ifdef CONFIG_BPF_WIFIMON
+	struct bpf_prog *filter;
+	bool deliver;
+#endif
 };
 
 /**
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 5bb0c5012819..05258fd9dda2 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -1107,8 +1107,15 @@  static void ieee80211_teardown_sdata(struct ieee80211_sub_if_data *sdata)
 		__skb_queue_purge(&sdata->fragments[i].skb_list);
 	sdata->fragment_next = 0;
 
-	if (ieee80211_vif_is_mesh(&sdata->vif))
+	if (ieee80211_vif_is_mesh(&sdata->vif)) {
 		ieee80211_mesh_teardown_sdata(sdata);
+	} else if (sdata->vif.type == NL80211_IFTYPE_MONITOR) {
+		struct bpf_prog *old = rtnl_dereference(sdata->u.mntr.filter);
+
+		RCU_INIT_POINTER(sdata->u.mntr.filter, NULL);
+		if (old)
+			bpf_prog_put(old);
+	}
 }
 
 static void ieee80211_uninit(struct net_device *dev)
diff --git a/net/mac80211/main.c b/net/mac80211/main.c
index 56fb47953b72..e9d13dedcca4 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -551,6 +551,9 @@  struct ieee80211_hw *ieee80211_alloc_hw_nm(size_t priv_data_len,
 			   NL80211_FEATURE_FULL_AP_CLIENT_STATE;
 	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_FILS_STA);
 
+	if (IS_ENABLED(CONFIG_BPF_WIFIMON))
+		wiphy_ext_feature_isset(wiphy, NL80211_EXT_FEATURE_WIFIMON_BPF);
+
 	if (!ops->hw_scan)
 		wiphy->features |= NL80211_FEATURE_LOW_PRIORITY_SCAN |
 				   NL80211_FEATURE_AP_SCAN;
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 335c7843169f..8c811deaf3cd 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -508,6 +508,7 @@  ieee80211_rx_monitor(struct ieee80211_local *local, struct sk_buff *origskb,
 	struct ieee80211_mgmt *mgmt;
 	struct ieee80211_sub_if_data *monitor_sdata =
 		rcu_dereference(local->monitor_sdata);
+	bool deliver __maybe_unused = false;
 
 	if (unlikely(status->flag & RX_FLAG_RADIOTAP_VENDOR_DATA)) {
 		struct ieee80211_vendor_radiotap *rtap = (void *)origskb->data;
@@ -552,6 +553,45 @@  ieee80211_rx_monitor(struct ieee80211_local *local, struct sk_buff *origskb,
 		return origskb;
 	}
 
+#ifdef CONFIG_BPF_WIFIMON
+	/* pretend all the monitor info isn't there */
+	__pskb_pull(origskb, rtap_vendor_space);
+	origskb->len -= present_fcs_len;
+
+	list_for_each_entry_rcu(sdata, &local->interfaces, list) {
+		const struct bpf_prog *filter;
+
+		if (sdata->vif.type != NL80211_IFTYPE_MONITOR)
+			continue;
+
+		if (sdata->u.mntr.flags & MONITOR_FLAG_COOK_FRAMES)
+			continue;
+
+		if (!ieee80211_sdata_running(sdata))
+			continue;
+
+		filter = rcu_dereference(sdata->u.mntr.filter);
+		if (filter) {
+			sdata->u.mntr.deliver = BPF_PROG_RUN(filter, origskb);
+			if (sdata->u.mntr.deliver)
+				deliver = true;
+		} else {
+			sdata->u.mntr.deliver = true;
+			deliver = true;
+		}
+	}
+
+	/* stop pretending the monitor info isn't there */
+	origskb->len += present_fcs_len;
+	__skb_push(origskb, rtap_vendor_space);
+
+	if (!deliver) {
+		remove_monitor_info(local, origskb, present_fcs_len,
+				    rtap_vendor_space);
+		return origskb;
+	}
+#endif
+
 	/* room for the radiotap header based on driver features */
 	rt_hdrlen = ieee80211_rx_radiotap_hdrlen(local, status, origskb);
 	needed_headroom = rt_hdrlen - rtap_vendor_space;
@@ -598,11 +638,16 @@  ieee80211_rx_monitor(struct ieee80211_local *local, struct sk_buff *origskb,
 		if (sdata->vif.type != NL80211_IFTYPE_MONITOR)
 			continue;
 
+#ifdef CONFIG_BPF_WIFIMON
+		if (!sdata->u.mntr.deliver)
+			continue;
+#else
 		if (sdata->u.mntr.flags & MONITOR_FLAG_COOK_FRAMES)
 			continue;
 
 		if (!ieee80211_sdata_running(sdata))
 			continue;
+#endif
 
 		if (prev_dev) {
 			skb2 = skb_clone(skb, GFP_ATOMIC);