ieee80211_acl.c revision 170530
1138568Ssam/*-
2170360Ssam * Copyright (c) 2004-2007 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: head/sys/net80211/ieee80211_acl.c 170530 2007-06-11 03:36:55Z sam $");
28138568Ssam
29138568Ssam/*
30138568Ssam * IEEE 802.11 MAC ACL support.
31138568Ssam *
32138568Ssam * When this module is loaded the sender address of each received
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 */
40138568Ssam#include <sys/param.h>
41138568Ssam#include <sys/kernel.h>
42138568Ssam#include <sys/systm.h>
43138568Ssam#include <sys/mbuf.h>
44138568Ssam#include <sys/module.h>
45138568Ssam#include <sys/queue.h>
46138568Ssam
47138568Ssam#include <sys/socket.h>
48138568Ssam
49138568Ssam#include <net/if.h>
50138568Ssam#include <net/if_media.h>
51138568Ssam#include <net/ethernet.h>
52138568Ssam#include <net/route.h>
53138568Ssam
54138568Ssam#include <net80211/ieee80211_var.h>
55138568Ssam
56138568Ssamenum {
57138568Ssam	ACL_POLICY_OPEN		= 0,	/* open, don't check ACL's */
58138568Ssam	ACL_POLICY_ALLOW	= 1,	/* allow traffic from MAC */
59138568Ssam	ACL_POLICY_DENY		= 2,	/* deny traffic from MAC */
60138568Ssam};
61138568Ssam
62138568Ssam#define	ACL_HASHSIZE	32
63138568Ssam
64138568Ssamstruct acl {
65138568Ssam	TAILQ_ENTRY(acl)	acl_list;
66138568Ssam	LIST_ENTRY(acl)		acl_hash;
67170530Ssam	uint8_t			acl_macaddr[IEEE80211_ADDR_LEN];
68138568Ssam};
69138568Ssamstruct aclstate {
70138568Ssam	acl_lock_t		as_lock;
71138568Ssam	int			as_policy;
72149028Ssam	int			as_nacls;
73138568Ssam	TAILQ_HEAD(, acl)	as_list;	/* list of all ACL's */
74138568Ssam	LIST_HEAD(, acl)	as_hash[ACL_HASHSIZE];
75138568Ssam	struct ieee80211com	*as_ic;
76138568Ssam};
77138568Ssam
78138568Ssam/* simple hash is enough for variation of macaddr */
79138568Ssam#define	ACL_HASH(addr)	\
80170530Ssam	(((const uint8_t *)(addr))[IEEE80211_ADDR_LEN - 1] % ACL_HASHSIZE)
81138568Ssam
82138568SsamMALLOC_DEFINE(M_80211_ACL, "acl", "802.11 station acl");
83138568Ssam
84138568Ssamstatic	int acl_free_all(struct ieee80211com *);
85138568Ssam
86138568Ssamstatic int
87138568Ssamacl_attach(struct ieee80211com *ic)
88138568Ssam{
89138568Ssam	struct aclstate *as;
90138568Ssam
91138568Ssam	MALLOC(as, struct aclstate *, sizeof(struct aclstate),
92149028Ssam		M_80211_ACL, M_NOWAIT | M_ZERO);
93138568Ssam	if (as == NULL)
94138568Ssam		return 0;
95138568Ssam	ACL_LOCK_INIT(as, "acl");
96138568Ssam	TAILQ_INIT(&as->as_list);
97138568Ssam	as->as_policy = ACL_POLICY_OPEN;
98138568Ssam	as->as_ic = ic;
99138568Ssam	ic->ic_as = as;
100138568Ssam	return 1;
101138568Ssam}
102138568Ssam
103138568Ssamstatic void
104138568Ssamacl_detach(struct ieee80211com *ic)
105138568Ssam{
106138568Ssam	struct aclstate *as = ic->ic_as;
107138568Ssam
108138568Ssam	acl_free_all(ic);
109138568Ssam	ic->ic_as = NULL;
110138568Ssam	ACL_LOCK_DESTROY(as);
111138568Ssam	FREE(as, M_DEVBUF);
112138568Ssam}
113138568Ssam
114139503Ssamstatic __inline struct acl *
115170530Ssam_find_acl(struct aclstate *as, const uint8_t *macaddr)
116138568Ssam{
117138568Ssam	struct acl *acl;
118138568Ssam	int hash;
119138568Ssam
120138568Ssam	hash = ACL_HASH(macaddr);
121138568Ssam	LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) {
122138568Ssam		if (IEEE80211_ADDR_EQ(acl->acl_macaddr, macaddr))
123138568Ssam			return acl;
124138568Ssam	}
125138568Ssam	return NULL;
126138568Ssam}
127138568Ssam
128138568Ssamstatic void
129138568Ssam_acl_free(struct aclstate *as, struct acl *acl)
130138568Ssam{
131138568Ssam	ACL_LOCK_ASSERT(as);
132138568Ssam
133138568Ssam	TAILQ_REMOVE(&as->as_list, acl, acl_list);
134138568Ssam	LIST_REMOVE(acl, acl_hash);
135138568Ssam	FREE(acl, M_80211_ACL);
136149028Ssam	as->as_nacls--;
137138568Ssam}
138138568Ssam
139138568Ssamstatic int
140170530Ssamacl_check(struct ieee80211com *ic, const uint8_t mac[IEEE80211_ADDR_LEN])
141138568Ssam{
142138568Ssam	struct aclstate *as = ic->ic_as;
143138568Ssam
144138568Ssam	switch (as->as_policy) {
145138568Ssam	case ACL_POLICY_OPEN:
146138568Ssam		return 1;
147138568Ssam	case ACL_POLICY_ALLOW:
148138568Ssam		return _find_acl(as, mac) != NULL;
149138568Ssam	case ACL_POLICY_DENY:
150138568Ssam		return _find_acl(as, mac) == NULL;
151138568Ssam	}
152138568Ssam	return 0;		/* should not happen */
153138568Ssam}
154138568Ssam
155138568Ssamstatic int
156170530Ssamacl_add(struct ieee80211com *ic, const uint8_t mac[IEEE80211_ADDR_LEN])
157138568Ssam{
158138568Ssam	struct aclstate *as = ic->ic_as;
159138568Ssam	struct acl *acl, *new;
160138568Ssam	int hash;
161138568Ssam
162138568Ssam	MALLOC(new, struct acl *, sizeof(struct acl), M_80211_ACL, M_NOWAIT | M_ZERO);
163138568Ssam	if (new == NULL) {
164138568Ssam		IEEE80211_DPRINTF(ic, IEEE80211_MSG_ACL,
165138568Ssam			"ACL: add %s failed, no memory\n", ether_sprintf(mac));
166138568Ssam		/* XXX statistic */
167138568Ssam		return ENOMEM;
168138568Ssam	}
169138568Ssam
170138568Ssam	ACL_LOCK(as);
171138568Ssam	hash = ACL_HASH(mac);
172138568Ssam	LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) {
173138568Ssam		if (IEEE80211_ADDR_EQ(acl->acl_macaddr, mac)) {
174138568Ssam			ACL_UNLOCK(as);
175138568Ssam			FREE(new, M_80211_ACL);
176138568Ssam			IEEE80211_DPRINTF(ic, IEEE80211_MSG_ACL,
177138568Ssam				"ACL: add %s failed, already present\n",
178138568Ssam				ether_sprintf(mac));
179138568Ssam			return EEXIST;
180138568Ssam		}
181138568Ssam	}
182138568Ssam	IEEE80211_ADDR_COPY(new->acl_macaddr, mac);
183138568Ssam	TAILQ_INSERT_TAIL(&as->as_list, new, acl_list);
184138568Ssam	LIST_INSERT_HEAD(&as->as_hash[hash], new, acl_hash);
185149028Ssam	as->as_nacls++;
186138568Ssam	ACL_UNLOCK(as);
187138568Ssam
188138568Ssam	IEEE80211_DPRINTF(ic, IEEE80211_MSG_ACL,
189138568Ssam		"ACL: add %s\n", ether_sprintf(mac));
190138568Ssam	return 0;
191138568Ssam}
192138568Ssam
193138568Ssamstatic int
194170530Ssamacl_remove(struct ieee80211com *ic, const uint8_t mac[IEEE80211_ADDR_LEN])
195138568Ssam{
196138568Ssam	struct aclstate *as = ic->ic_as;
197138568Ssam	struct acl *acl;
198138568Ssam
199138568Ssam	ACL_LOCK(as);
200138568Ssam	acl = _find_acl(as, mac);
201138568Ssam	if (acl != NULL)
202138568Ssam		_acl_free(as, acl);
203138568Ssam	ACL_UNLOCK(as);
204138568Ssam
205138568Ssam	IEEE80211_DPRINTF(ic, IEEE80211_MSG_ACL,
206138568Ssam		"ACL: remove %s%s\n", ether_sprintf(mac),
207138568Ssam		acl == NULL ? ", not present" : "");
208138568Ssam
209138568Ssam	return (acl == NULL ? ENOENT : 0);
210138568Ssam}
211138568Ssam
212138568Ssamstatic int
213138568Ssamacl_free_all(struct ieee80211com *ic)
214138568Ssam{
215138568Ssam	struct aclstate *as = ic->ic_as;
216138568Ssam	struct acl *acl;
217138568Ssam
218138568Ssam	IEEE80211_DPRINTF(ic, IEEE80211_MSG_ACL, "ACL: %s\n", "free all");
219138568Ssam
220138568Ssam	ACL_LOCK(as);
221138568Ssam	while ((acl = TAILQ_FIRST(&as->as_list)) != NULL)
222138568Ssam		_acl_free(as, acl);
223138568Ssam	ACL_UNLOCK(as);
224138568Ssam
225138568Ssam	return 0;
226138568Ssam}
227138568Ssam
228138568Ssamstatic int
229138568Ssamacl_setpolicy(struct ieee80211com *ic, int policy)
230138568Ssam{
231138568Ssam	struct aclstate *as = ic->ic_as;
232138568Ssam
233138568Ssam	IEEE80211_DPRINTF(ic, IEEE80211_MSG_ACL,
234138568Ssam		"ACL: set policy to %u\n", policy);
235138568Ssam
236138568Ssam	switch (policy) {
237138568Ssam	case IEEE80211_MACCMD_POLICY_OPEN:
238138568Ssam		as->as_policy = ACL_POLICY_OPEN;
239138568Ssam		break;
240138568Ssam	case IEEE80211_MACCMD_POLICY_ALLOW:
241138568Ssam		as->as_policy = ACL_POLICY_ALLOW;
242138568Ssam		break;
243138568Ssam	case IEEE80211_MACCMD_POLICY_DENY:
244138568Ssam		as->as_policy = ACL_POLICY_DENY;
245138568Ssam		break;
246138568Ssam	default:
247138568Ssam		return EINVAL;
248138568Ssam	}
249138568Ssam	return 0;
250138568Ssam}
251138568Ssam
252138568Ssamstatic int
253138568Ssamacl_getpolicy(struct ieee80211com *ic)
254138568Ssam{
255138568Ssam	struct aclstate *as = ic->ic_as;
256138568Ssam
257138568Ssam	return as->as_policy;
258138568Ssam}
259138568Ssam
260149028Ssamstatic int
261149028Ssamacl_setioctl(struct ieee80211com *ic, struct ieee80211req *ireq)
262149028Ssam{
263149028Ssam
264149028Ssam	return EINVAL;
265149028Ssam}
266149028Ssam
267149028Ssamstatic int
268149028Ssamacl_getioctl(struct ieee80211com *ic, struct ieee80211req *ireq)
269149028Ssam{
270149028Ssam	struct aclstate *as = ic->ic_as;
271149028Ssam	struct acl *acl;
272149028Ssam	struct ieee80211req_maclist *ap;
273149028Ssam	int error, space, i;
274149028Ssam
275149028Ssam	switch (ireq->i_val) {
276149028Ssam	case IEEE80211_MACCMD_POLICY:
277149028Ssam		ireq->i_val = as->as_policy;
278149028Ssam		return 0;
279149028Ssam	case IEEE80211_MACCMD_LIST:
280149028Ssam		space = as->as_nacls * IEEE80211_ADDR_LEN;
281149028Ssam		if (ireq->i_len == 0) {
282149028Ssam			ireq->i_len = space;	/* return required space */
283149028Ssam			return 0;		/* NB: must not error */
284149028Ssam		}
285149028Ssam		MALLOC(ap, struct ieee80211req_maclist *, space,
286149028Ssam			M_TEMP, M_NOWAIT);
287149028Ssam		if (ap == NULL)
288149028Ssam			return ENOMEM;
289149028Ssam		i = 0;
290149028Ssam		ACL_LOCK(as);
291149028Ssam		TAILQ_FOREACH(acl, &as->as_list, acl_list) {
292149028Ssam			IEEE80211_ADDR_COPY(ap[i].ml_macaddr, acl->acl_macaddr);
293149028Ssam			i++;
294149028Ssam		}
295149028Ssam		ACL_UNLOCK(as);
296149028Ssam		if (ireq->i_len >= space) {
297149028Ssam			error = copyout(ap, ireq->i_data, space);
298149028Ssam			ireq->i_len = space;
299149028Ssam		} else
300149028Ssam			error = copyout(ap, ireq->i_data, ireq->i_len);
301149028Ssam		FREE(ap, M_TEMP);
302149028Ssam		return error;
303149028Ssam	}
304149028Ssam	return EINVAL;
305149028Ssam}
306149028Ssam
307138568Ssamstatic const struct ieee80211_aclator mac = {
308138568Ssam	.iac_name	= "mac",
309138568Ssam	.iac_attach	= acl_attach,
310138568Ssam	.iac_detach	= acl_detach,
311138568Ssam	.iac_check	= acl_check,
312138568Ssam	.iac_add	= acl_add,
313138568Ssam	.iac_remove	= acl_remove,
314138568Ssam	.iac_flush	= acl_free_all,
315138568Ssam	.iac_setpolicy	= acl_setpolicy,
316138568Ssam	.iac_getpolicy	= acl_getpolicy,
317149028Ssam	.iac_setioctl	= acl_setioctl,
318149028Ssam	.iac_getioctl	= acl_getioctl,
319138568Ssam};
320138568Ssam
321138568Ssam/*
322138568Ssam * Module glue.
323138568Ssam */
324138568Ssamstatic int
325138568Ssamwlan_acl_modevent(module_t mod, int type, void *unused)
326138568Ssam{
327138568Ssam	switch (type) {
328138568Ssam	case MOD_LOAD:
329138568Ssam		if (bootverbose)
330138568Ssam			printf("wlan: <802.11 MAC ACL support>\n");
331138568Ssam		ieee80211_aclator_register(&mac);
332138568Ssam		return 0;
333138568Ssam	case MOD_UNLOAD:
334138568Ssam		ieee80211_aclator_unregister(&mac);
335138568Ssam		return 0;
336138568Ssam	}
337138568Ssam	return EINVAL;
338138568Ssam}
339138568Ssam
340138568Ssamstatic moduledata_t wlan_acl_mod = {
341138568Ssam	"wlan_acl",
342138568Ssam	wlan_acl_modevent,
343138568Ssam	0
344138568Ssam};
345138568SsamDECLARE_MODULE(wlan_acl, wlan_acl_mod, SI_SUB_DRIVERS, SI_ORDER_FIRST);
346138568SsamMODULE_VERSION(wlan_acl, 1);
347138568SsamMODULE_DEPEND(wlan_acl, wlan, 1, 1, 1);
348