1138568Ssam/*- 2178354Ssam * Copyright (c) 2004-2008 Sam Leffler, Errno Consulting 3138568Ssam * All rights reserved. 4138568Ssam * 5138568Ssam * Redistribution and use in source and binary forms, with or without 6138568Ssam * modification, are permitted provided that the following conditions 7138568Ssam * are met: 8138568Ssam * 1. Redistributions of source code must retain the above copyright 9138568Ssam * notice, this list of conditions and the following disclaimer. 10138568Ssam * 2. Redistributions in binary form must reproduce the above copyright 11138568Ssam * notice, this list of conditions and the following disclaimer in the 12138568Ssam * documentation and/or other materials provided with the distribution. 13138568Ssam * 14138568Ssam * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15138568Ssam * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16138568Ssam * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17138568Ssam * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18138568Ssam * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19138568Ssam * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20138568Ssam * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21138568Ssam * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22138568Ssam * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23138568Ssam * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24138568Ssam */ 25138568Ssam 26138568Ssam#include <sys/cdefs.h> 27138568Ssam__FBSDID("$FreeBSD$"); 28138568Ssam 29138568Ssam/* 30138568Ssam * IEEE 802.11 MAC ACL support. 31138568Ssam * 32178354Ssam * When this module is loaded the sender address of each auth mgt 33138568Ssam * frame is passed to the iac_check method and the module indicates 34138568Ssam * if the frame should be accepted or rejected. If the policy is 35138568Ssam * set to ACL_POLICY_OPEN then all frames are accepted w/o checking 36138568Ssam * the address. Otherwise, the address is looked up in the database 37138568Ssam * and if found the frame is either accepted (ACL_POLICY_ALLOW) 38138568Ssam * or rejected (ACL_POLICY_DENT). 39138568Ssam */ 40178354Ssam#include "opt_wlan.h" 41178354Ssam 42138568Ssam#include <sys/param.h> 43138568Ssam#include <sys/kernel.h> 44138568Ssam#include <sys/systm.h> 45138568Ssam#include <sys/mbuf.h> 46138568Ssam#include <sys/module.h> 47138568Ssam#include <sys/queue.h> 48138568Ssam 49138568Ssam#include <sys/socket.h> 50138568Ssam 51138568Ssam#include <net/if.h> 52138568Ssam#include <net/if_media.h> 53138568Ssam#include <net/ethernet.h> 54138568Ssam#include <net/route.h> 55138568Ssam 56138568Ssam#include <net80211/ieee80211_var.h> 57138568Ssam 58138568Ssamenum { 59138568Ssam ACL_POLICY_OPEN = 0, /* open, don't check ACL's */ 60138568Ssam ACL_POLICY_ALLOW = 1, /* allow traffic from MAC */ 61138568Ssam ACL_POLICY_DENY = 2, /* deny traffic from MAC */ 62178354Ssam /* 63178354Ssam * NB: ACL_POLICY_RADIUS must be the same value as 64178354Ssam * IEEE80211_MACCMD_POLICY_RADIUS because of the way 65178354Ssam * acl_getpolicy() works. 66178354Ssam */ 67178354Ssam ACL_POLICY_RADIUS = 7, /* defer to RADIUS ACL server */ 68138568Ssam}; 69138568Ssam 70138568Ssam#define ACL_HASHSIZE 32 71138568Ssam 72138568Ssamstruct acl { 73138568Ssam TAILQ_ENTRY(acl) acl_list; 74138568Ssam LIST_ENTRY(acl) acl_hash; 75170530Ssam uint8_t acl_macaddr[IEEE80211_ADDR_LEN]; 76138568Ssam}; 77138568Ssamstruct aclstate { 78138568Ssam acl_lock_t as_lock; 79138568Ssam int as_policy; 80223145Skevlo uint32_t as_nacls; 81138568Ssam TAILQ_HEAD(, acl) as_list; /* list of all ACL's */ 82138568Ssam LIST_HEAD(, acl) as_hash[ACL_HASHSIZE]; 83178354Ssam struct ieee80211vap *as_vap; 84138568Ssam}; 85138568Ssam 86138568Ssam/* simple hash is enough for variation of macaddr */ 87138568Ssam#define ACL_HASH(addr) \ 88170530Ssam (((const uint8_t *)(addr))[IEEE80211_ADDR_LEN - 1] % ACL_HASHSIZE) 89138568Ssam 90227293Sedstatic MALLOC_DEFINE(M_80211_ACL, "acl", "802.11 station acl"); 91138568Ssam 92178354Ssamstatic int acl_free_all(struct ieee80211vap *); 93138568Ssam 94178354Ssam/* number of references from net80211 layer */ 95178354Ssamstatic int nrefs = 0; 96178354Ssam 97138568Ssamstatic int 98178354Ssamacl_attach(struct ieee80211vap *vap) 99138568Ssam{ 100138568Ssam struct aclstate *as; 101138568Ssam 102186302Ssam as = (struct aclstate *) malloc(sizeof(struct aclstate), 103149028Ssam M_80211_ACL, M_NOWAIT | M_ZERO); 104138568Ssam if (as == NULL) 105138568Ssam return 0; 106138568Ssam ACL_LOCK_INIT(as, "acl"); 107138568Ssam TAILQ_INIT(&as->as_list); 108138568Ssam as->as_policy = ACL_POLICY_OPEN; 109178354Ssam as->as_vap = vap; 110178354Ssam vap->iv_as = as; 111178354Ssam nrefs++; /* NB: we assume caller locking */ 112138568Ssam return 1; 113138568Ssam} 114138568Ssam 115138568Ssamstatic void 116178354Ssamacl_detach(struct ieee80211vap *vap) 117138568Ssam{ 118178354Ssam struct aclstate *as = vap->iv_as; 119138568Ssam 120178354Ssam KASSERT(nrefs > 0, ("imbalanced attach/detach")); 121178354Ssam nrefs--; /* NB: we assume caller locking */ 122178354Ssam 123178354Ssam acl_free_all(vap); 124178354Ssam vap->iv_as = NULL; 125138568Ssam ACL_LOCK_DESTROY(as); 126186302Ssam free(as, M_80211_ACL); 127138568Ssam} 128138568Ssam 129139503Ssamstatic __inline struct acl * 130170530Ssam_find_acl(struct aclstate *as, const uint8_t *macaddr) 131138568Ssam{ 132138568Ssam struct acl *acl; 133138568Ssam int hash; 134138568Ssam 135138568Ssam hash = ACL_HASH(macaddr); 136138568Ssam LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) { 137138568Ssam if (IEEE80211_ADDR_EQ(acl->acl_macaddr, macaddr)) 138138568Ssam return acl; 139138568Ssam } 140138568Ssam return NULL; 141138568Ssam} 142138568Ssam 143138568Ssamstatic void 144138568Ssam_acl_free(struct aclstate *as, struct acl *acl) 145138568Ssam{ 146138568Ssam ACL_LOCK_ASSERT(as); 147138568Ssam 148138568Ssam TAILQ_REMOVE(&as->as_list, acl, acl_list); 149138568Ssam LIST_REMOVE(acl, acl_hash); 150186302Ssam free(acl, M_80211_ACL); 151149028Ssam as->as_nacls--; 152138568Ssam} 153138568Ssam 154138568Ssamstatic int 155228622Sbschmidtacl_check(struct ieee80211vap *vap, const struct ieee80211_frame *wh) 156138568Ssam{ 157178354Ssam struct aclstate *as = vap->iv_as; 158138568Ssam 159138568Ssam switch (as->as_policy) { 160138568Ssam case ACL_POLICY_OPEN: 161178354Ssam case ACL_POLICY_RADIUS: 162138568Ssam return 1; 163138568Ssam case ACL_POLICY_ALLOW: 164228622Sbschmidt return _find_acl(as, wh->i_addr2) != NULL; 165138568Ssam case ACL_POLICY_DENY: 166228622Sbschmidt return _find_acl(as, wh->i_addr2) == NULL; 167138568Ssam } 168138568Ssam return 0; /* should not happen */ 169138568Ssam} 170138568Ssam 171138568Ssamstatic int 172178354Ssamacl_add(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN]) 173138568Ssam{ 174178354Ssam struct aclstate *as = vap->iv_as; 175138568Ssam struct acl *acl, *new; 176138568Ssam int hash; 177138568Ssam 178186302Ssam new = (struct acl *) malloc(sizeof(struct acl), M_80211_ACL, M_NOWAIT | M_ZERO); 179138568Ssam if (new == NULL) { 180178354Ssam IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL, 181138568Ssam "ACL: add %s failed, no memory\n", ether_sprintf(mac)); 182138568Ssam /* XXX statistic */ 183138568Ssam return ENOMEM; 184138568Ssam } 185138568Ssam 186138568Ssam ACL_LOCK(as); 187138568Ssam hash = ACL_HASH(mac); 188138568Ssam LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) { 189138568Ssam if (IEEE80211_ADDR_EQ(acl->acl_macaddr, mac)) { 190138568Ssam ACL_UNLOCK(as); 191186302Ssam free(new, M_80211_ACL); 192178354Ssam IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL, 193138568Ssam "ACL: add %s failed, already present\n", 194138568Ssam ether_sprintf(mac)); 195138568Ssam return EEXIST; 196138568Ssam } 197138568Ssam } 198138568Ssam IEEE80211_ADDR_COPY(new->acl_macaddr, mac); 199138568Ssam TAILQ_INSERT_TAIL(&as->as_list, new, acl_list); 200138568Ssam LIST_INSERT_HEAD(&as->as_hash[hash], new, acl_hash); 201149028Ssam as->as_nacls++; 202138568Ssam ACL_UNLOCK(as); 203138568Ssam 204178354Ssam IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL, 205138568Ssam "ACL: add %s\n", ether_sprintf(mac)); 206138568Ssam return 0; 207138568Ssam} 208138568Ssam 209138568Ssamstatic int 210178354Ssamacl_remove(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN]) 211138568Ssam{ 212178354Ssam struct aclstate *as = vap->iv_as; 213138568Ssam struct acl *acl; 214138568Ssam 215138568Ssam ACL_LOCK(as); 216138568Ssam acl = _find_acl(as, mac); 217138568Ssam if (acl != NULL) 218138568Ssam _acl_free(as, acl); 219138568Ssam ACL_UNLOCK(as); 220138568Ssam 221178354Ssam IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL, 222138568Ssam "ACL: remove %s%s\n", ether_sprintf(mac), 223138568Ssam acl == NULL ? ", not present" : ""); 224138568Ssam 225138568Ssam return (acl == NULL ? ENOENT : 0); 226138568Ssam} 227138568Ssam 228138568Ssamstatic int 229178354Ssamacl_free_all(struct ieee80211vap *vap) 230138568Ssam{ 231178354Ssam struct aclstate *as = vap->iv_as; 232138568Ssam struct acl *acl; 233138568Ssam 234178354Ssam IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL, "ACL: %s\n", "free all"); 235138568Ssam 236138568Ssam ACL_LOCK(as); 237138568Ssam while ((acl = TAILQ_FIRST(&as->as_list)) != NULL) 238138568Ssam _acl_free(as, acl); 239138568Ssam ACL_UNLOCK(as); 240138568Ssam 241138568Ssam return 0; 242138568Ssam} 243138568Ssam 244138568Ssamstatic int 245178354Ssamacl_setpolicy(struct ieee80211vap *vap, int policy) 246138568Ssam{ 247178354Ssam struct aclstate *as = vap->iv_as; 248138568Ssam 249178354Ssam IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL, 250138568Ssam "ACL: set policy to %u\n", policy); 251138568Ssam 252138568Ssam switch (policy) { 253138568Ssam case IEEE80211_MACCMD_POLICY_OPEN: 254138568Ssam as->as_policy = ACL_POLICY_OPEN; 255138568Ssam break; 256138568Ssam case IEEE80211_MACCMD_POLICY_ALLOW: 257138568Ssam as->as_policy = ACL_POLICY_ALLOW; 258138568Ssam break; 259138568Ssam case IEEE80211_MACCMD_POLICY_DENY: 260138568Ssam as->as_policy = ACL_POLICY_DENY; 261138568Ssam break; 262178354Ssam case IEEE80211_MACCMD_POLICY_RADIUS: 263178354Ssam as->as_policy = ACL_POLICY_RADIUS; 264178354Ssam break; 265138568Ssam default: 266138568Ssam return EINVAL; 267138568Ssam } 268138568Ssam return 0; 269138568Ssam} 270138568Ssam 271138568Ssamstatic int 272178354Ssamacl_getpolicy(struct ieee80211vap *vap) 273138568Ssam{ 274178354Ssam struct aclstate *as = vap->iv_as; 275138568Ssam 276138568Ssam return as->as_policy; 277138568Ssam} 278138568Ssam 279149028Ssamstatic int 280178354Ssamacl_setioctl(struct ieee80211vap *vap, struct ieee80211req *ireq) 281149028Ssam{ 282149028Ssam 283149028Ssam return EINVAL; 284149028Ssam} 285149028Ssam 286149028Ssamstatic int 287178354Ssamacl_getioctl(struct ieee80211vap *vap, struct ieee80211req *ireq) 288149028Ssam{ 289178354Ssam struct aclstate *as = vap->iv_as; 290149028Ssam struct acl *acl; 291149028Ssam struct ieee80211req_maclist *ap; 292223145Skevlo int error; 293223145Skevlo uint32_t i, space; 294149028Ssam 295149028Ssam switch (ireq->i_val) { 296149028Ssam case IEEE80211_MACCMD_POLICY: 297149028Ssam ireq->i_val = as->as_policy; 298149028Ssam return 0; 299149028Ssam case IEEE80211_MACCMD_LIST: 300149028Ssam space = as->as_nacls * IEEE80211_ADDR_LEN; 301149028Ssam if (ireq->i_len == 0) { 302149028Ssam ireq->i_len = space; /* return required space */ 303149028Ssam return 0; /* NB: must not error */ 304149028Ssam } 305186302Ssam ap = (struct ieee80211req_maclist *) malloc(space, 306178354Ssam M_TEMP, M_NOWAIT); 307149028Ssam if (ap == NULL) 308149028Ssam return ENOMEM; 309149028Ssam i = 0; 310149028Ssam ACL_LOCK(as); 311149028Ssam TAILQ_FOREACH(acl, &as->as_list, acl_list) { 312149028Ssam IEEE80211_ADDR_COPY(ap[i].ml_macaddr, acl->acl_macaddr); 313149028Ssam i++; 314149028Ssam } 315149028Ssam ACL_UNLOCK(as); 316149028Ssam if (ireq->i_len >= space) { 317149028Ssam error = copyout(ap, ireq->i_data, space); 318149028Ssam ireq->i_len = space; 319149028Ssam } else 320149028Ssam error = copyout(ap, ireq->i_data, ireq->i_len); 321186302Ssam free(ap, M_TEMP); 322149028Ssam return error; 323149028Ssam } 324149028Ssam return EINVAL; 325149028Ssam} 326149028Ssam 327138568Ssamstatic const struct ieee80211_aclator mac = { 328138568Ssam .iac_name = "mac", 329138568Ssam .iac_attach = acl_attach, 330138568Ssam .iac_detach = acl_detach, 331138568Ssam .iac_check = acl_check, 332138568Ssam .iac_add = acl_add, 333138568Ssam .iac_remove = acl_remove, 334138568Ssam .iac_flush = acl_free_all, 335138568Ssam .iac_setpolicy = acl_setpolicy, 336138568Ssam .iac_getpolicy = acl_getpolicy, 337149028Ssam .iac_setioctl = acl_setioctl, 338149028Ssam .iac_getioctl = acl_getioctl, 339138568Ssam}; 340178354SsamIEEE80211_ACL_MODULE(wlan_acl, mac, 1); 341