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> 45295126Sglebius#include <sys/malloc.h> 46138568Ssam#include <sys/mbuf.h> 47138568Ssam#include <sys/module.h> 48138568Ssam#include <sys/queue.h> 49138568Ssam 50138568Ssam#include <sys/socket.h> 51138568Ssam 52138568Ssam#include <net/if.h> 53138568Ssam#include <net/if_media.h> 54138568Ssam#include <net/ethernet.h> 55138568Ssam#include <net/route.h> 56138568Ssam 57138568Ssam#include <net80211/ieee80211_var.h> 58138568Ssam 59138568Ssamenum { 60138568Ssam ACL_POLICY_OPEN = 0, /* open, don't check ACL's */ 61138568Ssam ACL_POLICY_ALLOW = 1, /* allow traffic from MAC */ 62138568Ssam ACL_POLICY_DENY = 2, /* deny traffic from MAC */ 63178354Ssam /* 64178354Ssam * NB: ACL_POLICY_RADIUS must be the same value as 65178354Ssam * IEEE80211_MACCMD_POLICY_RADIUS because of the way 66178354Ssam * acl_getpolicy() works. 67178354Ssam */ 68178354Ssam ACL_POLICY_RADIUS = 7, /* defer to RADIUS ACL server */ 69138568Ssam}; 70138568Ssam 71138568Ssam#define ACL_HASHSIZE 32 72138568Ssam 73138568Ssamstruct acl { 74138568Ssam TAILQ_ENTRY(acl) acl_list; 75138568Ssam LIST_ENTRY(acl) acl_hash; 76170530Ssam uint8_t acl_macaddr[IEEE80211_ADDR_LEN]; 77138568Ssam}; 78138568Ssamstruct aclstate { 79138568Ssam acl_lock_t as_lock; 80138568Ssam int as_policy; 81223145Skevlo uint32_t as_nacls; 82138568Ssam TAILQ_HEAD(, acl) as_list; /* list of all ACL's */ 83138568Ssam LIST_HEAD(, acl) as_hash[ACL_HASHSIZE]; 84178354Ssam struct ieee80211vap *as_vap; 85138568Ssam}; 86138568Ssam 87138568Ssam/* simple hash is enough for variation of macaddr */ 88138568Ssam#define ACL_HASH(addr) \ 89170530Ssam (((const uint8_t *)(addr))[IEEE80211_ADDR_LEN - 1] % ACL_HASHSIZE) 90138568Ssam 91227293Sedstatic MALLOC_DEFINE(M_80211_ACL, "acl", "802.11 station acl"); 92138568Ssam 93178354Ssamstatic int acl_free_all(struct ieee80211vap *); 94138568Ssam 95178354Ssam/* number of references from net80211 layer */ 96178354Ssamstatic int nrefs = 0; 97178354Ssam 98138568Ssamstatic int 99178354Ssamacl_attach(struct ieee80211vap *vap) 100138568Ssam{ 101138568Ssam struct aclstate *as; 102138568Ssam 103283538Sadrian as = (struct aclstate *) IEEE80211_MALLOC(sizeof(struct aclstate), 104283538Sadrian M_80211_ACL, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); 105138568Ssam if (as == NULL) 106138568Ssam return 0; 107138568Ssam ACL_LOCK_INIT(as, "acl"); 108138568Ssam TAILQ_INIT(&as->as_list); 109138568Ssam as->as_policy = ACL_POLICY_OPEN; 110178354Ssam as->as_vap = vap; 111178354Ssam vap->iv_as = as; 112178354Ssam nrefs++; /* NB: we assume caller locking */ 113138568Ssam return 1; 114138568Ssam} 115138568Ssam 116138568Ssamstatic void 117178354Ssamacl_detach(struct ieee80211vap *vap) 118138568Ssam{ 119178354Ssam struct aclstate *as = vap->iv_as; 120138568Ssam 121178354Ssam KASSERT(nrefs > 0, ("imbalanced attach/detach")); 122178354Ssam nrefs--; /* NB: we assume caller locking */ 123178354Ssam 124178354Ssam acl_free_all(vap); 125178354Ssam vap->iv_as = NULL; 126138568Ssam ACL_LOCK_DESTROY(as); 127283538Sadrian IEEE80211_FREE(as, M_80211_ACL); 128138568Ssam} 129138568Ssam 130139503Ssamstatic __inline struct acl * 131170530Ssam_find_acl(struct aclstate *as, const uint8_t *macaddr) 132138568Ssam{ 133138568Ssam struct acl *acl; 134138568Ssam int hash; 135138568Ssam 136138568Ssam hash = ACL_HASH(macaddr); 137138568Ssam LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) { 138138568Ssam if (IEEE80211_ADDR_EQ(acl->acl_macaddr, macaddr)) 139138568Ssam return acl; 140138568Ssam } 141138568Ssam return NULL; 142138568Ssam} 143138568Ssam 144138568Ssamstatic void 145138568Ssam_acl_free(struct aclstate *as, struct acl *acl) 146138568Ssam{ 147138568Ssam ACL_LOCK_ASSERT(as); 148138568Ssam 149138568Ssam TAILQ_REMOVE(&as->as_list, acl, acl_list); 150138568Ssam LIST_REMOVE(acl, acl_hash); 151283538Sadrian IEEE80211_FREE(acl, M_80211_ACL); 152149028Ssam as->as_nacls--; 153138568Ssam} 154138568Ssam 155138568Ssamstatic int 156228622Sbschmidtacl_check(struct ieee80211vap *vap, const struct ieee80211_frame *wh) 157138568Ssam{ 158178354Ssam struct aclstate *as = vap->iv_as; 159138568Ssam 160138568Ssam switch (as->as_policy) { 161138568Ssam case ACL_POLICY_OPEN: 162178354Ssam case ACL_POLICY_RADIUS: 163138568Ssam return 1; 164138568Ssam case ACL_POLICY_ALLOW: 165228622Sbschmidt return _find_acl(as, wh->i_addr2) != NULL; 166138568Ssam case ACL_POLICY_DENY: 167228622Sbschmidt return _find_acl(as, wh->i_addr2) == NULL; 168138568Ssam } 169138568Ssam return 0; /* should not happen */ 170138568Ssam} 171138568Ssam 172138568Ssamstatic int 173178354Ssamacl_add(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN]) 174138568Ssam{ 175178354Ssam struct aclstate *as = vap->iv_as; 176138568Ssam struct acl *acl, *new; 177138568Ssam int hash; 178138568Ssam 179283538Sadrian new = (struct acl *) IEEE80211_MALLOC(sizeof(struct acl), 180283538Sadrian M_80211_ACL, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); 181138568Ssam if (new == NULL) { 182178354Ssam IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL, 183138568Ssam "ACL: add %s failed, no memory\n", ether_sprintf(mac)); 184138568Ssam /* XXX statistic */ 185138568Ssam return ENOMEM; 186138568Ssam } 187138568Ssam 188138568Ssam ACL_LOCK(as); 189138568Ssam hash = ACL_HASH(mac); 190138568Ssam LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) { 191138568Ssam if (IEEE80211_ADDR_EQ(acl->acl_macaddr, mac)) { 192138568Ssam ACL_UNLOCK(as); 193283538Sadrian IEEE80211_FREE(new, M_80211_ACL); 194178354Ssam IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL, 195138568Ssam "ACL: add %s failed, already present\n", 196138568Ssam ether_sprintf(mac)); 197138568Ssam return EEXIST; 198138568Ssam } 199138568Ssam } 200138568Ssam IEEE80211_ADDR_COPY(new->acl_macaddr, mac); 201138568Ssam TAILQ_INSERT_TAIL(&as->as_list, new, acl_list); 202138568Ssam LIST_INSERT_HEAD(&as->as_hash[hash], new, acl_hash); 203149028Ssam as->as_nacls++; 204138568Ssam ACL_UNLOCK(as); 205138568Ssam 206178354Ssam IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL, 207138568Ssam "ACL: add %s\n", ether_sprintf(mac)); 208138568Ssam return 0; 209138568Ssam} 210138568Ssam 211138568Ssamstatic int 212178354Ssamacl_remove(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN]) 213138568Ssam{ 214178354Ssam struct aclstate *as = vap->iv_as; 215138568Ssam struct acl *acl; 216138568Ssam 217138568Ssam ACL_LOCK(as); 218138568Ssam acl = _find_acl(as, mac); 219138568Ssam if (acl != NULL) 220138568Ssam _acl_free(as, acl); 221138568Ssam ACL_UNLOCK(as); 222138568Ssam 223178354Ssam IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL, 224138568Ssam "ACL: remove %s%s\n", ether_sprintf(mac), 225138568Ssam acl == NULL ? ", not present" : ""); 226138568Ssam 227138568Ssam return (acl == NULL ? ENOENT : 0); 228138568Ssam} 229138568Ssam 230138568Ssamstatic int 231178354Ssamacl_free_all(struct ieee80211vap *vap) 232138568Ssam{ 233178354Ssam struct aclstate *as = vap->iv_as; 234138568Ssam struct acl *acl; 235138568Ssam 236178354Ssam IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL, "ACL: %s\n", "free all"); 237138568Ssam 238138568Ssam ACL_LOCK(as); 239138568Ssam while ((acl = TAILQ_FIRST(&as->as_list)) != NULL) 240138568Ssam _acl_free(as, acl); 241138568Ssam ACL_UNLOCK(as); 242138568Ssam 243138568Ssam return 0; 244138568Ssam} 245138568Ssam 246138568Ssamstatic int 247178354Ssamacl_setpolicy(struct ieee80211vap *vap, int policy) 248138568Ssam{ 249178354Ssam struct aclstate *as = vap->iv_as; 250138568Ssam 251178354Ssam IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL, 252138568Ssam "ACL: set policy to %u\n", policy); 253138568Ssam 254138568Ssam switch (policy) { 255138568Ssam case IEEE80211_MACCMD_POLICY_OPEN: 256138568Ssam as->as_policy = ACL_POLICY_OPEN; 257138568Ssam break; 258138568Ssam case IEEE80211_MACCMD_POLICY_ALLOW: 259138568Ssam as->as_policy = ACL_POLICY_ALLOW; 260138568Ssam break; 261138568Ssam case IEEE80211_MACCMD_POLICY_DENY: 262138568Ssam as->as_policy = ACL_POLICY_DENY; 263138568Ssam break; 264178354Ssam case IEEE80211_MACCMD_POLICY_RADIUS: 265178354Ssam as->as_policy = ACL_POLICY_RADIUS; 266178354Ssam break; 267138568Ssam default: 268138568Ssam return EINVAL; 269138568Ssam } 270138568Ssam return 0; 271138568Ssam} 272138568Ssam 273138568Ssamstatic int 274178354Ssamacl_getpolicy(struct ieee80211vap *vap) 275138568Ssam{ 276178354Ssam struct aclstate *as = vap->iv_as; 277138568Ssam 278138568Ssam return as->as_policy; 279138568Ssam} 280138568Ssam 281149028Ssamstatic int 282178354Ssamacl_setioctl(struct ieee80211vap *vap, struct ieee80211req *ireq) 283149028Ssam{ 284149028Ssam 285149028Ssam return EINVAL; 286149028Ssam} 287149028Ssam 288149028Ssamstatic int 289178354Ssamacl_getioctl(struct ieee80211vap *vap, struct ieee80211req *ireq) 290149028Ssam{ 291178354Ssam struct aclstate *as = vap->iv_as; 292149028Ssam struct acl *acl; 293149028Ssam struct ieee80211req_maclist *ap; 294223145Skevlo int error; 295223145Skevlo uint32_t i, space; 296149028Ssam 297149028Ssam switch (ireq->i_val) { 298149028Ssam case IEEE80211_MACCMD_POLICY: 299149028Ssam ireq->i_val = as->as_policy; 300149028Ssam return 0; 301149028Ssam case IEEE80211_MACCMD_LIST: 302149028Ssam space = as->as_nacls * IEEE80211_ADDR_LEN; 303149028Ssam if (ireq->i_len == 0) { 304149028Ssam ireq->i_len = space; /* return required space */ 305149028Ssam return 0; /* NB: must not error */ 306149028Ssam } 307283538Sadrian ap = (struct ieee80211req_maclist *) IEEE80211_MALLOC(space, 308283538Sadrian M_TEMP, IEEE80211_M_NOWAIT); 309149028Ssam if (ap == NULL) 310149028Ssam return ENOMEM; 311149028Ssam i = 0; 312149028Ssam ACL_LOCK(as); 313149028Ssam TAILQ_FOREACH(acl, &as->as_list, acl_list) { 314149028Ssam IEEE80211_ADDR_COPY(ap[i].ml_macaddr, acl->acl_macaddr); 315149028Ssam i++; 316149028Ssam } 317149028Ssam ACL_UNLOCK(as); 318149028Ssam if (ireq->i_len >= space) { 319149028Ssam error = copyout(ap, ireq->i_data, space); 320149028Ssam ireq->i_len = space; 321149028Ssam } else 322149028Ssam error = copyout(ap, ireq->i_data, ireq->i_len); 323283538Sadrian IEEE80211_FREE(ap, M_TEMP); 324149028Ssam return error; 325149028Ssam } 326149028Ssam return EINVAL; 327149028Ssam} 328149028Ssam 329138568Ssamstatic const struct ieee80211_aclator mac = { 330138568Ssam .iac_name = "mac", 331138568Ssam .iac_attach = acl_attach, 332138568Ssam .iac_detach = acl_detach, 333138568Ssam .iac_check = acl_check, 334138568Ssam .iac_add = acl_add, 335138568Ssam .iac_remove = acl_remove, 336138568Ssam .iac_flush = acl_free_all, 337138568Ssam .iac_setpolicy = acl_setpolicy, 338138568Ssam .iac_getpolicy = acl_getpolicy, 339149028Ssam .iac_setioctl = acl_setioctl, 340149028Ssam .iac_getioctl = acl_getioctl, 341138568Ssam}; 342178354SsamIEEE80211_ACL_MODULE(wlan_acl, mac, 1); 343