ieee80211_acl.c revision 228622
132516Sgibbs/*-
240029Sgibbs * Copyright (c) 2004-2008 Sam Leffler, Errno Consulting
332516Sgibbs * All rights reserved.
432516Sgibbs *
532516Sgibbs * Redistribution and use in source and binary forms, with or without
632516Sgibbs * modification, are permitted provided that the following conditions
732516Sgibbs * are met:
832516Sgibbs * 1. Redistributions of source code must retain the above copyright
932516Sgibbs *    notice, this list of conditions and the following disclaimer.
1032516Sgibbs * 2. Redistributions in binary form must reproduce the above copyright
1132516Sgibbs *    notice, this list of conditions and the following disclaimer in the
1232516Sgibbs *    documentation and/or other materials provided with the distribution.
1332516Sgibbs *
1432516Sgibbs * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1532516Sgibbs * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1632516Sgibbs * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
1732516Sgibbs * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
1832516Sgibbs * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
1932516Sgibbs * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2032516Sgibbs * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2132516Sgibbs * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2232516Sgibbs * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2332516Sgibbs * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2432516Sgibbs */
2532516Sgibbs
2650477Speter#include <sys/cdefs.h>
2732516Sgibbs__FBSDID("$FreeBSD: head/sys/net80211/ieee80211_acl.c 228622 2011-12-17 10:32:31Z bschmidt $");
2832516Sgibbs
2932516Sgibbs/*
3032516Sgibbs * IEEE 802.11 MAC ACL support.
3132516Sgibbs *
3267551Sjhb * When this module is loaded the sender address of each auth mgt
3367551Sjhb * frame is passed to the iac_check method and the module indicates
34112346Smux * if the frame should be accepted or rejected.  If the policy is
3576827Salfred * set to ACL_POLICY_OPEN then all frames are accepted w/o checking
3679224Sdillon * the address.  Otherwise, the address is looked up in the database
3776827Salfred * and if found the frame is either accepted (ACL_POLICY_ALLOW)
38104486Ssam * or rejected (ACL_POLICY_DENT).
39104486Ssam */
4032516Sgibbs#include "opt_wlan.h"
4132516Sgibbs
4232516Sgibbs#include <sys/param.h>
43104486Ssam#include <sys/kernel.h>
4432516Sgibbs#include <sys/systm.h>
45112436Smux#include <sys/mbuf.h>
4632516Sgibbs#include <sys/module.h>
4732516Sgibbs#include <sys/queue.h>
4832516Sgibbs
49113228Sjake#include <sys/socket.h>
5032516Sgibbs
5132516Sgibbs#include <net/if.h>
5232516Sgibbs#include <net/if_media.h>
5335767Sgibbs#include <net/ethernet.h>
5432516Sgibbs#include <net/route.h>
5532516Sgibbs
5632516Sgibbs#include <net80211/ieee80211_var.h>
5732516Sgibbs
5832516Sgibbsenum {
5932516Sgibbs	ACL_POLICY_OPEN		= 0,	/* open, don't check ACL's */
6035767Sgibbs	ACL_POLICY_ALLOW	= 1,	/* allow traffic from MAC */
6132516Sgibbs	ACL_POLICY_DENY		= 2,	/* deny traffic from MAC */
6232516Sgibbs	/*
6332516Sgibbs	 * NB: ACL_POLICY_RADIUS must be the same value as
6432516Sgibbs	 *     IEEE80211_MACCMD_POLICY_RADIUS because of the way
6532516Sgibbs	 *     acl_getpolicy() works.
6632516Sgibbs	 */
6732516Sgibbs	ACL_POLICY_RADIUS	= 7,	/* defer to RADIUS ACL server */
6832516Sgibbs};
6932516Sgibbs
7032516Sgibbs#define	ACL_HASHSIZE	32
7132516Sgibbs
7260938Sjakestruct acl {
7332516Sgibbs	TAILQ_ENTRY(acl)	acl_list;
7432516Sgibbs	LIST_ENTRY(acl)		acl_hash;
7532516Sgibbs	uint8_t			acl_macaddr[IEEE80211_ADDR_LEN];
7632516Sgibbs};
7760938Sjakestruct aclstate {
7832516Sgibbs	acl_lock_t		as_lock;
7932516Sgibbs	int			as_policy;
8032516Sgibbs	uint32_t		as_nacls;
8132516Sgibbs	TAILQ_HEAD(, acl)	as_list;	/* list of all ACL's */
8232516Sgibbs	LIST_HEAD(, acl)	as_hash[ACL_HASHSIZE];
8332516Sgibbs	struct ieee80211vap	*as_vap;
8432516Sgibbs};
8532516Sgibbs
8632516Sgibbs/* simple hash is enough for variation of macaddr */
8732516Sgibbs#define	ACL_HASH(addr)	\
8832516Sgibbs	(((const uint8_t *)(addr))[IEEE80211_ADDR_LEN - 1] % ACL_HASHSIZE)
8932516Sgibbs
9032516Sgibbsstatic MALLOC_DEFINE(M_80211_ACL, "acl", "802.11 station acl");
9132516Sgibbs
9232516Sgibbsstatic	int acl_free_all(struct ieee80211vap *);
9360938Sjake
9432516Sgibbs/* number of references from net80211 layer */
9532516Sgibbsstatic	int nrefs = 0;
9660938Sjake
9760938Sjakestatic int
9832516Sgibbsacl_attach(struct ieee80211vap *vap)
9932516Sgibbs{
100112346Smux	struct aclstate *as;
10132516Sgibbs
102113228Sjake	as = (struct aclstate *) malloc(sizeof(struct aclstate),
103113228Sjake		M_80211_ACL, M_NOWAIT | M_ZERO);
104112569Sjake	if (as == NULL)
10532516Sgibbs		return 0;
10632516Sgibbs	ACL_LOCK_INIT(as, "acl");
10732516Sgibbs	TAILQ_INIT(&as->as_list);
10832516Sgibbs	as->as_policy = ACL_POLICY_OPEN;
109112346Smux	as->as_vap = vap;
110112346Smux	vap->iv_as = as;
111112346Smux	nrefs++;			/* NB: we assume caller locking */
11295076Salfred	return 1;
11395076Salfred}
11495076Salfred
11595076Salfredstatic void
11695076Salfredacl_detach(struct ieee80211vap *vap)
11795076Salfred{
11895076Salfred	struct aclstate *as = vap->iv_as;
11995076Salfred
12032516Sgibbs	KASSERT(nrefs > 0, ("imbalanced attach/detach"));
12132516Sgibbs	nrefs--;			/* NB: we assume caller locking */
12232516Sgibbs
12332516Sgibbs	acl_free_all(vap);
12432516Sgibbs	vap->iv_as = NULL;
12532516Sgibbs	ACL_LOCK_DESTROY(as);
12632516Sgibbs	free(as, M_80211_ACL);
12732516Sgibbs}
12832516Sgibbs
12932516Sgibbsstatic __inline struct acl *
13032516Sgibbs_find_acl(struct aclstate *as, const uint8_t *macaddr)
13132516Sgibbs{
13232516Sgibbs	struct acl *acl;
13332516Sgibbs	int hash;
13432516Sgibbs
13532516Sgibbs	hash = ACL_HASH(macaddr);
13632516Sgibbs	LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) {
13732516Sgibbs		if (IEEE80211_ADDR_EQ(acl->acl_macaddr, macaddr))
13835767Sgibbs			return acl;
13932516Sgibbs	}
14032516Sgibbs	return NULL;
14132516Sgibbs}
14232516Sgibbs
14335767Sgibbsstatic void
14435767Sgibbs_acl_free(struct aclstate *as, struct acl *acl)
14535767Sgibbs{
14635767Sgibbs	ACL_LOCK_ASSERT(as);
14735767Sgibbs
14832516Sgibbs	TAILQ_REMOVE(&as->as_list, acl, acl_list);
14932516Sgibbs	LIST_REMOVE(acl, acl_hash);
15032516Sgibbs	free(acl, M_80211_ACL);
15132516Sgibbs	as->as_nacls--;
15232516Sgibbs}
15332516Sgibbs
15432516Sgibbsstatic int
15532516Sgibbsacl_check(struct ieee80211vap *vap, const struct ieee80211_frame *wh)
15632516Sgibbs{
15732516Sgibbs	struct aclstate *as = vap->iv_as;
15832516Sgibbs
15932516Sgibbs	switch (as->as_policy) {
16048449Smjacob	case ACL_POLICY_OPEN:
16132516Sgibbs	case ACL_POLICY_RADIUS:
162112569Sjake		return 1;
163112569Sjake	case ACL_POLICY_ALLOW:
164112569Sjake		return _find_acl(as, wh->i_addr2) != NULL;
16532516Sgibbs	case ACL_POLICY_DENY:
16632516Sgibbs		return _find_acl(as, wh->i_addr2) == NULL;
16732516Sgibbs	}
16832516Sgibbs	return 0;		/* should not happen */
16932516Sgibbs}
17032516Sgibbs
17132516Sgibbsstatic int
17232516Sgibbsacl_add(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN])
17332516Sgibbs{
17432516Sgibbs	struct aclstate *as = vap->iv_as;
17532516Sgibbs	struct acl *acl, *new;
17632516Sgibbs	int hash;
17732516Sgibbs
17832516Sgibbs	new = (struct acl *) malloc(sizeof(struct acl), M_80211_ACL, M_NOWAIT | M_ZERO);
17932516Sgibbs	if (new == NULL) {
18032516Sgibbs		IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
18132516Sgibbs			"ACL: add %s failed, no memory\n", ether_sprintf(mac));
18235767Sgibbs		/* XXX statistic */
18332516Sgibbs		return ENOMEM;
18432516Sgibbs	}
18532516Sgibbs
18635256Sdes	ACL_LOCK(as);
18732516Sgibbs	hash = ACL_HASH(mac);
18832516Sgibbs	LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) {
18932516Sgibbs		if (IEEE80211_ADDR_EQ(acl->acl_macaddr, mac)) {
19032516Sgibbs			ACL_UNLOCK(as);
19132516Sgibbs			free(new, M_80211_ACL);
192112436Smux			IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
193112436Smux				"ACL: add %s failed, already present\n",
19432516Sgibbs				ether_sprintf(mac));
19532516Sgibbs			return EEXIST;
196112569Sjake		}
197112569Sjake	}
19832516Sgibbs	IEEE80211_ADDR_COPY(new->acl_macaddr, mac);
19932516Sgibbs	TAILQ_INSERT_TAIL(&as->as_list, new, acl_list);
20032516Sgibbs	LIST_INSERT_HEAD(&as->as_hash[hash], new, acl_hash);
20132516Sgibbs	as->as_nacls++;
20232516Sgibbs	ACL_UNLOCK(as);
20332516Sgibbs
20432516Sgibbs	IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
20535767Sgibbs		"ACL: add %s\n", ether_sprintf(mac));
20632516Sgibbs	return 0;
20732516Sgibbs}
20832516Sgibbs
20932516Sgibbsstatic int
21032516Sgibbsacl_remove(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN])
21132516Sgibbs{
21232516Sgibbs	struct aclstate *as = vap->iv_as;
21332516Sgibbs	struct acl *acl;
21432516Sgibbs
21532516Sgibbs	ACL_LOCK(as);
21632516Sgibbs	acl = _find_acl(as, mac);
21735767Sgibbs	if (acl != NULL)
21835767Sgibbs		_acl_free(as, acl);
21932516Sgibbs	ACL_UNLOCK(as);
22032516Sgibbs
22132516Sgibbs	IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
22232516Sgibbs		"ACL: remove %s%s\n", ether_sprintf(mac),
22332516Sgibbs		acl == NULL ? ", not present" : "");
22432516Sgibbs
22532516Sgibbs	return (acl == NULL ? ENOENT : 0);
22632516Sgibbs}
22732516Sgibbs
22832516Sgibbsstatic int
22932516Sgibbsacl_free_all(struct ieee80211vap *vap)
23032516Sgibbs{
23132516Sgibbs	struct aclstate *as = vap->iv_as;
23232516Sgibbs	struct acl *acl;
23332516Sgibbs
23432516Sgibbs	IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL, "ACL: %s\n", "free all");
23532516Sgibbs
23632516Sgibbs	ACL_LOCK(as);
23732516Sgibbs	while ((acl = TAILQ_FIRST(&as->as_list)) != NULL)
23832516Sgibbs		_acl_free(as, acl);
23932516Sgibbs	ACL_UNLOCK(as);
24032516Sgibbs
241112436Smux	return 0;
24232516Sgibbs}
24332516Sgibbs
24440029Sgibbsstatic int
24540029Sgibbsacl_setpolicy(struct ieee80211vap *vap, int policy)
24640029Sgibbs{
24740029Sgibbs	struct aclstate *as = vap->iv_as;
24840029Sgibbs
24940029Sgibbs	IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
25040029Sgibbs		"ACL: set policy to %u\n", policy);
25140029Sgibbs
25232516Sgibbs	switch (policy) {
25332516Sgibbs	case IEEE80211_MACCMD_POLICY_OPEN:
25432516Sgibbs		as->as_policy = ACL_POLICY_OPEN;
25532516Sgibbs		break;
25632516Sgibbs	case IEEE80211_MACCMD_POLICY_ALLOW:
25732516Sgibbs		as->as_policy = ACL_POLICY_ALLOW;
25832516Sgibbs		break;
25932516Sgibbs	case IEEE80211_MACCMD_POLICY_DENY:
26032516Sgibbs		as->as_policy = ACL_POLICY_DENY;
26132516Sgibbs		break;
26232516Sgibbs	case IEEE80211_MACCMD_POLICY_RADIUS:
26332516Sgibbs		as->as_policy = ACL_POLICY_RADIUS;
26432516Sgibbs		break;
26532516Sgibbs	default:
26632516Sgibbs		return EINVAL;
26732516Sgibbs	}
268112569Sjake	return 0;
26932516Sgibbs}
27032516Sgibbs
27132516Sgibbsstatic int
27232516Sgibbsacl_getpolicy(struct ieee80211vap *vap)
27369781Sdwmalone{
27469781Sdwmalone	struct aclstate *as = vap->iv_as;
27535767Sgibbs
27669781Sdwmalone	return as->as_policy;
27769781Sdwmalone}
27869781Sdwmalone
27969781Sdwmalonestatic int
28032516Sgibbsacl_setioctl(struct ieee80211vap *vap, struct ieee80211req *ireq)
28132516Sgibbs{
28232516Sgibbs
28332516Sgibbs	return EINVAL;
28432516Sgibbs}
28535767Sgibbs
28635767Sgibbsstatic int
28735767Sgibbsacl_getioctl(struct ieee80211vap *vap, struct ieee80211req *ireq)
28832516Sgibbs{
28932516Sgibbs	struct aclstate *as = vap->iv_as;
29035767Sgibbs	struct acl *acl;
29135767Sgibbs	struct ieee80211req_maclist *ap;
29235767Sgibbs	int error;
29335767Sgibbs	uint32_t i, space;
29435767Sgibbs
29535767Sgibbs	switch (ireq->i_val) {
29635767Sgibbs	case IEEE80211_MACCMD_POLICY:
29735767Sgibbs		ireq->i_val = as->as_policy;
298113228Sjake		return 0;
29932516Sgibbs	case IEEE80211_MACCMD_LIST:
300113228Sjake		space = as->as_nacls * IEEE80211_ADDR_LEN;
301113228Sjake		if (ireq->i_len == 0) {
30235767Sgibbs			ireq->i_len = space;	/* return required space */
30335767Sgibbs			return 0;		/* NB: must not error */
30435767Sgibbs		}
30535767Sgibbs		ap = (struct ieee80211req_maclist *) malloc(space,
30635767Sgibbs		    M_TEMP, M_NOWAIT);
30735767Sgibbs		if (ap == NULL)
30835767Sgibbs			return ENOMEM;
30932516Sgibbs		i = 0;
31032516Sgibbs		ACL_LOCK(as);
31140029Sgibbs		TAILQ_FOREACH(acl, &as->as_list, acl_list) {
31232516Sgibbs			IEEE80211_ADDR_COPY(ap[i].ml_macaddr, acl->acl_macaddr);
31332516Sgibbs			i++;
31432516Sgibbs		}
31532516Sgibbs		ACL_UNLOCK(as);
31632516Sgibbs		if (ireq->i_len >= space) {
31732516Sgibbs			error = copyout(ap, ireq->i_data, space);
31832516Sgibbs			ireq->i_len = space;
31932516Sgibbs		} else
32032516Sgibbs			error = copyout(ap, ireq->i_data, ireq->i_len);
32132516Sgibbs		free(ap, M_TEMP);
32232516Sgibbs		return error;
32332516Sgibbs	}
32432516Sgibbs	return EINVAL;
32532516Sgibbs}
32632516Sgibbs
32732516Sgibbsstatic const struct ieee80211_aclator mac = {
32832516Sgibbs	.iac_name	= "mac",
32932516Sgibbs	.iac_attach	= acl_attach,
33032516Sgibbs	.iac_detach	= acl_detach,
33132516Sgibbs	.iac_check	= acl_check,
33232516Sgibbs	.iac_add	= acl_add,
33332516Sgibbs	.iac_remove	= acl_remove,
33435767Sgibbs	.iac_flush	= acl_free_all,
33535767Sgibbs	.iac_setpolicy	= acl_setpolicy,
33635767Sgibbs	.iac_getpolicy	= acl_getpolicy,
33735767Sgibbs	.iac_setioctl	= acl_setioctl,
33835767Sgibbs	.iac_getioctl	= acl_getioctl,
33935767Sgibbs};
34035767SgibbsIEEE80211_ACL_MODULE(wlan_acl, mac, 1);
341110030Sscottl