mac80211: Add support for hardware ARP query filtering
authorJuuso Oikarinen <juuso.oikarinen@nokia.com>
Thu, 27 May 2010 12:32:13 +0000 (15:32 +0300)
committerJohn W. Linville <linville@tuxdriver.com>
Thu, 3 Jun 2010 18:10:45 +0000 (14:10 -0400)
Some hardware allow extended filtering of ARP frames not intended for
the host. To perform such filtering, the hardware needs to know the current
IP address(es) of the host, bound to its interface.

Add support for ARP filtering to mac80211 by adding a new op to the driver
interface, allowing to configure the current IP addresses. This op is called
upon association with the currently configured address(es), and when
associated whenever the IP address(es) change.

This patch adds configuration of IPv4 addresses only, as IPv6 addresses don't
need ARP filtering.

Signed-off-by: Juuso Oikarinen <juuso.oikarinen@nokia.com>
Reviewed-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
include/net/mac80211.h
net/mac80211/driver-ops.h
net/mac80211/driver-trace.h
net/mac80211/ieee80211_i.h
net/mac80211/main.c
net/mac80211/mlme.c

index f26440a46df079a1e15781dd636cc345a5419308..74b9b49ddfae7f1f4d564446428def7cca1514ca 100644 (file)
@@ -19,6 +19,7 @@
 #include <linux/wireless.h>
 #include <linux/device.h>
 #include <linux/ieee80211.h>
+#include <linux/inetdevice.h>
 #include <net/cfg80211.h>
 
 /**
@@ -1532,6 +1533,16 @@ enum ieee80211_ampdu_mlme_action {
  *     of the bss parameters has changed when a call is made. The callback
  *     can sleep.
  *
+ * @configure_arp_filter: Configuration function for hardware ARP query filter.
+ *     This function is called with all the IP addresses configured to the
+ *     interface as argument - all ARP queries targeted to any of these
+ *     addresses must pass through. If the hardware filter does not support
+ *     enought addresses, hardware filtering must be disabled. The ifa_list
+ *     argument may be NULL, indicating that filtering must be disabled.
+ *     This function is called upon association complete with current
+ *     address(es), and while associated whenever the IP address(es) change.
+ *     The callback can sleep.
+ *
  * @prepare_multicast: Prepare for multicast filter configuration.
  *     This callback is optional, and its return value is passed
  *     to configure_filter(). This callback must be atomic.
@@ -1671,6 +1682,9 @@ struct ieee80211_ops {
                                 struct ieee80211_vif *vif,
                                 struct ieee80211_bss_conf *info,
                                 u32 changed);
+       int (*configure_arp_filter)(struct ieee80211_hw *hw,
+                                   struct ieee80211_vif *vif,
+                                   struct in_ifaddr *ifa_list);
        u64 (*prepare_multicast)(struct ieee80211_hw *hw,
                                 struct netdev_hw_addr_list *mc_list);
        void (*configure_filter)(struct ieee80211_hw *hw,
index 4f2271316650d98f08de2f1c460b4b8fb283d752..978850ee3a5fdc397e4ee350e97ebc6bc0e0c605 100644 (file)
@@ -83,6 +83,23 @@ static inline void drv_bss_info_changed(struct ieee80211_local *local,
        trace_drv_bss_info_changed(local, sdata, info, changed);
 }
 
+struct in_ifaddr;
+static inline int drv_configure_arp_filter(struct ieee80211_local *local,
+                                          struct ieee80211_vif *vif,
+                                          struct in_ifaddr *ifa_list)
+{
+       int ret = 0;
+
+       might_sleep();
+
+       if (local->ops->configure_arp_filter)
+               ret = local->ops->configure_arp_filter(&local->hw, vif,
+                                                      ifa_list);
+
+       trace_drv_configure_arp_filter(local, vif_to_sdata(vif), ifa_list, ret);
+       return ret;
+}
+
 static inline u64 drv_prepare_multicast(struct ieee80211_local *local,
                                        struct netdev_hw_addr_list *mc_list)
 {
index 6a9b2342a9c2d3982c83d235149762271a4f9c83..577460da2ea15faa5aba7f80f9030cdcd81347c6 100644 (file)
@@ -219,6 +219,31 @@ TRACE_EVENT(drv_bss_info_changed,
        )
 );
 
+TRACE_EVENT(drv_configure_arp_filter,
+       TP_PROTO(struct ieee80211_local *local,
+                struct ieee80211_sub_if_data *sdata,
+                struct in_ifaddr *ifa_list, int ret),
+
+       TP_ARGS(local, sdata, ifa_list, ret),
+
+       TP_STRUCT__entry(
+               LOCAL_ENTRY
+               VIF_ENTRY
+               __field(int, ret)
+       ),
+
+       TP_fast_assign(
+               LOCAL_ASSIGN;
+               VIF_ASSIGN;
+               __entry->ret = ret;
+       ),
+
+       TP_printk(
+               VIF_PR_FMT LOCAL_PR_FMT " ret:%d",
+               VIF_PR_ARG, LOCAL_PR_ARG, __entry->ret
+       )
+);
+
 TRACE_EVENT(drv_prepare_multicast,
        TP_PROTO(struct ieee80211_local *local, int mc_count, u64 ret),
 
index d4677efd3a36e02ea0eb5df2cba22e114484c288..47d67537f1702f6f49330b23ce89cc29be5e06c2 100644 (file)
@@ -851,6 +851,7 @@ struct ieee80211_local {
        struct work_struct dynamic_ps_disable_work;
        struct timer_list dynamic_ps_timer;
        struct notifier_block network_latency_notifier;
+       struct notifier_block ifa_notifier;
 
        int user_power_level; /* in dBm */
        int power_constr_level; /* in dBm */
@@ -997,6 +998,7 @@ void ieee80211_send_pspoll(struct ieee80211_local *local,
 void ieee80211_recalc_ps(struct ieee80211_local *local, s32 latency);
 int ieee80211_max_network_latency(struct notifier_block *nb,
                                  unsigned long data, void *dummy);
+int ieee80211_set_arp_filter(struct ieee80211_sub_if_data *sdata);
 void ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
                                      struct ieee80211_channel_sw_ie *sw_elem,
                                      struct ieee80211_bss *bss,
index c8548e61f86096629caa72e5a4081fd4122da01e..4051b232c6e6fe21b26cbcc7cd505aed2dbc2a60 100644 (file)
@@ -329,6 +329,58 @@ static void ieee80211_recalc_smps_work(struct work_struct *work)
        mutex_unlock(&local->iflist_mtx);
 }
 
+int ieee80211_set_arp_filter(struct ieee80211_sub_if_data *sdata)
+{
+       struct in_device *idev;
+       int ret = 0;
+
+       BUG_ON(!sdata);
+       ASSERT_RTNL();
+
+       idev = sdata->dev->ip_ptr;
+       if (!idev)
+               return 0;
+
+       ret = drv_configure_arp_filter(sdata->local, &sdata->vif,
+                                      idev->ifa_list);
+       return ret;
+}
+
+static int ieee80211_ifa_changed(struct notifier_block *nb,
+                                unsigned long data, void *arg)
+{
+       struct in_ifaddr *ifa = arg;
+       struct ieee80211_local *local =
+               container_of(nb, struct ieee80211_local,
+                            ifa_notifier);
+       struct net_device *ndev = ifa->ifa_dev->dev;
+       struct wireless_dev *wdev = ndev->ieee80211_ptr;
+       struct ieee80211_sub_if_data *sdata;
+       struct ieee80211_if_managed *ifmgd;
+
+       /* Make sure it's our interface that got changed */
+       if (!wdev)
+               return NOTIFY_DONE;
+
+       if (wdev->wiphy != local->hw.wiphy)
+               return NOTIFY_DONE;
+
+       /* We are concerned about IP addresses only when associated */
+       sdata = IEEE80211_DEV_TO_SUB_IF(ndev);
+
+       /* ARP filtering is only supported in managed mode */
+       if (sdata->vif.type != NL80211_IFTYPE_STATION)
+               return NOTIFY_DONE;
+
+       ifmgd = &sdata->u.mgd;
+       mutex_lock(&ifmgd->mtx);
+       if (ifmgd->associated)
+               ieee80211_set_arp_filter(sdata);
+       mutex_unlock(&ifmgd->mtx);
+
+       return NOTIFY_DONE;
+}
+
 struct ieee80211_hw *ieee80211_alloc_hw(size_t priv_data_len,
                                        const struct ieee80211_ops *ops)
 {
@@ -612,14 +664,22 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
                ieee80211_max_network_latency;
        result = pm_qos_add_notifier(PM_QOS_NETWORK_LATENCY,
                                     &local->network_latency_notifier);
-
        if (result) {
                rtnl_lock();
                goto fail_pm_qos;
        }
 
+       local->ifa_notifier.notifier_call = ieee80211_ifa_changed;
+       result = register_inetaddr_notifier(&local->ifa_notifier);
+       if (result)
+               goto fail_ifa;
+
        return 0;
 
+ fail_ifa:
+       pm_qos_remove_notifier(PM_QOS_NETWORK_LATENCY,
+                              &local->network_latency_notifier);
+       rtnl_lock();
  fail_pm_qos:
        ieee80211_led_exit(local);
        ieee80211_remove_interfaces(local);
@@ -647,6 +707,7 @@ void ieee80211_unregister_hw(struct ieee80211_hw *hw)
 
        pm_qos_remove_notifier(PM_QOS_NETWORK_LATENCY,
                               &local->network_latency_notifier);
+       unregister_inetaddr_notifier(&local->ifa_notifier);
 
        rtnl_lock();
 
index 29c3a75a7ad043cebe1ccce4d53c6edf03c3abd5..7e720133358cb8cb49d09a512fb442c7ecd27602 100644 (file)
@@ -2078,8 +2078,17 @@ static enum work_done_result ieee80211_assoc_done(struct ieee80211_work *wk,
                        cfg80211_send_assoc_timeout(wk->sdata->dev,
                                                    wk->filter_ta);
                        return WORK_DONE_DESTROY;
+               } else {
+                       mutex_unlock(&wk->sdata->u.mgd.mtx);
+
+                       /*
+                        * configure ARP filter IP addresses to the driver,
+                        * intentionally outside the mgd mutex.
+                        */
+                       rtnl_lock();
+                       ieee80211_set_arp_filter(wk->sdata);
+                       rtnl_unlock();
                }
-               mutex_unlock(&wk->sdata->u.mgd.mtx);
        }
 
        cfg80211_send_rx_assoc(wk->sdata->dev, skb->data, skb->len);