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