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