2 * SPDX-License-Identifier: BSD-2-Clause
4 * Copyright (c) 2003 IPNET Internet Communication Company
5 * Copyright (c) 2011 - 2012 Rozhuk Ivan <rozhuk.im@gmail.com>
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * Author: Ruslan Ermilov <ru@FreeBSD.org>
32 #include <sys/param.h>
33 #include <sys/errno.h>
34 #include <sys/kernel.h>
35 #include <sys/malloc.h>
37 #include <sys/queue.h>
38 #include <sys/socket.h>
39 #include <sys/systm.h>
41 #include <net/ethernet.h>
43 #include <net/if_vlan_var.h>
45 #include <netgraph/ng_message.h>
46 #include <netgraph/ng_parse.h>
47 #include <netgraph/ng_vlan.h>
48 #include <netgraph/netgraph.h>
50 struct ng_vlan_private {
51 hook_p downstream_hook;
53 uint32_t decap_enable;
54 uint32_t encap_enable;
56 hook_p vlan_hook[(EVL_VLID_MASK + 1)];
58 typedef struct ng_vlan_private *priv_p;
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)
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;
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
82 ng_vlan_getTableLength(const struct ng_parse_type *type,
83 const u_char *start, const u_char *buf)
85 const struct ng_vlan_table *const table =
86 (const struct ng_vlan_table *)(buf - sizeof(u_int32_t));
91 /* Parse type for struct ng_vlan_table. */
92 static const struct ng_parse_array_info ng_vlan_table_array_info = {
94 ng_vlan_getTableLength
96 static const struct ng_parse_type ng_vlan_table_array_type = {
98 &ng_vlan_table_array_info
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
107 /* List of commands and how to convert arguments to/from ASCII. */
108 static const struct ng_cmdlist ng_vlan_cmdlist[] = {
113 &ng_vlan_filter_type,
120 &ng_parse_hookbuf_type,
132 NGM_VLAN_DEL_VID_FLT,
134 &ng_parse_uint16_type,
142 &ng_parse_hint32_type
148 &ng_parse_hint32_type,
156 &ng_parse_hint32_type
162 &ng_parse_hint32_type,
167 NGM_VLAN_GET_ENCAP_PROTO,
170 &ng_parse_hint16_type
174 NGM_VLAN_SET_ENCAP_PROTO,
176 &ng_parse_hint16_type,
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,
193 NETGRAPH_INIT(vlan, &ng_vlan_typestruct);
200 m_chk(struct mbuf **mp, int len)
203 if ((*mp)->m_pkthdr.len < len) {
208 if ((*mp)->m_len < len && ((*mp) = m_pullup((*mp), len)) == NULL)
215 * Netgraph node functions.
219 ng_vlan_constructor(node_p node)
223 priv = malloc(sizeof(*priv), M_NETGRAPH, M_WAITOK | M_ZERO);
224 priv->decap_enable = 0;
225 priv->encap_enable = VLAN_ENCAP_FROM_FILTER;
226 priv->encap_proto = htons(ETHERTYPE_VLAN);
227 NG_NODE_SET_PRIVATE(node, priv);
232 ng_vlan_newhook(node_p node, hook_p hook, const char *name)
234 const priv_p priv = NG_NODE_PRIVATE(node);
236 if (strcmp(name, NG_VLAN_HOOK_DOWNSTREAM) == 0)
237 priv->downstream_hook = hook;
238 else if (strcmp(name, NG_VLAN_HOOK_NOMATCH) == 0)
239 priv->nomatch_hook = hook;
242 * Any other hook name is valid and can
243 * later be associated with a filter rule.
246 NG_HOOK_SET_PRIVATE(hook, NULL);
251 ng_vlan_rcvmsg(node_p node, item_p item, hook_p lasthook)
253 const priv_p priv = NG_NODE_PRIVATE(node);
254 struct ng_mesg *msg, *resp = NULL;
255 struct ng_vlan_filter *vf;
257 struct ng_vlan_table *t;
263 NGI_GET_MSG(item, msg);
264 /* Deal with message according to cookie and command. */
265 switch (msg->header.typecookie) {
266 case NGM_VLAN_COOKIE:
267 switch (msg->header.cmd) {
268 case NGM_VLAN_ADD_FILTER:
269 /* Check that message is long enough. */
270 if (msg->header.arglen != sizeof(*vf)) {
274 vf = (struct ng_vlan_filter *)msg->data;
275 /* Sanity check the VLAN ID value. */
276 #ifdef NG_VLAN_USE_OLD_VLAN_NAME
277 if (vf->vid == 0 && vf->vid != vf->vlan) {
279 } else if (vf->vid != 0 && vf->vlan != 0 &&
280 vf->vid != vf->vlan) {
285 if (vf->vid & ~EVL_VLID_MASK ||
291 /* Check that a referenced hook exists. */
292 hook = ng_findhook(node, vf->hook_name);
297 /* And is not one of the special hooks. */
298 if (hook == priv->downstream_hook ||
299 hook == priv->nomatch_hook) {
303 /* And is not already in service. */
304 if (IS_HOOK_VLAN_SET(NG_HOOK_PRIVATE(hook))) {
308 /* Check we don't already trap this VLAN. */
309 if (priv->vlan_hook[vf->vid] != NULL) {
313 /* Link vlan and hook together. */
314 NG_HOOK_SET_PRIVATE(hook,
315 (void *)(HOOK_VLAN_TAG_SET_MASK |
316 EVL_MAKETAG(vf->vid, vf->pcp, vf->cfi)));
317 priv->vlan_hook[vf->vid] = hook;
319 case NGM_VLAN_DEL_FILTER:
320 /* Check that message is long enough. */
321 if (msg->header.arglen != NG_HOOKSIZ) {
325 /* Check that hook exists and is active. */
326 hook = ng_findhook(node, (char *)msg->data);
331 hook_data = (uintptr_t)NG_HOOK_PRIVATE(hook);
332 if (IS_HOOK_VLAN_SET(hook_data) == 0) {
337 KASSERT(priv->vlan_hook[EVL_VLANOFTAG(hook_data)] == hook,
338 ("%s: NGM_VLAN_DEL_FILTER: Invalid VID for Hook = %s\n",
339 __func__, (char *)msg->data));
341 /* Purge a rule that refers to this hook. */
342 priv->vlan_hook[EVL_VLANOFTAG(hook_data)] = NULL;
343 NG_HOOK_SET_PRIVATE(hook, NULL);
345 case NGM_VLAN_DEL_VID_FLT:
346 /* Check that message is long enough. */
347 if (msg->header.arglen != sizeof(uint16_t)) {
351 vid = (*((uint16_t *)msg->data));
352 /* Sanity check the VLAN ID value. */
353 if (vid & ~EVL_VLID_MASK) {
357 /* Check that hook exists and is active. */
358 hook = priv->vlan_hook[vid];
363 hook_data = (uintptr_t)NG_HOOK_PRIVATE(hook);
364 if (IS_HOOK_VLAN_SET(hook_data) == 0) {
369 KASSERT(EVL_VLANOFTAG(hook_data) == vid,
370 ("%s: NGM_VLAN_DEL_VID_FLT:"
371 " Invalid VID Hook = %us, must be: %us\n",
372 __func__, (uint16_t )EVL_VLANOFTAG(hook_data),
375 /* Purge a rule that refers to this hook. */
376 priv->vlan_hook[vid] = NULL;
377 NG_HOOK_SET_PRIVATE(hook, NULL);
379 case NGM_VLAN_GET_TABLE:
380 /* Calculate vlans. */
382 for (i = 0; i < (EVL_VLID_MASK + 1); i ++) {
383 if (priv->vlan_hook[i] != NULL &&
384 NG_HOOK_IS_VALID(priv->vlan_hook[i]))
388 /* Allocate memory for response. */
389 NG_MKRESPONSE(resp, msg, sizeof(*t) +
390 vlan_count * sizeof(*t->filter), M_NOWAIT);
396 /* Pack data to response. */
397 t = (struct ng_vlan_table *)resp->data;
400 for (i = 0; i < (EVL_VLID_MASK + 1); i ++) {
401 hook = priv->vlan_hook[i];
402 if (hook == NULL || NG_HOOK_NOT_VALID(hook))
404 hook_data = (uintptr_t)NG_HOOK_PRIVATE(hook);
405 if (IS_HOOK_VLAN_SET(hook_data) == 0)
408 KASSERT(EVL_VLANOFTAG(hook_data) == i,
409 ("%s: NGM_VLAN_GET_TABLE:"
410 " hook %s VID = %us, must be: %i\n",
411 __func__, NG_HOOK_NAME(hook),
412 (uint16_t)EVL_VLANOFTAG(hook_data), i));
414 #ifdef NG_VLAN_USE_OLD_VLAN_NAME
418 vf->pcp = EVL_PRIOFTAG(hook_data);
419 vf->cfi = EVL_CFIOFTAG(hook_data);
420 strncpy(vf->hook_name,
421 NG_HOOK_NAME(hook), NG_HOOKSIZ);
426 case NGM_VLAN_GET_DECAP:
427 NG_MKRESPONSE(resp, msg, sizeof(uint32_t), M_NOWAIT);
432 (*((uint32_t *)resp->data)) = priv->decap_enable;
434 case NGM_VLAN_SET_DECAP:
435 if (msg->header.arglen != sizeof(uint32_t)) {
439 priv->decap_enable = (*((uint32_t *)msg->data));
441 case NGM_VLAN_GET_ENCAP:
442 NG_MKRESPONSE(resp, msg, sizeof(uint32_t), M_NOWAIT);
447 (*((uint32_t *)resp->data)) = priv->encap_enable;
449 case NGM_VLAN_SET_ENCAP:
450 if (msg->header.arglen != sizeof(uint32_t)) {
454 priv->encap_enable = (*((uint32_t *)msg->data));
456 case NGM_VLAN_GET_ENCAP_PROTO:
457 NG_MKRESPONSE(resp, msg, sizeof(uint16_t), M_NOWAIT);
462 (*((uint16_t *)resp->data)) = ntohs(priv->encap_proto);
464 case NGM_VLAN_SET_ENCAP_PROTO:
465 if (msg->header.arglen != sizeof(uint16_t)) {
469 priv->encap_proto = htons((*((uint16_t *)msg->data)));
471 default: /* Unknown command. */
476 case NGM_FLOW_COOKIE:
478 struct ng_mesg *copy;
481 * Flow control messages should come only
485 if (lasthook == NULL)
487 if (lasthook != priv->downstream_hook)
489 /* Broadcast the event to all uplinks. */
490 for (i = 0; i < (EVL_VLID_MASK + 1); i ++) {
491 if (priv->vlan_hook[i] == NULL)
494 NG_COPYMESSAGE(copy, msg, M_NOWAIT);
497 NG_SEND_MSG_HOOK(error, node, copy,
498 priv->vlan_hook[i], 0);
502 default: /* Unknown type cookie. */
506 NG_RESPOND_MSG(error, node, item, resp);
512 ng_vlan_rcvdata(hook_p hook, item_p item)
514 const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
515 struct ether_header *eh;
516 struct ether_vlan_header *evl;
519 uint16_t vid, eth_vtag;
525 /* Make sure we have an entire header. */
526 error = m_chk(&m, ETHER_HDR_LEN);
530 eh = mtod(m, struct ether_header *);
531 if (hook == priv->downstream_hook) {
533 * If from downstream, select between a match hook
534 * or the nomatch hook.
537 dst_hook = priv->nomatch_hook;
539 /* Skip packets without tag. */
540 if ((m->m_flags & M_VLANTAG) == 0 &&
541 eh->ether_type != priv->encap_proto) {
542 if (dst_hook == NULL)
547 /* Process packets with tag. */
548 if (m->m_flags & M_VLANTAG) {
550 * Packet is tagged, m contains a normal
551 * Ethernet frame; tag is stored out-of-band.
554 vid = EVL_VLANOFTAG(m->m_pkthdr.ether_vtag);
555 } else { /* eh->ether_type == priv->encap_proto */
556 error = m_chk(&m, ETHER_VLAN_HDR_LEN);
559 evl = mtod(m, struct ether_vlan_header *);
560 vid = EVL_VLANOFTAG(ntohs(evl->evl_tag));
563 if (priv->vlan_hook[vid] != NULL) {
565 * VLAN filter: always remove vlan tags and
566 * decapsulate packet.
568 dst_hook = priv->vlan_hook[vid];
569 if (evl == NULL) { /* m->m_flags & M_VLANTAG */
570 m->m_pkthdr.ether_vtag = 0;
571 m->m_flags &= ~M_VLANTAG;
574 } else { /* nomatch_hook */
575 if (dst_hook == NULL)
577 if (evl == NULL || priv->decap_enable == 0)
579 /* Save tag out-of-band. */
580 m->m_pkthdr.ether_vtag = ntohs(evl->evl_tag);
581 m->m_flags |= M_VLANTAG;
586 * TPID = ether type encap
587 * Move DstMAC and SrcMAC to ETHER_TYPE.
589 * [dmac] [smac] [TPID] [PCP/CFI/VID] [ether_type] [payload]
590 * |-----------| >>>>>>>>>>>>>>>>>>>> |--------------------|
592 * [free space ] [dmac] [smac] [ether_type] [payload]
593 * |-----------| |--------------------|
595 bcopy((char *)evl, ((char *)evl + ETHER_VLAN_ENCAP_LEN),
596 (ETHER_ADDR_LEN * 2));
597 m_adj(m, ETHER_VLAN_ENCAP_LEN);
600 * It is heading towards the downstream.
601 * If from nomatch, pass it unmodified.
602 * Otherwise, do the VLAN encapsulation.
604 dst_hook = priv->downstream_hook;
605 if (dst_hook == NULL)
607 if (hook != priv->nomatch_hook) {/* Filter hook. */
608 hook_data = (uintptr_t)NG_HOOK_PRIVATE(hook);
609 if (IS_HOOK_VLAN_SET(hook_data) == 0) {
611 * Packet from hook not in filter
612 * call addfilter for this hook to fix.
617 eth_vtag = (hook_data & VLAN_TAG_MASK);
618 if ((priv->encap_enable & VLAN_ENCAP_FROM_FILTER) == 0) {
619 /* Just set packet header tag and send. */
620 m->m_flags |= M_VLANTAG;
621 m->m_pkthdr.ether_vtag = eth_vtag;
624 } else { /* nomatch_hook */
625 if ((priv->encap_enable & VLAN_ENCAP_FROM_NOMATCH) == 0 ||
626 (m->m_flags & M_VLANTAG) == 0)
628 /* Encapsulate tagged packet. */
629 eth_vtag = m->m_pkthdr.ether_vtag;
630 m->m_pkthdr.ether_vtag = 0;
631 m->m_flags &= ~M_VLANTAG;
635 * Transform the Ethernet header into an Ethernet header
636 * with 802.1Q encapsulation.
637 * Mod of: ether_vlanencap.
639 * TPID = ether type encap
640 * Move DstMAC and SrcMAC from ETHER_TYPE.
642 * [free space ] [dmac] [smac] [ether_type] [payload]
643 * <<<<<<<<<<<<< |-----------| |--------------------|
645 * [dmac] [smac] [TPID] [PCP/CFI/VID] [ether_type] [payload]
646 * |-----------| |-- inserted tag --| |--------------------|
648 M_PREPEND(m, ETHER_VLAN_ENCAP_LEN, M_NOWAIT);
652 error = m_chk(&m, ETHER_VLAN_HDR_LEN);
656 evl = mtod(m, struct ether_vlan_header *);
657 bcopy(((char *)evl + ETHER_VLAN_ENCAP_LEN),
658 (char *)evl, (ETHER_ADDR_LEN * 2));
659 evl->evl_encap_proto = priv->encap_proto;
660 evl->evl_tag = htons(eth_vtag);
664 NG_FWD_NEW_DATA(error, item, dst_hook, m);
676 ng_vlan_shutdown(node_p node)
678 const priv_p priv = NG_NODE_PRIVATE(node);
680 NG_NODE_SET_PRIVATE(node, NULL);
682 free(priv, M_NETGRAPH);
687 ng_vlan_disconnect(hook_p hook)
689 const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
692 if (hook == priv->downstream_hook)
693 priv->downstream_hook = NULL;
694 else if (hook == priv->nomatch_hook)
695 priv->nomatch_hook = NULL;
697 /* Purge a rule that refers to this hook. */
698 hook_data = (uintptr_t)NG_HOOK_PRIVATE(hook);
699 if (IS_HOOK_VLAN_SET(hook_data))
700 priv->vlan_hook[EVL_VLANOFTAG(hook_data)] = NULL;
702 NG_HOOK_SET_PRIVATE(hook, NULL);
703 if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) &&
704 (NG_NODE_IS_VALID(NG_HOOK_NODE(hook))))
705 ng_rmnode_self(NG_HOOK_NODE(hook));