cfg80211/nl80211: add API for MAC address ACLs
authorVasanthakumar Thiagarajan <vthiagar@qca.qualcomm.com>
Fri, 18 Jan 2013 05:48:45 +0000 (11:18 +0530)
committerJohannes Berg <johannes.berg@intel.com>
Fri, 25 Jan 2013 17:36:44 +0000 (18:36 +0100)
Add API to enable drivers to implement MAC address based
access control in AP/P2P GO mode. Capable drivers advertise
this capability by setting the maximum number of MAC
addresses in such a list in wiphy->max_acl_mac_addrs.

An initial ACL may be given to the NL80211_CMD_START_AP
command and/or changed later with NL80211_CMD_SET_MAC_ACL.

Black- and whitelists are supported, but not simultaneously.

Signed-off-by: Vasanthakumar Thiagarajan <vthiagar@qca.qualcomm.com>
[rewrite commit log, many cleanups]
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
include/net/cfg80211.h
include/uapi/linux/nl80211.h
net/wireless/core.c
net/wireless/nl80211.c
net/wireless/rdev-ops.h
net/wireless/trace.h

index 183033789e696a714b519699d68843500581ca4d..36e076e374d22b3d3f4fd82495fd49ad591e9207 100644 (file)
@@ -531,6 +531,22 @@ struct mac_address {
        u8 addr[ETH_ALEN];
 };
 
+/**
+ * struct cfg80211_acl_data - Access control list data
+ *
+ * @acl_policy: ACL policy to be applied on the station's
+       entry specified by mac_addr
+ * @n_acl_entries: Number of MAC address entries passed
+ * @mac_addrs: List of MAC addresses of stations to be used for ACL
+ */
+struct cfg80211_acl_data {
+       enum nl80211_acl_policy acl_policy;
+       int n_acl_entries;
+
+       /* Keep it last */
+       struct mac_address mac_addrs[];
+};
+
 /**
  * struct cfg80211_ap_settings - AP configuration
  *
@@ -550,6 +566,8 @@ struct mac_address {
  * @inactivity_timeout: time in seconds to determine station's inactivity.
  * @p2p_ctwindow: P2P CT Window
  * @p2p_opp_ps: P2P opportunistic PS
+ * @acl: ACL configuration used by the drivers which has support for
+ *     MAC address based access control
  */
 struct cfg80211_ap_settings {
        struct cfg80211_chan_def chandef;
@@ -566,6 +584,7 @@ struct cfg80211_ap_settings {
        int inactivity_timeout;
        u8 p2p_ctwindow;
        bool p2p_opp_ps;
+       const struct cfg80211_acl_data *acl;
 };
 
 /**
@@ -1800,6 +1819,13 @@ struct cfg80211_gtk_rekey_data {
  *
  * @start_p2p_device: Start the given P2P device.
  * @stop_p2p_device: Stop the given P2P device.
+ *
+ * @set_mac_acl: Sets MAC address control list in AP and P2P GO mode.
+ *     Parameters include ACL policy, an array of MAC address of stations
+ *     and the number of MAC addresses. If there is already a list in driver
+ *     this new list replaces the existing one. Driver has to clear its ACL
+ *     when number of MAC addresses entries is passed as 0. Drivers which
+ *     advertise the support for MAC based ACL have to implement this callback.
  */
 struct cfg80211_ops {
        int     (*suspend)(struct wiphy *wiphy, struct cfg80211_wowlan *wow);
@@ -2020,6 +2046,9 @@ struct cfg80211_ops {
                                    struct wireless_dev *wdev);
        void    (*stop_p2p_device)(struct wiphy *wiphy,
                                   struct wireless_dev *wdev);
+
+       int     (*set_mac_acl)(struct wiphy *wiphy, struct net_device *dev,
+                              const struct cfg80211_acl_data *params);
 };
 
 /*
@@ -2325,6 +2354,9 @@ struct wiphy_wowlan_support {
  * @ap_sme_capa: AP SME capabilities, flags from &enum nl80211_ap_sme_features.
  * @ht_capa_mod_mask:  Specify what ht_cap values can be over-ridden.
  *     If null, then none can be over-ridden.
+ *
+ * @max_acl_mac_addrs: Maximum number of MAC addresses that the device
+ *     supports for ACL.
  */
 struct wiphy {
        /* assign these fields before you register the wiphy */
@@ -2346,6 +2378,8 @@ struct wiphy {
        /* Supported interface modes, OR together BIT(NL80211_IFTYPE_...) */
        u16 interface_modes;
 
+       u16 max_acl_mac_addrs;
+
        u32 flags, features;
 
        u32 ap_sme_capa;
index e6eeb4ba5dc58c83ce20c7773a1f20819e883d36..5b7dbc1ea966b052899ec7b3ae2713da678903e2 100644 (file)
  *     %NL80211_ATTR_HIDDEN_SSID, %NL80211_ATTR_CIPHERS_PAIRWISE,
  *     %NL80211_ATTR_CIPHER_GROUP, %NL80211_ATTR_WPA_VERSIONS,
  *     %NL80211_ATTR_AKM_SUITES, %NL80211_ATTR_PRIVACY,
- *     %NL80211_ATTR_AUTH_TYPE and %NL80211_ATTR_INACTIVITY_TIMEOUT.
+ *     %NL80211_ATTR_AUTH_TYPE, %NL80211_ATTR_INACTIVITY_TIMEOUT,
+ *     %NL80211_ATTR_ACL_POLICY and %NL80211_ATTR_MAC_ADDRS.
  *     The channel to use can be set on the interface or be given using the
  *     %NL80211_ATTR_WIPHY_FREQ and the attributes determining channel width.
  * @NL80211_CMD_NEW_BEACON: old alias for %NL80211_CMD_START_AP
  * @NL80211_CMD_SET_MCAST_RATE: Change the rate used to send multicast frames
  *     for IBSS or MESH vif.
  *
+ * @NL80211_CMD_SET_MAC_ACL: sets ACL for MAC address based access control.
+ *     This is to be used with the drivers advertising the support of MAC
+ *     address based access control. List of MAC addresses is passed in
+ *     %NL80211_ATTR_MAC_ADDRS and ACL policy is passed in
+ *     %NL80211_ATTR_ACL_POLICY. Driver will enable ACL with this list, if it
+ *     is not already done. The new list will replace any existing list. Driver
+ *     will clear its ACL when the list of MAC addresses passed is empty. This
+ *     command is used in AP/P2P GO mode. Driver has to make sure to clear its
+ *     ACL list during %NL80211_CMD_STOP_AP.
+ *
  * @NL80211_CMD_MAX: highest used command number
  * @__NL80211_CMD_AFTER_LAST: internal use
  */
@@ -736,6 +747,8 @@ enum nl80211_commands {
 
        NL80211_CMD_SET_MCAST_RATE,
 
+       NL80211_CMD_SET_MAC_ACL,
+
        /* add new commands above here */
 
        /* used to define NL80211_CMD_MAX below */
@@ -1313,6 +1326,16 @@ enum nl80211_commands {
  * @NL80211_ATTR_LOCAL_MESH_POWER_MODE: local mesh STA link-specific power mode
  *     defined in &enum nl80211_mesh_power_mode.
  *
+ * @NL80211_ATTR_ACL_POLICY: ACL policy, see &enum nl80211_acl_policy,
+ *     carried in a u32 attribute
+ *
+ * @NL80211_ATTR_MAC_ADDRS: Array of nested MAC addresses, used for
+ *     MAC ACL.
+ *
+ * @NL80211_ATTR_MAC_ACL_MAX: u32 attribute to advertise the maximum
+ *     number of MAC addresses that a device can support for MAC
+ *     ACL.
+ *
  * @NL80211_ATTR_MAX: highest attribute number currently defined
  * @__NL80211_ATTR_AFTER_LAST: internal use
  */
@@ -1585,6 +1608,12 @@ enum nl80211_attrs {
 
        NL80211_ATTR_LOCAL_MESH_POWER_MODE,
 
+       NL80211_ATTR_ACL_POLICY,
+
+       NL80211_ATTR_MAC_ADDRS,
+
+       NL80211_ATTR_MAC_ACL_MAX,
+
        /* add attributes here, update the policy in nl80211.c */
 
        __NL80211_ATTR_AFTER_LAST,
@@ -3248,7 +3277,7 @@ enum nl80211_probe_resp_offload_support_attr {
  * enum nl80211_connect_failed_reason - connection request failed reasons
  * @NL80211_CONN_FAIL_MAX_CLIENTS: Maximum number of clients that can be
  *     handled by the AP is reached.
- * @NL80211_CONN_FAIL_BLOCKED_CLIENT: Client's MAC is in the AP's blocklist.
+ * @NL80211_CONN_FAIL_BLOCKED_CLIENT: Connection request is rejected due to ACL.
  */
 enum nl80211_connect_failed_reason {
        NL80211_CONN_FAIL_MAX_CLIENTS,
@@ -3276,4 +3305,22 @@ enum nl80211_scan_flags {
        NL80211_SCAN_FLAG_AP                            = 1<<2,
 };
 
+/**
+ * enum nl80211_acl_policy - access control policy
+ *
+ * Access control policy is applied on a MAC list set by
+ * %NL80211_CMD_START_AP and %NL80211_CMD_SET_MAC_ACL, to
+ * be used with %NL80211_ATTR_ACL_POLICY.
+ *
+ * @NL80211_ACL_POLICY_ACCEPT_UNLESS_LISTED: Deny stations which are
+ *     listed in ACL, i.e. allow all the stations which are not listed
+ *     in ACL to authenticate.
+ * @NL80211_ACL_POLICY_DENY_UNLESS_LISTED: Allow the stations which are listed
+ *     in ACL, i.e. deny all the stations which are not listed in ACL.
+ */
+enum nl80211_acl_policy {
+       NL80211_ACL_POLICY_ACCEPT_UNLESS_LISTED,
+       NL80211_ACL_POLICY_DENY_UNLESS_LISTED,
+};
+
 #endif /* __LINUX_NL80211_H */
index 0e702cdc60434ef53a31884b64883405c155b2b6..ce827242f3903ae15cca52ed98dcdcf4b5901886 100644 (file)
@@ -478,6 +478,11 @@ int wiphy_register(struct wiphy *wiphy)
                           ETH_ALEN)))
                return -EINVAL;
 
+       if (WARN_ON(wiphy->max_acl_mac_addrs &&
+                   (!(wiphy->flags & WIPHY_FLAG_HAVE_AP_SME) ||
+                    !rdev->ops->set_mac_acl)))
+               return -EINVAL;
+
        if (wiphy->addresses)
                memcpy(wiphy->perm_addr, wiphy->addresses[0].addr, ETH_ALEN);
 
index 33de80364c5cd183f344b8cc72f03810855141f3..b5978ab4ad7af95071169368b3e3a6064c215373 100644 (file)
@@ -365,6 +365,8 @@ static const struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] = {
        [NL80211_ATTR_SCAN_FLAGS] = { .type = NLA_U32 },
        [NL80211_ATTR_P2P_CTWINDOW] = { .type = NLA_U8 },
        [NL80211_ATTR_P2P_OPPPS] = { .type = NLA_U8 },
+       [NL80211_ATTR_ACL_POLICY] = {. type = NLA_U32 },
+       [NL80211_ATTR_MAC_ADDRS] = { .type = NLA_NESTED },
 };
 
 /* policy for the key attributes */
@@ -1268,6 +1270,12 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 portid, u32 seq, int flag
                    dev->wiphy.ht_capa_mod_mask))
                goto nla_put_failure;
 
+       if (dev->wiphy.flags & WIPHY_FLAG_HAVE_AP_SME &&
+           dev->wiphy.max_acl_mac_addrs &&
+           nla_put_u32(msg, NL80211_ATTR_MAC_ACL_MAX,
+                       dev->wiphy.max_acl_mac_addrs))
+               goto nla_put_failure;
+
        return genlmsg_end(msg, hdr);
 
  nla_put_failure:
@@ -2491,6 +2499,97 @@ static int nl80211_del_key(struct sk_buff *skb, struct genl_info *info)
        return err;
 }
 
+/* This function returns an error or the number of nested attributes */
+static int validate_acl_mac_addrs(struct nlattr *nl_attr)
+{
+       struct nlattr *attr;
+       int n_entries = 0, tmp;
+
+       nla_for_each_nested(attr, nl_attr, tmp) {
+               if (nla_len(attr) != ETH_ALEN)
+                       return -EINVAL;
+
+               n_entries++;
+       }
+
+       return n_entries;
+}
+
+/*
+ * This function parses ACL information and allocates memory for ACL data.
+ * On successful return, the calling function is responsible to free the
+ * ACL buffer returned by this function.
+ */
+static struct cfg80211_acl_data *parse_acl_data(struct wiphy *wiphy,
+                                               struct genl_info *info)
+{
+       enum nl80211_acl_policy acl_policy;
+       struct nlattr *attr;
+       struct cfg80211_acl_data *acl;
+       int i = 0, n_entries, tmp;
+
+       if (!wiphy->max_acl_mac_addrs)
+               return ERR_PTR(-EOPNOTSUPP);
+
+       if (!info->attrs[NL80211_ATTR_ACL_POLICY])
+               return ERR_PTR(-EINVAL);
+
+       acl_policy = nla_get_u32(info->attrs[NL80211_ATTR_ACL_POLICY]);
+       if (acl_policy != NL80211_ACL_POLICY_ACCEPT_UNLESS_LISTED &&
+           acl_policy != NL80211_ACL_POLICY_DENY_UNLESS_LISTED)
+               return ERR_PTR(-EINVAL);
+
+       if (!info->attrs[NL80211_ATTR_MAC_ADDRS])
+               return ERR_PTR(-EINVAL);
+
+       n_entries = validate_acl_mac_addrs(info->attrs[NL80211_ATTR_MAC_ADDRS]);
+       if (n_entries < 0)
+               return ERR_PTR(n_entries);
+
+       if (n_entries > wiphy->max_acl_mac_addrs)
+               return ERR_PTR(-ENOTSUPP);
+
+       acl = kzalloc(sizeof(*acl) + (sizeof(struct mac_address) * n_entries),
+                     GFP_KERNEL);
+       if (!acl)
+               return ERR_PTR(-ENOMEM);
+
+       nla_for_each_nested(attr, info->attrs[NL80211_ATTR_MAC_ADDRS], tmp) {
+               memcpy(acl->mac_addrs[i].addr, nla_data(attr), ETH_ALEN);
+               i++;
+       }
+
+       acl->n_acl_entries = n_entries;
+       acl->acl_policy = acl_policy;
+
+       return acl;
+}
+
+static int nl80211_set_mac_acl(struct sk_buff *skb, struct genl_info *info)
+{
+       struct cfg80211_registered_device *rdev = info->user_ptr[0];
+       struct net_device *dev = info->user_ptr[1];
+       struct cfg80211_acl_data *acl;
+       int err;
+
+       if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP &&
+           dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO)
+               return -EOPNOTSUPP;
+
+       if (!dev->ieee80211_ptr->beacon_interval)
+               return -EINVAL;
+
+       acl = parse_acl_data(&rdev->wiphy, info);
+       if (IS_ERR(acl))
+               return PTR_ERR(acl);
+
+       err = rdev_set_mac_acl(rdev, dev, acl);
+
+       kfree(acl);
+
+       return err;
+}
+
 static int nl80211_parse_beacon(struct genl_info *info,
                                struct cfg80211_beacon_data *bcn)
 {
@@ -2734,6 +2833,12 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
        if (err)
                return err;
 
+       if (info->attrs[NL80211_ATTR_ACL_POLICY]) {
+               params.acl = parse_acl_data(&rdev->wiphy, info);
+               if (IS_ERR(params.acl))
+                       return PTR_ERR(params.acl);
+       }
+
        err = rdev_start_ap(rdev, dev, &params);
        if (!err) {
                wdev->preset_chandef = params.chandef;
@@ -2742,6 +2847,9 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
                wdev->ssid_len = params.ssid_len;
                memcpy(wdev->ssid, params.ssid, wdev->ssid_len);
        }
+
+       kfree(params.acl);
+
        return err;
 }
 
@@ -7876,6 +7984,14 @@ static struct genl_ops nl80211_ops[] = {
                .internal_flags = NL80211_FLAG_NEED_NETDEV |
                                  NL80211_FLAG_NEED_RTNL,
        },
+       {
+               .cmd = NL80211_CMD_SET_MAC_ACL,
+               .doit = nl80211_set_mac_acl,
+               .policy = nl80211_policy,
+               .flags = GENL_ADMIN_PERM,
+               .internal_flags = NL80211_FLAG_NEED_NETDEV |
+                                 NL80211_FLAG_NEED_RTNL,
+       },
 };
 
 static struct genl_multicast_group nl80211_mlme_mcgrp = {
index 6c0c8191f83771e361fd1f4a14424383d347d7f6..422d38291d66e3eb00c8cf2cdb5b9369182530fd 100644 (file)
@@ -875,4 +875,16 @@ static inline void rdev_stop_p2p_device(struct cfg80211_registered_device *rdev,
        rdev->ops->stop_p2p_device(&rdev->wiphy, wdev);
        trace_rdev_return_void(&rdev->wiphy);
 }                                      
+
+static inline int rdev_set_mac_acl(struct cfg80211_registered_device *rdev,
+                                  struct net_device *dev,
+                                  struct cfg80211_acl_data *params)
+{
+       int ret;
+
+       trace_rdev_set_mac_acl(&rdev->wiphy, dev, params);
+       ret = rdev->ops->set_mac_acl(&rdev->wiphy, dev, params);
+       trace_rdev_return_int(&rdev->wiphy, ret);
+       return ret;
+}
 #endif /* __CFG80211_RDEV_OPS */
index 2134576f426e0f5853255b3d76fb2e3bff5fe24c..8bc5531996867c9d47df9e6709f9cc76c0d7b7b9 100644 (file)
@@ -1767,6 +1767,24 @@ DEFINE_EVENT(wiphy_wdev_evt, rdev_stop_p2p_device,
        TP_ARGS(wiphy, wdev)
 );
 
+TRACE_EVENT(rdev_set_mac_acl,
+       TP_PROTO(struct wiphy *wiphy, struct net_device *netdev,
+                struct cfg80211_acl_data *params),
+       TP_ARGS(wiphy, netdev, params),
+       TP_STRUCT__entry(
+               WIPHY_ENTRY
+               NETDEV_ENTRY
+               __field(u32, acl_policy)
+       ),
+       TP_fast_assign(
+               WIPHY_ASSIGN;
+               WIPHY_ASSIGN;
+               __entry->acl_policy = params->acl_policy;
+       ),
+       TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", acl policy: %d",
+                 WIPHY_PR_ARG, NETDEV_PR_ARG, __entry->acl_policy)
+);
+
 /*************************************************************
  *          cfg80211 exported functions traces              *
  *************************************************************/