cfg80211/nl80211: add API for MAC address ACLs
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>
Git-commit: 77765eaf5cfb6b8dd98ec8b54b411d74ff6095f1
Git-repo: git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
CRs-fixed: 487410
Change-Id: I9593b887941be5efa5e96546ad92cd6c6bf8fb87
[duttus@codeaurora.org: resolved 3.4 backport issues]
Signed-off-by: Sunil Dutt <duttus@codeaurora.org>
diff --git a/include/linux/nl80211.h b/include/linux/nl80211.h
index f551e75..6d7d178 100644
--- a/include/linux/nl80211.h
+++ b/include/linux/nl80211.h
@@ -3069,4 +3069,32 @@
NL80211_PROBE_RESP_OFFLOAD_SUPPORT_80211U = 1<<3,
};
+/**
+ * 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: Connection request is rejected due to ACL.
+ */
+enum nl80211_connect_failed_reason {
+ NL80211_CONN_FAIL_MAX_CLIENTS,
+ NL80211_CONN_FAIL_BLOCKED_CLIENT,
+};
+
+/**
+ * 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 */
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index dc29eb9..fb2b57a 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -416,6 +416,26 @@
size_t probe_resp_len;
};
+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
*
@@ -432,6 +452,8 @@
* @privacy: the BSS uses privacy
* @auth_type: Authentication type (algorithm)
* @inactivity_timeout: time in seconds to determine station's inactivity.
+ * @acl: ACL configuration used by the drivers which has support for
+ * MAC address based access control
*/
struct cfg80211_ap_settings {
struct cfg80211_beacon_data beacon;
@@ -444,6 +466,7 @@
bool privacy;
enum nl80211_auth_type auth_type;
int inactivity_timeout;
+ const struct cfg80211_acl_data *acl;
};
/**
@@ -1559,6 +1582,13 @@
* later passes to cfg80211_probe_status().
*
* @set_noack_map: Set the NoAck Map for the TIDs.
+ * @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);
@@ -1757,6 +1787,9 @@
struct ieee80211_channel *(*get_channel)(struct wiphy *wiphy);
int (*update_ft_ies)(struct wiphy *wiphy, struct net_device *dev,
struct cfg80211_update_ft_ies_params *ftie);
+
+ int (*set_mac_acl)(struct wiphy *wiphy, struct net_device *dev,
+ const struct cfg80211_acl_data *params);
};
/*
@@ -1924,10 +1957,6 @@
bool beacon_int_infra_match;
};
-struct mac_address {
- u8 addr[ETH_ALEN];
-};
-
struct ieee80211_txrx_stypes {
u16 tx, rx;
};
@@ -2068,6 +2097,9 @@
* @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 */
@@ -2089,6 +2121,8 @@
/* Supported interface modes, OR together BIT(NL80211_IFTYPE_...) */
u16 interface_modes;
+ u16 max_acl_mac_addrs;
+
u32 flags, features;
u32 ap_sme_capa;
diff --git a/net/wireless/core.c b/net/wireless/core.c
index ccdfed8..674c1fa 100644
--- a/net/wireless/core.c
+++ b/net/wireless/core.c
@@ -505,6 +505,11 @@
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);
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index e1fa62e..6ed6d3e 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -210,6 +210,8 @@
[NL80211_ATTR_USER_REG_HINT_TYPE] = { .type = NLA_U32 },
[NL80211_ATTR_SAE_DATA] = { .type = NLA_BINARY, },
[NL80211_ATTR_VHT_CAPABILITY] = { .len = NL80211_VHT_CAPABILITY_LEN },
+ [NL80211_ATTR_ACL_POLICY] = {. type = NLA_U32 },
+ [NL80211_ATTR_MAC_ADDRS] = { .type = NLA_NESTED },
[NL80211_ATTR_STA_CAPABILITY] = { .type = NLA_U16 },
[NL80211_ATTR_STA_EXT_CAPABILITY] = { .type = NLA_BINARY, },
[NL80211_ATTR_SPLIT_WIPHY_DUMP] = { .type = NLA_FLAG, },
@@ -1066,6 +1068,11 @@
sizeof(*dev->wiphy.ht_capa_mod_mask),
dev->wiphy.ht_capa_mod_mask);
+ 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);
+
return genlmsg_end(msg, hdr);
nla_put_failure:
@@ -2105,6 +2112,97 @@
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->ops->set_mac_acl(&rdev->wiphy, dev, acl);
+
+ kfree(acl);
+
+ return err;
+}
+
static int nl80211_parse_beacon(struct genl_info *info,
struct cfg80211_beacon_data *bcn)
{
@@ -2251,9 +2349,18 @@
info->attrs[NL80211_ATTR_INACTIVITY_TIMEOUT]);
}
+ 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->ops->start_ap(&rdev->wiphy, dev, ¶ms);
if (!err)
wdev->beacon_interval = params.beacon_interval;
+
+ kfree(params.acl);
+
return err;
}
@@ -7063,6 +7170,14 @@
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,
+ },
+ {
.cmd = NL80211_CMD_UPDATE_FT_IES,
.doit = nl80211_update_ft_ies,
.policy = nl80211_policy,