xref: /freebsd/sys/netgraph/ng_etf.c (revision 95ee2897e98f5d444f26ed2334cc7c439f9c16c6)
1 /*-
2  * ng_etf.c  Ethertype filter
3  */
4 
5 /*-
6  * SPDX-License-Identifier: BSD-2-Clause
7  *
8  * Copyright (c) 2001, FreeBSD Incorporated
9  * All rights reserved.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice unmodified, this list of conditions, and the following
16  *    disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  * Author: Julian Elischer <julian@freebsd.org>
34  */
35 
36 #include <sys/param.h>
37 #include <sys/systm.h>
38 #include <sys/kernel.h>
39 #include <sys/mbuf.h>
40 #include <sys/malloc.h>
41 #include <sys/ctype.h>
42 #include <sys/errno.h>
43 #include <sys/queue.h>
44 #include <sys/syslog.h>
45 
46 #include <net/ethernet.h>
47 
48 #include <netgraph/ng_message.h>
49 #include <netgraph/ng_parse.h>
50 #include <netgraph/ng_etf.h>
51 #include <netgraph/netgraph.h>
52 
53 /* If you do complicated mallocs you may want to do this */
54 /* and use it for your mallocs */
55 #ifdef NG_SEPARATE_MALLOC
56 static MALLOC_DEFINE(M_NETGRAPH_ETF, "netgraph_etf", "netgraph etf node ");
57 #else
58 #define M_NETGRAPH_ETF M_NETGRAPH
59 #endif
60 
61 /*
62  * This section contains the netgraph method declarations for the
63  * etf node. These methods define the netgraph 'type'.
64  */
65 
66 static ng_constructor_t	ng_etf_constructor;
67 static ng_rcvmsg_t	ng_etf_rcvmsg;
68 static ng_shutdown_t	ng_etf_shutdown;
69 static ng_newhook_t	ng_etf_newhook;
70 static ng_rcvdata_t	ng_etf_rcvdata;	 /* note these are both ng_rcvdata_t */
71 static ng_disconnect_t	ng_etf_disconnect;
72 
73 /* Parse type for struct ng_etfstat */
74 static const struct ng_parse_struct_field ng_etf_stat_type_fields[]
75 	= NG_ETF_STATS_TYPE_INFO;
76 static const struct ng_parse_type ng_etf_stat_type = {
77 	&ng_parse_struct_type,
78 	&ng_etf_stat_type_fields
79 };
80 /* Parse type for struct ng_setfilter */
81 static const struct ng_parse_struct_field ng_etf_filter_type_fields[]
82 	= NG_ETF_FILTER_TYPE_INFO;
83 static const struct ng_parse_type ng_etf_filter_type = {
84 	&ng_parse_struct_type,
85 	&ng_etf_filter_type_fields
86 };
87 
88 /* List of commands and how to convert arguments to/from ASCII */
89 static const struct ng_cmdlist ng_etf_cmdlist[] = {
90 	{
91 	  NGM_ETF_COOKIE,
92 	  NGM_ETF_GET_STATUS,
93 	  "getstatus",
94 	  NULL,
95 	  &ng_etf_stat_type,
96 	},
97 	{
98 	  NGM_ETF_COOKIE,
99 	  NGM_ETF_SET_FLAG,
100 	  "setflag",
101 	  &ng_parse_int32_type,
102 	  NULL
103 	},
104 	{
105 	  NGM_ETF_COOKIE,
106 	  NGM_ETF_SET_FILTER,
107 	  "setfilter",
108 	  &ng_etf_filter_type,
109 	  NULL
110 	},
111 	{ 0 }
112 };
113 
114 /* Netgraph node type descriptor */
115 static struct ng_type typestruct = {
116 	.version =	NG_ABI_VERSION,
117 	.name =		NG_ETF_NODE_TYPE,
118 	.constructor =	ng_etf_constructor,
119 	.rcvmsg =	ng_etf_rcvmsg,
120 	.shutdown =	ng_etf_shutdown,
121 	.newhook =	ng_etf_newhook,
122 	.rcvdata =	ng_etf_rcvdata,
123 	.disconnect =	ng_etf_disconnect,
124 	.cmdlist =	ng_etf_cmdlist,
125 };
126 NETGRAPH_INIT(etf, &typestruct);
127 
128 /* Information we store for each hook on each node */
129 struct ETF_hookinfo {
130 	hook_p  hook;
131 };
132 
133 struct filter {
134 	LIST_ENTRY(filter) next;
135 	u_int16_t	ethertype;	/* network order ethertype */
136 	hook_p		match_hook;	/* Hook to use on a match */
137 };
138 
139 #define HASHSIZE 16 /* Dont change this without changing HASH() */
140 #define HASH(et) ((((et)>>12)+((et)>>8)+((et)>>4)+(et)) & 0x0f)
141 LIST_HEAD(filterhead, filter);
142 
143 /* Information we store for each node */
144 struct ETF {
145 	struct ETF_hookinfo downstream_hook;
146 	struct ETF_hookinfo nomatch_hook;
147 	node_p		node;		/* back pointer to node */
148 	u_int   	packets_in;	/* packets in from downstream */
149 	u_int   	packets_out;	/* packets out towards downstream */
150 	u_int32_t	flags;
151 	struct filterhead hashtable[HASHSIZE];
152 };
153 typedef struct ETF *etf_p;
154 
155 static struct filter *
ng_etf_findentry(etf_p etfp,u_int16_t ethertype)156 ng_etf_findentry(etf_p etfp, u_int16_t ethertype)
157 {
158 	struct filterhead *chain = etfp->hashtable + HASH(ethertype);
159 	struct filter *fil;
160 
161 	LIST_FOREACH(fil, chain, next) {
162 		if (fil->ethertype == ethertype) {
163 			return (fil);
164 		}
165 	}
166 	return (NULL);
167 }
168 
169 /*
170  * Allocate the private data structure. The generic node has already
171  * been created. Link them together. We arrive with a reference to the node
172  * i.e. the reference count is incremented for us already.
173  */
174 static int
ng_etf_constructor(node_p node)175 ng_etf_constructor(node_p node)
176 {
177 	etf_p privdata;
178 	int i;
179 
180 	/* Initialize private descriptor */
181 	privdata = malloc(sizeof(*privdata), M_NETGRAPH_ETF, M_WAITOK | M_ZERO);
182 	for (i = 0; i < HASHSIZE; i++) {
183 		LIST_INIT((privdata->hashtable + i));
184 	}
185 
186 	/* Link structs together; this counts as our one reference to node */
187 	NG_NODE_SET_PRIVATE(node, privdata);
188 	privdata->node = node;
189 	return (0);
190 }
191 
192 /*
193  * Give our ok for a hook to be added...
194  * All names are ok. Two names are special.
195  */
196 static int
ng_etf_newhook(node_p node,hook_p hook,const char * name)197 ng_etf_newhook(node_p node, hook_p hook, const char *name)
198 {
199 	const etf_p etfp = NG_NODE_PRIVATE(node);
200 	struct ETF_hookinfo *hpriv;
201 
202 	if (strcmp(name, NG_ETF_HOOK_DOWNSTREAM) == 0) {
203 		etfp->downstream_hook.hook = hook;
204 		NG_HOOK_SET_PRIVATE(hook, &etfp->downstream_hook);
205 		etfp->packets_in = 0;
206 		etfp->packets_out = 0;
207 	} else if (strcmp(name, NG_ETF_HOOK_NOMATCH) == 0) {
208 		etfp->nomatch_hook.hook = hook;
209 		NG_HOOK_SET_PRIVATE(hook, &etfp->nomatch_hook);
210 	} else {
211 		/*
212 		 * Any other hook name is valid and can
213 		 * later be associated with a filter rule.
214 		 */
215 		hpriv = malloc(sizeof(*hpriv),
216 			M_NETGRAPH_ETF, M_NOWAIT | M_ZERO);
217 		if (hpriv == NULL) {
218 			return (ENOMEM);
219 		}
220 
221 		NG_HOOK_SET_PRIVATE(hook, hpriv);
222 		hpriv->hook = hook;
223 	}
224 	return(0);
225 }
226 
227 /*
228  * Get a netgraph control message.
229  * We actually receive a queue item that has a pointer to the message.
230  * If we free the item, the message will be freed too, unless we remove
231  * it from the item using NGI_GET_MSG();
232  * The return address is also stored in the item, as an ng_ID_t,
233  * accessible as NGI_RETADDR(item);
234  * Check it is one we understand. If needed, send a response.
235  * We could save the address for an async action later, but don't here.
236  * Always free the message.
237  * The response should be in a malloc'd region that the caller can 'free'.
238  * The NG_MKRESPONSE macro does all this for us.
239  * A response is not required.
240  * Theoretically you could respond defferently to old message types if
241  * the cookie in the header didn't match what we consider to be current
242  * (so that old userland programs could continue to work).
243  */
244 static int
ng_etf_rcvmsg(node_p node,item_p item,hook_p lasthook)245 ng_etf_rcvmsg(node_p node, item_p item, hook_p lasthook)
246 {
247 	const etf_p etfp = NG_NODE_PRIVATE(node);
248 	struct ng_mesg *resp = NULL;
249 	int error = 0;
250 	struct ng_mesg *msg;
251 
252 	NGI_GET_MSG(item, msg);
253 	/* Deal with message according to cookie and command */
254 	switch (msg->header.typecookie) {
255 	case NGM_ETF_COOKIE:
256 		switch (msg->header.cmd) {
257 		case NGM_ETF_GET_STATUS:
258 		    {
259 			struct ng_etfstat *stats;
260 
261 			NG_MKRESPONSE(resp, msg, sizeof(*stats), M_NOWAIT);
262 			if (!resp) {
263 				error = ENOMEM;
264 				break;
265 			}
266 			stats = (struct ng_etfstat *) resp->data;
267 			stats->packets_in = etfp->packets_in;
268 			stats->packets_out = etfp->packets_out;
269 			break;
270 		    }
271 		case NGM_ETF_SET_FLAG:
272 			if (msg->header.arglen != sizeof(u_int32_t)) {
273 				error = EINVAL;
274 				break;
275 			}
276 			etfp->flags = *((u_int32_t *) msg->data);
277 			break;
278 		case NGM_ETF_SET_FILTER:
279 			{
280 				struct ng_etffilter *f;
281 				struct filter *fil;
282 				hook_p  hook;
283 
284 				/* Check message long enough for this command */
285 				if (msg->header.arglen != sizeof(*f)) {
286 					error = EINVAL;
287 					break;
288 				}
289 
290 				/* Make sure hook referenced exists */
291 				f = (struct ng_etffilter *)msg->data;
292 				hook = ng_findhook(node, f->matchhook);
293 				if (hook == NULL) {
294 					error = ENOENT;
295 					break;
296 				}
297 
298 				/* and is not the downstream hook */
299 				if (hook == etfp->downstream_hook.hook) {
300 					error = EINVAL;
301 					break;
302 				}
303 
304 				/* Check we don't already trap this ethertype */
305 				if (ng_etf_findentry(etfp,
306 						htons(f->ethertype))) {
307 					error = EEXIST;
308 					break;
309 				}
310 
311 				/*
312 				 * Ok, make the filter and put it in the
313 				 * hashtable ready for matching.
314 				 */
315 				fil = malloc(sizeof(*fil),
316 					M_NETGRAPH_ETF, M_NOWAIT | M_ZERO);
317 				if (fil == NULL) {
318 					error = ENOMEM;
319 					break;
320 				}
321 
322 				fil->match_hook = hook;
323 				fil->ethertype = htons(f->ethertype);
324 				LIST_INSERT_HEAD( etfp->hashtable
325 					+ HASH(fil->ethertype),
326 						fil, next);
327 			}
328 			break;
329 		default:
330 			error = EINVAL;		/* unknown command */
331 			break;
332 		}
333 		break;
334 	default:
335 		error = EINVAL;			/* unknown cookie type */
336 		break;
337 	}
338 
339 	/* Take care of synchronous response, if any */
340 	NG_RESPOND_MSG(error, node, item, resp);
341 	/* Free the message and return */
342 	NG_FREE_MSG(msg);
343 	return(error);
344 }
345 
346 /*
347  * Receive data, and do something with it.
348  * Actually we receive a queue item which holds the data.
349  * If we free the item it will also free the data unless we have previously
350  * disassociated it using the NGI_GET_etf() macro.
351  * Possibly send it out on another link after processing.
352  * Possibly do something different if it comes from different
353  * hooks. The caller will never free m , so if we use up this data
354  * or abort we must free it.
355  *
356  * If we want, we may decide to force this data to be queued and reprocessed
357  * at the netgraph NETISR time.
358  * We would do that by setting the HK_QUEUE flag on our hook. We would do that
359  * in the connect() method.
360  */
361 static int
ng_etf_rcvdata(hook_p hook,item_p item)362 ng_etf_rcvdata(hook_p hook, item_p item )
363 {
364 	const etf_p etfp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
365 	struct ether_header *eh;
366 	int error = 0;
367 	struct mbuf *m;
368 	u_int16_t ethertype;
369 	struct filter *fil;
370 
371 	if (NG_HOOK_PRIVATE(hook) == NULL) { /* Shouldn't happen but.. */
372 		NG_FREE_ITEM(item);
373 	}
374 
375 	/*
376 	 * Everything not from the downstream hook goes to the
377 	 * downstream hook. But only if it matches the ethertype
378 	 * of the source hook. Un matching must go to/from 'nomatch'.
379 	 */
380 
381 	/* Make sure we have an entire header */
382 	NGI_GET_M(item, m);
383 	if (m->m_len < sizeof(*eh) ) {
384 		m = m_pullup(m, sizeof(*eh));
385 		if (m == NULL) {
386 			NG_FREE_ITEM(item);
387 			return(EINVAL);
388 		}
389 	}
390 
391 	eh = mtod(m, struct ether_header *);
392 	ethertype = eh->ether_type;
393 	fil = ng_etf_findentry(etfp, ethertype);
394 
395 	/*
396 	 * if from downstream, select between a match hook or
397 	 * the nomatch hook
398 	 */
399 	if (hook == etfp->downstream_hook.hook) {
400 		etfp->packets_in++;
401 		if (fil && fil->match_hook) {
402 			NG_FWD_NEW_DATA(error, item, fil->match_hook, m);
403 		} else {
404 			NG_FWD_NEW_DATA(error, item,etfp->nomatch_hook.hook, m);
405 		}
406 	} else {
407 		/*
408 		 * It must be heading towards the downstream.
409 		 * Check that it's ethertype matches
410 		 * the filters for it's input hook.
411 		 * If it doesn't have one, check it's from nomatch.
412 		 */
413 		if ((fil && (fil->match_hook != hook))
414 		|| ((fil == NULL) && (hook != etfp->nomatch_hook.hook))) {
415 			NG_FREE_ITEM(item);
416 			NG_FREE_M(m);
417 			return (EPROTOTYPE);
418 		}
419 		NG_FWD_NEW_DATA( error, item, etfp->downstream_hook.hook, m);
420 		if (error == 0) {
421 			etfp->packets_out++;
422 		}
423 	}
424 	return (error);
425 }
426 
427 /*
428  * Do local shutdown processing..
429  * All our links and the name have already been removed.
430  */
431 static int
ng_etf_shutdown(node_p node)432 ng_etf_shutdown(node_p node)
433 {
434 	const etf_p privdata = NG_NODE_PRIVATE(node);
435 
436 	NG_NODE_SET_PRIVATE(node, NULL);
437 	NG_NODE_UNREF(privdata->node);
438 	free(privdata, M_NETGRAPH_ETF);
439 	return (0);
440 }
441 
442 /*
443  * Hook disconnection
444  *
445  * For this type, removal of the last link destroys the node
446  */
447 static int
ng_etf_disconnect(hook_p hook)448 ng_etf_disconnect(hook_p hook)
449 {
450 	const etf_p etfp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
451 	int i;
452 	struct filter *fil1, *fil2;
453 
454 	/* purge any rules that refer to this filter */
455 	for (i = 0; i < HASHSIZE; i++) {
456 		fil1 = LIST_FIRST(&etfp->hashtable[i]);
457 		while (fil1 != NULL) {
458 			fil2 = LIST_NEXT(fil1, next);
459 			if (fil1->match_hook == hook) {
460 				LIST_REMOVE(fil1, next);
461 				free(fil1, M_NETGRAPH_ETF);
462 			}
463 			fil1 = fil2;
464 		}
465 	}
466 
467 	/* If it's not one of the special hooks, then free it */
468 	if (hook == etfp->downstream_hook.hook) {
469 		etfp->downstream_hook.hook = NULL;
470 	} else if (hook == etfp->nomatch_hook.hook) {
471 		etfp->nomatch_hook.hook = NULL;
472 	} else {
473 		if (NG_HOOK_PRIVATE(hook)) /* Paranoia */
474 			free(NG_HOOK_PRIVATE(hook), M_NETGRAPH_ETF);
475 	}
476 
477 	NG_HOOK_SET_PRIVATE(hook, NULL);
478 
479 	if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0)
480 	&& (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) /* already shutting down? */
481 		ng_rmnode_self(NG_HOOK_NODE(hook));
482 	return (0);
483 }
484