xref: /freebsd/sys/netgraph/ng_vlan.c (revision 674d86bf9177ff80b5f38f7191951f303a816cac)
1407ea290SRuslan Ermilov /*-
2407ea290SRuslan Ermilov  * Copyright (c) 2003 IPNET Internet Communication Company
3407ea290SRuslan Ermilov  * All rights reserved.
4407ea290SRuslan Ermilov  *
5407ea290SRuslan Ermilov  * Redistribution and use in source and binary forms, with or without
6407ea290SRuslan Ermilov  * modification, are permitted provided that the following conditions
7407ea290SRuslan Ermilov  * are met:
8407ea290SRuslan Ermilov  * 1. Redistributions of source code must retain the above copyright
9407ea290SRuslan Ermilov  *    notice, this list of conditions and the following disclaimer.
10407ea290SRuslan Ermilov  * 2. Redistributions in binary form must reproduce the above copyright
11407ea290SRuslan Ermilov  *    notice, this list of conditions and the following disclaimer in the
12407ea290SRuslan Ermilov  *    documentation and/or other materials provided with the distribution.
13407ea290SRuslan Ermilov  *
14407ea290SRuslan Ermilov  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15407ea290SRuslan Ermilov  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16407ea290SRuslan Ermilov  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17407ea290SRuslan Ermilov  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18407ea290SRuslan Ermilov  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19407ea290SRuslan Ermilov  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20407ea290SRuslan Ermilov  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21407ea290SRuslan Ermilov  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22407ea290SRuslan Ermilov  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23407ea290SRuslan Ermilov  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24407ea290SRuslan Ermilov  * SUCH DAMAGE.
25407ea290SRuslan Ermilov  *
26407ea290SRuslan Ermilov  * Author: Ruslan Ermilov <ru@FreeBSD.org>
27407ea290SRuslan Ermilov  *
28407ea290SRuslan Ermilov  * $FreeBSD$
29407ea290SRuslan Ermilov  */
30407ea290SRuslan Ermilov 
31407ea290SRuslan Ermilov #include <sys/param.h>
32407ea290SRuslan Ermilov #include <sys/errno.h>
33407ea290SRuslan Ermilov #include <sys/kernel.h>
34407ea290SRuslan Ermilov #include <sys/malloc.h>
35407ea290SRuslan Ermilov #include <sys/mbuf.h>
36407ea290SRuslan Ermilov #include <sys/queue.h>
37407ea290SRuslan Ermilov #include <sys/socket.h>
38407ea290SRuslan Ermilov #include <sys/systm.h>
39407ea290SRuslan Ermilov 
40407ea290SRuslan Ermilov #include <net/ethernet.h>
41407ea290SRuslan Ermilov #include <net/if.h>
42407ea290SRuslan Ermilov #include <net/if_vlan_var.h>
43407ea290SRuslan Ermilov 
44407ea290SRuslan Ermilov #include <netgraph/ng_message.h>
45407ea290SRuslan Ermilov #include <netgraph/ng_parse.h>
46407ea290SRuslan Ermilov #include <netgraph/ng_vlan.h>
47407ea290SRuslan Ermilov #include <netgraph/netgraph.h>
48407ea290SRuslan Ermilov 
49407ea290SRuslan Ermilov static ng_constructor_t	ng_vlan_constructor;
50407ea290SRuslan Ermilov static ng_rcvmsg_t	ng_vlan_rcvmsg;
51407ea290SRuslan Ermilov static ng_shutdown_t	ng_vlan_shutdown;
52407ea290SRuslan Ermilov static ng_newhook_t	ng_vlan_newhook;
53407ea290SRuslan Ermilov static ng_rcvdata_t	ng_vlan_rcvdata;
54407ea290SRuslan Ermilov static ng_disconnect_t	ng_vlan_disconnect;
55407ea290SRuslan Ermilov 
56407ea290SRuslan Ermilov /* Parse type for struct ng_vlan_filter. */
57407ea290SRuslan Ermilov static const struct ng_parse_struct_field ng_vlan_filter_fields[] =
58407ea290SRuslan Ermilov 	NG_VLAN_FILTER_FIELDS;
59407ea290SRuslan Ermilov static const struct ng_parse_type ng_vlan_filter_type = {
60407ea290SRuslan Ermilov 	&ng_parse_struct_type,
61407ea290SRuslan Ermilov 	&ng_vlan_filter_fields
62407ea290SRuslan Ermilov };
63407ea290SRuslan Ermilov 
64407ea290SRuslan Ermilov static int
65407ea290SRuslan Ermilov ng_vlan_getTableLength(const struct ng_parse_type *type,
66407ea290SRuslan Ermilov     const u_char *start, const u_char *buf)
67407ea290SRuslan Ermilov {
68407ea290SRuslan Ermilov 	const struct ng_vlan_table *const table =
69407ea290SRuslan Ermilov 	    (const struct ng_vlan_table *)(buf - sizeof(u_int32_t));
70407ea290SRuslan Ermilov 
71407ea290SRuslan Ermilov 	return table->n;
72407ea290SRuslan Ermilov }
73407ea290SRuslan Ermilov 
74407ea290SRuslan Ermilov /* Parse type for struct ng_vlan_table. */
75407ea290SRuslan Ermilov static const struct ng_parse_array_info ng_vlan_table_array_info = {
76407ea290SRuslan Ermilov 	&ng_vlan_filter_type,
77407ea290SRuslan Ermilov 	ng_vlan_getTableLength
78407ea290SRuslan Ermilov };
79407ea290SRuslan Ermilov static const struct ng_parse_type ng_vlan_table_array_type = {
80407ea290SRuslan Ermilov 	&ng_parse_array_type,
81407ea290SRuslan Ermilov 	&ng_vlan_table_array_info
82407ea290SRuslan Ermilov };
83407ea290SRuslan Ermilov static const struct ng_parse_struct_field ng_vlan_table_fields[] =
84407ea290SRuslan Ermilov 	NG_VLAN_TABLE_FIELDS;
85407ea290SRuslan Ermilov static const struct ng_parse_type ng_vlan_table_type = {
86407ea290SRuslan Ermilov 	&ng_parse_struct_type,
87407ea290SRuslan Ermilov 	&ng_vlan_table_fields
88407ea290SRuslan Ermilov };
89407ea290SRuslan Ermilov 
90407ea290SRuslan Ermilov /* List of commands and how to convert arguments to/from ASCII. */
91407ea290SRuslan Ermilov static const struct ng_cmdlist ng_vlan_cmdlist[] = {
92407ea290SRuslan Ermilov 	{
93407ea290SRuslan Ermilov 	  NGM_VLAN_COOKIE,
94407ea290SRuslan Ermilov 	  NGM_VLAN_ADD_FILTER,
95407ea290SRuslan Ermilov 	  "addfilter",
96407ea290SRuslan Ermilov 	  &ng_vlan_filter_type,
97407ea290SRuslan Ermilov 	  NULL
98407ea290SRuslan Ermilov 	},
99407ea290SRuslan Ermilov 	{
100407ea290SRuslan Ermilov 	  NGM_VLAN_COOKIE,
101407ea290SRuslan Ermilov 	  NGM_VLAN_DEL_FILTER,
102407ea290SRuslan Ermilov 	  "delfilter",
103407ea290SRuslan Ermilov 	  &ng_parse_hookbuf_type,
104407ea290SRuslan Ermilov 	  NULL
105407ea290SRuslan Ermilov 	},
106407ea290SRuslan Ermilov 	{
107407ea290SRuslan Ermilov 	  NGM_VLAN_COOKIE,
108407ea290SRuslan Ermilov 	  NGM_VLAN_GET_TABLE,
109407ea290SRuslan Ermilov 	  "gettable",
110407ea290SRuslan Ermilov 	  NULL,
111407ea290SRuslan Ermilov 	  &ng_vlan_table_type
112407ea290SRuslan Ermilov 	},
113407ea290SRuslan Ermilov 	{ 0 }
114407ea290SRuslan Ermilov };
115407ea290SRuslan Ermilov 
116407ea290SRuslan Ermilov static struct ng_type ng_vlan_typestruct = {
117f8aae777SJulian Elischer 	.version =	NG_ABI_VERSION,
118f8aae777SJulian Elischer 	.name =		NG_VLAN_NODE_TYPE,
119f8aae777SJulian Elischer 	.constructor =	ng_vlan_constructor,
120f8aae777SJulian Elischer 	.rcvmsg =	ng_vlan_rcvmsg,
121f8aae777SJulian Elischer 	.shutdown =	ng_vlan_shutdown,
122f8aae777SJulian Elischer 	.newhook =	ng_vlan_newhook,
123f8aae777SJulian Elischer 	.rcvdata =	ng_vlan_rcvdata,
124f8aae777SJulian Elischer 	.disconnect =	ng_vlan_disconnect,
125f8aae777SJulian Elischer 	.cmdlist =	ng_vlan_cmdlist,
126407ea290SRuslan Ermilov };
127407ea290SRuslan Ermilov NETGRAPH_INIT(vlan, &ng_vlan_typestruct);
128407ea290SRuslan Ermilov 
129407ea290SRuslan Ermilov struct filter {
130407ea290SRuslan Ermilov 	LIST_ENTRY(filter) next;
131407ea290SRuslan Ermilov 	u_int16_t	vlan;
132407ea290SRuslan Ermilov 	hook_p		hook;
133407ea290SRuslan Ermilov };
134407ea290SRuslan Ermilov 
135407ea290SRuslan Ermilov #define	HASHSIZE	16
136407ea290SRuslan Ermilov #define	HASH(id)	((((id) >> 8) ^ ((id) >> 4) ^ (id)) & 0x0f)
137407ea290SRuslan Ermilov LIST_HEAD(filterhead, filter);
138407ea290SRuslan Ermilov 
139407ea290SRuslan Ermilov typedef struct {
140407ea290SRuslan Ermilov 	hook_p		downstream_hook;
141407ea290SRuslan Ermilov 	hook_p		nomatch_hook;
142407ea290SRuslan Ermilov 	struct filterhead hashtable[HASHSIZE];
143407ea290SRuslan Ermilov 	u_int32_t	nent;
144407ea290SRuslan Ermilov } *priv_p;
145407ea290SRuslan Ermilov 
146407ea290SRuslan Ermilov static struct filter *
147407ea290SRuslan Ermilov ng_vlan_findentry(priv_p priv, u_int16_t vlan)
148407ea290SRuslan Ermilov {
149407ea290SRuslan Ermilov 	struct filterhead *chain = &priv->hashtable[HASH(vlan)];
150407ea290SRuslan Ermilov 	struct filter *f;
151407ea290SRuslan Ermilov 
152407ea290SRuslan Ermilov 	LIST_FOREACH(f, chain, next)
153407ea290SRuslan Ermilov 		if (f->vlan == vlan)
154407ea290SRuslan Ermilov 			return (f);
155407ea290SRuslan Ermilov 	return (NULL);
156407ea290SRuslan Ermilov }
157407ea290SRuslan Ermilov 
158407ea290SRuslan Ermilov static int
159407ea290SRuslan Ermilov ng_vlan_constructor(node_p node)
160407ea290SRuslan Ermilov {
161407ea290SRuslan Ermilov 	priv_p priv;
162407ea290SRuslan Ermilov 	int i;
163407ea290SRuslan Ermilov 
164*674d86bfSGleb Smirnoff 	priv = malloc(sizeof(*priv), M_NETGRAPH, M_WAITOK | M_ZERO);
165407ea290SRuslan Ermilov 	for (i = 0; i < HASHSIZE; i++)
166407ea290SRuslan Ermilov 		LIST_INIT(&priv->hashtable[i]);
167407ea290SRuslan Ermilov 	NG_NODE_SET_PRIVATE(node, priv);
168407ea290SRuslan Ermilov 	return (0);
169407ea290SRuslan Ermilov }
170407ea290SRuslan Ermilov 
171407ea290SRuslan Ermilov static int
172407ea290SRuslan Ermilov ng_vlan_newhook(node_p node, hook_p hook, const char *name)
173407ea290SRuslan Ermilov {
174407ea290SRuslan Ermilov 	const priv_p priv = NG_NODE_PRIVATE(node);
175407ea290SRuslan Ermilov 
176407ea290SRuslan Ermilov 	if (strcmp(name, NG_VLAN_HOOK_DOWNSTREAM) == 0)
177407ea290SRuslan Ermilov 		priv->downstream_hook = hook;
178407ea290SRuslan Ermilov 	else if (strcmp(name, NG_VLAN_HOOK_NOMATCH) == 0)
179407ea290SRuslan Ermilov 		priv->nomatch_hook = hook;
180407ea290SRuslan Ermilov 	else {
181407ea290SRuslan Ermilov 		/*
182407ea290SRuslan Ermilov 		 * Any other hook name is valid and can
183407ea290SRuslan Ermilov 		 * later be associated with a filter rule.
184407ea290SRuslan Ermilov 		 */
185407ea290SRuslan Ermilov 	}
186407ea290SRuslan Ermilov 	NG_HOOK_SET_PRIVATE(hook, NULL);
187407ea290SRuslan Ermilov 	return (0);
188407ea290SRuslan Ermilov }
189407ea290SRuslan Ermilov 
190407ea290SRuslan Ermilov static int
191407ea290SRuslan Ermilov ng_vlan_rcvmsg(node_p node, item_p item, hook_p lasthook)
192407ea290SRuslan Ermilov {
193407ea290SRuslan Ermilov 	const priv_p priv = NG_NODE_PRIVATE(node);
194407ea290SRuslan Ermilov 	int error = 0;
195407ea290SRuslan Ermilov 	struct ng_mesg *msg, *resp = NULL;
196407ea290SRuslan Ermilov 	struct ng_vlan_filter *vf;
197407ea290SRuslan Ermilov 	struct filter *f;
198407ea290SRuslan Ermilov 	hook_p hook;
199407ea290SRuslan Ermilov 	struct ng_vlan_table *t;
200407ea290SRuslan Ermilov 	int i;
201407ea290SRuslan Ermilov 
202407ea290SRuslan Ermilov 	NGI_GET_MSG(item, msg);
203407ea290SRuslan Ermilov 	/* Deal with message according to cookie and command. */
204407ea290SRuslan Ermilov 	switch (msg->header.typecookie) {
205407ea290SRuslan Ermilov 	case NGM_VLAN_COOKIE:
206407ea290SRuslan Ermilov 		switch (msg->header.cmd) {
207407ea290SRuslan Ermilov 		case NGM_VLAN_ADD_FILTER:
208407ea290SRuslan Ermilov 			/* Check that message is long enough. */
209407ea290SRuslan Ermilov 			if (msg->header.arglen != sizeof(*vf)) {
210407ea290SRuslan Ermilov 				error = EINVAL;
211407ea290SRuslan Ermilov 				break;
212407ea290SRuslan Ermilov 			}
213407ea290SRuslan Ermilov 			vf = (struct ng_vlan_filter *)msg->data;
214407ea290SRuslan Ermilov 			/* Sanity check the VLAN ID value. */
215407ea290SRuslan Ermilov 			if (vf->vlan & ~EVL_VLID_MASK) {
216407ea290SRuslan Ermilov 				error = EINVAL;
217407ea290SRuslan Ermilov 				break;
218407ea290SRuslan Ermilov 			}
219407ea290SRuslan Ermilov 			/* Check that a referenced hook exists. */
220407ea290SRuslan Ermilov 			hook = ng_findhook(node, vf->hook);
221407ea290SRuslan Ermilov 			if (hook == NULL) {
222407ea290SRuslan Ermilov 				error = ENOENT;
223407ea290SRuslan Ermilov 				break;
224407ea290SRuslan Ermilov 			}
225407ea290SRuslan Ermilov 			/* And is not one of the special hooks. */
226407ea290SRuslan Ermilov 			if (hook == priv->downstream_hook ||
227407ea290SRuslan Ermilov 			    hook == priv->nomatch_hook) {
228407ea290SRuslan Ermilov 				error = EINVAL;
229407ea290SRuslan Ermilov 				break;
230407ea290SRuslan Ermilov 			}
231407ea290SRuslan Ermilov 			/* And is not already in service. */
232407ea290SRuslan Ermilov 			if (NG_HOOK_PRIVATE(hook) != NULL) {
233407ea290SRuslan Ermilov 				error = EEXIST;
234407ea290SRuslan Ermilov 				break;
235407ea290SRuslan Ermilov 			}
236407ea290SRuslan Ermilov 			/* Check we don't already trap this VLAN. */
237407ea290SRuslan Ermilov 			if (ng_vlan_findentry(priv, vf->vlan)) {
238407ea290SRuslan Ermilov 				error = EEXIST;
239407ea290SRuslan Ermilov 				break;
240407ea290SRuslan Ermilov 			}
241407ea290SRuslan Ermilov 			/* Create filter. */
2421ede983cSDag-Erling Smørgrav 			f = malloc(sizeof(*f),
243407ea290SRuslan Ermilov 			    M_NETGRAPH, M_NOWAIT | M_ZERO);
244407ea290SRuslan Ermilov 			if (f == NULL) {
245407ea290SRuslan Ermilov 				error = ENOMEM;
246407ea290SRuslan Ermilov 				break;
247407ea290SRuslan Ermilov 			}
248407ea290SRuslan Ermilov 			/* Link filter and hook together. */
249407ea290SRuslan Ermilov 			f->hook = hook;
250407ea290SRuslan Ermilov 			f->vlan = vf->vlan;
251407ea290SRuslan Ermilov 			NG_HOOK_SET_PRIVATE(hook, f);
252407ea290SRuslan Ermilov 			/* Register filter in a hash table. */
253407ea290SRuslan Ermilov 			LIST_INSERT_HEAD(
254407ea290SRuslan Ermilov 			    &priv->hashtable[HASH(f->vlan)], f, next);
255407ea290SRuslan Ermilov 			priv->nent++;
256407ea290SRuslan Ermilov 			break;
257407ea290SRuslan Ermilov 		case NGM_VLAN_DEL_FILTER:
258407ea290SRuslan Ermilov 			/* Check that message is long enough. */
259407ea290SRuslan Ermilov 			if (msg->header.arglen != NG_HOOKSIZ) {
260407ea290SRuslan Ermilov 				error = EINVAL;
261407ea290SRuslan Ermilov 				break;
262407ea290SRuslan Ermilov 			}
263407ea290SRuslan Ermilov 			/* Check that hook exists and is active. */
264407ea290SRuslan Ermilov 			hook = ng_findhook(node, (char *)msg->data);
265407ea290SRuslan Ermilov 			if (hook == NULL ||
266407ea290SRuslan Ermilov 			    (f = NG_HOOK_PRIVATE(hook)) == NULL) {
267407ea290SRuslan Ermilov 				error = ENOENT;
268407ea290SRuslan Ermilov 				break;
269407ea290SRuslan Ermilov 			}
270407ea290SRuslan Ermilov 			/* Purge a rule that refers to this hook. */
271407ea290SRuslan Ermilov 			NG_HOOK_SET_PRIVATE(hook, NULL);
272407ea290SRuslan Ermilov 			LIST_REMOVE(f, next);
273407ea290SRuslan Ermilov 			priv->nent--;
2741ede983cSDag-Erling Smørgrav 			free(f, M_NETGRAPH);
275407ea290SRuslan Ermilov 			break;
276407ea290SRuslan Ermilov 		case NGM_VLAN_GET_TABLE:
277407ea290SRuslan Ermilov 			NG_MKRESPONSE(resp, msg, sizeof(*t) +
278407ea290SRuslan Ermilov 			    priv->nent * sizeof(*t->filter), M_NOWAIT);
279407ea290SRuslan Ermilov 			if (resp == NULL) {
280407ea290SRuslan Ermilov 				error = ENOMEM;
281407ea290SRuslan Ermilov 				break;
282407ea290SRuslan Ermilov 			}
283407ea290SRuslan Ermilov 			t = (struct ng_vlan_table *)resp->data;
284407ea290SRuslan Ermilov 			t->n = priv->nent;
285407ea290SRuslan Ermilov 			vf = &t->filter[0];
286407ea290SRuslan Ermilov 			for (i = 0; i < HASHSIZE; i++) {
287407ea290SRuslan Ermilov 				LIST_FOREACH(f, &priv->hashtable[i], next) {
288407ea290SRuslan Ermilov 					vf->vlan = f->vlan;
289407ea290SRuslan Ermilov 					strncpy(vf->hook, NG_HOOK_NAME(f->hook),
290407ea290SRuslan Ermilov 					    NG_HOOKSIZ);
291407ea290SRuslan Ermilov 					vf++;
292407ea290SRuslan Ermilov 				}
293407ea290SRuslan Ermilov 			}
294407ea290SRuslan Ermilov 			break;
295407ea290SRuslan Ermilov 		default:		/* Unknown command. */
296407ea290SRuslan Ermilov 			error = EINVAL;
297407ea290SRuslan Ermilov 			break;
298407ea290SRuslan Ermilov 		}
299407ea290SRuslan Ermilov 		break;
3003b1c41c5SGleb Smirnoff 	case NGM_FLOW_COOKIE:
3013b1c41c5SGleb Smirnoff 	    {
3023b1c41c5SGleb Smirnoff 		struct ng_mesg *copy;
3033b1c41c5SGleb Smirnoff 		struct filterhead *chain;
3043b1c41c5SGleb Smirnoff 		struct filter *f;
3053b1c41c5SGleb Smirnoff 
3063b1c41c5SGleb Smirnoff 		/*
3073b1c41c5SGleb Smirnoff 		 * Flow control messages should come only
3083b1c41c5SGleb Smirnoff 		 * from downstream.
3093b1c41c5SGleb Smirnoff 		 */
3103b1c41c5SGleb Smirnoff 
3113b1c41c5SGleb Smirnoff 		if (lasthook == NULL)
3123b1c41c5SGleb Smirnoff 			break;
3133b1c41c5SGleb Smirnoff 		if (lasthook != priv->downstream_hook)
3143b1c41c5SGleb Smirnoff 			break;
3153b1c41c5SGleb Smirnoff 
3163b1c41c5SGleb Smirnoff 		/* Broadcast the event to all uplinks. */
3173b1c41c5SGleb Smirnoff 		for (i = 0, chain = priv->hashtable; i < HASHSIZE;
3183b1c41c5SGleb Smirnoff 		    i++, chain++)
3193b1c41c5SGleb Smirnoff 		LIST_FOREACH(f, chain, next) {
3203b1c41c5SGleb Smirnoff 			NG_COPYMESSAGE(copy, msg, M_NOWAIT);
3213b1c41c5SGleb Smirnoff 			if (copy == NULL)
3223b1c41c5SGleb Smirnoff 				continue;
3233b1c41c5SGleb Smirnoff 			NG_SEND_MSG_HOOK(error, node, copy, f->hook, 0);
3243b1c41c5SGleb Smirnoff 		}
3253b1c41c5SGleb Smirnoff 
3263b1c41c5SGleb Smirnoff 		break;
3273b1c41c5SGleb Smirnoff 	    }
328407ea290SRuslan Ermilov 	default:			/* Unknown type cookie. */
329407ea290SRuslan Ermilov 		error = EINVAL;
330407ea290SRuslan Ermilov 		break;
331407ea290SRuslan Ermilov 	}
332407ea290SRuslan Ermilov 	NG_RESPOND_MSG(error, node, item, resp);
333407ea290SRuslan Ermilov 	NG_FREE_MSG(msg);
334407ea290SRuslan Ermilov 	return (error);
335407ea290SRuslan Ermilov }
336407ea290SRuslan Ermilov 
337407ea290SRuslan Ermilov static int
338407ea290SRuslan Ermilov ng_vlan_rcvdata(hook_p hook, item_p item)
339407ea290SRuslan Ermilov {
340407ea290SRuslan Ermilov 	const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
341407ea290SRuslan Ermilov 	struct ether_header *eh;
342c7b8e2f5SWarner Losh 	struct ether_vlan_header *evl = NULL;
343407ea290SRuslan Ermilov 	int error;
344407ea290SRuslan Ermilov 	u_int16_t vlan;
345407ea290SRuslan Ermilov 	struct mbuf *m;
346407ea290SRuslan Ermilov 	struct filter *f;
347407ea290SRuslan Ermilov 
348407ea290SRuslan Ermilov 	/* Make sure we have an entire header. */
349407ea290SRuslan Ermilov 	NGI_GET_M(item, m);
350407ea290SRuslan Ermilov 	if (m->m_len < sizeof(*eh) &&
351407ea290SRuslan Ermilov 	    (m = m_pullup(m, sizeof(*eh))) == NULL) {
352407ea290SRuslan Ermilov 		NG_FREE_ITEM(item);
353407ea290SRuslan Ermilov 		return (EINVAL);
354407ea290SRuslan Ermilov 	}
355407ea290SRuslan Ermilov 	eh = mtod(m, struct ether_header *);
356407ea290SRuslan Ermilov 	if (hook == priv->downstream_hook) {
357407ea290SRuslan Ermilov 		/*
358407ea290SRuslan Ermilov 		 * If from downstream, select between a match hook
359407ea290SRuslan Ermilov 		 * or the nomatch hook.
360407ea290SRuslan Ermilov 		 */
36178ba57b9SAndre Oppermann 		if (m->m_flags & M_VLANTAG ||
36278ba57b9SAndre Oppermann 		    eh->ether_type == htons(ETHERTYPE_VLAN)) {
36378ba57b9SAndre Oppermann 			if (m->m_flags & M_VLANTAG) {
364407ea290SRuslan Ermilov 				/*
365407ea290SRuslan Ermilov 				 * Packet is tagged, m contains a normal
366407ea290SRuslan Ermilov 				 * Ethernet frame; tag is stored out-of-band.
367407ea290SRuslan Ermilov 				 */
36878ba57b9SAndre Oppermann 				vlan = EVL_VLANOFTAG(m->m_pkthdr.ether_vtag);
369407ea290SRuslan Ermilov 			} else {
370407ea290SRuslan Ermilov 				if (m->m_len < sizeof(*evl) &&
371407ea290SRuslan Ermilov 				    (m = m_pullup(m, sizeof(*evl))) == NULL) {
372407ea290SRuslan Ermilov 					NG_FREE_ITEM(item);
373407ea290SRuslan Ermilov 					return (EINVAL);
374407ea290SRuslan Ermilov 				}
375407ea290SRuslan Ermilov 				evl = mtod(m, struct ether_vlan_header *);
376407ea290SRuslan Ermilov 				vlan = EVL_VLANOFTAG(ntohs(evl->evl_tag));
377407ea290SRuslan Ermilov 			}
378407ea290SRuslan Ermilov 			if ((f = ng_vlan_findentry(priv, vlan)) != NULL) {
37978ba57b9SAndre Oppermann 				if (m->m_flags & M_VLANTAG) {
38078ba57b9SAndre Oppermann 					m->m_pkthdr.ether_vtag = 0;
38178ba57b9SAndre Oppermann 					m->m_flags &= ~M_VLANTAG;
38278ba57b9SAndre Oppermann 				} else {
383407ea290SRuslan Ermilov 					evl->evl_encap_proto = evl->evl_proto;
384407ea290SRuslan Ermilov 					bcopy(mtod(m, caddr_t),
385407ea290SRuslan Ermilov 					    mtod(m, caddr_t) +
386407ea290SRuslan Ermilov 					    ETHER_VLAN_ENCAP_LEN,
387407ea290SRuslan Ermilov 					    ETHER_HDR_LEN);
388407ea290SRuslan Ermilov 					m_adj(m, ETHER_VLAN_ENCAP_LEN);
389407ea290SRuslan Ermilov 				}
390407ea290SRuslan Ermilov 			}
391407ea290SRuslan Ermilov 		} else
392407ea290SRuslan Ermilov 			f = NULL;
393407ea290SRuslan Ermilov 		if (f != NULL)
394407ea290SRuslan Ermilov 			NG_FWD_NEW_DATA(error, item, f->hook, m);
395407ea290SRuslan Ermilov 		else
396407ea290SRuslan Ermilov 			NG_FWD_NEW_DATA(error, item, priv->nomatch_hook, m);
397407ea290SRuslan Ermilov 	} else {
398407ea290SRuslan Ermilov 		/*
399407ea290SRuslan Ermilov 		 * It is heading towards the downstream.
400407ea290SRuslan Ermilov 		 * If from nomatch, pass it unmodified.
401407ea290SRuslan Ermilov 		 * Otherwise, do the VLAN encapsulation.
402407ea290SRuslan Ermilov 		 */
403407ea290SRuslan Ermilov 		if (hook != priv->nomatch_hook) {
404407ea290SRuslan Ermilov 			if ((f = NG_HOOK_PRIVATE(hook)) == NULL) {
405407ea290SRuslan Ermilov 				NG_FREE_ITEM(item);
406407ea290SRuslan Ermilov 				NG_FREE_M(m);
407407ea290SRuslan Ermilov 				return (EOPNOTSUPP);
408407ea290SRuslan Ermilov 			}
409407ea290SRuslan Ermilov 			M_PREPEND(m, ETHER_VLAN_ENCAP_LEN, M_DONTWAIT);
410407ea290SRuslan Ermilov 			/* M_PREPEND takes care of m_len and m_pkthdr.len. */
411407ea290SRuslan Ermilov 			if (m == NULL || (m->m_len < sizeof(*evl) &&
412407ea290SRuslan Ermilov 			    (m = m_pullup(m, sizeof(*evl))) == NULL)) {
413407ea290SRuslan Ermilov 				NG_FREE_ITEM(item);
414407ea290SRuslan Ermilov 				return (ENOMEM);
415407ea290SRuslan Ermilov 			}
416407ea290SRuslan Ermilov 			/*
417407ea290SRuslan Ermilov 			 * Transform the Ethernet header into an Ethernet header
418407ea290SRuslan Ermilov 			 * with 802.1Q encapsulation.
419407ea290SRuslan Ermilov 			 */
420407ea290SRuslan Ermilov 			bcopy(mtod(m, char *) + ETHER_VLAN_ENCAP_LEN,
421407ea290SRuslan Ermilov 			    mtod(m, char *), ETHER_HDR_LEN);
422407ea290SRuslan Ermilov 			evl = mtod(m, struct ether_vlan_header *);
423407ea290SRuslan Ermilov 			evl->evl_proto = evl->evl_encap_proto;
424407ea290SRuslan Ermilov 			evl->evl_encap_proto = htons(ETHERTYPE_VLAN);
425407ea290SRuslan Ermilov 			evl->evl_tag = htons(f->vlan);
426407ea290SRuslan Ermilov 		}
427407ea290SRuslan Ermilov 		NG_FWD_NEW_DATA(error, item, priv->downstream_hook, m);
428407ea290SRuslan Ermilov 	}
429407ea290SRuslan Ermilov 	return (error);
430407ea290SRuslan Ermilov }
431407ea290SRuslan Ermilov 
432407ea290SRuslan Ermilov static int
433407ea290SRuslan Ermilov ng_vlan_shutdown(node_p node)
434407ea290SRuslan Ermilov {
435407ea290SRuslan Ermilov 	const priv_p priv = NG_NODE_PRIVATE(node);
436407ea290SRuslan Ermilov 
437407ea290SRuslan Ermilov 	NG_NODE_SET_PRIVATE(node, NULL);
438407ea290SRuslan Ermilov 	NG_NODE_UNREF(node);
4391ede983cSDag-Erling Smørgrav 	free(priv, M_NETGRAPH);
440407ea290SRuslan Ermilov 	return (0);
441407ea290SRuslan Ermilov }
442407ea290SRuslan Ermilov 
443407ea290SRuslan Ermilov static int
444407ea290SRuslan Ermilov ng_vlan_disconnect(hook_p hook)
445407ea290SRuslan Ermilov {
446407ea290SRuslan Ermilov 	const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
447407ea290SRuslan Ermilov 	struct filter *f;
448407ea290SRuslan Ermilov 
449407ea290SRuslan Ermilov 	if (hook == priv->downstream_hook)
450407ea290SRuslan Ermilov 		priv->downstream_hook = NULL;
451407ea290SRuslan Ermilov 	else if (hook == priv->nomatch_hook)
452407ea290SRuslan Ermilov 		priv->nomatch_hook = NULL;
453407ea290SRuslan Ermilov 	else {
454407ea290SRuslan Ermilov 		/* Purge a rule that refers to this hook. */
455407ea290SRuslan Ermilov 		if ((f = NG_HOOK_PRIVATE(hook)) != NULL) {
456407ea290SRuslan Ermilov 			LIST_REMOVE(f, next);
457407ea290SRuslan Ermilov 			priv->nent--;
4581ede983cSDag-Erling Smørgrav 			free(f, M_NETGRAPH);
459407ea290SRuslan Ermilov 		}
460407ea290SRuslan Ermilov 	}
461407ea290SRuslan Ermilov 	NG_HOOK_SET_PRIVATE(hook, NULL);
462407ea290SRuslan Ermilov 	if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) &&
463407ea290SRuslan Ermilov 	    (NG_NODE_IS_VALID(NG_HOOK_NODE(hook))))
464407ea290SRuslan Ermilov 		ng_rmnode_self(NG_HOOK_NODE(hook));
465407ea290SRuslan Ermilov 	return (0);
466407ea290SRuslan Ermilov }
467