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
65static void handle_ifdetach(struct ip_fw_chain *ch, struct ipfw_iface *iif,
66    uint16_t ifindex);
67static void handle_ifattach(struct ip_fw_chain *ch, struct ipfw_iface *iif,
68    uint16_t ifindex);
69static int list_ifaces(struct ip_fw_chain *ch, ip_fw3_opheader *op3,
70    struct sockopt_data *sd);
71
72static struct ipfw_sopt_handler	scodes[] = {
73	{ IP_FW_XIFLIST,	0,	HDIR_GET,	list_ifaces },
74};
75
76/*
77 * FreeBSD Kernel interface.
78 */
79static void ipfw_kifhandler(void *arg, struct ifnet *ifp);
80static int ipfw_kiflookup(char *name);
81static void iface_khandler_register(void);
82static void iface_khandler_deregister(void);
83
84static eventhandler_tag ipfw_ifdetach_event, ipfw_ifattach_event;
85static int num_vnets = 0;
86static 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 */
92static void
93ipfw_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 */
127static void
128iface_khandler_register()
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 */
158static void
159iface_khandler_deregister()
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 */
184static int
185ipfw_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 */
205int
206ipfw_iface_init()
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 */
218void
219ipfw_iface_destroy()
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 */
230static void
231vnet_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
252static int
253destroy_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 */
266void
267vnet_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 */
291int
292ipfw_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 */
368void
369ipfw_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 */
387void
388ipfw_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 */
402void
403ipfw_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 */
419static void
420handle_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 */
440static void
441handle_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
458struct dump_iface_args {
459	struct ip_fw_chain *ch;
460	struct sockopt_data *sd;
461};
462
463static int
464export_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 */
495static int
496list_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