]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - sys/netgraph/ng_vlan.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / sys / netgraph / ng_vlan.c
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
50 struct 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 };
58 typedef 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
66 static ng_constructor_t ng_vlan_constructor;
67 static ng_rcvmsg_t      ng_vlan_rcvmsg;
68 static ng_shutdown_t    ng_vlan_shutdown;
69 static ng_newhook_t     ng_vlan_newhook;
70 static ng_rcvdata_t     ng_vlan_rcvdata;
71 static ng_disconnect_t  ng_vlan_disconnect;
72
73 /* Parse type for struct ng_vlan_filter. */
74 static const struct ng_parse_struct_field ng_vlan_filter_fields[] =
75         NG_VLAN_FILTER_FIELDS;
76 static const struct ng_parse_type ng_vlan_filter_type = {
77         &ng_parse_struct_type,
78         &ng_vlan_filter_fields
79 };
80
81 static int
82 ng_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. */
92 static const struct ng_parse_array_info ng_vlan_table_array_info = {
93         &ng_vlan_filter_type,
94         ng_vlan_getTableLength
95 };
96 static const struct ng_parse_type ng_vlan_table_array_type = {
97         &ng_parse_array_type,
98         &ng_vlan_table_array_info
99 };
100 static const struct ng_parse_struct_field ng_vlan_table_fields[] =
101         NG_VLAN_TABLE_FIELDS;
102 static 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. */
108 static 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
182 static 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 };
193 NETGRAPH_INIT(vlan, &ng_vlan_typestruct);
194
195
196 /*
197  * Helper functions.
198  */
199
200 static __inline int
201 m_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
220 static int
221 ng_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
233 static int
234 ng_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
252 static int
253 ng_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 responce. */
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 responce. */
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
513 static int
514 ng_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: allways 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
666 send_packet:
667         NG_FWD_NEW_DATA(error, item, dst_hook, m);
668         return (error);
669 net_down:
670         error = ENETDOWN;
671 drop:
672         m_freem(m);
673 mchk_err:
674         NG_FREE_ITEM(item);
675         return (error);
676 }
677
678 static int
679 ng_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
689 static int
690 ng_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 }