xref: /freebsd/sys/netpfil/ipfw/ip_fw_iface.c (revision aa24f48b361effe51163877d84f1b70d32b77e04)
1 /*-
2  * Copyright (c) 2014 Yandex LLC.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23  * SUCH DAMAGE.
24  */
25 
26 #include <sys/cdefs.h>
27 __FBSDID("$FreeBSD$");
28 
29 /*
30  * Kernel interface tracking API.
31  *
32  */
33 
34 #include "opt_ipfw.h"
35 #include "opt_inet.h"
36 #ifndef INET
37 #error IPFIREWALL requires INET.
38 #endif /* INET */
39 #include "opt_inet6.h"
40 
41 #include <sys/param.h>
42 #include <sys/systm.h>
43 #include <sys/malloc.h>
44 #include <sys/kernel.h>
45 #include <sys/lock.h>
46 #include <sys/rwlock.h>
47 #include <sys/rmlock.h>
48 #include <sys/socket.h>
49 #include <sys/queue.h>
50 #include <sys/eventhandler.h>
51 #include <net/if.h>
52 #include <net/if_var.h>
53 #include <net/pfil.h>
54 #include <net/vnet.h>
55 
56 #include <netinet/in.h>
57 #include <netinet/ip_var.h>	/* struct ipfw_rule_ref */
58 #include <netinet/ip_fw.h>
59 
60 #include <netpfil/ipfw/ip_fw_private.h>
61 
62 #define	CHAIN_TO_II(ch)		((struct namedobj_instance *)ch->ifcfg)
63 
64 #define	DEFAULT_IFACES	128
65 
66 static void handle_ifdetach(struct ip_fw_chain *ch, struct ipfw_iface *iif,
67     uint16_t ifindex);
68 static void handle_ifattach(struct ip_fw_chain *ch, struct ipfw_iface *iif,
69     uint16_t ifindex);
70 static int list_ifaces(struct ip_fw_chain *ch, ip_fw3_opheader *op3,
71     struct sockopt_data *sd);
72 
73 static struct ipfw_sopt_handler	scodes[] = {
74 	{ IP_FW_XIFLIST,	0,	HDIR_GET,	list_ifaces },
75 };
76 
77 /*
78  * FreeBSD Kernel interface.
79  */
80 static void ipfw_kifhandler(void *arg, struct ifnet *ifp);
81 static int ipfw_kiflookup(char *name);
82 static void iface_khandler_register(void);
83 static void iface_khandler_deregister(void);
84 
85 static eventhandler_tag ipfw_ifdetach_event, ipfw_ifattach_event;
86 static int num_vnets = 0;
87 static struct mtx vnet_mtx;
88 
89 /*
90  * Checks if kernel interface is contained in our tracked
91  * interface list and calls attach/detach handler.
92  */
93 static void
94 ipfw_kifhandler(void *arg, struct ifnet *ifp)
95 {
96 	struct ip_fw_chain *ch;
97 	struct ipfw_iface *iif;
98 	struct namedobj_instance *ii;
99 	uintptr_t htype;
100 
101 	if (V_ipfw_vnet_ready == 0)
102 		return;
103 
104 	ch = &V_layer3_chain;
105 	htype = (uintptr_t)arg;
106 
107 	IPFW_UH_WLOCK(ch);
108 	ii = CHAIN_TO_II(ch);
109 	if (ii == NULL) {
110 		IPFW_UH_WUNLOCK(ch);
111 		return;
112 	}
113 	iif = (struct ipfw_iface*)ipfw_objhash_lookup_name(ii, 0,
114 	    if_name(ifp));
115 	if (iif != NULL) {
116 		if (htype == 1)
117 			handle_ifattach(ch, iif, ifp->if_index);
118 		else
119 			handle_ifdetach(ch, iif, ifp->if_index);
120 	}
121 	IPFW_UH_WUNLOCK(ch);
122 }
123 
124 /*
125  * Reference current VNET as iface tracking API user.
126  * Registers interface tracking handlers for first VNET.
127  */
128 static void
129 iface_khandler_register()
130 {
131 	int create;
132 
133 	create = 0;
134 
135 	mtx_lock(&vnet_mtx);
136 	if (num_vnets == 0)
137 		create = 1;
138 	num_vnets++;
139 	mtx_unlock(&vnet_mtx);
140 
141 	if (create == 0)
142 		return;
143 
144 	printf("IPFW: starting up interface tracker\n");
145 
146 	ipfw_ifdetach_event = EVENTHANDLER_REGISTER(
147 	    ifnet_departure_event, ipfw_kifhandler, NULL,
148 	    EVENTHANDLER_PRI_ANY);
149 	ipfw_ifattach_event = EVENTHANDLER_REGISTER(
150 	    ifnet_arrival_event, ipfw_kifhandler, (void*)((uintptr_t)1),
151 	    EVENTHANDLER_PRI_ANY);
152 }
153 
154 /*
155  *
156  * Detach interface event handlers on last VNET instance
157  * detach.
158  */
159 static void
160 iface_khandler_deregister()
161 {
162 	int destroy;
163 
164 	destroy = 0;
165 	mtx_lock(&vnet_mtx);
166 	if (num_vnets == 1)
167 		destroy = 1;
168 	num_vnets--;
169 	mtx_unlock(&vnet_mtx);
170 
171 	if (destroy == 0)
172 		return;
173 
174 	EVENTHANDLER_DEREGISTER(ifnet_arrival_event,
175 	    ipfw_ifattach_event);
176 	EVENTHANDLER_DEREGISTER(ifnet_departure_event,
177 	    ipfw_ifdetach_event);
178 }
179 
180 /*
181  * Retrieves ifindex for given @name.
182  *
183  * Returns ifindex or 0.
184  */
185 static int
186 ipfw_kiflookup(char *name)
187 {
188 	struct ifnet *ifp;
189 	int ifindex;
190 
191 	ifindex = 0;
192 
193 	if ((ifp = ifunit_ref(name)) != NULL) {
194 		ifindex = ifp->if_index;
195 		if_rele(ifp);
196 	}
197 
198 	return (ifindex);
199 }
200 
201 /*
202  * Global ipfw startup hook.
203  * Since we perform lazy initialization, do nothing except
204  * mutex init.
205  */
206 int
207 ipfw_iface_init()
208 {
209 
210 	mtx_init(&vnet_mtx, "IPFW ifhandler mtx", NULL, MTX_DEF);
211 	IPFW_ADD_SOPT_HANDLER(1, scodes);
212 	return (0);
213 }
214 
215 /*
216  * Global ipfw destroy hook.
217  * Unregister khandlers iff init has been done.
218  */
219 void
220 ipfw_iface_destroy()
221 {
222 
223 	IPFW_DEL_SOPT_HANDLER(1, scodes);
224 	mtx_destroy(&vnet_mtx);
225 }
226 
227 /*
228  * Perform actual init on internal request.
229  * Inits both namehash and global khandler.
230  */
231 static void
232 vnet_ipfw_iface_init(struct ip_fw_chain *ch)
233 {
234 	struct namedobj_instance *ii;
235 
236 	ii = ipfw_objhash_create(DEFAULT_IFACES);
237 	IPFW_UH_WLOCK(ch);
238 	if (ch->ifcfg == NULL) {
239 		ch->ifcfg = ii;
240 		ii = NULL;
241 	}
242 	IPFW_UH_WUNLOCK(ch);
243 
244 	if (ii != NULL) {
245 		/* Already initialized. Free namehash. */
246 		ipfw_objhash_destroy(ii);
247 	} else {
248 		/* We're the first ones. Init kernel hooks. */
249 		iface_khandler_register();
250 	}
251 }
252 
253 static int
254 destroy_iface(struct namedobj_instance *ii, struct named_object *no,
255     void *arg)
256 {
257 
258 	/* Assume all consumers have been already detached */
259 	free(no, M_IPFW);
260 	return (0);
261 }
262 
263 /*
264  * Per-VNET ipfw detach hook.
265  *
266  */
267 void
268 vnet_ipfw_iface_destroy(struct ip_fw_chain *ch)
269 {
270 	struct namedobj_instance *ii;
271 
272 	IPFW_UH_WLOCK(ch);
273 	ii = CHAIN_TO_II(ch);
274 	ch->ifcfg = NULL;
275 	IPFW_UH_WUNLOCK(ch);
276 
277 	if (ii != NULL) {
278 		ipfw_objhash_foreach(ii, destroy_iface, ch);
279 		ipfw_objhash_destroy(ii);
280 		iface_khandler_deregister();
281 	}
282 }
283 
284 /*
285  * Notify the subsystem that we are interested in tracking
286  * interface @name. This function has to be called without
287  * holding any locks to permit allocating the necessary states
288  * for proper interface tracking.
289  *
290  * Returns 0 on success.
291  */
292 int
293 ipfw_iface_ref(struct ip_fw_chain *ch, char *name,
294     struct ipfw_ifc *ic)
295 {
296 	struct namedobj_instance *ii;
297 	struct ipfw_iface *iif, *tmp;
298 
299 	if (strlen(name) >= sizeof(iif->ifname))
300 		return (EINVAL);
301 
302 	IPFW_UH_WLOCK(ch);
303 
304 	ii = CHAIN_TO_II(ch);
305 	if (ii == NULL) {
306 
307 		/*
308 		 * First request to subsystem.
309 		 * Let's perform init.
310 		 */
311 		IPFW_UH_WUNLOCK(ch);
312 		vnet_ipfw_iface_init(ch);
313 		IPFW_UH_WLOCK(ch);
314 		ii = CHAIN_TO_II(ch);
315 	}
316 
317 	iif = (struct ipfw_iface *)ipfw_objhash_lookup_name(ii, 0, name);
318 
319 	if (iif != NULL) {
320 		iif->no.refcnt++;
321 		ic->iface = iif;
322 		IPFW_UH_WUNLOCK(ch);
323 		return (0);
324 	}
325 
326 	IPFW_UH_WUNLOCK(ch);
327 
328 	/* Not found. Let's create one */
329 	iif = malloc(sizeof(struct ipfw_iface), M_IPFW, M_WAITOK | M_ZERO);
330 	TAILQ_INIT(&iif->consumers);
331 	iif->no.name = iif->ifname;
332 	strlcpy(iif->ifname, name, sizeof(iif->ifname));
333 
334 	/*
335 	 * Ref & link to the list.
336 	 *
337 	 * We assume  ifnet_arrival_event / ifnet_departure_event
338 	 * are not holding any locks.
339 	 */
340 	iif->no.refcnt = 1;
341 	IPFW_UH_WLOCK(ch);
342 
343 	tmp = (struct ipfw_iface *)ipfw_objhash_lookup_name(ii, 0, name);
344 	if (tmp != NULL) {
345 		/* Interface has been created since unlock. Ref and return */
346 		tmp->no.refcnt++;
347 		ic->iface = tmp;
348 		IPFW_UH_WUNLOCK(ch);
349 		free(iif, M_IPFW);
350 		return (0);
351 	}
352 
353 	iif->ifindex = ipfw_kiflookup(name);
354 	if (iif->ifindex != 0)
355 		iif->resolved = 1;
356 
357 	ipfw_objhash_add(ii, &iif->no);
358 	ic->iface = iif;
359 
360 	IPFW_UH_WUNLOCK(ch);
361 
362 	return (0);
363 }
364 
365 /*
366  * Adds @ic to the list of iif interface consumers.
367  * Must be called with holding both UH+WLOCK.
368  * Callback may be immediately called (if interface exists).
369  */
370 void
371 ipfw_iface_add_notify(struct ip_fw_chain *ch, struct ipfw_ifc *ic)
372 {
373 	struct ipfw_iface *iif;
374 
375 	IPFW_UH_WLOCK_ASSERT(ch);
376 	IPFW_WLOCK_ASSERT(ch);
377 
378 	iif = ic->iface;
379 
380 	TAILQ_INSERT_TAIL(&iif->consumers, ic, next);
381 	if (iif->resolved != 0)
382 		ic->cb(ch, ic->cbdata, iif->ifindex);
383 }
384 
385 /*
386  * Unlinks interface tracker object @ic from interface.
387  * Must be called while holding UH lock.
388  */
389 void
390 ipfw_iface_del_notify(struct ip_fw_chain *ch, struct ipfw_ifc *ic)
391 {
392 	struct ipfw_iface *iif;
393 
394 	IPFW_UH_WLOCK_ASSERT(ch);
395 
396 	iif = ic->iface;
397 	TAILQ_REMOVE(&iif->consumers, ic, next);
398 }
399 
400 /*
401  * Unreference interface specified by @ic.
402  * Must be called while holding UH lock.
403  */
404 void
405 ipfw_iface_unref(struct ip_fw_chain *ch, struct ipfw_ifc *ic)
406 {
407 	struct ipfw_iface *iif;
408 
409 	IPFW_UH_WLOCK_ASSERT(ch);
410 
411 	iif = ic->iface;
412 	ic->iface = NULL;
413 
414 	iif->no.refcnt--;
415 	/* TODO: check for references & delete */
416 }
417 
418 /*
419  * Interface arrival handler.
420  */
421 static void
422 handle_ifattach(struct ip_fw_chain *ch, struct ipfw_iface *iif,
423     uint16_t ifindex)
424 {
425 	struct ipfw_ifc *ic;
426 
427 	IPFW_UH_WLOCK_ASSERT(ch);
428 
429 	iif->gencnt++;
430 	iif->resolved = 1;
431 	iif->ifindex = ifindex;
432 
433 	IPFW_WLOCK(ch);
434 	TAILQ_FOREACH(ic, &iif->consumers, next)
435 		ic->cb(ch, ic->cbdata, iif->ifindex);
436 	IPFW_WUNLOCK(ch);
437 }
438 
439 /*
440  * Interface departure handler.
441  */
442 static void
443 handle_ifdetach(struct ip_fw_chain *ch, struct ipfw_iface *iif,
444     uint16_t ifindex)
445 {
446 	struct ipfw_ifc *ic;
447 
448 	IPFW_UH_WLOCK_ASSERT(ch);
449 
450 	IPFW_WLOCK(ch);
451 	TAILQ_FOREACH(ic, &iif->consumers, next)
452 		ic->cb(ch, ic->cbdata, 0);
453 	IPFW_WUNLOCK(ch);
454 
455 	iif->gencnt++;
456 	iif->resolved = 0;
457 	iif->ifindex = 0;
458 }
459 
460 struct dump_iface_args {
461 	struct ip_fw_chain *ch;
462 	struct sockopt_data *sd;
463 };
464 
465 static int
466 export_iface_internal(struct namedobj_instance *ii, struct named_object *no,
467     void *arg)
468 {
469 	ipfw_iface_info *i;
470 	struct dump_iface_args *da;
471 	struct ipfw_iface *iif;
472 
473 	da = (struct dump_iface_args *)arg;
474 
475 	i = (ipfw_iface_info *)ipfw_get_sopt_space(da->sd, sizeof(*i));
476 	KASSERT(i != NULL, ("previously checked buffer is not enough"));
477 
478 	iif = (struct ipfw_iface *)no;
479 
480 	strlcpy(i->ifname, iif->ifname, sizeof(i->ifname));
481 	if (iif->resolved)
482 		i->flags |= IPFW_IFFLAG_RESOLVED;
483 	i->ifindex = iif->ifindex;
484 	i->refcnt = iif->no.refcnt;
485 	i->gencnt = iif->gencnt;
486 	return (0);
487 }
488 
489 /*
490  * Lists all interface currently tracked by ipfw.
491  * Data layout (v0)(current):
492  * Request: [ ipfw_obj_lheader ], size = ipfw_obj_lheader.size
493  * Reply: [ ipfw_obj_lheader ipfw_iface_info x N ]
494  *
495  * Returns 0 on success
496  */
497 static int
498 list_ifaces(struct ip_fw_chain *ch, ip_fw3_opheader *op3,
499     struct sockopt_data *sd)
500 {
501 	struct namedobj_instance *ii;
502 	struct _ipfw_obj_lheader *olh;
503 	struct dump_iface_args da;
504 	uint32_t count, size;
505 
506 	olh = (struct _ipfw_obj_lheader *)ipfw_get_sopt_header(sd,sizeof(*olh));
507 	if (olh == NULL)
508 		return (EINVAL);
509 	if (sd->valsize < olh->size)
510 		return (EINVAL);
511 
512 	IPFW_UH_RLOCK(ch);
513 	ii = CHAIN_TO_II(ch);
514 	if (ii != NULL)
515 		count = ipfw_objhash_count(ii);
516 	else
517 		count = 0;
518 	size = count * sizeof(ipfw_iface_info) + sizeof(ipfw_obj_lheader);
519 
520 	/* Fill in header regadless of buffer size */
521 	olh->count = count;
522 	olh->objsize = sizeof(ipfw_iface_info);
523 
524 	if (size > olh->size) {
525 		olh->size = size;
526 		IPFW_UH_RUNLOCK(ch);
527 		return (ENOMEM);
528 	}
529 	olh->size = size;
530 
531 	da.ch = ch;
532 	da.sd = sd;
533 
534 	if (ii != NULL)
535 		ipfw_objhash_foreach(ii, export_iface_internal, &da);
536 	IPFW_UH_RUNLOCK(ch);
537 
538 	return (0);
539 }
540 
541