ieee80211_acl.c revision 138568
1138568Ssam/*-
2138568Ssam * Copyright (c) 2004 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 * 3. The name of the author may not be used to endorse or promote products
14138568Ssam *    derived from this software without specific prior written permission.
15138568Ssam *
16138568Ssam * Alternatively, this software may be distributed under the terms of the
17138568Ssam * GNU General Public License ("GPL") version 2 as published by the Free
18138568Ssam * Software Foundation.
19138568Ssam *
20138568Ssam * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21138568Ssam * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22138568Ssam * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23138568Ssam * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24138568Ssam * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25138568Ssam * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26138568Ssam * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27138568Ssam * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28138568Ssam * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29138568Ssam * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30138568Ssam */
31138568Ssam
32138568Ssam#include <sys/cdefs.h>
33138568Ssam__FBSDID("$FreeBSD: head/sys/net80211/ieee80211_acl.c 138568 2004-12-08 17:26:47Z sam $");
34138568Ssam
35138568Ssam/*
36138568Ssam * IEEE 802.11 MAC ACL support.
37138568Ssam *
38138568Ssam * When this module is loaded the sender address of each received
39138568Ssam * frame is passed to the iac_check method and the module indicates
40138568Ssam * if the frame should be accepted or rejected.  If the policy is
41138568Ssam * set to ACL_POLICY_OPEN then all frames are accepted w/o checking
42138568Ssam * the address.  Otherwise, the address is looked up in the database
43138568Ssam * and if found the frame is either accepted (ACL_POLICY_ALLOW)
44138568Ssam * or rejected (ACL_POLICY_DENT).
45138568Ssam */
46138568Ssam#include <sys/param.h>
47138568Ssam#include <sys/kernel.h>
48138568Ssam#include <sys/systm.h>
49138568Ssam#include <sys/mbuf.h>
50138568Ssam#include <sys/module.h>
51138568Ssam#include <sys/queue.h>
52138568Ssam
53138568Ssam#include <sys/socket.h>
54138568Ssam
55138568Ssam#include <net/if.h>
56138568Ssam#include <net/if_media.h>
57138568Ssam#include <net/ethernet.h>
58138568Ssam#include <net/route.h>
59138568Ssam
60138568Ssam#include <net80211/ieee80211_var.h>
61138568Ssam
62138568Ssamenum {
63138568Ssam	ACL_POLICY_OPEN		= 0,	/* open, don't check ACL's */
64138568Ssam	ACL_POLICY_ALLOW	= 1,	/* allow traffic from MAC */
65138568Ssam	ACL_POLICY_DENY		= 2,	/* deny traffic from MAC */
66138568Ssam};
67138568Ssam
68138568Ssam#define	ACL_HASHSIZE	32
69138568Ssam
70138568Ssamstruct acl {
71138568Ssam	TAILQ_ENTRY(acl)	acl_list;
72138568Ssam	LIST_ENTRY(acl)		acl_hash;
73138568Ssam	u_int8_t		acl_macaddr[IEEE80211_ADDR_LEN];
74138568Ssam};
75138568Ssamstruct aclstate {
76138568Ssam	acl_lock_t		as_lock;
77138568Ssam	int			as_policy;
78138568Ssam	TAILQ_HEAD(, acl)	as_list;	/* list of all ACL's */
79138568Ssam	LIST_HEAD(, acl)	as_hash[ACL_HASHSIZE];
80138568Ssam	struct ieee80211com	*as_ic;
81138568Ssam};
82138568Ssam
83138568Ssam/* simple hash is enough for variation of macaddr */
84138568Ssam#define	ACL_HASH(addr)	\
85138568Ssam	(((const u_int8_t *)(addr))[IEEE80211_ADDR_LEN - 1] % ACL_HASHSIZE)
86138568Ssam
87138568SsamMALLOC_DEFINE(M_80211_ACL, "acl", "802.11 station acl");
88138568Ssam
89138568Ssamstatic	int acl_free_all(struct ieee80211com *);
90138568Ssam
91138568Ssamstatic int
92138568Ssamacl_attach(struct ieee80211com *ic)
93138568Ssam{
94138568Ssam	struct aclstate *as;
95138568Ssam
96138568Ssam	MALLOC(as, struct aclstate *, sizeof(struct aclstate),
97138568Ssam		M_DEVBUF, M_NOWAIT | M_ZERO);
98138568Ssam	if (as == NULL)
99138568Ssam		return 0;
100138568Ssam	ACL_LOCK_INIT(as, "acl");
101138568Ssam	TAILQ_INIT(&as->as_list);
102138568Ssam	as->as_policy = ACL_POLICY_OPEN;
103138568Ssam	as->as_ic = ic;
104138568Ssam	ic->ic_as = as;
105138568Ssam	return 1;
106138568Ssam}
107138568Ssam
108138568Ssamstatic void
109138568Ssamacl_detach(struct ieee80211com *ic)
110138568Ssam{
111138568Ssam	struct aclstate *as = ic->ic_as;
112138568Ssam
113138568Ssam	acl_free_all(ic);
114138568Ssam	ic->ic_as = NULL;
115138568Ssam	ACL_LOCK_DESTROY(as);
116138568Ssam	FREE(as, M_DEVBUF);
117138568Ssam}
118138568Ssam
119138568Ssamstatic inline struct acl *
120138568Ssam_find_acl(struct aclstate *as, const u_int8_t *macaddr)
121138568Ssam{
122138568Ssam	struct acl *acl;
123138568Ssam	int hash;
124138568Ssam
125138568Ssam	hash = ACL_HASH(macaddr);
126138568Ssam	LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) {
127138568Ssam		if (IEEE80211_ADDR_EQ(acl->acl_macaddr, macaddr))
128138568Ssam			return acl;
129138568Ssam	}
130138568Ssam	return NULL;
131138568Ssam}
132138568Ssam
133138568Ssamstatic void
134138568Ssam_acl_free(struct aclstate *as, struct acl *acl)
135138568Ssam{
136138568Ssam	ACL_LOCK_ASSERT(as);
137138568Ssam
138138568Ssam	TAILQ_REMOVE(&as->as_list, acl, acl_list);
139138568Ssam	LIST_REMOVE(acl, acl_hash);
140138568Ssam	FREE(acl, M_80211_ACL);
141138568Ssam}
142138568Ssam
143138568Ssamstatic int
144138568Ssamacl_check(struct ieee80211com *ic, const u_int8_t mac[IEEE80211_ADDR_LEN])
145138568Ssam{
146138568Ssam	struct aclstate *as = ic->ic_as;
147138568Ssam
148138568Ssam	switch (as->as_policy) {
149138568Ssam	case ACL_POLICY_OPEN:
150138568Ssam		return 1;
151138568Ssam	case ACL_POLICY_ALLOW:
152138568Ssam		return _find_acl(as, mac) != NULL;
153138568Ssam	case ACL_POLICY_DENY:
154138568Ssam		return _find_acl(as, mac) == NULL;
155138568Ssam	}
156138568Ssam	return 0;		/* should not happen */
157138568Ssam}
158138568Ssam
159138568Ssamstatic int
160138568Ssamacl_add(struct ieee80211com *ic, const u_int8_t mac[IEEE80211_ADDR_LEN])
161138568Ssam{
162138568Ssam	struct aclstate *as = ic->ic_as;
163138568Ssam	struct acl *acl, *new;
164138568Ssam	int hash;
165138568Ssam
166138568Ssam	MALLOC(new, struct acl *, sizeof(struct acl), M_80211_ACL, M_NOWAIT | M_ZERO);
167138568Ssam	if (new == NULL) {
168138568Ssam		IEEE80211_DPRINTF(ic, IEEE80211_MSG_ACL,
169138568Ssam			"ACL: add %s failed, no memory\n", ether_sprintf(mac));
170138568Ssam		/* XXX statistic */
171138568Ssam		return ENOMEM;
172138568Ssam	}
173138568Ssam
174138568Ssam	ACL_LOCK(as);
175138568Ssam	hash = ACL_HASH(mac);
176138568Ssam	LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) {
177138568Ssam		if (IEEE80211_ADDR_EQ(acl->acl_macaddr, mac)) {
178138568Ssam			ACL_UNLOCK(as);
179138568Ssam			FREE(new, M_80211_ACL);
180138568Ssam			IEEE80211_DPRINTF(ic, IEEE80211_MSG_ACL,
181138568Ssam				"ACL: add %s failed, already present\n",
182138568Ssam				ether_sprintf(mac));
183138568Ssam			return EEXIST;
184138568Ssam		}
185138568Ssam	}
186138568Ssam	IEEE80211_ADDR_COPY(new->acl_macaddr, mac);
187138568Ssam	TAILQ_INSERT_TAIL(&as->as_list, new, acl_list);
188138568Ssam	LIST_INSERT_HEAD(&as->as_hash[hash], new, acl_hash);
189138568Ssam	ACL_UNLOCK(as);
190138568Ssam
191138568Ssam	IEEE80211_DPRINTF(ic, IEEE80211_MSG_ACL,
192138568Ssam		"ACL: add %s\n", ether_sprintf(mac));
193138568Ssam	return 0;
194138568Ssam}
195138568Ssam
196138568Ssamstatic int
197138568Ssamacl_remove(struct ieee80211com *ic, const u_int8_t mac[IEEE80211_ADDR_LEN])
198138568Ssam{
199138568Ssam	struct aclstate *as = ic->ic_as;
200138568Ssam	struct acl *acl;
201138568Ssam
202138568Ssam	ACL_LOCK(as);
203138568Ssam	acl = _find_acl(as, mac);
204138568Ssam	if (acl != NULL)
205138568Ssam		_acl_free(as, acl);
206138568Ssam	ACL_UNLOCK(as);
207138568Ssam
208138568Ssam	IEEE80211_DPRINTF(ic, IEEE80211_MSG_ACL,
209138568Ssam		"ACL: remove %s%s\n", ether_sprintf(mac),
210138568Ssam		acl == NULL ? ", not present" : "");
211138568Ssam
212138568Ssam	return (acl == NULL ? ENOENT : 0);
213138568Ssam}
214138568Ssam
215138568Ssamstatic int
216138568Ssamacl_free_all(struct ieee80211com *ic)
217138568Ssam{
218138568Ssam	struct aclstate *as = ic->ic_as;
219138568Ssam	struct acl *acl;
220138568Ssam
221138568Ssam	IEEE80211_DPRINTF(ic, IEEE80211_MSG_ACL, "ACL: %s\n", "free all");
222138568Ssam
223138568Ssam	ACL_LOCK(as);
224138568Ssam	while ((acl = TAILQ_FIRST(&as->as_list)) != NULL)
225138568Ssam		_acl_free(as, acl);
226138568Ssam	ACL_UNLOCK(as);
227138568Ssam
228138568Ssam	return 0;
229138568Ssam}
230138568Ssam
231138568Ssamstatic int
232138568Ssamacl_setpolicy(struct ieee80211com *ic, int policy)
233138568Ssam{
234138568Ssam	struct aclstate *as = ic->ic_as;
235138568Ssam
236138568Ssam	IEEE80211_DPRINTF(ic, IEEE80211_MSG_ACL,
237138568Ssam		"ACL: set policy to %u\n", policy);
238138568Ssam
239138568Ssam	switch (policy) {
240138568Ssam	case IEEE80211_MACCMD_POLICY_OPEN:
241138568Ssam		as->as_policy = ACL_POLICY_OPEN;
242138568Ssam		break;
243138568Ssam	case IEEE80211_MACCMD_POLICY_ALLOW:
244138568Ssam		as->as_policy = ACL_POLICY_ALLOW;
245138568Ssam		break;
246138568Ssam	case IEEE80211_MACCMD_POLICY_DENY:
247138568Ssam		as->as_policy = ACL_POLICY_DENY;
248138568Ssam		break;
249138568Ssam	default:
250138568Ssam		return EINVAL;
251138568Ssam	}
252138568Ssam	return 0;
253138568Ssam}
254138568Ssam
255138568Ssamstatic int
256138568Ssamacl_getpolicy(struct ieee80211com *ic)
257138568Ssam{
258138568Ssam	struct aclstate *as = ic->ic_as;
259138568Ssam
260138568Ssam	return as->as_policy;
261138568Ssam}
262138568Ssam
263138568Ssamstatic const struct ieee80211_aclator mac = {
264138568Ssam	.iac_name	= "mac",
265138568Ssam	.iac_attach	= acl_attach,
266138568Ssam	.iac_detach	= acl_detach,
267138568Ssam	.iac_check	= acl_check,
268138568Ssam	.iac_add	= acl_add,
269138568Ssam	.iac_remove	= acl_remove,
270138568Ssam	.iac_flush	= acl_free_all,
271138568Ssam	.iac_setpolicy	= acl_setpolicy,
272138568Ssam	.iac_getpolicy	= acl_getpolicy,
273138568Ssam};
274138568Ssam
275138568Ssam/*
276138568Ssam * Module glue.
277138568Ssam */
278138568Ssamstatic int
279138568Ssamwlan_acl_modevent(module_t mod, int type, void *unused)
280138568Ssam{
281138568Ssam	switch (type) {
282138568Ssam	case MOD_LOAD:
283138568Ssam		if (bootverbose)
284138568Ssam			printf("wlan: <802.11 MAC ACL support>\n");
285138568Ssam		ieee80211_aclator_register(&mac);
286138568Ssam		return 0;
287138568Ssam	case MOD_UNLOAD:
288138568Ssam		ieee80211_aclator_unregister(&mac);
289138568Ssam		return 0;
290138568Ssam	}
291138568Ssam	return EINVAL;
292138568Ssam}
293138568Ssam
294138568Ssamstatic moduledata_t wlan_acl_mod = {
295138568Ssam	"wlan_acl",
296138568Ssam	wlan_acl_modevent,
297138568Ssam	0
298138568Ssam};
299138568SsamDECLARE_MODULE(wlan_acl, wlan_acl_mod, SI_SUB_DRIVERS, SI_ORDER_FIRST);
300138568SsamMODULE_VERSION(wlan_acl, 1);
301138568SsamMODULE_DEPEND(wlan_acl, wlan, 1, 1, 1);
302