xref: /freebsd/sys/net80211/ieee80211_acl.c (revision 325151a3)
1 /*-
2  * Copyright (c) 2004-2008 Sam Leffler, Errno Consulting
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 
26 #include <sys/cdefs.h>
27 __FBSDID("$FreeBSD$");
28 
29 /*
30  * IEEE 802.11 MAC ACL support.
31  *
32  * When this module is loaded the sender address of each auth mgt
33  * frame is passed to the iac_check method and the module indicates
34  * if the frame should be accepted or rejected.  If the policy is
35  * set to ACL_POLICY_OPEN then all frames are accepted w/o checking
36  * the address.  Otherwise, the address is looked up in the database
37  * and if found the frame is either accepted (ACL_POLICY_ALLOW)
38  * or rejected (ACL_POLICY_DENT).
39  */
40 #include "opt_wlan.h"
41 
42 #include <sys/param.h>
43 #include <sys/kernel.h>
44 #include <sys/systm.h>
45 #include <sys/mbuf.h>
46 #include <sys/module.h>
47 #include <sys/queue.h>
48 
49 #include <sys/socket.h>
50 
51 #include <net/if.h>
52 #include <net/if_media.h>
53 #include <net/ethernet.h>
54 #include <net/route.h>
55 
56 #include <net80211/ieee80211_var.h>
57 
58 enum {
59 	ACL_POLICY_OPEN		= 0,	/* open, don't check ACL's */
60 	ACL_POLICY_ALLOW	= 1,	/* allow traffic from MAC */
61 	ACL_POLICY_DENY		= 2,	/* deny traffic from MAC */
62 	/*
63 	 * NB: ACL_POLICY_RADIUS must be the same value as
64 	 *     IEEE80211_MACCMD_POLICY_RADIUS because of the way
65 	 *     acl_getpolicy() works.
66 	 */
67 	ACL_POLICY_RADIUS	= 7,	/* defer to RADIUS ACL server */
68 };
69 
70 #define	ACL_HASHSIZE	32
71 
72 struct acl {
73 	TAILQ_ENTRY(acl)	acl_list;
74 	LIST_ENTRY(acl)		acl_hash;
75 	uint8_t			acl_macaddr[IEEE80211_ADDR_LEN];
76 };
77 struct aclstate {
78 	acl_lock_t		as_lock;
79 	int			as_policy;
80 	uint32_t		as_nacls;
81 	TAILQ_HEAD(, acl)	as_list;	/* list of all ACL's */
82 	LIST_HEAD(, acl)	as_hash[ACL_HASHSIZE];
83 	struct ieee80211vap	*as_vap;
84 };
85 
86 /* simple hash is enough for variation of macaddr */
87 #define	ACL_HASH(addr)	\
88 	(((const uint8_t *)(addr))[IEEE80211_ADDR_LEN - 1] % ACL_HASHSIZE)
89 
90 static MALLOC_DEFINE(M_80211_ACL, "acl", "802.11 station acl");
91 
92 static	int acl_free_all(struct ieee80211vap *);
93 
94 /* number of references from net80211 layer */
95 static	int nrefs = 0;
96 
97 static int
98 acl_attach(struct ieee80211vap *vap)
99 {
100 	struct aclstate *as;
101 
102 	as = (struct aclstate *) IEEE80211_MALLOC(sizeof(struct aclstate),
103 		M_80211_ACL, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO);
104 	if (as == NULL)
105 		return 0;
106 	ACL_LOCK_INIT(as, "acl");
107 	TAILQ_INIT(&as->as_list);
108 	as->as_policy = ACL_POLICY_OPEN;
109 	as->as_vap = vap;
110 	vap->iv_as = as;
111 	nrefs++;			/* NB: we assume caller locking */
112 	return 1;
113 }
114 
115 static void
116 acl_detach(struct ieee80211vap *vap)
117 {
118 	struct aclstate *as = vap->iv_as;
119 
120 	KASSERT(nrefs > 0, ("imbalanced attach/detach"));
121 	nrefs--;			/* NB: we assume caller locking */
122 
123 	acl_free_all(vap);
124 	vap->iv_as = NULL;
125 	ACL_LOCK_DESTROY(as);
126 	IEEE80211_FREE(as, M_80211_ACL);
127 }
128 
129 static __inline struct acl *
130 _find_acl(struct aclstate *as, const uint8_t *macaddr)
131 {
132 	struct acl *acl;
133 	int hash;
134 
135 	hash = ACL_HASH(macaddr);
136 	LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) {
137 		if (IEEE80211_ADDR_EQ(acl->acl_macaddr, macaddr))
138 			return acl;
139 	}
140 	return NULL;
141 }
142 
143 static void
144 _acl_free(struct aclstate *as, struct acl *acl)
145 {
146 	ACL_LOCK_ASSERT(as);
147 
148 	TAILQ_REMOVE(&as->as_list, acl, acl_list);
149 	LIST_REMOVE(acl, acl_hash);
150 	IEEE80211_FREE(acl, M_80211_ACL);
151 	as->as_nacls--;
152 }
153 
154 static int
155 acl_check(struct ieee80211vap *vap, const struct ieee80211_frame *wh)
156 {
157 	struct aclstate *as = vap->iv_as;
158 
159 	switch (as->as_policy) {
160 	case ACL_POLICY_OPEN:
161 	case ACL_POLICY_RADIUS:
162 		return 1;
163 	case ACL_POLICY_ALLOW:
164 		return _find_acl(as, wh->i_addr2) != NULL;
165 	case ACL_POLICY_DENY:
166 		return _find_acl(as, wh->i_addr2) == NULL;
167 	}
168 	return 0;		/* should not happen */
169 }
170 
171 static int
172 acl_add(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN])
173 {
174 	struct aclstate *as = vap->iv_as;
175 	struct acl *acl, *new;
176 	int hash;
177 
178 	new = (struct acl *) IEEE80211_MALLOC(sizeof(struct acl),
179 	    M_80211_ACL, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO);
180 	if (new == NULL) {
181 		IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
182 			"ACL: add %s failed, no memory\n", ether_sprintf(mac));
183 		/* XXX statistic */
184 		return ENOMEM;
185 	}
186 
187 	ACL_LOCK(as);
188 	hash = ACL_HASH(mac);
189 	LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) {
190 		if (IEEE80211_ADDR_EQ(acl->acl_macaddr, mac)) {
191 			ACL_UNLOCK(as);
192 			IEEE80211_FREE(new, M_80211_ACL);
193 			IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
194 				"ACL: add %s failed, already present\n",
195 				ether_sprintf(mac));
196 			return EEXIST;
197 		}
198 	}
199 	IEEE80211_ADDR_COPY(new->acl_macaddr, mac);
200 	TAILQ_INSERT_TAIL(&as->as_list, new, acl_list);
201 	LIST_INSERT_HEAD(&as->as_hash[hash], new, acl_hash);
202 	as->as_nacls++;
203 	ACL_UNLOCK(as);
204 
205 	IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
206 		"ACL: add %s\n", ether_sprintf(mac));
207 	return 0;
208 }
209 
210 static int
211 acl_remove(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN])
212 {
213 	struct aclstate *as = vap->iv_as;
214 	struct acl *acl;
215 
216 	ACL_LOCK(as);
217 	acl = _find_acl(as, mac);
218 	if (acl != NULL)
219 		_acl_free(as, acl);
220 	ACL_UNLOCK(as);
221 
222 	IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
223 		"ACL: remove %s%s\n", ether_sprintf(mac),
224 		acl == NULL ? ", not present" : "");
225 
226 	return (acl == NULL ? ENOENT : 0);
227 }
228 
229 static int
230 acl_free_all(struct ieee80211vap *vap)
231 {
232 	struct aclstate *as = vap->iv_as;
233 	struct acl *acl;
234 
235 	IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL, "ACL: %s\n", "free all");
236 
237 	ACL_LOCK(as);
238 	while ((acl = TAILQ_FIRST(&as->as_list)) != NULL)
239 		_acl_free(as, acl);
240 	ACL_UNLOCK(as);
241 
242 	return 0;
243 }
244 
245 static int
246 acl_setpolicy(struct ieee80211vap *vap, int policy)
247 {
248 	struct aclstate *as = vap->iv_as;
249 
250 	IEEE80211_DPRINTF(vap, IEEE80211_MSG_ACL,
251 		"ACL: set policy to %u\n", policy);
252 
253 	switch (policy) {
254 	case IEEE80211_MACCMD_POLICY_OPEN:
255 		as->as_policy = ACL_POLICY_OPEN;
256 		break;
257 	case IEEE80211_MACCMD_POLICY_ALLOW:
258 		as->as_policy = ACL_POLICY_ALLOW;
259 		break;
260 	case IEEE80211_MACCMD_POLICY_DENY:
261 		as->as_policy = ACL_POLICY_DENY;
262 		break;
263 	case IEEE80211_MACCMD_POLICY_RADIUS:
264 		as->as_policy = ACL_POLICY_RADIUS;
265 		break;
266 	default:
267 		return EINVAL;
268 	}
269 	return 0;
270 }
271 
272 static int
273 acl_getpolicy(struct ieee80211vap *vap)
274 {
275 	struct aclstate *as = vap->iv_as;
276 
277 	return as->as_policy;
278 }
279 
280 static int
281 acl_setioctl(struct ieee80211vap *vap, struct ieee80211req *ireq)
282 {
283 
284 	return EINVAL;
285 }
286 
287 static int
288 acl_getioctl(struct ieee80211vap *vap, struct ieee80211req *ireq)
289 {
290 	struct aclstate *as = vap->iv_as;
291 	struct acl *acl;
292 	struct ieee80211req_maclist *ap;
293 	int error;
294 	uint32_t i, space;
295 
296 	switch (ireq->i_val) {
297 	case IEEE80211_MACCMD_POLICY:
298 		ireq->i_val = as->as_policy;
299 		return 0;
300 	case IEEE80211_MACCMD_LIST:
301 		space = as->as_nacls * IEEE80211_ADDR_LEN;
302 		if (ireq->i_len == 0) {
303 			ireq->i_len = space;	/* return required space */
304 			return 0;		/* NB: must not error */
305 		}
306 		ap = (struct ieee80211req_maclist *) IEEE80211_MALLOC(space,
307 		    M_TEMP, IEEE80211_M_NOWAIT);
308 		if (ap == NULL)
309 			return ENOMEM;
310 		i = 0;
311 		ACL_LOCK(as);
312 		TAILQ_FOREACH(acl, &as->as_list, acl_list) {
313 			IEEE80211_ADDR_COPY(ap[i].ml_macaddr, acl->acl_macaddr);
314 			i++;
315 		}
316 		ACL_UNLOCK(as);
317 		if (ireq->i_len >= space) {
318 			error = copyout(ap, ireq->i_data, space);
319 			ireq->i_len = space;
320 		} else
321 			error = copyout(ap, ireq->i_data, ireq->i_len);
322 		IEEE80211_FREE(ap, M_TEMP);
323 		return error;
324 	}
325 	return EINVAL;
326 }
327 
328 static const struct ieee80211_aclator mac = {
329 	.iac_name	= "mac",
330 	.iac_attach	= acl_attach,
331 	.iac_detach	= acl_detach,
332 	.iac_check	= acl_check,
333 	.iac_add	= acl_add,
334 	.iac_remove	= acl_remove,
335 	.iac_flush	= acl_free_all,
336 	.iac_setpolicy	= acl_setpolicy,
337 	.iac_getpolicy	= acl_getpolicy,
338 	.iac_setioctl	= acl_setioctl,
339 	.iac_getioctl	= acl_getioctl,
340 };
341 IEEE80211_ACL_MODULE(wlan_acl, mac, 1);
342