linux-wireless.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC v2 1/3] bpf/wireless: add wifimon program type
@ 2017-04-13 13:51 Johannes Berg
  2017-04-13 13:51 ` [RFC v2 2/3] cfg80211: add API to attach monitor filter program Johannes Berg
  2017-04-13 13:51 ` [RFC v2 3/3] mac80211: support bpf monitor filter Johannes Berg
  0 siblings, 2 replies; 4+ messages in thread
From: Johannes Berg @ 2017-04-13 13:51 UTC (permalink / raw)
  To: linux-wireless; +Cc: Johannes Berg

From: Johannes Berg <johannes.berg@intel.com>

Add a new program type for wifi monitor interfaces. This program
type can
 * access __sk_buff, but only skb->len
 * call bpf_skb_load_bytes()

The program type is only enabled when something selects the new
Kconfig symbol WANT_BPF_WIFIMON, which will be done by mac80211
in a follow-up patch.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
 include/linux/bpf_types.h |  3 +++
 include/uapi/linux/bpf.h  |  1 +
 net/core/filter.c         | 41 +++++++++++++++++++++++++++++++++++++++++
 net/wireless/Kconfig      |  8 ++++++++
 4 files changed, 53 insertions(+)

diff --git a/include/linux/bpf_types.h b/include/linux/bpf_types.h
index 03bf223f18be..fcaba84128dd 100644
--- a/include/linux/bpf_types.h
+++ b/include/linux/bpf_types.h
@@ -16,6 +16,9 @@ BPF_PROG_TYPE(BPF_PROG_TYPE_KPROBE, kprobe_prog_ops)
 BPF_PROG_TYPE(BPF_PROG_TYPE_TRACEPOINT, tracepoint_prog_ops)
 BPF_PROG_TYPE(BPF_PROG_TYPE_PERF_EVENT, perf_event_prog_ops)
 #endif
+#ifdef CONFIG_BPF_WIFIMON
+BPF_PROG_TYPE(BPF_PROG_TYPE_WIFIMON, wifimon_prog_ops)
+#endif
 
 BPF_MAP_TYPE(BPF_MAP_TYPE_ARRAY, array_map_ops)
 BPF_MAP_TYPE(BPF_MAP_TYPE_PERCPU_ARRAY, percpu_array_map_ops)
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index 1e062bb54eec..b0dfe8a79b3f 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -115,6 +115,7 @@ enum bpf_prog_type {
 	BPF_PROG_TYPE_LWT_IN,
 	BPF_PROG_TYPE_LWT_OUT,
 	BPF_PROG_TYPE_LWT_XMIT,
+	BPF_PROG_TYPE_WIFIMON,
 };
 
 enum bpf_attach_type {
diff --git a/net/core/filter.c b/net/core/filter.c
index ce2a19da8aa4..c20624c81f6b 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -3415,3 +3415,44 @@ int sk_get_filter(struct sock *sk, struct sock_filter __user *ubuf,
 	release_sock(sk);
 	return ret;
 }
+
+#ifdef CONFIG_BPF_WIFIMON
+static const struct bpf_func_proto *
+wifimon_func_proto(enum bpf_func_id func_id)
+{
+	switch (func_id) {
+	case BPF_FUNC_skb_load_bytes:
+		return &bpf_skb_load_bytes_proto;
+	default:
+		return bpf_base_func_proto(func_id);
+	}
+}
+
+static bool wifimon_is_valid_access(int off, int size,
+				    enum bpf_access_type type,
+				    enum bpf_reg_type *reg_type)
+{
+	if (type == BPF_WRITE)
+		return false;
+
+	switch (off) {
+	case offsetof(struct __sk_buff, len):
+		break;
+	default:
+		return false;
+	}
+	/* The verifier guarantees that size > 0. */
+	if (off % size != 0)
+		return false;
+	if (size != sizeof(__u32))
+		return false;
+
+	return true;
+}
+
+const struct bpf_verifier_ops wifimon_prog_ops = {
+	.get_func_proto		= wifimon_func_proto,
+	.is_valid_access	= wifimon_is_valid_access,
+	.convert_ctx_access	= bpf_convert_ctx_access,
+};
+#endif
diff --git a/net/wireless/Kconfig b/net/wireless/Kconfig
index 6c606120abfe..50ffebafce46 100644
--- a/net/wireless/Kconfig
+++ b/net/wireless/Kconfig
@@ -214,3 +214,11 @@ config LIB80211_DEBUG
 	  from lib80211.
 
 	  If unsure, say N.
+
+config WANT_BPF_WIFIMON
+	bool
+
+config BPF_WIFIMON
+	bool
+	default y
+	depends on WANT_BPF_WIFIMON && BPF_SYSCALL
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [RFC v2 2/3] cfg80211: add API to attach monitor filter program
  2017-04-13 13:51 [RFC v2 1/3] bpf/wireless: add wifimon program type Johannes Berg
@ 2017-04-13 13:51 ` Johannes Berg
  2017-04-13 13:51 ` [RFC v2 3/3] mac80211: support bpf monitor filter Johannes Berg
  1 sibling, 0 replies; 4+ messages in thread
From: Johannes Berg @ 2017-04-13 13:51 UTC (permalink / raw)
  To: linux-wireless; +Cc: Johannes Berg

From: Johannes Berg <johannes.berg@intel.com>

Some people have suggested that the nl80211 API to look at
management frames should be extended to allow multiple
registrations, in order to allow applications other than
the usual controllers (wpa_supplicant/hostapd) to look out
for frames, e.g. for statistics or similar. Suggestions to
use monitor interfaces were rejected, because the overhead
is too big, even when a socket filter is installed. Adding
this to the nl80211 API is, however, problematic because
it then becomes undefined who is responsible for a frame,
e.g. when action frames have multiple listeners - but only
one can be responsible for handling them.

To solve this problem, we can allow installing a BPF filter
program that gets run on the raw 802.11 frame received from
the driver, before the frame is even considered for copying
the SKB or building the radiotap header for a given monitor
interface.

Add the necessary API to attach a monitor filter program
to a newly created monitor interface, or allow changing
it. Also allow clearing it by passing -1 as the FD.

NOTE/WARNING
We really should first figure out what we want to do with
802.3 frames that are received by the driver. Do they at
all show up on monitor? For the use case described above,
that doesn't matter - however, it's easy to imagine other
use cases, like using BPF for packet statistics (like the
never-merged per-rate statistics), and then seeing data
frames as well would be good. Perhaps the program can get
a different metadata struct rather than __sk_buff, that
indiciates whether the frames has 802.11 or 802.3 format?
However, if it already has 802.3 format, the TA might no
longer be present (in AP -> STA frames), which would then
no longer allow collecting such statistics anyway ...

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
 include/net/cfg80211.h       |  3 +++
 include/uapi/linux/nl80211.h |  7 +++++++
 net/wireless/core.c          |  5 +++++
 net/wireless/nl80211.c       | 27 +++++++++++++++++++++++++++
 4 files changed, 42 insertions(+)

diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index a8faf9f0cac2..a59a8917a74c 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -376,6 +376,8 @@ static inline void wiphy_read_of_freq_limits(struct wiphy *wiphy)
  *	belonging to that MU-MIMO groupID; %NULL if not changed
  * @vht_mumimo_follow_addr: MU-MIMO follow address, used for monitoring
  *	MU-MIMO packets going to the specified station; %NULL if not changed
+ * @filter: wifi monitor BPF program, %NULL if not changed, an ERR_PTR()
+ *	if the program should be removed
  */
 struct vif_params {
 	u32 flags;
@@ -383,6 +385,7 @@ struct vif_params {
 	u8 macaddr[ETH_ALEN];
 	const u8 *vht_mumimo_groups;
 	const u8 *vht_mumimo_follow_addr;
+	struct bpf_prog *filter;
 };
 
 /**
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index 5ed257c4cd4e..4a33cac2a41a 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -2012,6 +2012,8 @@ enum nl80211_commands {
  *	u32 attribute with an &enum nl80211_timeout_reason value. This is used,
  *	e.g., with %NL80211_CMD_CONNECT event.
  *
+ * @NL80211_ATTR_BPF_FD: BPF file descriptor (s32), use -1 to remove a program
+ *
  * @NUM_NL80211_ATTR: total number of nl80211_attrs available
  * @NL80211_ATTR_MAX: highest attribute number currently defined
  * @__NL80211_ATTR_AFTER_LAST: internal use
@@ -2423,6 +2425,8 @@ enum nl80211_attrs {
 
 	NL80211_ATTR_TIMEOUT_REASON,
 
+	NL80211_ATTR_BPF_FD,
+
 	/* add attributes here, update the policy in nl80211.c */
 
 	__NL80211_ATTR_AFTER_LAST,
@@ -4753,6 +4757,8 @@ enum nl80211_feature_flags {
  * @NL80211_EXT_FEATURE_SCHED_SCAN_RELATIVE_RSSI: The driver supports sched_scan
  *	for reporting BSSs with better RSSI than the current connected BSS
  *	(%NL80211_ATTR_SCHED_SCAN_RELATIVE_RSSI).
+ * @NL80211_EXT_FEATURE_WIFIMON_BPF: supports running BPF filter programs
+ *	for frames seen on monitor interfaces
  *
  * @NUM_NL80211_EXT_FEATURES: number of extended features.
  * @MAX_NL80211_EXT_FEATURES: highest extended feature index.
@@ -4771,6 +4777,7 @@ enum nl80211_ext_feature_index {
 	NL80211_EXT_FEATURE_MGMT_TX_RANDOM_TA,
 	NL80211_EXT_FEATURE_MGMT_TX_RANDOM_TA_CONNECTED,
 	NL80211_EXT_FEATURE_SCHED_SCAN_RELATIVE_RSSI,
+	NL80211_EXT_FEATURE_WIFIMON_BPF,
 
 	/* add new features before the definition below */
 	NUM_NL80211_EXT_FEATURES,
diff --git a/net/wireless/core.c b/net/wireless/core.c
index e55e05bc4805..6d9a38e9a375 100644
--- a/net/wireless/core.c
+++ b/net/wireless/core.c
@@ -691,6 +691,11 @@ int wiphy_register(struct wiphy *wiphy)
 		    (wiphy->bss_select_support & ~(BIT(__NL80211_BSS_SELECT_ATTR_AFTER_LAST) - 2))))
 		return -EINVAL;
 
+	if (WARN_ON(!IS_ENABLED(CONFIG_BPF_WIFIMON) &&
+		    wiphy_ext_feature_isset(wiphy,
+					    NL80211_EXT_FEATURE_WIFIMON_BPF)))
+		return -EINVAL;
+
 	if (wiphy->addresses)
 		memcpy(wiphy->perm_addr, wiphy->addresses[0].addr, ETH_ALEN);
 
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 9516840b6e5f..505e6db633c6 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -17,6 +17,7 @@
 #include <linux/rtnetlink.h>
 #include <linux/netlink.h>
 #include <linux/etherdevice.h>
+#include <linux/bpf.h>
 #include <net/net_namespace.h>
 #include <net/genetlink.h>
 #include <net/cfg80211.h>
@@ -410,6 +411,7 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
 		.len = sizeof(struct nl80211_bss_select_rssi_adjust)
 	},
 	[NL80211_ATTR_TIMEOUT_REASON] = { .type = NLA_U32 },
+	[NL80211_ATTR_BPF_FD] = { .type = NLA_U32 },
 };
 
 /* policy for the key attributes */
@@ -2768,6 +2770,28 @@ static int nl80211_parse_mon_options(struct cfg80211_registered_device *rdev,
 		change = true;
 	}
 
+	if (info->attrs[NL80211_ATTR_BPF_FD]) {
+		struct bpf_prog *prog = ERR_PTR(-ENODATA);
+		int fd = nla_get_s32(info->attrs[NL80211_ATTR_BPF_FD]);
+		u32 cap_flag = NL80211_EXT_FEATURE_WIFIMON_BPF;
+
+		if (!IS_ENABLED(CONFIG_BPF_WIFIMON) ||
+		    !wiphy_ext_feature_isset(&rdev->wiphy, cap_flag))
+			return -EOPNOTSUPP;
+
+		if (type != NL80211_IFTYPE_MONITOR)
+			return -EINVAL;
+
+		if (fd >= 0) {
+			prog = bpf_prog_get_type(fd, BPF_PROG_TYPE_WIFIMON);
+			if (IS_ERR(prog))
+				return PTR_ERR(prog);
+		}
+
+		params->filter = prog;
+		change = true;
+	}
+
 	return change ? 1 : 0;
 }
 
@@ -2857,6 +2881,9 @@ static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
 	else
 		err = 0;
 
+	if (err && params.filter)
+		bpf_prog_put(params.filter);
+
 	if (!err && params.use_4addr != -1)
 		dev->ieee80211_ptr->use_4addr = params.use_4addr;
 
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [RFC v2 3/3] mac80211: support bpf monitor filter
  2017-04-13 13:51 [RFC v2 1/3] bpf/wireless: add wifimon program type Johannes Berg
  2017-04-13 13:51 ` [RFC v2 2/3] cfg80211: add API to attach monitor filter program Johannes Berg
@ 2017-04-13 13:51 ` Johannes Berg
  2017-04-13 13:55   ` Johannes Berg
  1 sibling, 1 reply; 4+ messages in thread
From: Johannes Berg @ 2017-04-13 13:51 UTC (permalink / raw)
  To: linux-wireless; +Cc: Johannes Berg

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         | 15 +++++++++++++++
 net/mac80211/ieee80211_i.h |  6 ++++++
 net/mac80211/iface.c       | 11 ++++++++++-
 net/mac80211/main.c        |  3 +++
 net/mac80211/rx.c          | 33 +++++++++++++++++++++++++++++++--
 6 files changed, 66 insertions(+), 3 deletions(-)

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 7af648e95bc6..5d427ed8b6c8 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -107,6 +107,21 @@ static int ieee80211_set_mon_options(struct ieee80211_sub_if_data *sdata,
 		}
 	}
 
+#ifdef CONFIG_BPF_WIFIMON
+	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);
+	}
+#endif
+
 	return 0;
 }
 
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index cf6d5abb65a3..fb0592e0f7b3 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>
@@ -841,6 +843,10 @@ struct ieee80211_if_mntr {
 	u8 mu_follow_addr[ETH_ALEN] __aligned(2);
 
 	struct list_head list;
+
+#ifdef CONFIG_BPF_WIFIMON
+	struct bpf_prog *filter;
+#endif
 };
 
 /**
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 3bd5b81f5d81..5c25afa6cf77 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -1122,8 +1122,17 @@ 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) {
+#ifdef CONFIG_BPF_WIFIMON
+		struct bpf_prog *old = rtnl_dereference(sdata->u.mntr.filter);
+
+		RCU_INIT_POINTER(sdata->u.mntr.filter, NULL);
+		if (old)
+			bpf_prog_put(old);
+#endif
+	}
 }
 
 static void ieee80211_uninit(struct net_device *dev)
diff --git a/net/mac80211/main.c b/net/mac80211/main.c
index ae408a96c407..b7857e710fd5 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_set(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 50d8f8435830..ea7d56774dd3 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -651,17 +651,39 @@ ieee80211_rx_monitor(struct ieee80211_local *local, struct sk_buff *origskb,
 
 	ieee80211_handle_mu_mimo_mon(monitor_sdata, origskb, rtap_vendor_space);
 
+	/* 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->mon_list, u.mntr.list) {
+		const struct bpf_prog *filter __maybe_unused;
 		bool last_monitor = list_is_last(&sdata->u.mntr.list,
 						 &local->mon_list);
 
-		if (!monskb)
+#ifdef CONFIG_BPF_WIFIMON
+		filter = rcu_dereference(sdata->u.mntr.filter);
+		if (filter && !BPF_PROG_RUN(filter, origskb))
+			continue;
+#endif
+
+		if (!monskb) {
+			/* stop pretending the monitor info isn't there */
+			origskb->len += present_fcs_len;
+			__skb_push(origskb, rtap_vendor_space);
+
 			monskb = ieee80211_make_monitor_skb(local, &origskb,
 							    rate,
 							    rtap_vendor_space,
 							    only_monitor &&
 							    last_monitor);
 
+			if (origskb) {
+				/* pretend all the monitor info isn't there */
+				__pskb_pull(origskb, rtap_vendor_space);
+				origskb->len -= present_fcs_len;
+			}
+		}
+
 		if (monskb) {
 			struct sk_buff *skb;
 
@@ -683,13 +705,20 @@ ieee80211_rx_monitor(struct ieee80211_local *local, struct sk_buff *origskb,
 			break;
 	}
 
-	/* this happens if last_monitor was erroneously false */
+	/*
+	 * this may happen if filtering, or if the list RCU handling
+	 * got last_monitor erroneously false
+	 */
 	dev_kfree_skb(monskb);
 
 	/* ditto */
 	if (!origskb)
 		return NULL;
 
+	/* stop pretending the monitor info isn't there */
+	origskb->len += present_fcs_len;
+	__skb_push(origskb, rtap_vendor_space);
+
 	remove_monitor_info(origskb, present_fcs_len, rtap_vendor_space);
 	return origskb;
 }
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [RFC v2 3/3] mac80211: support bpf monitor filter
  2017-04-13 13:51 ` [RFC v2 3/3] mac80211: support bpf monitor filter Johannes Berg
@ 2017-04-13 13:55   ` Johannes Berg
  0 siblings, 0 replies; 4+ messages in thread
From: Johannes Berg @ 2017-04-13 13:55 UTC (permalink / raw)
  To: linux-wireless

If anyone wants to play with this ...

The necessary code is in the "bpf" branch of my mac80211-next and iw
trees. The setup of the filter is a bit stupid right now - the test
program in mac80211-next/bpf (samples/bpf/wifimon) will call iw for the
nl80211 part ... I should make an ELF parser in iw, but that's not as
simple as using the bpf loader in samples/bpf/ :)

johannes

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2017-04-13 13:55 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-04-13 13:51 [RFC v2 1/3] bpf/wireless: add wifimon program type Johannes Berg
2017-04-13 13:51 ` [RFC v2 2/3] cfg80211: add API to attach monitor filter program Johannes Berg
2017-04-13 13:51 ` [RFC v2 3/3] mac80211: support bpf monitor filter Johannes Berg
2017-04-13 13:55   ` Johannes Berg

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).