18a1b9b6aSSam Leffler /*- 2fe267a55SPedro F. Giffuni * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3fe267a55SPedro F. Giffuni * 4b032f27cSSam Leffler * Copyright (c) 2004-2008 Sam Leffler, Errno Consulting 58a1b9b6aSSam Leffler * All rights reserved. 68a1b9b6aSSam Leffler * 78a1b9b6aSSam Leffler * Redistribution and use in source and binary forms, with or without 88a1b9b6aSSam Leffler * modification, are permitted provided that the following conditions 98a1b9b6aSSam Leffler * are met: 108a1b9b6aSSam Leffler * 1. Redistributions of source code must retain the above copyright 118a1b9b6aSSam Leffler * notice, this list of conditions and the following disclaimer. 128a1b9b6aSSam Leffler * 2. Redistributions in binary form must reproduce the above copyright 138a1b9b6aSSam Leffler * notice, this list of conditions and the following disclaimer in the 148a1b9b6aSSam Leffler * documentation and/or other materials provided with the distribution. 158a1b9b6aSSam Leffler * 168a1b9b6aSSam Leffler * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 178a1b9b6aSSam Leffler * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 188a1b9b6aSSam Leffler * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 198a1b9b6aSSam Leffler * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 208a1b9b6aSSam Leffler * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 218a1b9b6aSSam Leffler * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 228a1b9b6aSSam Leffler * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 238a1b9b6aSSam Leffler * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 248a1b9b6aSSam Leffler * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 258a1b9b6aSSam Leffler * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 268a1b9b6aSSam Leffler */ 278a1b9b6aSSam Leffler 288a1b9b6aSSam Leffler #include <sys/cdefs.h> 298a1b9b6aSSam Leffler __FBSDID("$FreeBSD$"); 308a1b9b6aSSam Leffler 318a1b9b6aSSam Leffler /* 328a1b9b6aSSam Leffler * IEEE 802.11 MAC ACL support. 338a1b9b6aSSam Leffler * 34b032f27cSSam Leffler * When this module is loaded the sender address of each auth mgt 358a1b9b6aSSam Leffler * frame is passed to the iac_check method and the module indicates 368a1b9b6aSSam Leffler * if the frame should be accepted or rejected. If the policy is 378a1b9b6aSSam Leffler * set to ACL_POLICY_OPEN then all frames are accepted w/o checking 388a1b9b6aSSam Leffler * the address. Otherwise, the address is looked up in the database 398a1b9b6aSSam Leffler * and if found the frame is either accepted (ACL_POLICY_ALLOW) 408a1b9b6aSSam Leffler * or rejected (ACL_POLICY_DENT). 418a1b9b6aSSam Leffler */ 42b032f27cSSam Leffler #include "opt_wlan.h" 43b032f27cSSam Leffler 448a1b9b6aSSam Leffler #include <sys/param.h> 458a1b9b6aSSam Leffler #include <sys/kernel.h> 468a1b9b6aSSam Leffler #include <sys/systm.h> 478ec07310SGleb Smirnoff #include <sys/malloc.h> 488a1b9b6aSSam Leffler #include <sys/mbuf.h> 498a1b9b6aSSam Leffler #include <sys/module.h> 508a1b9b6aSSam Leffler #include <sys/queue.h> 518a1b9b6aSSam Leffler 528a1b9b6aSSam Leffler #include <sys/socket.h> 538a1b9b6aSSam Leffler 548a1b9b6aSSam Leffler #include <net/if.h> 558a1b9b6aSSam Leffler #include <net/if_media.h> 568a1b9b6aSSam Leffler #include <net/ethernet.h> 578a1b9b6aSSam Leffler #include <net/route.h> 588a1b9b6aSSam Leffler 598a1b9b6aSSam Leffler #include <net80211/ieee80211_var.h> 608a1b9b6aSSam Leffler 618a1b9b6aSSam Leffler enum { 628a1b9b6aSSam Leffler ACL_POLICY_OPEN = 0, /* open, don't check ACL's */ 638a1b9b6aSSam Leffler ACL_POLICY_ALLOW = 1, /* allow traffic from MAC */ 648a1b9b6aSSam Leffler ACL_POLICY_DENY = 2, /* deny traffic from MAC */ 65b032f27cSSam Leffler /* 66b032f27cSSam Leffler * NB: ACL_POLICY_RADIUS must be the same value as 67b032f27cSSam Leffler * IEEE80211_MACCMD_POLICY_RADIUS because of the way 68b032f27cSSam Leffler * acl_getpolicy() works. 69b032f27cSSam Leffler */ 70b032f27cSSam Leffler ACL_POLICY_RADIUS = 7, /* defer to RADIUS ACL server */ 718a1b9b6aSSam Leffler }; 728a1b9b6aSSam Leffler 738a1b9b6aSSam Leffler #define ACL_HASHSIZE 32 748a1b9b6aSSam Leffler 758a1b9b6aSSam Leffler struct acl { 768a1b9b6aSSam Leffler TAILQ_ENTRY(acl) acl_list; 778a1b9b6aSSam Leffler LIST_ENTRY(acl) acl_hash; 7868e8e04eSSam Leffler uint8_t acl_macaddr[IEEE80211_ADDR_LEN]; 798a1b9b6aSSam Leffler }; 808a1b9b6aSSam Leffler struct aclstate { 818a1b9b6aSSam Leffler acl_lock_t as_lock; 828a1b9b6aSSam Leffler int as_policy; 83db9ff08bSKevin Lo uint32_t as_nacls; 848a1b9b6aSSam Leffler TAILQ_HEAD(, acl) as_list; /* list of all ACL's */ 858a1b9b6aSSam Leffler LIST_HEAD(, acl) as_hash[ACL_HASHSIZE]; 86b032f27cSSam Leffler struct ieee80211vap *as_vap; 878a1b9b6aSSam Leffler }; 888a1b9b6aSSam Leffler 898a1b9b6aSSam Leffler /* simple hash is enough for variation of macaddr */ 908a1b9b6aSSam Leffler #define ACL_HASH(addr) \ 9168e8e04eSSam Leffler (((const uint8_t *)(addr))[IEEE80211_ADDR_LEN - 1] % ACL_HASHSIZE) 928a1b9b6aSSam Leffler 93d745c852SEd Schouten static MALLOC_DEFINE(M_80211_ACL, "acl", "802.11 station acl"); 948a1b9b6aSSam Leffler 95b032f27cSSam Leffler static int acl_free_all(struct ieee80211vap *); 96b032f27cSSam Leffler 97b032f27cSSam Leffler /* number of references from net80211 layer */ 98b032f27cSSam Leffler static int nrefs = 0; 998a1b9b6aSSam Leffler 1008a1b9b6aSSam Leffler static int 101b032f27cSSam Leffler acl_attach(struct ieee80211vap *vap) 1028a1b9b6aSSam Leffler { 1038a1b9b6aSSam Leffler struct aclstate *as; 1048a1b9b6aSSam Leffler 105b9b53389SAdrian Chadd as = (struct aclstate *) IEEE80211_MALLOC(sizeof(struct aclstate), 106b9b53389SAdrian Chadd M_80211_ACL, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); 1078a1b9b6aSSam Leffler if (as == NULL) 1088a1b9b6aSSam Leffler return 0; 1098a1b9b6aSSam Leffler ACL_LOCK_INIT(as, "acl"); 1108a1b9b6aSSam Leffler TAILQ_INIT(&as->as_list); 1118a1b9b6aSSam Leffler as->as_policy = ACL_POLICY_OPEN; 112b032f27cSSam Leffler as->as_vap = vap; 113b032f27cSSam Leffler vap->iv_as = as; 114b032f27cSSam Leffler nrefs++; /* NB: we assume caller locking */ 1158a1b9b6aSSam Leffler return 1; 1168a1b9b6aSSam Leffler } 1178a1b9b6aSSam Leffler 1188a1b9b6aSSam Leffler static void 119b032f27cSSam Leffler acl_detach(struct ieee80211vap *vap) 1208a1b9b6aSSam Leffler { 121b032f27cSSam Leffler struct aclstate *as = vap->iv_as; 1228a1b9b6aSSam Leffler 123b032f27cSSam Leffler KASSERT(nrefs > 0, ("imbalanced attach/detach")); 124b032f27cSSam Leffler nrefs--; /* NB: we assume caller locking */ 125b032f27cSSam Leffler 126b032f27cSSam Leffler acl_free_all(vap); 127b032f27cSSam Leffler vap->iv_as = NULL; 1288a1b9b6aSSam Leffler ACL_LOCK_DESTROY(as); 129b9b53389SAdrian Chadd IEEE80211_FREE(as, M_80211_ACL); 1308a1b9b6aSSam Leffler } 1318a1b9b6aSSam Leffler 13268f5ddcdSSam Leffler static __inline struct acl * 13368e8e04eSSam Leffler _find_acl(struct aclstate *as, const uint8_t *macaddr) 1348a1b9b6aSSam Leffler { 1358a1b9b6aSSam Leffler struct acl *acl; 1368a1b9b6aSSam Leffler int hash; 1378a1b9b6aSSam Leffler 1388a1b9b6aSSam Leffler hash = ACL_HASH(macaddr); 1398a1b9b6aSSam Leffler LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) { 1408a1b9b6aSSam Leffler if (IEEE80211_ADDR_EQ(acl->acl_macaddr, macaddr)) 1418a1b9b6aSSam Leffler return acl; 1428a1b9b6aSSam Leffler } 1438a1b9b6aSSam Leffler return NULL; 1448a1b9b6aSSam Leffler } 1458a1b9b6aSSam Leffler 1468a1b9b6aSSam Leffler static void 1478a1b9b6aSSam Leffler _acl_free(struct aclstate *as, struct acl *acl) 1488a1b9b6aSSam Leffler { 1498a1b9b6aSSam Leffler ACL_LOCK_ASSERT(as); 1508a1b9b6aSSam Leffler 1518a1b9b6aSSam Leffler TAILQ_REMOVE(&as->as_list, acl, acl_list); 1528a1b9b6aSSam Leffler LIST_REMOVE(acl, acl_hash); 153b9b53389SAdrian Chadd IEEE80211_FREE(acl, M_80211_ACL); 154188757f5SSam Leffler as->as_nacls--; 1558a1b9b6aSSam Leffler } 1568a1b9b6aSSam Leffler 1578a1b9b6aSSam Leffler static int 1585a8801b0SBernhard Schmidt acl_check(struct ieee80211vap *vap, const struct ieee80211_frame *wh) 1598a1b9b6aSSam Leffler { 160b032f27cSSam Leffler struct aclstate *as = vap->iv_as; 1618a1b9b6aSSam Leffler 1628a1b9b6aSSam Leffler switch (as->as_policy) { 1638a1b9b6aSSam Leffler case ACL_POLICY_OPEN: 164b032f27cSSam Leffler case ACL_POLICY_RADIUS: 1658a1b9b6aSSam Leffler return 1; 1668a1b9b6aSSam Leffler case ACL_POLICY_ALLOW: 1675a8801b0SBernhard Schmidt return _find_acl(as, wh->i_addr2) != NULL; 1688a1b9b6aSSam Leffler case ACL_POLICY_DENY: 1695a8801b0SBernhard Schmidt return _find_acl(as, wh->i_addr2) == NULL; 1708a1b9b6aSSam Leffler } 1718a1b9b6aSSam Leffler return 0; /* should not happen */ 1728a1b9b6aSSam Leffler } 1738a1b9b6aSSam Leffler 1748a1b9b6aSSam Leffler static int 175b032f27cSSam Leffler acl_add(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN]) 1768a1b9b6aSSam Leffler { 177b032f27cSSam Leffler struct aclstate *as = vap->iv_as; 1788a1b9b6aSSam Leffler struct acl *acl, *new; 1798a1b9b6aSSam Leffler int hash; 1808a1b9b6aSSam Leffler 181b9b53389SAdrian Chadd new = (struct acl *) IEEE80211_MALLOC(sizeof(struct acl), 182b9b53389SAdrian Chadd M_80211_ACL, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); 1838a1b9b6aSSam Leffler if (new == NULL) { 184b032f27cSSam Leffler IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL, 1858a1b9b6aSSam Leffler "ACL: add %s failed, no memory\n", ether_sprintf(mac)); 1868a1b9b6aSSam Leffler /* XXX statistic */ 1878a1b9b6aSSam Leffler return ENOMEM; 1888a1b9b6aSSam Leffler } 1898a1b9b6aSSam Leffler 1908a1b9b6aSSam Leffler ACL_LOCK(as); 1918a1b9b6aSSam Leffler hash = ACL_HASH(mac); 1928a1b9b6aSSam Leffler LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) { 1938a1b9b6aSSam Leffler if (IEEE80211_ADDR_EQ(acl->acl_macaddr, mac)) { 1948a1b9b6aSSam Leffler ACL_UNLOCK(as); 195b9b53389SAdrian Chadd IEEE80211_FREE(new, M_80211_ACL); 196b032f27cSSam Leffler IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL, 1978a1b9b6aSSam Leffler "ACL: add %s failed, already present\n", 1988a1b9b6aSSam Leffler ether_sprintf(mac)); 1998a1b9b6aSSam Leffler return EEXIST; 2008a1b9b6aSSam Leffler } 2018a1b9b6aSSam Leffler } 2028a1b9b6aSSam Leffler IEEE80211_ADDR_COPY(new->acl_macaddr, mac); 2038a1b9b6aSSam Leffler TAILQ_INSERT_TAIL(&as->as_list, new, acl_list); 2048a1b9b6aSSam Leffler LIST_INSERT_HEAD(&as->as_hash[hash], new, acl_hash); 205188757f5SSam Leffler as->as_nacls++; 2068a1b9b6aSSam Leffler ACL_UNLOCK(as); 2078a1b9b6aSSam Leffler 208b032f27cSSam Leffler IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL, 2098a1b9b6aSSam Leffler "ACL: add %s\n", ether_sprintf(mac)); 2108a1b9b6aSSam Leffler return 0; 2118a1b9b6aSSam Leffler } 2128a1b9b6aSSam Leffler 2138a1b9b6aSSam Leffler static int 214b032f27cSSam Leffler acl_remove(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN]) 2158a1b9b6aSSam Leffler { 216b032f27cSSam Leffler struct aclstate *as = vap->iv_as; 2178a1b9b6aSSam Leffler struct acl *acl; 2188a1b9b6aSSam Leffler 2198a1b9b6aSSam Leffler ACL_LOCK(as); 2208a1b9b6aSSam Leffler acl = _find_acl(as, mac); 2218a1b9b6aSSam Leffler if (acl != NULL) 2228a1b9b6aSSam Leffler _acl_free(as, acl); 2238a1b9b6aSSam Leffler ACL_UNLOCK(as); 2248a1b9b6aSSam Leffler 225b032f27cSSam Leffler IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL, 2268a1b9b6aSSam Leffler "ACL: remove %s%s\n", ether_sprintf(mac), 2278a1b9b6aSSam Leffler acl == NULL ? ", not present" : ""); 2288a1b9b6aSSam Leffler 2298a1b9b6aSSam Leffler return (acl == NULL ? ENOENT : 0); 2308a1b9b6aSSam Leffler } 2318a1b9b6aSSam Leffler 2328a1b9b6aSSam Leffler static int 233b032f27cSSam Leffler acl_free_all(struct ieee80211vap *vap) 2348a1b9b6aSSam Leffler { 235b032f27cSSam Leffler struct aclstate *as = vap->iv_as; 2368a1b9b6aSSam Leffler struct acl *acl; 2378a1b9b6aSSam Leffler 238b032f27cSSam Leffler IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL, "ACL: %s\n", "free all"); 2398a1b9b6aSSam Leffler 2408a1b9b6aSSam Leffler ACL_LOCK(as); 2418a1b9b6aSSam Leffler while ((acl = TAILQ_FIRST(&as->as_list)) != NULL) 2428a1b9b6aSSam Leffler _acl_free(as, acl); 2438a1b9b6aSSam Leffler ACL_UNLOCK(as); 2448a1b9b6aSSam Leffler 2458a1b9b6aSSam Leffler return 0; 2468a1b9b6aSSam Leffler } 2478a1b9b6aSSam Leffler 2488a1b9b6aSSam Leffler static int 249b032f27cSSam Leffler acl_setpolicy(struct ieee80211vap *vap, int policy) 2508a1b9b6aSSam Leffler { 251b032f27cSSam Leffler struct aclstate *as = vap->iv_as; 2528a1b9b6aSSam Leffler 253b032f27cSSam Leffler IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL, 2548a1b9b6aSSam Leffler "ACL: set policy to %u\n", policy); 2558a1b9b6aSSam Leffler 2568a1b9b6aSSam Leffler switch (policy) { 2578a1b9b6aSSam Leffler case IEEE80211_MACCMD_POLICY_OPEN: 2588a1b9b6aSSam Leffler as->as_policy = ACL_POLICY_OPEN; 2598a1b9b6aSSam Leffler break; 2608a1b9b6aSSam Leffler case IEEE80211_MACCMD_POLICY_ALLOW: 2618a1b9b6aSSam Leffler as->as_policy = ACL_POLICY_ALLOW; 2628a1b9b6aSSam Leffler break; 2638a1b9b6aSSam Leffler case IEEE80211_MACCMD_POLICY_DENY: 2648a1b9b6aSSam Leffler as->as_policy = ACL_POLICY_DENY; 2658a1b9b6aSSam Leffler break; 266b032f27cSSam Leffler case IEEE80211_MACCMD_POLICY_RADIUS: 267b032f27cSSam Leffler as->as_policy = ACL_POLICY_RADIUS; 268b032f27cSSam Leffler break; 2698a1b9b6aSSam Leffler default: 2708a1b9b6aSSam Leffler return EINVAL; 2718a1b9b6aSSam Leffler } 2728a1b9b6aSSam Leffler return 0; 2738a1b9b6aSSam Leffler } 2748a1b9b6aSSam Leffler 2758a1b9b6aSSam Leffler static int 276b032f27cSSam Leffler acl_getpolicy(struct ieee80211vap *vap) 2778a1b9b6aSSam Leffler { 278b032f27cSSam Leffler struct aclstate *as = vap->iv_as; 2798a1b9b6aSSam Leffler 2808a1b9b6aSSam Leffler return as->as_policy; 2818a1b9b6aSSam Leffler } 2828a1b9b6aSSam Leffler 283188757f5SSam Leffler static int 284b032f27cSSam Leffler acl_setioctl(struct ieee80211vap *vap, struct ieee80211req *ireq) 285188757f5SSam Leffler { 286188757f5SSam Leffler 287188757f5SSam Leffler return EINVAL; 288188757f5SSam Leffler } 289188757f5SSam Leffler 290188757f5SSam Leffler static int 291b032f27cSSam Leffler acl_getioctl(struct ieee80211vap *vap, struct ieee80211req *ireq) 292188757f5SSam Leffler { 293b032f27cSSam Leffler struct aclstate *as = vap->iv_as; 294188757f5SSam Leffler struct acl *acl; 295188757f5SSam Leffler struct ieee80211req_maclist *ap; 296db9ff08bSKevin Lo int error; 297db9ff08bSKevin Lo uint32_t i, space; 298188757f5SSam Leffler 299188757f5SSam Leffler switch (ireq->i_val) { 300188757f5SSam Leffler case IEEE80211_MACCMD_POLICY: 301188757f5SSam Leffler ireq->i_val = as->as_policy; 302188757f5SSam Leffler return 0; 303188757f5SSam Leffler case IEEE80211_MACCMD_LIST: 304188757f5SSam Leffler space = as->as_nacls * IEEE80211_ADDR_LEN; 305188757f5SSam Leffler if (ireq->i_len == 0) { 306188757f5SSam Leffler ireq->i_len = space; /* return required space */ 307188757f5SSam Leffler return 0; /* NB: must not error */ 308188757f5SSam Leffler } 309b9b53389SAdrian Chadd ap = (struct ieee80211req_maclist *) IEEE80211_MALLOC(space, 310b9b53389SAdrian Chadd M_TEMP, IEEE80211_M_NOWAIT); 311188757f5SSam Leffler if (ap == NULL) 312188757f5SSam Leffler return ENOMEM; 313188757f5SSam Leffler i = 0; 314188757f5SSam Leffler ACL_LOCK(as); 315188757f5SSam Leffler TAILQ_FOREACH(acl, &as->as_list, acl_list) { 316188757f5SSam Leffler IEEE80211_ADDR_COPY(ap[i].ml_macaddr, acl->acl_macaddr); 317188757f5SSam Leffler i++; 318188757f5SSam Leffler } 319188757f5SSam Leffler ACL_UNLOCK(as); 320188757f5SSam Leffler if (ireq->i_len >= space) { 321188757f5SSam Leffler error = copyout(ap, ireq->i_data, space); 322188757f5SSam Leffler ireq->i_len = space; 323188757f5SSam Leffler } else 324188757f5SSam Leffler error = copyout(ap, ireq->i_data, ireq->i_len); 325b9b53389SAdrian Chadd IEEE80211_FREE(ap, M_TEMP); 326188757f5SSam Leffler return error; 327188757f5SSam Leffler } 328188757f5SSam Leffler return EINVAL; 329188757f5SSam Leffler } 330188757f5SSam Leffler 3318a1b9b6aSSam Leffler static const struct ieee80211_aclator mac = { 3328a1b9b6aSSam Leffler .iac_name = "mac", 3338a1b9b6aSSam Leffler .iac_attach = acl_attach, 3348a1b9b6aSSam Leffler .iac_detach = acl_detach, 3358a1b9b6aSSam Leffler .iac_check = acl_check, 3368a1b9b6aSSam Leffler .iac_add = acl_add, 3378a1b9b6aSSam Leffler .iac_remove = acl_remove, 3388a1b9b6aSSam Leffler .iac_flush = acl_free_all, 3398a1b9b6aSSam Leffler .iac_setpolicy = acl_setpolicy, 3408a1b9b6aSSam Leffler .iac_getpolicy = acl_getpolicy, 341188757f5SSam Leffler .iac_setioctl = acl_setioctl, 342188757f5SSam Leffler .iac_getioctl = acl_getioctl, 3438a1b9b6aSSam Leffler }; 344b032f27cSSam Leffler IEEE80211_ACL_MODULE(wlan_acl, mac, 1); 345