xref: /freebsd/sys/net80211/ieee80211_acl.c (revision 1e413cf93298b5b97441a21d9a50fdcd0ee9945e)
1 /*-
2  * Copyright (c) 2004-2007 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 received
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 <sys/param.h>
41 #include <sys/kernel.h>
42 #include <sys/systm.h>
43 #include <sys/mbuf.h>
44 #include <sys/module.h>
45 #include <sys/queue.h>
46 
47 #include <sys/socket.h>
48 
49 #include <net/if.h>
50 #include <net/if_media.h>
51 #include <net/ethernet.h>
52 #include <net/route.h>
53 
54 #include <net80211/ieee80211_var.h>
55 
56 enum {
57 	ACL_POLICY_OPEN		= 0,	/* open, don't check ACL's */
58 	ACL_POLICY_ALLOW	= 1,	/* allow traffic from MAC */
59 	ACL_POLICY_DENY		= 2,	/* deny traffic from MAC */
60 };
61 
62 #define	ACL_HASHSIZE	32
63 
64 struct acl {
65 	TAILQ_ENTRY(acl)	acl_list;
66 	LIST_ENTRY(acl)		acl_hash;
67 	uint8_t			acl_macaddr[IEEE80211_ADDR_LEN];
68 };
69 struct aclstate {
70 	acl_lock_t		as_lock;
71 	int			as_policy;
72 	int			as_nacls;
73 	TAILQ_HEAD(, acl)	as_list;	/* list of all ACL's */
74 	LIST_HEAD(, acl)	as_hash[ACL_HASHSIZE];
75 	struct ieee80211com	*as_ic;
76 };
77 
78 /* simple hash is enough for variation of macaddr */
79 #define	ACL_HASH(addr)	\
80 	(((const uint8_t *)(addr))[IEEE80211_ADDR_LEN - 1] % ACL_HASHSIZE)
81 
82 MALLOC_DEFINE(M_80211_ACL, "acl", "802.11 station acl");
83 
84 static	int acl_free_all(struct ieee80211com *);
85 
86 static int
87 acl_attach(struct ieee80211com *ic)
88 {
89 	struct aclstate *as;
90 
91 	MALLOC(as, struct aclstate *, sizeof(struct aclstate),
92 		M_80211_ACL, M_NOWAIT | M_ZERO);
93 	if (as == NULL)
94 		return 0;
95 	ACL_LOCK_INIT(as, "acl");
96 	TAILQ_INIT(&as->as_list);
97 	as->as_policy = ACL_POLICY_OPEN;
98 	as->as_ic = ic;
99 	ic->ic_as = as;
100 	return 1;
101 }
102 
103 static void
104 acl_detach(struct ieee80211com *ic)
105 {
106 	struct aclstate *as = ic->ic_as;
107 
108 	acl_free_all(ic);
109 	ic->ic_as = NULL;
110 	ACL_LOCK_DESTROY(as);
111 	FREE(as, M_DEVBUF);
112 }
113 
114 static __inline struct acl *
115 _find_acl(struct aclstate *as, const uint8_t *macaddr)
116 {
117 	struct acl *acl;
118 	int hash;
119 
120 	hash = ACL_HASH(macaddr);
121 	LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) {
122 		if (IEEE80211_ADDR_EQ(acl->acl_macaddr, macaddr))
123 			return acl;
124 	}
125 	return NULL;
126 }
127 
128 static void
129 _acl_free(struct aclstate *as, struct acl *acl)
130 {
131 	ACL_LOCK_ASSERT(as);
132 
133 	TAILQ_REMOVE(&as->as_list, acl, acl_list);
134 	LIST_REMOVE(acl, acl_hash);
135 	FREE(acl, M_80211_ACL);
136 	as->as_nacls--;
137 }
138 
139 static int
140 acl_check(struct ieee80211com *ic, const uint8_t mac[IEEE80211_ADDR_LEN])
141 {
142 	struct aclstate *as = ic->ic_as;
143 
144 	switch (as->as_policy) {
145 	case ACL_POLICY_OPEN:
146 		return 1;
147 	case ACL_POLICY_ALLOW:
148 		return _find_acl(as, mac) != NULL;
149 	case ACL_POLICY_DENY:
150 		return _find_acl(as, mac) == NULL;
151 	}
152 	return 0;		/* should not happen */
153 }
154 
155 static int
156 acl_add(struct ieee80211com *ic, const uint8_t mac[IEEE80211_ADDR_LEN])
157 {
158 	struct aclstate *as = ic->ic_as;
159 	struct acl *acl, *new;
160 	int hash;
161 
162 	MALLOC(new, struct acl *, sizeof(struct acl), M_80211_ACL, M_NOWAIT | M_ZERO);
163 	if (new == NULL) {
164 		IEEE80211_DPRINTF(ic, IEEE80211_MSG_ACL,
165 			"ACL: add %s failed, no memory\n", ether_sprintf(mac));
166 		/* XXX statistic */
167 		return ENOMEM;
168 	}
169 
170 	ACL_LOCK(as);
171 	hash = ACL_HASH(mac);
172 	LIST_FOREACH(acl, &as->as_hash[hash], acl_hash) {
173 		if (IEEE80211_ADDR_EQ(acl->acl_macaddr, mac)) {
174 			ACL_UNLOCK(as);
175 			FREE(new, M_80211_ACL);
176 			IEEE80211_DPRINTF(ic, IEEE80211_MSG_ACL,
177 				"ACL: add %s failed, already present\n",
178 				ether_sprintf(mac));
179 			return EEXIST;
180 		}
181 	}
182 	IEEE80211_ADDR_COPY(new->acl_macaddr, mac);
183 	TAILQ_INSERT_TAIL(&as->as_list, new, acl_list);
184 	LIST_INSERT_HEAD(&as->as_hash[hash], new, acl_hash);
185 	as->as_nacls++;
186 	ACL_UNLOCK(as);
187 
188 	IEEE80211_DPRINTF(ic, IEEE80211_MSG_ACL,
189 		"ACL: add %s\n", ether_sprintf(mac));
190 	return 0;
191 }
192 
193 static int
194 acl_remove(struct ieee80211com *ic, const uint8_t mac[IEEE80211_ADDR_LEN])
195 {
196 	struct aclstate *as = ic->ic_as;
197 	struct acl *acl;
198 
199 	ACL_LOCK(as);
200 	acl = _find_acl(as, mac);
201 	if (acl != NULL)
202 		_acl_free(as, acl);
203 	ACL_UNLOCK(as);
204 
205 	IEEE80211_DPRINTF(ic, IEEE80211_MSG_ACL,
206 		"ACL: remove %s%s\n", ether_sprintf(mac),
207 		acl == NULL ? ", not present" : "");
208 
209 	return (acl == NULL ? ENOENT : 0);
210 }
211 
212 static int
213 acl_free_all(struct ieee80211com *ic)
214 {
215 	struct aclstate *as = ic->ic_as;
216 	struct acl *acl;
217 
218 	IEEE80211_DPRINTF(ic, IEEE80211_MSG_ACL, "ACL: %s\n", "free all");
219 
220 	ACL_LOCK(as);
221 	while ((acl = TAILQ_FIRST(&as->as_list)) != NULL)
222 		_acl_free(as, acl);
223 	ACL_UNLOCK(as);
224 
225 	return 0;
226 }
227 
228 static int
229 acl_setpolicy(struct ieee80211com *ic, int policy)
230 {
231 	struct aclstate *as = ic->ic_as;
232 
233 	IEEE80211_DPRINTF(ic, IEEE80211_MSG_ACL,
234 		"ACL: set policy to %u\n", policy);
235 
236 	switch (policy) {
237 	case IEEE80211_MACCMD_POLICY_OPEN:
238 		as->as_policy = ACL_POLICY_OPEN;
239 		break;
240 	case IEEE80211_MACCMD_POLICY_ALLOW:
241 		as->as_policy = ACL_POLICY_ALLOW;
242 		break;
243 	case IEEE80211_MACCMD_POLICY_DENY:
244 		as->as_policy = ACL_POLICY_DENY;
245 		break;
246 	default:
247 		return EINVAL;
248 	}
249 	return 0;
250 }
251 
252 static int
253 acl_getpolicy(struct ieee80211com *ic)
254 {
255 	struct aclstate *as = ic->ic_as;
256 
257 	return as->as_policy;
258 }
259 
260 static int
261 acl_setioctl(struct ieee80211com *ic, struct ieee80211req *ireq)
262 {
263 
264 	return EINVAL;
265 }
266 
267 static int
268 acl_getioctl(struct ieee80211com *ic, struct ieee80211req *ireq)
269 {
270 	struct aclstate *as = ic->ic_as;
271 	struct acl *acl;
272 	struct ieee80211req_maclist *ap;
273 	int error, space, i;
274 
275 	switch (ireq->i_val) {
276 	case IEEE80211_MACCMD_POLICY:
277 		ireq->i_val = as->as_policy;
278 		return 0;
279 	case IEEE80211_MACCMD_LIST:
280 		space = as->as_nacls * IEEE80211_ADDR_LEN;
281 		if (ireq->i_len == 0) {
282 			ireq->i_len = space;	/* return required space */
283 			return 0;		/* NB: must not error */
284 		}
285 		MALLOC(ap, struct ieee80211req_maclist *, space,
286 			M_TEMP, M_NOWAIT);
287 		if (ap == NULL)
288 			return ENOMEM;
289 		i = 0;
290 		ACL_LOCK(as);
291 		TAILQ_FOREACH(acl, &as->as_list, acl_list) {
292 			IEEE80211_ADDR_COPY(ap[i].ml_macaddr, acl->acl_macaddr);
293 			i++;
294 		}
295 		ACL_UNLOCK(as);
296 		if (ireq->i_len >= space) {
297 			error = copyout(ap, ireq->i_data, space);
298 			ireq->i_len = space;
299 		} else
300 			error = copyout(ap, ireq->i_data, ireq->i_len);
301 		FREE(ap, M_TEMP);
302 		return error;
303 	}
304 	return EINVAL;
305 }
306 
307 static const struct ieee80211_aclator mac = {
308 	.iac_name	= "mac",
309 	.iac_attach	= acl_attach,
310 	.iac_detach	= acl_detach,
311 	.iac_check	= acl_check,
312 	.iac_add	= acl_add,
313 	.iac_remove	= acl_remove,
314 	.iac_flush	= acl_free_all,
315 	.iac_setpolicy	= acl_setpolicy,
316 	.iac_getpolicy	= acl_getpolicy,
317 	.iac_setioctl	= acl_setioctl,
318 	.iac_getioctl	= acl_getioctl,
319 };
320 
321 /*
322  * Module glue.
323  */
324 static int
325 wlan_acl_modevent(module_t mod, int type, void *unused)
326 {
327 	switch (type) {
328 	case MOD_LOAD:
329 		if (bootverbose)
330 			printf("wlan: <802.11 MAC ACL support>\n");
331 		ieee80211_aclator_register(&mac);
332 		return 0;
333 	case MOD_UNLOAD:
334 		ieee80211_aclator_unregister(&mac);
335 		return 0;
336 	}
337 	return EINVAL;
338 }
339 
340 static moduledata_t wlan_acl_mod = {
341 	"wlan_acl",
342 	wlan_acl_modevent,
343 	0
344 };
345 DECLARE_MODULE(wlan_acl, wlan_acl_mod, SI_SUB_DRIVERS, SI_ORDER_FIRST);
346 MODULE_VERSION(wlan_acl, 1);
347 MODULE_DEPEND(wlan_acl, wlan, 1, 1, 1);
348