Netgraph node type for IEEE 802.1Q VLAN tagging.

This commit is contained in:
Ruslan Ermilov 2004-03-01 17:22:16 +00:00
parent 57f5e24fec
commit 407ea29095
6 changed files with 671 additions and 1 deletions

View File

@ -189,6 +189,7 @@ MAN= aac.4 \
ng_UI.4 \
ng_uni.4 \
ng_vjc.4 \
ng_vlan.4 \
nmdm.4 \
nsp.4 \
null.4 \

143
share/man/man4/ng_vlan.4 Normal file
View File

@ -0,0 +1,143 @@
.\" Copyright (c) 2003 Ruslan Ermilov
.\" All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\" $FreeBSD$
.\"
.Dd March 1, 2004
.Dt NG_VLAN 4
.Os
.Sh NAME
.Nm ng_vlan
.Nd IEEE 802.1Q VLAN tagging netgraph node type
.Sh SYNOPSIS
.In netgraph/ng_vlan.h
.Sh DESCRIPTION
The
.Nm vlan
node type multiplexes frames tagged according to
the IEEE 802.1Q standard between different hooks.
.Pp
Each node has two special hooks,
.Va downsteam
and
.Va nomatch ,
and an arbitrary number of
.Dq vlan
hooks, each associated with a particular VLAN tag.
.Pp
An
.Dv ETHERTYPE_VLAN
frame received on the
.Va downstream
hook with a tag that the node has been configured to filter
is sent out the corresponding
.Dq vlan
hook.
If it does not match any of the configured tags, or is not of a type
.Dv ETHERTYPE_VLAN ,
it is sent out the
.Va nomatch
hook.
If the
.Va nomatch
hook is not connected, the packet is dropped.
.Pp
An Ethernet frame received on the
.Va nomatch
hook is passed unmodified to the
.Va downstream
hook.
.Pp
An Ethernet frame received on any of the
.Dq vlan
hooks is tagged accordingly and sent out the
.Va downstream
hook.
.Sh HOOKS
This node type supports the following hooks:
.Bl -tag -width ".Va downstream"
.It Va downstream
Typically this hook would be connected to a
.Xr ng_ether 4
node, using the
.Va lower
hook.
.It Va nomatch
Typically this hook would also be connected to an
.Xr ng_ether 4
type node using the
.Va upper
hook.
.It Aq Em "any valid name"
Any other hook name will be accepted and should later be associated with
a particular tag.
Typically this hook would be attached to an
.Xr ng_eiface 4
type node using the
.Va ether
hook.
.El
.Sh CONTROL MESSAGES
This node type supports the generic control messages, plus the following:
.Bl -tag -width indent
.It Dv NGM_VLAN_ADD_FILTER Pq Li addfilter
Associates a hook with the tag.
.It Dv NGM_VLAN_DEL_FILTER Pq Li delfilter
Disassociates a hook from the tag.
.It Dv NGM_VLAN_GET_TABLE Pq Li gettable
Returns a table of all hook/tag associations.
.El
.Sh EXAMPLES
.Bd -literal
#!/bin/sh
ETHER_IF=rl0
ngctl -f- <<EOF
shutdown ${ETHER_IF}:
mkpeer ${ETHER_IF}: vlan lower downstream
name ${ETHER_IF}:lower vlan
connect ${ETHER_IF}: vlan: upper nomatch
EOF
ngctl mkpeer vlan: eiface vlan123 ether
ngctl msg vlan: addfilter '{ vlan=123 hook="vlan123" }'
.Ed
.Sh SHUTDOWN
This node shuts down upon receipt of a
.Dv NGM_SHUTDOWN
control message, or when all hooks have been disconnected.
.Sh SEE ALSO
.Xr netgraph 4 ,
.Xr ng_eiface 4 ,
.Xr ng_ether 4 ,
.Xr ngctl 8 ,
.Xr nghook 8
.Sh HISTORY
The
.Nm
node type appeared in
.Fx 4.10 .
.Sh AUTHORS
.An Ruslan Ermilov Aq ru@FreeBSD.org

View File

@ -35,7 +35,8 @@ SUBDIR= UI \
sync_sr \
tee \
tty \
vjc
vjc \
vlan
.if !defined(NOCRYPT) && exists(${.CURDIR}/../../crypto/rc4/rc4.c)
_mppc= mppc

View File

@ -0,0 +1,6 @@
# $FreeBSD$
KMOD= ng_vlan
SRCS= ng_vlan.c
.include <bsd.kmod.mk>

444
sys/netgraph/ng_vlan.c Normal file
View File

@ -0,0 +1,444 @@
/*-
* Copyright (c) 2003 IPNET Internet Communication Company
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* Author: Ruslan Ermilov <ru@FreeBSD.org>
*
* $FreeBSD$
*/
#include <sys/param.h>
#include <sys/errno.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/systm.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_vlan_var.h>
#include <netgraph/ng_message.h>
#include <netgraph/ng_parse.h>
#include <netgraph/ng_vlan.h>
#include <netgraph/netgraph.h>
static ng_constructor_t ng_vlan_constructor;
static ng_rcvmsg_t ng_vlan_rcvmsg;
static ng_shutdown_t ng_vlan_shutdown;
static ng_newhook_t ng_vlan_newhook;
static ng_rcvdata_t ng_vlan_rcvdata;
static ng_disconnect_t ng_vlan_disconnect;
/* Parse type for struct ng_vlan_filter. */
static const struct ng_parse_struct_field ng_vlan_filter_fields[] =
NG_VLAN_FILTER_FIELDS;
static const struct ng_parse_type ng_vlan_filter_type = {
&ng_parse_struct_type,
&ng_vlan_filter_fields
};
static int
ng_vlan_getTableLength(const struct ng_parse_type *type,
const u_char *start, const u_char *buf)
{
const struct ng_vlan_table *const table =
(const struct ng_vlan_table *)(buf - sizeof(u_int32_t));
return table->n;
}
/* Parse type for struct ng_vlan_table. */
static const struct ng_parse_array_info ng_vlan_table_array_info = {
&ng_vlan_filter_type,
ng_vlan_getTableLength
};
static const struct ng_parse_type ng_vlan_table_array_type = {
&ng_parse_array_type,
&ng_vlan_table_array_info
};
static const struct ng_parse_struct_field ng_vlan_table_fields[] =
NG_VLAN_TABLE_FIELDS;
static const struct ng_parse_type ng_vlan_table_type = {
&ng_parse_struct_type,
&ng_vlan_table_fields
};
/* List of commands and how to convert arguments to/from ASCII. */
static const struct ng_cmdlist ng_vlan_cmdlist[] = {
{
NGM_VLAN_COOKIE,
NGM_VLAN_ADD_FILTER,
"addfilter",
&ng_vlan_filter_type,
NULL
},
{
NGM_VLAN_COOKIE,
NGM_VLAN_DEL_FILTER,
"delfilter",
&ng_parse_hookbuf_type,
NULL
},
{
NGM_VLAN_COOKIE,
NGM_VLAN_GET_TABLE,
"gettable",
NULL,
&ng_vlan_table_type
},
{ 0 }
};
static struct ng_type ng_vlan_typestruct = {
NG_ABI_VERSION,
NG_VLAN_NODE_TYPE,
NULL,
ng_vlan_constructor,
ng_vlan_rcvmsg,
ng_vlan_shutdown,
ng_vlan_newhook,
NULL,
NULL,
ng_vlan_rcvdata,
ng_vlan_disconnect,
ng_vlan_cmdlist
};
NETGRAPH_INIT(vlan, &ng_vlan_typestruct);
struct filter {
LIST_ENTRY(filter) next;
u_int16_t vlan;
hook_p hook;
};
#define HASHSIZE 16
#define HASH(id) ((((id) >> 8) ^ ((id) >> 4) ^ (id)) & 0x0f)
LIST_HEAD(filterhead, filter);
typedef struct {
hook_p downstream_hook;
hook_p nomatch_hook;
struct filterhead hashtable[HASHSIZE];
u_int32_t nent;
} *priv_p;
static struct filter *
ng_vlan_findentry(priv_p priv, u_int16_t vlan)
{
struct filterhead *chain = &priv->hashtable[HASH(vlan)];
struct filter *f;
LIST_FOREACH(f, chain, next)
if (f->vlan == vlan)
return (f);
return (NULL);
}
static int
ng_vlan_constructor(node_p node)
{
priv_p priv;
int i;
MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO);
if (priv == NULL)
return (ENOMEM);
for (i = 0; i < HASHSIZE; i++)
LIST_INIT(&priv->hashtable[i]);
NG_NODE_SET_PRIVATE(node, priv);
return (0);
}
static int
ng_vlan_newhook(node_p node, hook_p hook, const char *name)
{
const priv_p priv = NG_NODE_PRIVATE(node);
if (strcmp(name, NG_VLAN_HOOK_DOWNSTREAM) == 0)
priv->downstream_hook = hook;
else if (strcmp(name, NG_VLAN_HOOK_NOMATCH) == 0)
priv->nomatch_hook = hook;
else {
/*
* Any other hook name is valid and can
* later be associated with a filter rule.
*/
}
NG_HOOK_SET_PRIVATE(hook, NULL);
return (0);
}
static int
ng_vlan_rcvmsg(node_p node, item_p item, hook_p lasthook)
{
const priv_p priv = NG_NODE_PRIVATE(node);
int error = 0;
struct ng_mesg *msg, *resp = NULL;
struct ng_vlan_filter *vf;
struct filter *f;
hook_p hook;
struct ng_vlan_table *t;
int i;
NGI_GET_MSG(item, msg);
/* Deal with message according to cookie and command. */
switch (msg->header.typecookie) {
case NGM_VLAN_COOKIE:
switch (msg->header.cmd) {
case NGM_VLAN_ADD_FILTER:
/* Check that message is long enough. */
if (msg->header.arglen != sizeof(*vf)) {
error = EINVAL;
break;
}
vf = (struct ng_vlan_filter *)msg->data;
/* Sanity check the VLAN ID value. */
if (vf->vlan & ~EVL_VLID_MASK) {
error = EINVAL;
break;
}
/* Check that a referenced hook exists. */
hook = ng_findhook(node, vf->hook);
if (hook == NULL) {
error = ENOENT;
break;
}
/* And is not one of the special hooks. */
if (hook == priv->downstream_hook ||
hook == priv->nomatch_hook) {
error = EINVAL;
break;
}
/* And is not already in service. */
if (NG_HOOK_PRIVATE(hook) != NULL) {
error = EEXIST;
break;
}
/* Check we don't already trap this VLAN. */
if (ng_vlan_findentry(priv, vf->vlan)) {
error = EEXIST;
break;
}
/* Create filter. */
MALLOC(f, struct filter *, sizeof(*f),
M_NETGRAPH, M_NOWAIT | M_ZERO);
if (f == NULL) {
error = ENOMEM;
break;
}
/* Link filter and hook together. */
f->hook = hook;
f->vlan = vf->vlan;
NG_HOOK_SET_PRIVATE(hook, f);
/* Register filter in a hash table. */
LIST_INSERT_HEAD(
&priv->hashtable[HASH(f->vlan)], f, next);
priv->nent++;
break;
case NGM_VLAN_DEL_FILTER:
/* Check that message is long enough. */
if (msg->header.arglen != NG_HOOKSIZ) {
error = EINVAL;
break;
}
/* Check that hook exists and is active. */
hook = ng_findhook(node, (char *)msg->data);
if (hook == NULL ||
(f = NG_HOOK_PRIVATE(hook)) == NULL) {
error = ENOENT;
break;
}
/* Purge a rule that refers to this hook. */
NG_HOOK_SET_PRIVATE(hook, NULL);
LIST_REMOVE(f, next);
priv->nent--;
FREE(f, M_NETGRAPH);
break;
case NGM_VLAN_GET_TABLE:
NG_MKRESPONSE(resp, msg, sizeof(*t) +
priv->nent * sizeof(*t->filter), M_NOWAIT);
if (resp == NULL) {
error = ENOMEM;
break;
}
t = (struct ng_vlan_table *)resp->data;
t->n = priv->nent;
vf = &t->filter[0];
for (i = 0; i < HASHSIZE; i++) {
LIST_FOREACH(f, &priv->hashtable[i], next) {
vf->vlan = f->vlan;
strncpy(vf->hook, NG_HOOK_NAME(f->hook),
NG_HOOKSIZ);
vf++;
}
}
break;
default: /* Unknown command. */
error = EINVAL;
break;
}
break;
default: /* Unknown type cookie. */
error = EINVAL;
break;
}
NG_RESPOND_MSG(error, node, item, resp);
NG_FREE_MSG(msg);
return (error);
}
static int
ng_vlan_rcvdata(hook_p hook, item_p item)
{
const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
struct ether_header *eh;
struct ether_vlan_header *evl;
int error;
u_int16_t vlan;
struct mbuf *m;
struct m_tag *mtag;
struct filter *f;
/* Make sure we have an entire header. */
NGI_GET_M(item, m);
if (m->m_len < sizeof(*eh) &&
(m = m_pullup(m, sizeof(*eh))) == NULL) {
NG_FREE_ITEM(item);
return (EINVAL);
}
eh = mtod(m, struct ether_header *);
if (hook == priv->downstream_hook) {
/*
* If from downstream, select between a match hook
* or the nomatch hook.
*/
mtag = m_tag_locate(m, MTAG_VLAN, MTAG_VLAN_TAG, NULL);
if (mtag != NULL || eh->ether_type == htons(ETHERTYPE_VLAN)) {
if (mtag != NULL) {
/*
* Packet is tagged, m contains a normal
* Ethernet frame; tag is stored out-of-band.
*/
vlan = EVL_VLANOFTAG(VLAN_TAG_VALUE(mtag));
(void)&evl; /* XXX silence GCC */
} else {
if (m->m_len < sizeof(*evl) &&
(m = m_pullup(m, sizeof(*evl))) == NULL) {
NG_FREE_ITEM(item);
return (EINVAL);
}
evl = mtod(m, struct ether_vlan_header *);
vlan = EVL_VLANOFTAG(ntohs(evl->evl_tag));
}
if ((f = ng_vlan_findentry(priv, vlan)) != NULL) {
if (mtag != NULL)
m_tag_delete(m, mtag);
else {
evl->evl_encap_proto = evl->evl_proto;
bcopy(mtod(m, caddr_t),
mtod(m, caddr_t) +
ETHER_VLAN_ENCAP_LEN,
ETHER_HDR_LEN);
m_adj(m, ETHER_VLAN_ENCAP_LEN);
}
}
} else
f = NULL;
if (f != NULL)
NG_FWD_NEW_DATA(error, item, f->hook, m);
else
NG_FWD_NEW_DATA(error, item, priv->nomatch_hook, m);
} else {
/*
* It is heading towards the downstream.
* If from nomatch, pass it unmodified.
* Otherwise, do the VLAN encapsulation.
*/
if (hook != priv->nomatch_hook) {
if ((f = NG_HOOK_PRIVATE(hook)) == NULL) {
NG_FREE_ITEM(item);
NG_FREE_M(m);
return (EOPNOTSUPP);
}
M_PREPEND(m, ETHER_VLAN_ENCAP_LEN, M_DONTWAIT);
/* M_PREPEND takes care of m_len and m_pkthdr.len. */
if (m == NULL || (m->m_len < sizeof(*evl) &&
(m = m_pullup(m, sizeof(*evl))) == NULL)) {
NG_FREE_ITEM(item);
return (ENOMEM);
}
/*
* Transform the Ethernet header into an Ethernet header
* with 802.1Q encapsulation.
*/
bcopy(mtod(m, char *) + ETHER_VLAN_ENCAP_LEN,
mtod(m, char *), ETHER_HDR_LEN);
evl = mtod(m, struct ether_vlan_header *);
evl->evl_proto = evl->evl_encap_proto;
evl->evl_encap_proto = htons(ETHERTYPE_VLAN);
evl->evl_tag = htons(f->vlan);
}
NG_FWD_NEW_DATA(error, item, priv->downstream_hook, m);
}
return (error);
}
static int
ng_vlan_shutdown(node_p node)
{
const priv_p priv = NG_NODE_PRIVATE(node);
NG_NODE_SET_PRIVATE(node, NULL);
NG_NODE_UNREF(node);
FREE(priv, M_NETGRAPH);
return (0);
}
static int
ng_vlan_disconnect(hook_p hook)
{
const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
struct filter *f;
if (hook == priv->downstream_hook)
priv->downstream_hook = NULL;
else if (hook == priv->nomatch_hook)
priv->nomatch_hook = NULL;
else {
/* Purge a rule that refers to this hook. */
if ((f = NG_HOOK_PRIVATE(hook)) != NULL) {
LIST_REMOVE(f, next);
priv->nent--;
FREE(f, M_NETGRAPH);
}
}
NG_HOOK_SET_PRIVATE(hook, NULL);
if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) &&
(NG_NODE_IS_VALID(NG_HOOK_NODE(hook))))
ng_rmnode_self(NG_HOOK_NODE(hook));
return (0);
}

75
sys/netgraph/ng_vlan.h Normal file
View File

@ -0,0 +1,75 @@
/*-
* Copyright (c) 2003 IPNET Internet Communication Company
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* Author: Ruslan Ermilov <ru@FreeBSD.org>
*
* $FreeBSD$
*/
#ifndef _NETGRAPH_NG_VLAN_H_
#define _NETGRAPH_NG_VLAN_H_
/* Node type name and magic cookie. */
#define NG_VLAN_NODE_TYPE "vlan"
#define NGM_VLAN_COOKIE 1068486472
/* Hook names. */
#define NG_VLAN_HOOK_DOWNSTREAM "downstream"
#define NG_VLAN_HOOK_NOMATCH "nomatch"
/* Netgraph commands. */
enum {
NGM_VLAN_ADD_FILTER = 1,
NGM_VLAN_DEL_FILTER,
NGM_VLAN_GET_TABLE
};
/* For NGM_VLAN_ADD_FILTER control message. */
struct ng_vlan_filter {
char hook[NG_HOOKSIZ];
u_int16_t vlan;
};
/* Keep this in sync with the above structure definition. */
#define NG_VLAN_FILTER_FIELDS { \
{ "hook", &ng_parse_hookbuf_type }, \
{ "vlan", &ng_parse_uint16_type }, \
{ NULL } \
}
/* Structure returned by NGM_VLAN_GET_TABLE. */
struct ng_vlan_table {
u_int32_t n;
struct ng_vlan_filter filter[0];
};
/* Keep this in sync with the above structure definition. */
#define NG_VLAN_TABLE_FIELDS { \
{ "n", &ng_parse_uint32_type }, \
{ "filter", &ng_vlan_table_array_type }, \
{ NULL } \
}
#endif /* _NETGRAPH_NG_VLAN_H_ */