1/*-
2 * Copyright (c) 2003 IPNET Internet Communication Company
3 * Copyright (c) 2011 - 2012 Rozhuk Ivan <rozhuk.im@gmail.com>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 *
27 * Author: Ruslan Ermilov <ru@FreeBSD.org>
28 *
29 * $FreeBSD$
30 */
31
32#include <sys/param.h>
33#include <sys/errno.h>
34#include <sys/kernel.h>
35#include <sys/malloc.h>
36#include <sys/mbuf.h>
37#include <sys/queue.h>
38#include <sys/socket.h>
39#include <sys/systm.h>
40
41#include <net/ethernet.h>
42#include <net/if.h>
43#include <net/if_vlan_var.h>
44
45#include <netgraph/ng_message.h>
46#include <netgraph/ng_parse.h>
47#include <netgraph/ng_vlan.h>
48#include <netgraph/netgraph.h>
49
50struct ng_vlan_private {
51	hook_p		downstream_hook;
52	hook_p		nomatch_hook;
53	uint32_t	decap_enable;
54	uint32_t	encap_enable;
55	uint16_t	encap_proto;
56	hook_p		vlan_hook[(EVL_VLID_MASK + 1)];
57};
58typedef struct ng_vlan_private *priv_p;
59
60#define	ETHER_VLAN_HDR_LEN (ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN)
61#define	VLAN_TAG_MASK	0xFFFF
62#define	HOOK_VLAN_TAG_SET_MASK ((uintptr_t)((~0) & ~(VLAN_TAG_MASK)))
63#define	IS_HOOK_VLAN_SET(hdata) \
64	    ((((uintptr_t)hdata) & HOOK_VLAN_TAG_SET_MASK) == HOOK_VLAN_TAG_SET_MASK)
65
66static ng_constructor_t	ng_vlan_constructor;
67static ng_rcvmsg_t	ng_vlan_rcvmsg;
68static ng_shutdown_t	ng_vlan_shutdown;
69static ng_newhook_t	ng_vlan_newhook;
70static ng_rcvdata_t	ng_vlan_rcvdata;
71static ng_disconnect_t	ng_vlan_disconnect;
72
73/* Parse type for struct ng_vlan_filter. */
74static const struct ng_parse_struct_field ng_vlan_filter_fields[] =
75	NG_VLAN_FILTER_FIELDS;
76static const struct ng_parse_type ng_vlan_filter_type = {
77	&ng_parse_struct_type,
78	&ng_vlan_filter_fields
79};
80
81static int
82ng_vlan_getTableLength(const struct ng_parse_type *type,
83    const u_char *start, const u_char *buf)
84{
85	const struct ng_vlan_table *const table =
86	    (const struct ng_vlan_table *)(buf - sizeof(u_int32_t));
87
88	return table->n;
89}
90
91/* Parse type for struct ng_vlan_table. */
92static const struct ng_parse_array_info ng_vlan_table_array_info = {
93	&ng_vlan_filter_type,
94	ng_vlan_getTableLength
95};
96static const struct ng_parse_type ng_vlan_table_array_type = {
97	&ng_parse_array_type,
98	&ng_vlan_table_array_info
99};
100static const struct ng_parse_struct_field ng_vlan_table_fields[] =
101	NG_VLAN_TABLE_FIELDS;
102static const struct ng_parse_type ng_vlan_table_type = {
103	&ng_parse_struct_type,
104	&ng_vlan_table_fields
105};
106
107/* List of commands and how to convert arguments to/from ASCII. */
108static const struct ng_cmdlist ng_vlan_cmdlist[] = {
109	{
110	  NGM_VLAN_COOKIE,
111	  NGM_VLAN_ADD_FILTER,
112	  "addfilter",
113	  &ng_vlan_filter_type,
114	  NULL
115	},
116	{
117	  NGM_VLAN_COOKIE,
118	  NGM_VLAN_DEL_FILTER,
119	  "delfilter",
120	  &ng_parse_hookbuf_type,
121	  NULL
122	},
123	{
124	  NGM_VLAN_COOKIE,
125	  NGM_VLAN_GET_TABLE,
126	  "gettable",
127	  NULL,
128	  &ng_vlan_table_type
129	},
130	{
131	  NGM_VLAN_COOKIE,
132	  NGM_VLAN_DEL_VID_FLT,
133	  "delvidflt",
134	  &ng_parse_uint16_type,
135	  NULL
136	},
137	{
138	  NGM_VLAN_COOKIE,
139	  NGM_VLAN_GET_DECAP,
140	  "getdecap",
141	  NULL,
142	  &ng_parse_hint32_type
143	},
144	{
145	  NGM_VLAN_COOKIE,
146	  NGM_VLAN_SET_DECAP,
147	  "setdecap",
148	  &ng_parse_hint32_type,
149	  NULL
150	},
151	{
152	  NGM_VLAN_COOKIE,
153	  NGM_VLAN_GET_ENCAP,
154	  "getencap",
155	  NULL,
156	  &ng_parse_hint32_type
157	},
158	{
159	  NGM_VLAN_COOKIE,
160	  NGM_VLAN_SET_ENCAP,
161	  "setencap",
162	  &ng_parse_hint32_type,
163	  NULL
164	},
165	{
166	  NGM_VLAN_COOKIE,
167	  NGM_VLAN_GET_ENCAP_PROTO,
168	  "getencapproto",
169	  NULL,
170	  &ng_parse_hint16_type
171	},
172	{
173	  NGM_VLAN_COOKIE,
174	  NGM_VLAN_SET_ENCAP_PROTO,
175	  "setencapproto",
176	  &ng_parse_hint16_type,
177	  NULL
178	},
179	{ 0 }
180};
181
182static struct ng_type ng_vlan_typestruct = {
183	.version =	NG_ABI_VERSION,
184	.name =		NG_VLAN_NODE_TYPE,
185	.constructor =	ng_vlan_constructor,
186	.rcvmsg =	ng_vlan_rcvmsg,
187	.shutdown =	ng_vlan_shutdown,
188	.newhook =	ng_vlan_newhook,
189	.rcvdata =	ng_vlan_rcvdata,
190	.disconnect =	ng_vlan_disconnect,
191	.cmdlist =	ng_vlan_cmdlist,
192};
193NETGRAPH_INIT(vlan, &ng_vlan_typestruct);
194
195
196/*
197 * Helper functions.
198 */
199
200static __inline int
201m_chk(struct mbuf **mp, int len)
202{
203
204	if ((*mp)->m_pkthdr.len < len) {
205		m_freem((*mp));
206		(*mp) = NULL;
207		return (EINVAL);
208	}
209	if ((*mp)->m_len < len && ((*mp) = m_pullup((*mp), len)) == NULL)
210		return (ENOBUFS);
211
212	return (0);
213}
214
215
216/*
217 * Netgraph node functions.
218 */
219
220static int
221ng_vlan_constructor(node_p node)
222{
223	priv_p priv;
224
225	priv = malloc(sizeof(*priv), M_NETGRAPH, M_WAITOK | M_ZERO);
226	priv->decap_enable = 0;
227	priv->encap_enable = VLAN_ENCAP_FROM_FILTER;
228	priv->encap_proto = htons(ETHERTYPE_VLAN);
229	NG_NODE_SET_PRIVATE(node, priv);
230	return (0);
231}
232
233static int
234ng_vlan_newhook(node_p node, hook_p hook, const char *name)
235{
236	const priv_p priv = NG_NODE_PRIVATE(node);
237
238	if (strcmp(name, NG_VLAN_HOOK_DOWNSTREAM) == 0)
239		priv->downstream_hook = hook;
240	else if (strcmp(name, NG_VLAN_HOOK_NOMATCH) == 0)
241		priv->nomatch_hook = hook;
242	else {
243		/*
244		 * Any other hook name is valid and can
245		 * later be associated with a filter rule.
246		 */
247	}
248	NG_HOOK_SET_PRIVATE(hook, NULL);
249	return (0);
250}
251
252static int
253ng_vlan_rcvmsg(node_p node, item_p item, hook_p lasthook)
254{
255	const priv_p priv = NG_NODE_PRIVATE(node);
256	struct ng_mesg *msg, *resp = NULL;
257	struct ng_vlan_filter *vf;
258	hook_p hook;
259	struct ng_vlan_table *t;
260	uintptr_t hook_data;
261	int i, vlan_count;
262	uint16_t vid;
263	int error = 0;
264
265	NGI_GET_MSG(item, msg);
266	/* Deal with message according to cookie and command. */
267	switch (msg->header.typecookie) {
268	case NGM_VLAN_COOKIE:
269		switch (msg->header.cmd) {
270		case NGM_VLAN_ADD_FILTER:
271			/* Check that message is long enough. */
272			if (msg->header.arglen != sizeof(*vf)) {
273				error = EINVAL;
274				break;
275			}
276			vf = (struct ng_vlan_filter *)msg->data;
277			/* Sanity check the VLAN ID value. */
278#ifdef	NG_VLAN_USE_OLD_VLAN_NAME
279			if (vf->vid == 0 && vf->vid != vf->vlan) {
280				vf->vid = vf->vlan;
281			} else if (vf->vid != 0 && vf->vlan != 0 &&
282			    vf->vid != vf->vlan) {
283				error = EINVAL;
284				break;
285			}
286#endif
287			if (vf->vid & ~EVL_VLID_MASK ||
288			    vf->pcp & ~7 ||
289			    vf->cfi & ~1) {
290				error = EINVAL;
291				break;
292			}
293			/* Check that a referenced hook exists. */
294			hook = ng_findhook(node, vf->hook_name);
295			if (hook == NULL) {
296				error = ENOENT;
297				break;
298			}
299			/* And is not one of the special hooks. */
300			if (hook == priv->downstream_hook ||
301			    hook == priv->nomatch_hook) {
302				error = EINVAL;
303				break;
304			}
305			/* And is not already in service. */
306			if (IS_HOOK_VLAN_SET(NG_HOOK_PRIVATE(hook))) {
307				error = EEXIST;
308				break;
309			}
310			/* Check we don't already trap this VLAN. */
311			if (priv->vlan_hook[vf->vid] != NULL) {
312				error = EEXIST;
313				break;
314			}
315			/* Link vlan and hook together. */
316			NG_HOOK_SET_PRIVATE(hook,
317			    (void *)(HOOK_VLAN_TAG_SET_MASK |
318			    EVL_MAKETAG(vf->vid, vf->pcp, vf->cfi)));
319			priv->vlan_hook[vf->vid] = hook;
320			break;
321		case NGM_VLAN_DEL_FILTER:
322			/* Check that message is long enough. */
323			if (msg->header.arglen != NG_HOOKSIZ) {
324				error = EINVAL;
325				break;
326			}
327			/* Check that hook exists and is active. */
328			hook = ng_findhook(node, (char *)msg->data);
329			if (hook == NULL) {
330				error = ENOENT;
331				break;
332			}
333			hook_data = (uintptr_t)NG_HOOK_PRIVATE(hook);
334			if (IS_HOOK_VLAN_SET(hook_data) == 0) {
335				error = ENOENT;
336				break;
337			}
338
339			KASSERT(priv->vlan_hook[EVL_VLANOFTAG(hook_data)] == hook,
340			    ("%s: NGM_VLAN_DEL_FILTER: Invalid VID for Hook = %s\n",
341			    __func__, (char *)msg->data));
342
343			/* Purge a rule that refers to this hook. */
344			priv->vlan_hook[EVL_VLANOFTAG(hook_data)] = NULL;
345			NG_HOOK_SET_PRIVATE(hook, NULL);
346			break;
347		case NGM_VLAN_DEL_VID_FLT:
348			/* Check that message is long enough. */
349			if (msg->header.arglen != sizeof(uint16_t)) {
350				error = EINVAL;
351				break;
352			}
353			vid = (*((uint16_t *)msg->data));
354			/* Sanity check the VLAN ID value. */
355			if (vid & ~EVL_VLID_MASK) {
356				error = EINVAL;
357				break;
358			}
359			/* Check that hook exists and is active. */
360			hook = priv->vlan_hook[vid];
361			if (hook == NULL) {
362				error = ENOENT;
363				break;
364			}
365			hook_data = (uintptr_t)NG_HOOK_PRIVATE(hook);
366			if (IS_HOOK_VLAN_SET(hook_data) == 0) {
367				error = ENOENT;
368				break;
369			}
370
371			KASSERT(EVL_VLANOFTAG(hook_data) == vid,
372			    ("%s: NGM_VLAN_DEL_VID_FLT:"
373			    " Invalid VID Hook = %us, must be: %us\n",
374			    __func__, (uint16_t )EVL_VLANOFTAG(hook_data),
375			    vid));
376
377			/* Purge a rule that refers to this hook. */
378			priv->vlan_hook[vid] = NULL;
379			NG_HOOK_SET_PRIVATE(hook, NULL);
380			break;
381		case NGM_VLAN_GET_TABLE:
382			/* Calculate vlans. */
383			vlan_count = 0;
384			for (i = 0; i < (EVL_VLID_MASK + 1); i ++) {
385				if (priv->vlan_hook[i] != NULL &&
386				    NG_HOOK_IS_VALID(priv->vlan_hook[i]))
387					vlan_count ++;
388			}
389
390			/* Allocate memory for response. */
391			NG_MKRESPONSE(resp, msg, sizeof(*t) +
392			    vlan_count * sizeof(*t->filter), M_NOWAIT);
393			if (resp == NULL) {
394				error = ENOMEM;
395				break;
396			}
397
398			/* Pack data to response. */
399			t = (struct ng_vlan_table *)resp->data;
400			t->n = 0;
401			vf = &t->filter[0];
402			for (i = 0; i < (EVL_VLID_MASK + 1); i ++) {
403				hook = priv->vlan_hook[i];
404				if (hook == NULL || NG_HOOK_NOT_VALID(hook))
405					continue;
406				hook_data = (uintptr_t)NG_HOOK_PRIVATE(hook);
407				if (IS_HOOK_VLAN_SET(hook_data) == 0)
408					continue;
409
410				KASSERT(EVL_VLANOFTAG(hook_data) == i,
411				    ("%s: NGM_VLAN_GET_TABLE:"
412				    " hook %s VID = %us, must be: %i\n",
413				    __func__, NG_HOOK_NAME(hook),
414				    (uint16_t)EVL_VLANOFTAG(hook_data), i));
415
416#ifdef	NG_VLAN_USE_OLD_VLAN_NAME
417				vf->vlan = i;
418#endif
419				vf->vid = i;
420				vf->pcp = EVL_PRIOFTAG(hook_data);
421				vf->cfi = EVL_CFIOFTAG(hook_data);
422				strncpy(vf->hook_name,
423				    NG_HOOK_NAME(hook), NG_HOOKSIZ);
424				vf ++;
425				t->n ++;
426			}
427			break;
428		case NGM_VLAN_GET_DECAP:
429			NG_MKRESPONSE(resp, msg, sizeof(uint32_t), M_NOWAIT);
430			if (resp == NULL) {
431				error = ENOMEM;
432				break;
433			}
434			(*((uint32_t *)resp->data)) = priv->decap_enable;
435			break;
436		case NGM_VLAN_SET_DECAP:
437			if (msg->header.arglen != sizeof(uint32_t)) {
438				error = EINVAL;
439				break;
440			}
441			priv->decap_enable = (*((uint32_t *)msg->data));
442			break;
443		case NGM_VLAN_GET_ENCAP:
444			NG_MKRESPONSE(resp, msg, sizeof(uint32_t), M_NOWAIT);
445			if (resp == NULL) {
446				error = ENOMEM;
447				break;
448			}
449			(*((uint32_t *)resp->data)) = priv->encap_enable;
450			break;
451		case NGM_VLAN_SET_ENCAP:
452			if (msg->header.arglen != sizeof(uint32_t)) {
453				error = EINVAL;
454				break;
455			}
456			priv->encap_enable = (*((uint32_t *)msg->data));
457			break;
458		case NGM_VLAN_GET_ENCAP_PROTO:
459			NG_MKRESPONSE(resp, msg, sizeof(uint16_t), M_NOWAIT);
460			if (resp == NULL) {
461				error = ENOMEM;
462				break;
463			}
464			(*((uint16_t *)resp->data)) = ntohs(priv->encap_proto);
465			break;
466		case NGM_VLAN_SET_ENCAP_PROTO:
467			if (msg->header.arglen != sizeof(uint16_t)) {
468				error = EINVAL;
469				break;
470			}
471			priv->encap_proto = htons((*((uint16_t *)msg->data)));
472			break;
473		default: /* Unknown command. */
474			error = EINVAL;
475			break;
476		}
477		break;
478	case NGM_FLOW_COOKIE:
479	    {
480		struct ng_mesg *copy;
481
482		/*
483		 * Flow control messages should come only
484		 * from downstream.
485		 */
486
487		if (lasthook == NULL)
488			break;
489		if (lasthook != priv->downstream_hook)
490			break;
491		/* Broadcast the event to all uplinks. */
492		for (i = 0; i < (EVL_VLID_MASK + 1); i ++) {
493			if (priv->vlan_hook[i] == NULL)
494				continue;
495
496			NG_COPYMESSAGE(copy, msg, M_NOWAIT);
497			if (copy == NULL)
498				continue;
499			NG_SEND_MSG_HOOK(error, node, copy,
500			    priv->vlan_hook[i], 0);
501		}
502		break;
503	    }
504	default: /* Unknown type cookie. */
505		error = EINVAL;
506		break;
507	}
508	NG_RESPOND_MSG(error, node, item, resp);
509	NG_FREE_MSG(msg);
510	return (error);
511}
512
513static int
514ng_vlan_rcvdata(hook_p hook, item_p item)
515{
516	const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
517	struct ether_header *eh;
518	struct ether_vlan_header *evl;
519	int error;
520	uintptr_t hook_data;
521	uint16_t vid, eth_vtag;
522	struct mbuf *m;
523	hook_p dst_hook;
524
525
526	NGI_GET_M(item, m);
527
528	/* Make sure we have an entire header. */
529	error = m_chk(&m, ETHER_HDR_LEN);
530	if (error != 0)
531		goto mchk_err;
532
533	eh = mtod(m, struct ether_header *);
534	if (hook == priv->downstream_hook) {
535		/*
536		 * If from downstream, select between a match hook
537		 * or the nomatch hook.
538		 */
539
540		dst_hook = priv->nomatch_hook;
541
542		/* Skip packets without tag. */
543		if ((m->m_flags & M_VLANTAG) == 0 &&
544		    eh->ether_type != priv->encap_proto) {
545			if (dst_hook == NULL)
546				goto net_down;
547			goto send_packet;
548		}
549
550		/* Process packets with tag. */
551		if (m->m_flags & M_VLANTAG) {
552			/*
553			 * Packet is tagged, m contains a normal
554			 * Ethernet frame; tag is stored out-of-band.
555			 */
556			evl = NULL;
557			vid = EVL_VLANOFTAG(m->m_pkthdr.ether_vtag);
558		} else { /* eh->ether_type == priv->encap_proto */
559			error = m_chk(&m, ETHER_VLAN_HDR_LEN);
560			if (error != 0)
561				goto mchk_err;
562			evl = mtod(m, struct ether_vlan_header *);
563			vid = EVL_VLANOFTAG(ntohs(evl->evl_tag));
564		}
565
566		if (priv->vlan_hook[vid] != NULL) {
567			/*
568			 * VLAN filter: always remove vlan tags and
569			 * decapsulate packet.
570			 */
571			dst_hook = priv->vlan_hook[vid];
572			if (evl == NULL) { /* m->m_flags & M_VLANTAG */
573				m->m_pkthdr.ether_vtag = 0;
574				m->m_flags &= ~M_VLANTAG;
575				goto send_packet;
576			}
577		} else { /* nomatch_hook */
578			if (dst_hook == NULL)
579				goto net_down;
580			if (evl == NULL || priv->decap_enable == 0)
581				goto send_packet;
582			/* Save tag out-of-band. */
583			m->m_pkthdr.ether_vtag = ntohs(evl->evl_tag);
584			m->m_flags |= M_VLANTAG;
585		}
586
587		/*
588		 * Decapsulate:
589		 * TPID = ether type encap
590		 * Move DstMAC and SrcMAC to ETHER_TYPE.
591		 * Before:
592		 *  [dmac] [smac] [TPID] [PCP/CFI/VID] [ether_type] [payload]
593		 *  |-----------| >>>>>>>>>>>>>>>>>>>> |--------------------|
594		 * After:
595		 *  [free space ] [dmac] [smac] [ether_type] [payload]
596		 *                |-----------| |--------------------|
597		 */
598		bcopy((char *)evl, ((char *)evl + ETHER_VLAN_ENCAP_LEN),
599		    (ETHER_ADDR_LEN * 2));
600		m_adj(m, ETHER_VLAN_ENCAP_LEN);
601	} else {
602		/*
603		 * It is heading towards the downstream.
604		 * If from nomatch, pass it unmodified.
605		 * Otherwise, do the VLAN encapsulation.
606		 */
607		dst_hook = priv->downstream_hook;
608		if (dst_hook == NULL)
609			goto net_down;
610		if (hook != priv->nomatch_hook) {/* Filter hook. */
611			hook_data = (uintptr_t)NG_HOOK_PRIVATE(hook);
612			if (IS_HOOK_VLAN_SET(hook_data) == 0) {
613				/*
614				 * Packet from hook not in filter
615				 * call addfilter for this hook to fix.
616				 */
617				error = EOPNOTSUPP;
618				goto drop;
619			}
620			eth_vtag = (hook_data & VLAN_TAG_MASK);
621			if ((priv->encap_enable & VLAN_ENCAP_FROM_FILTER) == 0) {
622				/* Just set packet header tag and send. */
623				m->m_flags |= M_VLANTAG;
624				m->m_pkthdr.ether_vtag = eth_vtag;
625				goto send_packet;
626			}
627		} else { /* nomatch_hook */
628			if ((priv->encap_enable & VLAN_ENCAP_FROM_NOMATCH) == 0 ||
629			    (m->m_flags & M_VLANTAG) == 0)
630				goto send_packet;
631			/* Encapsulate tagged packet. */
632			eth_vtag = m->m_pkthdr.ether_vtag;
633			m->m_pkthdr.ether_vtag = 0;
634			m->m_flags &= ~M_VLANTAG;
635		}
636
637		/*
638		 * Transform the Ethernet header into an Ethernet header
639		 * with 802.1Q encapsulation.
640		 * Mod of: ether_vlanencap.
641		 *
642		 * TPID = ether type encap
643		 * Move DstMAC and SrcMAC from ETHER_TYPE.
644		 * Before:
645		 *  [free space ] [dmac] [smac] [ether_type] [payload]
646		 *  <<<<<<<<<<<<< |-----------| |--------------------|
647		 * After:
648		 *  [dmac] [smac] [TPID] [PCP/CFI/VID] [ether_type] [payload]
649		 *  |-----------| |-- inserted tag --| |--------------------|
650		 */
651		M_PREPEND(m, ETHER_VLAN_ENCAP_LEN, M_NOWAIT);
652		if (m == NULL)
653			error = ENOMEM;
654		else
655			error = m_chk(&m, ETHER_VLAN_HDR_LEN);
656		if (error != 0)
657			goto mchk_err;
658
659		evl = mtod(m, struct ether_vlan_header *);
660		bcopy(((char *)evl + ETHER_VLAN_ENCAP_LEN),
661		    (char *)evl, (ETHER_ADDR_LEN * 2));
662		evl->evl_encap_proto = priv->encap_proto;
663		evl->evl_tag = htons(eth_vtag);
664	}
665
666send_packet:
667	NG_FWD_NEW_DATA(error, item, dst_hook, m);
668	return (error);
669net_down:
670	error = ENETDOWN;
671drop:
672	m_freem(m);
673mchk_err:
674	NG_FREE_ITEM(item);
675	return (error);
676}
677
678static int
679ng_vlan_shutdown(node_p node)
680{
681	const priv_p priv = NG_NODE_PRIVATE(node);
682
683	NG_NODE_SET_PRIVATE(node, NULL);
684	NG_NODE_UNREF(node);
685	free(priv, M_NETGRAPH);
686	return (0);
687}
688
689static int
690ng_vlan_disconnect(hook_p hook)
691{
692	const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
693	uintptr_t hook_data;
694
695	if (hook == priv->downstream_hook)
696		priv->downstream_hook = NULL;
697	else if (hook == priv->nomatch_hook)
698		priv->nomatch_hook = NULL;
699	else {
700		/* Purge a rule that refers to this hook. */
701		hook_data = (uintptr_t)NG_HOOK_PRIVATE(hook);
702		if (IS_HOOK_VLAN_SET(hook_data))
703			priv->vlan_hook[EVL_VLANOFTAG(hook_data)] = NULL;
704	}
705	NG_HOOK_SET_PRIVATE(hook, NULL);
706	if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) &&
707	    (NG_NODE_IS_VALID(NG_HOOK_NODE(hook))))
708		ng_rmnode_self(NG_HOOK_NODE(hook));
709	return (0);
710}
711