New Netgraph module ng_macfilter:

Macfilter to route packets through different hooks based on sender MAC address.

Based on ng_macfilter written by Pekka Nikander

Sponsered by Retina b.v.

Reviewed by:	afedorov
MFC after:	2 weeks
Differential Revision:	https://reviews.freebsd.org/D27268
This commit is contained in:
Nick Hibma 2020-12-08 15:09:42 +00:00
parent b7b5d7d7f5
commit e8db04c389
10 changed files with 1689 additions and 0 deletions

View File

@ -356,6 +356,7 @@ MAN= aac.4 \
ng_l2cap.4 \
ng_l2tp.4 \
ng_lmi.4 \
ng_macfilter.4 \
ng_mppc.4 \
ng_nat.4 \
ng_netflow.4 \

View File

@ -0,0 +1,222 @@
.\" Copyright (c) 2012-2017 Pekka Nikander
.\" Copyright (c) 2018 Retina b.v.
.\" 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.
.\" 3. Neither the name of the University nor the names of its contributors
.\" may be used to endorse or promote products derived from this software
.\" without specific prior written permission.
.\"
.\" 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 December 10, 2018
.Dt NG_MACFILTER 4
.Os
.Sh NAME
.Nm ng_macfilter
.Nd packet filtering netgraph node using ethernet MAC addresses
.Sh SYNOPSIS
.In sys/types.h
.In netgraph/ng_macfilter.h
.Sh DESCRIPTION
The
.Nm macfilter
allows routing ethernet packets over different hooks based on the sender MAC
address.
.Pp
This processing is done when traffic flows from the
.Dq ether
hook trough
.Nm macfilter
to one of the outgoing hooks.
Outbound hooks can be added to and remove from
.Nm macfilter
and arbitrarily named.
By default one hook called
.Dq default
is present and used for all packets which have no MAC address in the MAC table.
By adding MAC addresses to the MAC table traffic coming from this host can be
directed out other hooks.
.Nm macfilter
keeps track of packets and bytes from and to this MAC address in the MAC table.
.Pp
Packets are not altered in any way.
If hooks are not connected, packets are
dropped.
.Sh HOOKS
This node type by default has an
.Dv ether
hook, to be connected to the
.Dv lower
hook of the NIC, and a
.Dv default
hook where packets are sent if the MAC adddress is not found in the table.
.Nm macfilter
supports up to
.Dv NG_MACFILTER_UPPER_NUM
hooks to be connected to the NIC's upper hook.
Other nodes can be inserted to provide additional processing.
All outbound can be combined back into one by using
.Dv ng_one2many .
.Sh CONTROL MESSAGES
This node type supports the generic control messages, plus the
following:
.Bl -tag -width foo
.It Dv NGM_MACFILTER_RESET Pq Ic reset
Resets the MAC table in the node.
.It Dv NGM_MACFILTER_DIRECT Pq Ic direct
Takes the following argument struct:
.Bd -literal -offset indent
struct ngm_macfilter_direct {
u_char ether[ETHER_ADDR_LEN]; /* MAC address */
u_char hookname[NG_HOOKSIZ]; /* Upper hook name*/
};
.Ed
The given ethernet MAC address will be forwarded out the named hook.
.It Dv NGM_MACFILTER_DIRECT_HOOKID Pq Ic directi
Takes the following argument struct:
.Bd -literal -offset indent
struct ngm_macfilter_direct_hookid {
u_char ether[ETHER_ADDR_LEN]; /* MAC address */
u_int16_t hookid; /* Upper hook hookid */
};
.Ed
The given ethernet MAC address will be forwarded out the hook at id
.Dv hookid .
.It Dv NGM_MACFILTER_GET_MACS Pq Ic getmacs
Returns the list of MAC addresses in the node in the following structure:
.Bd -literal -offset indent
struct ngm_macfilter_mac {
u_char ether[ETHER_ADDR_LEN]; /* MAC address */
u_int16_t hookid; /* Upper hook hookid */
u_int64_t packets_in; /* packets in from downstream */
u_int64_t bytes_in; /* bytes in from upstream */
u_int64_t packets_out; /* packets out towards downstream */
u_int64_t bytes_out; /* bytes out towards downstream */
};
struct ngm_macfilter_macs {
u_int32_t n; /* Number of entries in macs */
struct ngm_macfilter_mac macs[]; /* Macs table */
};
.Ed
.It Dv NGM_MACFILTER_GETCLR_MACS Pq Ic getclrmacs
Same as above, but will also atomically clear the
.Dv packets_in ,
.Dv bytes_in ,
.Dv packets_out , and
.Dv bytes_out
fields in the table.
.It Dv NGM_MACFILTER_CLR_STATS Pq Ic clrmacs
Will clear the per MAC address packet and byte counters.
.It Dv NGM_MACFILTER_GET_HOOKS Pq Ic gethooks
Will return a list of hooks and their hookids in an array of the following struct's:
.Bd -literal -offset indent
struct ngm_macfilter_hook {
u_char hookname[NG_HOOKSIZ]; /* Upper hook name*/
u_int16_t hookid; /* Upper hook hookid */
u_int32_t maccnt; /* Number of mac addresses associated with hook */
};
.Ed
.El
.Sh SHUTDOWN
This node shuts down upon receipt of a
.Dv NGM_SHUTDOWN
control message or when all have been disconnected.
.Sh EXAMPLES
The following netgraph configuration will apply
.Xr ipfw 8
tag 42 to each packet that is routed over the
.Dq accepted
hook.
The graph looks like the following:
.Bd -literal -offset indent
/------<one>-[combiner]-<many1>--------\\
<upper> | <out>
/ <many0> \\
[em0] | [tagger]
\\ <default> /
<lower> | <in>
\\----<ether>-[macfilter]-<accepted>-----/
.Ed
.Pp
Commands:
.Bd -literal -offset indent
ngctl mkpeer em0: macfilter lower ether
ngctl name em0:lower macfilter
# Funnel both streams back into ether:upper
ngctl mkpeer em0: one2many upper one
ngctl name em0:upper recombiner
# Connect macfilter:default to recombiner:many0
ngctl connect macfilter: recombiner: default many0
# Connect macfilter:accepted to tagger:in
ngctl mkpeer macfilter: tag accepted in
ngctl name macfilter:accepted tagger
# Connect tagger:out to recombiner:many1
ngctl connect tagger: recombiner: out many1
# Mark tag all traffic through tagger in -> out with an ipfw tag 42
ngctl msg tagger: sethookin '{ thisHook="in" ifNotMatch="out" }'
ngctl msg tagger: sethookout '{ thisHook="out" tag_cookie=1148380143 tag_id=42 }'
# Pass traffic from ether:upper / combiner:one via combiner:many0 on to
# macfilter:default and on to ether:lower.
ngctl msg recombiner: setconfig '{ xmitAlg=3 failAlg=1 enabledLinks=[ 1 1 ] }'
.Ed
.Pp
.Em Note :
The tag_cookie 1148380143 was retrieved from
.Dv MTAG_IPFW
in
.Pa /usr/include/netinet/ip_var.h .
.Pp
The following command can be used to add a MAC address to be output via
.Dv macfilter:accepted :
.Bd -literal -offset indent
ngctl msg macfilter: direct '{ hookname="known" ether=08:00:27:92:eb:aa }'
.Ed
.Pp
The following command can be used to retrieve the packet and byte counters :
.Bd -literal -offset indent
ngctl msg macfilter: getmacs
.Ed
.Pp
It will return the contents of the MAC table:
.Bd -literal -offset indent
Rec'd response "getmacs" (4) from "[54]:":
Args: { n=1 macs=[ { ether=08:00:27:92:eb:aa hookid=1 packets_in=3571 bytes_in=592631 packets_out=3437 bytes_out=777142 } ] }
.Ed
.Sh SEE ALSO
.Xr divert 4 ,
.Xr ipfw 4 ,
.Xr netgraph 4 ,
.Xr ng_ether 4 ,
.Xr ng_one2many 4 ,
.Xr ng_tag 4 ,
.Xr ngctl 8
.Sh AUTHORS
.An -nosplit
The original version of this code was written by Pekka Nikander, and
subsequently modified heavily by
.An Nick Hibma Aq Mt n_hibma@FreeBSD.org .
.Sh BUGS
None known.

View File

@ -4299,6 +4299,7 @@ netgraph/ng_ipfw.c optional netgraph_ipfw inet ipfirewall
netgraph/ng_ksocket.c optional netgraph_ksocket
netgraph/ng_l2tp.c optional netgraph_l2tp
netgraph/ng_lmi.c optional netgraph_lmi
netgraph/ng_macfilter.c optional netgraph_macfilter
netgraph/ng_mppc.c optional netgraph_mppc_compression | \
netgraph_mppc_encryption
netgraph/ng_nat.c optional netgraph_nat inet libalias

View File

@ -31,6 +31,7 @@ SUBDIR= async \
ksocket \
l2tp \
lmi \
macfilter \
${_mppc} \
nat \
netflow \

View File

@ -0,0 +1,9 @@
# $FreeBSD$
KMOD= ng_macfilter
SRCS= ng_macfilter.c
.include <bsd.kmod.mk>
#CFLAGS+= -DNG_MACFILTER_DEBUG
#CFLAGS+= -DNG_MACFILTER_DEBUG_RECVDATA

878
sys/netgraph/ng_macfilter.c Normal file
View File

@ -0,0 +1,878 @@
/*
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2002 Ericsson Research & Pekka Nikander
* Copyright (c) 2020 Nick Hibma <n_hibma@FreeBSD.org>
* 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 unmodified, 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$
*/
/*
* MACFILTER NETGRAPH NODE TYPE
*
* This node type routes packets from the ether hook to either the default hook
* if sender MAC address is not in the MAC table, or out over the specified
* hook if it is.
*
* Other node types can then be used to apply specific processing to the
* packets on each hook.
*
* If compiled with NG_MACFILTER_DEBUG the flow and resizing of the MAC table
* are logged to the console.
*
* If compiled with NG_MACFILTER_DEBUG_RECVDATA every packet handled is logged
* on the console.
*/
#include <sys/param.h>
#include <sys/ctype.h>
#include <sys/systm.h>
#include <sys/lock.h>
#include <sys/mbuf.h>
#include <sys/mutex.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/socket.h>
#include <net/ethernet.h>
#include <netgraph/ng_message.h>
#include <netgraph/netgraph.h>
#include <netgraph/ng_parse.h>
#include "ng_macfilter.h"
#ifdef NG_SEPARATE_MALLOC
MALLOC_DEFINE(M_NETGRAPH_MACFILTER, "netgraph_macfilter", "netgraph macfilter node ");
#else
#define M_NETGRAPH_MACFILTER M_NETGRAPH
#endif
#define MACTABLE_BLOCKSIZE 128 /* block size for incrementing table */
#ifdef NG_MACFILTER_DEBUG
#define DEBUG(fmt, ...) printf("%s:%d: " fmt "\n", __FUNCTION__, __LINE__, __VA_ARGS__)
#else
#define DEBUG(fmt, ...)
#endif
#define MAC_FMT "%02x:%02x:%02x:%02x:%02x:%02x"
#define MAC_S_ARGS(v) (v)[0], (v)[1], (v)[2], (v)[3], (v)[4], (v)[5]
/*
* Parse type for struct ngm_macfilter_direct
*/
static const struct ng_parse_struct_field macfilter_direct_fields[]
= NGM_MACFILTER_DIRECT_FIELDS;
static const struct ng_parse_type ng_macfilter_direct_type = {
&ng_parse_struct_type,
&macfilter_direct_fields
};
/*
* Parse type for struct ngm_macfilter_direct_hookid.
*/
static const struct ng_parse_struct_field macfilter_direct_ndx_fields[]
= NGM_MACFILTER_DIRECT_NDX_FIELDS;
static const struct ng_parse_type ng_macfilter_direct_hookid_type = {
&ng_parse_struct_type,
&macfilter_direct_ndx_fields
};
/*
* Parse types for struct ngm_macfilter_get_macs.
*/
static int
macfilter_get_macs_count(const struct ng_parse_type *type,
const u_char *start, const u_char *buf)
{
const struct ngm_macfilter_macs *const ngm_macs =
(const struct ngm_macfilter_macs *)(buf - OFFSETOF(struct ngm_macfilter_macs, macs));
return ngm_macs->n;
}
static const struct ng_parse_struct_field ng_macfilter_mac_fields[]
= NGM_MACFILTER_MAC_FIELDS;
static const struct ng_parse_type ng_macfilter_mac_type = {
&ng_parse_struct_type,
ng_macfilter_mac_fields,
};
static const struct ng_parse_array_info ng_macfilter_macs_array_info = {
&ng_macfilter_mac_type,
macfilter_get_macs_count
};
static const struct ng_parse_type ng_macfilter_macs_array_type = {
&ng_parse_array_type,
&ng_macfilter_macs_array_info
};
static const struct ng_parse_struct_field ng_macfilter_macs_fields[]
= NGM_MACFILTER_MACS_FIELDS;
static const struct ng_parse_type ng_macfilter_macs_type = {
&ng_parse_struct_type,
&ng_macfilter_macs_fields
};
/*
* Parse types for struct ngm_macfilter_get_hooks.
*/
static int
macfilter_get_upper_hook_count(const struct ng_parse_type *type,
const u_char *start, const u_char *buf)
{
const struct ngm_macfilter_hooks *const ngm_hooks =
(const struct ngm_macfilter_hooks *)(buf - OFFSETOF(struct ngm_macfilter_hooks, hooks));
DEBUG("buf %p, ngm_hooks %p, n %d", buf, ngm_hooks, ngm_hooks->n);
return ngm_hooks->n;
}
static const struct ng_parse_struct_field ng_macfilter_hook_fields[]
= NGM_MACFILTER_HOOK_FIELDS;
static const struct ng_parse_type ng_macfilter_hook_type = {
&ng_parse_struct_type,
ng_macfilter_hook_fields,
};
static const struct ng_parse_array_info ng_macfilter_hooks_array_info = {
&ng_macfilter_hook_type,
macfilter_get_upper_hook_count
};
static const struct ng_parse_type ng_macfilter_hooks_array_type = {
&ng_parse_array_type,
&ng_macfilter_hooks_array_info
};
static const struct ng_parse_struct_field ng_macfilter_hooks_fields[]
= NGM_MACFILTER_HOOKS_FIELDS;
static const struct ng_parse_type ng_macfilter_hooks_type = {
&ng_parse_struct_type,
&ng_macfilter_hooks_fields
};
/*
* List of commands and how to convert arguments to/from ASCII
*/
static const struct ng_cmdlist ng_macfilter_cmdlist[] = {
{
NGM_MACFILTER_COOKIE,
NGM_MACFILTER_RESET,
"reset",
NULL,
NULL
},
{
NGM_MACFILTER_COOKIE,
NGM_MACFILTER_DIRECT,
"direct",
&ng_macfilter_direct_type,
NULL
},
{
NGM_MACFILTER_COOKIE,
NGM_MACFILTER_DIRECT_HOOKID,
"directi",
&ng_macfilter_direct_hookid_type,
NULL
},
{
NGM_MACFILTER_COOKIE,
NGM_MACFILTER_GET_MACS,
"getmacs",
NULL,
&ng_macfilter_macs_type
},
{
NGM_MACFILTER_COOKIE,
NGM_MACFILTER_GETCLR_MACS,
"getclrmacs",
NULL,
&ng_macfilter_macs_type
},
{
NGM_MACFILTER_COOKIE,
NGM_MACFILTER_CLR_MACS,
"clrmacs",
NULL,
NULL,
},
{
NGM_MACFILTER_COOKIE,
NGM_MACFILTER_GET_HOOKS,
"gethooks",
NULL,
&ng_macfilter_hooks_type
},
{ 0 }
};
/*
* Netgraph node type descriptor
*/
static ng_constructor_t ng_macfilter_constructor;
static ng_rcvmsg_t ng_macfilter_rcvmsg;
static ng_shutdown_t ng_macfilter_shutdown;
static ng_newhook_t ng_macfilter_newhook;
static ng_rcvdata_t ng_macfilter_rcvdata;
static ng_disconnect_t ng_macfilter_disconnect;
static struct ng_type typestruct = {
.version = NG_ABI_VERSION,
.name = NG_MACFILTER_NODE_TYPE,
.constructor = ng_macfilter_constructor,
.rcvmsg = ng_macfilter_rcvmsg,
.shutdown = ng_macfilter_shutdown,
.newhook = ng_macfilter_newhook,
.rcvdata = ng_macfilter_rcvdata,
.disconnect = ng_macfilter_disconnect,
.cmdlist = ng_macfilter_cmdlist
};
NETGRAPH_INIT(macfilter, &typestruct);
/*
* Per MAC address info: the hook where to send to, the address
* Note: We use the same struct as in the netgraph message, so we can bcopy the
* array.
*/
typedef struct ngm_macfilter_mac *mf_mac_p;
/*
* Node info
*/
typedef struct {
hook_p mf_ether_hook; /* Ethernet hook */
hook_p *mf_upper; /* Upper hooks */
u_int mf_upper_cnt; /* Allocated # of upper slots */
struct mtx mtx; /* Mutex for MACs table */
mf_mac_p mf_macs; /* MAC info: dynamically allocated */
u_int mf_mac_allocated;/* Allocated # of MAC slots */
u_int mf_mac_used; /* Used # of MAC slots */
} *macfilter_p;
/*
* Resize the MAC table to accommodate at least mfp->mf_mac_used + 1 entries.
*
* Note: mtx already held
*/
static int
macfilter_mactable_resize(macfilter_p mfp)
{
int error = 0;
int n = mfp->mf_mac_allocated;
if (mfp->mf_mac_used < 2*MACTABLE_BLOCKSIZE-1) /* minimum size */
n = 2*MACTABLE_BLOCKSIZE-1;
else if (mfp->mf_mac_used + 2*MACTABLE_BLOCKSIZE < mfp->mf_mac_allocated) /* reduce size */
n = mfp->mf_mac_allocated - MACTABLE_BLOCKSIZE;
else if (mfp->mf_mac_used == mfp->mf_mac_allocated) /* increase size */
n = mfp->mf_mac_allocated + MACTABLE_BLOCKSIZE;
if (n != mfp->mf_mac_allocated) {
DEBUG("used=%d allocated=%d->%d",
mfp->mf_mac_used, mfp->mf_mac_allocated, n);
mf_mac_p mfp_new = realloc(mfp->mf_macs,
sizeof(mfp->mf_macs[0])*n,
M_NETGRAPH, M_NOWAIT | M_ZERO);
if (mfp_new == NULL) {
error = -1;
} else {
mfp->mf_macs = mfp_new;
mfp->mf_mac_allocated = n;
}
}
return error;
}
/*
* Resets the macfilter to pass all received packets
* to the default hook.
*
* Note: mtx already held
*/
static void
macfilter_reset(macfilter_p mfp)
{
mfp->mf_mac_used = 0;
macfilter_mactable_resize(mfp);
}
/*
* Resets the counts for each MAC address.
*
* Note: mtx already held
*/
static void
macfilter_reset_stats(macfilter_p mfp)
{
int i;
for (i = 0; i < mfp->mf_mac_used; i++) {
mf_mac_p p = &mfp->mf_macs[i];
p->packets_in = p->packets_out = 0;
p->bytes_in = p->bytes_out = 0;
}
}
/*
* Count the number of matching macs routed to this hook.
*
* Note: mtx already held
*/
static int
macfilter_mac_count(macfilter_p mfp, int hookid)
{
int i;
int cnt = 0;
for (i = 0; i < mfp->mf_mac_used; i++)
if (mfp->mf_macs[i].hookid == hookid)
cnt++;
return cnt;
}
/*
* Find a MAC address in the mac table.
*
* Returns 0 on failure with *ri set to index before which to insert a new
* element. Or returns 1 on success with *ri set to the index of the element
* that matches.
*
* Note: mtx already held.
*/
static u_int
macfilter_find_mac(macfilter_p mfp, const u_char *ether, u_int *ri)
{
mf_mac_p mf_macs = mfp->mf_macs;
u_int base = 0;
u_int range = mfp->mf_mac_used;
while (range > 0) {
u_int middle = base + (range >> 1); /* middle */
int d = bcmp(ether, mf_macs[middle].ether, ETHER_ADDR_LEN);
if (d == 0) { /* match */
*ri = middle;
return 1;
} else if (d > 0) { /* move right */
range -= middle - base + 1;
base = middle + 1;
} else { /* move left */
range = middle - base;
}
}
*ri = base;
return 0;
}
/*
* Change the upper hook for the given MAC address. If the hook id is zero (the
* default hook), the MAC address is removed from the table. Otherwise it is
* inserted to the table at a proper location, and the id of the hook is
* marked.
*
* Note: mtx already held.
*/
static int
macfilter_mactable_change(macfilter_p mfp, u_char *ether, int hookid)
{
u_int i;
int found = macfilter_find_mac(mfp, ether, &i);
mf_mac_p mf_macs = mfp->mf_macs;
DEBUG("ether=" MAC_FMT " found=%d i=%d ether=" MAC_FMT " hookid=%d->%d used=%d allocated=%d",
MAC_S_ARGS(ether), found, i, MAC_S_ARGS(mf_macs[i].ether),
(found? mf_macs[i].hookid:NG_MACFILTER_HOOK_DEFAULT_ID), hookid,
mfp->mf_mac_used, mfp->mf_mac_allocated);
if (found) {
if (hookid == NG_MACFILTER_HOOK_DEFAULT_ID) { /* drop */
/* Compress table */
mfp->mf_mac_used--;
size_t len = (mfp->mf_mac_used - i) * sizeof(mf_macs[0]);
if (len > 0)
bcopy(&mf_macs[i+1], &mf_macs[i], len);
macfilter_mactable_resize(mfp);
} else { /* modify */
mf_macs[i].hookid = hookid;
}
} else {
if (hookid == NG_MACFILTER_HOOK_DEFAULT_ID) { /* not found */
/* not present and not inserted */
return 0;
} else { /* add */
if (macfilter_mactable_resize(mfp) == -1) {
return ENOMEM;
} else {
mf_macs = mfp->mf_macs; /* reassign; might have moved during resize */
/* make room for new entry, unless appending */
size_t len = (mfp->mf_mac_used - i) * sizeof(mf_macs[0]);
if (len > 0)
bcopy(&mf_macs[i], &mf_macs[i+1], len);
mf_macs[i].hookid = hookid;
bcopy(ether, mf_macs[i].ether, ETHER_ADDR_LEN);
mfp->mf_mac_used++;
}
}
}
return 0;
}
static int
macfilter_mactable_remove_by_hookid(macfilter_p mfp, int hookid)
{
int i, j;
for (i = 0, j = 0; i < mfp->mf_mac_used; i++) {
if (mfp->mf_macs[i].hookid != hookid) {
if (i != j)
bcopy(&mfp->mf_macs[i], &mfp->mf_macs[j], sizeof(mfp->mf_macs[0]));
j++;
}
}
int removed = i - j;
mfp->mf_mac_used = j;
macfilter_mactable_resize(mfp);
return removed;
}
static int
macfilter_find_hook(macfilter_p mfp, const char *hookname)
{
int hookid;
for (hookid = 0; hookid < mfp->mf_upper_cnt; hookid++) {
if (mfp->mf_upper[hookid]) {
if (strncmp(NG_HOOK_NAME(mfp->mf_upper[hookid]),
hookname, NG_HOOKSIZ) == 0) {
return hookid;
}
}
}
return 0;
}
static int
macfilter_direct(macfilter_p mfp, struct ngm_macfilter_direct *md)
{
DEBUG("ether=" MAC_FMT " hook=%s",
MAC_S_ARGS(md->ether), md->hookname);
int hookid = macfilter_find_hook(mfp, md->hookname);
if (hookid < 0)
return ENOENT;
return macfilter_mactable_change(mfp, md->ether, hookid);
}
static int
macfilter_direct_hookid(macfilter_p mfp, struct ngm_macfilter_direct_hookid *mdi)
{
DEBUG("ether=" MAC_FMT " hookid=%d",
MAC_S_ARGS(mdi->ether), mdi->hookid);
if (mdi->hookid >= mfp->mf_upper_cnt)
return EINVAL;
else if (mfp->mf_upper[mdi->hookid] == NULL)
return EINVAL;
return macfilter_mactable_change(mfp, mdi->ether, mdi->hookid);
}
/*
* Packet handling
*/
/*
* Pass packets received from any upper hook to
* a lower hook
*/
static int
macfilter_ether_output(hook_p hook, macfilter_p mfp, struct mbuf *m, hook_p *next_hook)
{
struct ether_header *ether_header = mtod(m, struct ether_header *);
u_char *ether = ether_header->ether_dhost;
*next_hook = mfp->mf_ether_hook;
mtx_lock(&mfp->mtx);
u_int i;
int found = macfilter_find_mac(mfp, ether, &i);
if (found) {
mf_mac_p mf_macs = mfp->mf_macs;
mf_macs[i].packets_out++;
if (m->m_len > ETHER_HDR_LEN)
mf_macs[i].bytes_out += m->m_len - ETHER_HDR_LEN;
#ifdef NG_MACFILTER_DEBUG_RECVDATA
DEBUG("ether=" MAC_FMT " len=%db->%lldb: bytes: %s -> %s",
MAC_S_ARGS(ether), m->m_len - ETHER_HDR_LEN, mf_macs[i].bytes_out,
NG_HOOK_NAME(hook), NG_HOOK_NAME(*next_hook));
#endif
} else {
#ifdef NG_MACFILTER_DEBUG_RECVDATA
DEBUG("ether=" MAC_FMT " len=%db->?b: bytes: %s->%s",
MAC_S_ARGS(ether), m->m_len - ETHER_HDR_LEN,
NG_HOOK_NAME(hook), NG_HOOK_NAME(*next_hook));
#endif
}
mtx_unlock(&mfp->mtx);
return 0;
}
/*
* Search for the right upper hook, based on the source ethernet
* address. If not found, pass to the default upper hook.
*/
static int
macfilter_ether_input(hook_p hook, macfilter_p mfp, struct mbuf *m, hook_p *next_hook)
{
struct ether_header *ether_header = mtod(m, struct ether_header *);
u_char *ether = ether_header->ether_shost;
int hookid = NG_MACFILTER_HOOK_DEFAULT_ID;
mtx_lock(&mfp->mtx);
u_int i;
int found = macfilter_find_mac(mfp, ether, &i);
if (found) {
mf_mac_p mf_macs = mfp->mf_macs;
mf_macs[i].packets_in++;
if (m->m_len > ETHER_HDR_LEN)
mf_macs[i].bytes_in += m->m_len - ETHER_HDR_LEN;
hookid = mf_macs[i].hookid;
#ifdef NG_MACFILTER_DEBUG_RECVDATA
DEBUG("ether=" MAC_FMT " len=%db->%lldb: bytes: %s->%s",
MAC_S_ARGS(ether), m->m_len - ETHER_HDR_LEN, mf_macs[i].bytes_in,
NG_HOOK_NAME(hook), NG_HOOK_NAME(*next_hook));
#endif
} else {
#ifdef NG_MACFILTER_DEBUG_RECVDATA
DEBUG("ether=" MAC_FMT " len=%db->?b: bytes: %s->%s",
MAC_S_ARGS(ether), m->m_len - ETHER_HDR_LEN,
NG_HOOK_NAME(hook), NG_HOOK_NAME(*next_hook));
#endif
}
if (hookid >= mfp->mf_upper_cnt)
*next_hook = NULL;
else
*next_hook = mfp->mf_upper[hookid];
mtx_unlock(&mfp->mtx);
return 0;
}
/*
* ======================================================================
* Netgraph hooks
* ======================================================================
*/
/*
* See basic netgraph code for comments on the individual functions.
*/
static int
ng_macfilter_constructor(node_p node)
{
macfilter_p mfp = malloc(sizeof(*mfp), M_NETGRAPH, M_NOWAIT | M_ZERO);
if (mfp == NULL)
return ENOMEM;
int error = macfilter_mactable_resize(mfp);
if (error)
return error;
NG_NODE_SET_PRIVATE(node, mfp);
mtx_init(&mfp->mtx, "Macfilter table", NULL, MTX_DEF);
return (0);
}
static int
ng_macfilter_newhook(node_p node, hook_p hook, const char *hookname)
{
const macfilter_p mfp = NG_NODE_PRIVATE(node);
DEBUG("%s", hookname);
if (strcmp(hookname, NG_MACFILTER_HOOK_ETHER) == 0) {
mfp->mf_ether_hook = hook;
} else {
int hookid;
if (strcmp(hookname, NG_MACFILTER_HOOK_DEFAULT) == 0) {
hookid = NG_MACFILTER_HOOK_DEFAULT_ID;
} else {
for (hookid = 1; hookid < mfp->mf_upper_cnt; hookid++)
if (mfp->mf_upper[hookid] == NULL)
break;
}
if (hookid >= mfp->mf_upper_cnt) {
DEBUG("upper cnt %d -> %d", mfp->mf_upper_cnt, hookid + 1);
mfp->mf_upper_cnt = hookid + 1;
mfp->mf_upper = realloc(mfp->mf_upper,
sizeof(mfp->mf_upper[0])*mfp->mf_upper_cnt,
M_NETGRAPH, M_NOWAIT | M_ZERO);
}
mfp->mf_upper[hookid] = hook;
}
return(0);
}
static int
ng_macfilter_rcvmsg(node_p node, item_p item, hook_p lasthook)
{
const macfilter_p mfp = NG_NODE_PRIVATE(node);
struct ng_mesg *resp = NULL;
struct ng_mesg *msg;
int error = 0;
struct ngm_macfilter_macs *ngm_macs;
struct ngm_macfilter_hooks *ngm_hooks;
struct ngm_macfilter_direct *md;
struct ngm_macfilter_direct_hookid *mdi;
int n = 0, i = 0;
int hookid = 0;
int resplen;
NGI_GET_MSG(item, msg);
mtx_lock(&mfp->mtx);
switch (msg->header.typecookie) {
case NGM_MACFILTER_COOKIE:
switch (msg->header.cmd) {
case NGM_MACFILTER_RESET:
macfilter_reset(mfp);
break;
case NGM_MACFILTER_DIRECT:
if (msg->header.arglen != sizeof(struct ngm_macfilter_direct)) {
DEBUG("direct: wrong type length (%d, expected %d)",
msg->header.arglen, sizeof(struct ngm_macfilter_direct));
error = EINVAL;
break;
}
md = (struct ngm_macfilter_direct *)msg->data;
error = macfilter_direct(mfp, md);
break;
case NGM_MACFILTER_DIRECT_HOOKID:
if (msg->header.arglen != sizeof(struct ngm_macfilter_direct_hookid)) {
DEBUG("direct hookid: wrong type length (%d, expected %d)",
msg->header.arglen, sizeof(struct ngm_macfilter_direct));
error = EINVAL;
break;
}
mdi = (struct ngm_macfilter_direct_hookid *)msg->data;
error = macfilter_direct_hookid(mfp, mdi);
break;
case NGM_MACFILTER_GET_MACS:
case NGM_MACFILTER_GETCLR_MACS:
n = mfp->mf_mac_used;
resplen = sizeof(struct ngm_macfilter_macs) + n * sizeof(struct ngm_macfilter_mac);
NG_MKRESPONSE(resp, msg, resplen, M_NOWAIT);
if (resp == NULL) {
error = ENOMEM;
break;
}
ngm_macs = (struct ngm_macfilter_macs *)resp->data;
ngm_macs->n = n;
bcopy(mfp->mf_macs, &ngm_macs->macs[0], n * sizeof(struct ngm_macfilter_mac));
if (msg->header.cmd == NGM_MACFILTER_GETCLR_MACS)
macfilter_reset_stats(mfp);
break;
case NGM_MACFILTER_CLR_MACS:
macfilter_reset_stats(mfp);
break;
case NGM_MACFILTER_GET_HOOKS:
for (hookid = 0; hookid < mfp->mf_upper_cnt; hookid++)
if (mfp->mf_upper[hookid] != NULL)
n++;
resplen = sizeof(struct ngm_macfilter_hooks) + n * sizeof(struct ngm_macfilter_hook);
NG_MKRESPONSE(resp, msg, resplen, M_NOWAIT | M_ZERO);
if (resp == NULL) {
error = ENOMEM;
break;
}
ngm_hooks = (struct ngm_macfilter_hooks *)resp->data;
ngm_hooks->n = n;
for (hookid = 0; hookid < mfp->mf_upper_cnt; hookid++) {
if (mfp->mf_upper[hookid] != NULL) {
struct ngm_macfilter_hook *ngm_hook = &ngm_hooks->hooks[i++];
strlcpy(ngm_hook->hookname,
NG_HOOK_NAME(mfp->mf_upper[hookid]),
NG_HOOKSIZ);
ngm_hook->hookid = hookid;
ngm_hook->maccnt = macfilter_mac_count(mfp, hookid);
}
}
break;
default:
error = EINVAL; /* unknown command */
break;
}
break;
default:
error = EINVAL; /* unknown cookie type */
break;
}
mtx_unlock(&mfp->mtx);
NG_RESPOND_MSG(error, node, item, resp);
NG_FREE_MSG(msg);
return error;
}
static int
ng_macfilter_rcvdata(hook_p hook, item_p item)
{
const macfilter_p mfp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
int error;
hook_p next_hook = NULL;
struct mbuf *m;
m = NGI_M(item); /* 'item' still owns it. We are peeking */
DEBUG("%s", NG_HOOK_NAME(hook));
if (hook == mfp->mf_ether_hook)
error = macfilter_ether_input(hook, mfp, m, &next_hook);
else if (mfp->mf_ether_hook != NULL)
error = macfilter_ether_output(hook, mfp, m, &next_hook);
if (next_hook == NULL) {
NG_FREE_ITEM(item);
return (0);
}
NG_FWD_ITEM_HOOK(error, item, next_hook);
return error;
}
static int
ng_macfilter_disconnect(hook_p hook)
{
const macfilter_p mfp = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
mtx_lock(&mfp->mtx);
if (mfp->mf_ether_hook == hook) {
mfp->mf_ether_hook = NULL;
DEBUG("%s", NG_HOOK_NAME(hook));
} else {
int hookid;
for (hookid = 0; hookid < mfp->mf_upper_cnt; hookid++) {
if (mfp->mf_upper[hookid] == hook) {
mfp->mf_upper[hookid] = NULL;
#ifndef NG_MACFILTER_DEBUG
macfilter_mactable_remove_by_hookid(mfp, hookid);
#else
int cnt = macfilter_mactable_remove_by_hookid(mfp, hookid);
DEBUG("%s: removed %d MACs", NG_HOOK_NAME(hook), cnt);
#endif
break;
}
}
if (hookid == mfp->mf_upper_cnt - 1) {
/* Reduce the size of the array when the last element was removed */
for (--hookid; hookid >= 0 && mfp->mf_upper[hookid] == NULL; hookid--)
;
DEBUG("upper cnt %d -> %d", mfp->mf_upper_cnt, hookid + 1);
mfp->mf_upper_cnt = hookid + 1;
mfp->mf_upper = realloc(mfp->mf_upper,
sizeof(mfp->mf_upper[0])*mfp->mf_upper_cnt,
M_NETGRAPH, M_NOWAIT | M_ZERO);
}
}
mtx_unlock(&mfp->mtx);
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);
}
static int
ng_macfilter_shutdown(node_p node)
{
const macfilter_p mfp = NG_NODE_PRIVATE(node);
mtx_destroy(&mfp->mtx);
free(mfp->mf_upper, M_NETGRAPH);
free(mfp->mf_macs, M_NETGRAPH);
free(mfp, M_NETGRAPH);
NG_NODE_UNREF(node);
return (0);
}

132
sys/netgraph/ng_macfilter.h Normal file
View File

@ -0,0 +1,132 @@
/*
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2002 Ericsson Research & Pekka Nikander
* Copyright (c) 2020 Nick Hibma <n_hibma@FreeBSD.org>
* 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 unmodified, 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$
*/
#ifndef _NETGRAPH_MACFILTER_H_
#define _NETGRAPH_MACFILTER_H_
#define NG_MACFILTER_NODE_TYPE "macfilter"
#define NGM_MACFILTER_COOKIE 1042445461
/* Hook names */
#define NG_MACFILTER_HOOK_ETHER "ether" /* connected to ether:lower */
#define NG_MACFILTER_HOOK_DEFAULT "default" /* connected to ether:upper; upper[0] */
/* Other hooks may be named freely connected to ether:upper; upper[1..n]*/
#define NG_MACFILTER_HOOK_DEFAULT_ID 0
#define OFFSETOF(s, e) ((char *)&((s *)0)->e - (char *)((s *)0))
/* Netgraph commands understood/sent by this node type */
enum {
NGM_MACFILTER_RESET = 1,
NGM_MACFILTER_DIRECT = 2,
NGM_MACFILTER_DIRECT_HOOKID = 3,
NGM_MACFILTER_GET_MACS = 4,
NGM_MACFILTER_GETCLR_MACS = 5,
NGM_MACFILTER_CLR_MACS = 6,
NGM_MACFILTER_GET_HOOKS = 7
};
/* This structure is supplied with the NGM_MACFILTER_DIRECT command */
struct ngm_macfilter_direct {
u_char ether[ETHER_ADDR_LEN]; /* MAC address */
u_char hookname[NG_HOOKSIZ]; /* Upper hook name*/
};
#define NGM_MACFILTER_DIRECT_FIELDS { \
{ "ether", &ng_parse_enaddr_type }, \
{ "hookname", &ng_parse_hookbuf_type }, \
{ NULL } \
}
/* This structure is supplied with the NGM_MACFILTER_DIRECT_HOOKID command */
struct ngm_macfilter_direct_hookid {
u_char ether[ETHER_ADDR_LEN]; /* MAC address */
u_int16_t hookid; /* Upper hook hookid */
};
#define NGM_MACFILTER_DIRECT_NDX_FIELDS { \
{ "ether", &ng_parse_enaddr_type }, \
{ "hookid", &ng_parse_uint16_type }, \
{ NULL } \
}
/* This structure is returned in the array by the NGM_MACFILTER_GET(CLR)_MACS commands */
struct ngm_macfilter_mac {
u_char ether[ETHER_ADDR_LEN]; /* MAC address */
u_int16_t hookid; /* Upper hook hookid */
u_int64_t packets_in; /* packets in from downstream */
u_int64_t bytes_in; /* bytes in from upstream */
u_int64_t packets_out; /* packets out towards downstream */
u_int64_t bytes_out; /* bytes out towards downstream */
};
#define NGM_MACFILTER_MAC_FIELDS { \
{ "ether", &ng_parse_enaddr_type }, \
{ "hookid", &ng_parse_uint16_type }, \
{ "packets_in", &ng_parse_uint64_type }, \
{ "bytes_in", &ng_parse_uint64_type }, \
{ "packets_out", &ng_parse_uint64_type }, \
{ "bytes_out", &ng_parse_uint64_type }, \
{ NULL } \
}
/* This structure is returned by the NGM_MACFILTER_GET(CLR)_MACS commands */
struct ngm_macfilter_macs {
u_int32_t n; /* Number of entries in macs */
struct ngm_macfilter_mac macs[]; /* Macs table */
};
#define NGM_MACFILTER_MACS_FIELDS { \
{ "n", &ng_parse_uint32_type }, \
{ "macs", &ng_macfilter_macs_array_type },\
{ NULL } \
}
/* This structure is returned in an array by the NGM_MACFILTER_GET_HOOKS command */
struct ngm_macfilter_hook {
u_char hookname[NG_HOOKSIZ]; /* Upper hook name*/
u_int16_t hookid; /* Upper hook hookid */
u_int32_t maccnt; /* Number of mac addresses associated with hook */
};
#define NGM_MACFILTER_HOOK_FIELDS { \
{ "hookname", &ng_parse_hookbuf_type }, \
{ "hookid", &ng_parse_uint16_type }, \
{ "maccnt", &ng_parse_uint32_type }, \
{ NULL } \
}
/* This structure is returned by the NGM_MACFILTER_GET_HOOKS command */
struct ngm_macfilter_hooks {
u_int32_t n; /* Number of entries in hooks */
struct ngm_macfilter_hook hooks[]; /* Hooks table */
};
#define NGM_MACFILTER_HOOKS_FIELDS { \
{ "n", &ng_parse_uint32_type }, \
{ "hooks", &ng_macfilter_hooks_array_type },\
{ NULL } \
}
#endif /* _NETGRAPH_MACFILTER_H_ */

View File

@ -20,6 +20,7 @@ TESTS_SUBDIRS+= kqueue
TESTS_SUBDIRS+= mac
TESTS_SUBDIRS+= mqueue
TESTS_SUBDIRS+= net
TESTS_SUBDIRS+= netgraph
TESTS_SUBDIRS+= netinet
TESTS_SUBDIRS+= netinet6
TESTS_SUBDIRS+= netipsec

View File

@ -0,0 +1,14 @@
# $FreeBSD$
PACKAGE= tests
TESTSDIR= ${TESTSBASE}/sys/netgraph
BINDIR= ${TESTSDIR}
TAP_TESTS_SH+= ng_macfilter_test
TEST_METADATA.runtests+= required_user="root"
MAN=
.include <bsd.test.mk>

View File

@ -0,0 +1,430 @@
#!/bin/sh
#
# Copyright (c) 2018-2020 Retina b.v.
# 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.
# 3. Neither the name of the University nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# 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$
progname="$(basename $0 .sh)"
entries_lst="/tmp/$progname.entries.lst"
entries2_lst="/tmp/$progname.entries2.lst"
HOOKS=3
HOOKSADD=42
ITERATIONS=7
SUBITERATIONS=71
find_iface () {
# Figure out the first ethernet interface
ifconfig -u -l ether | awk '{print $1}'
}
loaded_modules=''
load_modules () {
for kmod in $*; do
if ! kldstat -q -m $kmod; then
test_comment "Loading $kmod..."
kldload $kmod
loaded_modules="$loaded_modules $kmod"
fi
done
}
unload_modules () {
for kmod in $loaded_modules; do
# These cannot be unloaded
test $kmod = 'ng_ether' -o $kmod = 'ng_socket' \
&& continue
test_comment "Unloading $kmod..."
kldunload $kmod
done
loaded_modules=''
}
configure_nodes () {
ngctl mkpeer $eth: macfilter lower ether # Connect the lower hook of the ether instance $eth to the ether hook of a new macfilter instance
ngctl name $eth:lower MF # Give the macfilter instance a name
ngctl mkpeer $eth: one2many upper one # Connect the upper hook of the ether instance $eth to the one hook of a new one2many instance
ngctl name $eth:upper O2M # Give the one2many instance a name
ngctl msg O2M: setconfig "{ xmitAlg=3 failAlg=1 enabledLinks=[ 1 1 ] }" # XMIT_FAILOVER -> send replies always out many0
ngctl connect MF: O2M: default many0 # Connect macfilter:default to the many0 hook of a one2many instance
for i in $(seq 1 1 $HOOKS); do
ngctl connect MF: O2M: out$i many$i
done
}
deconfigure_nodes () {
ngctl shutdown MF:
ngctl shutdown O2M:
}
cleanup () {
test_title "Cleaning up"
deconfigure_nodes
unload_modules
rm -f $entries_lst $entries2_lst
}
TSTNR=0
TSTFAILS=0
TSTSUCCS=0
_test_next () { TSTNR=$(($TSTNR + 1)); }
_test_succ () { TSTSUCCS=$(($TSTSUCCS + 1)); }
_test_fail () { TSTFAILS=$(($TSTFAILS + 1)); }
test_cnt () { echo "1..${1:-$TSTNR}"; }
test_title () {
local msg="$1"
printf '### %s ' "$msg"
printf '#%.0s' `seq $((80 - ${#msg} - 5))`
printf "\n"
}
test_comment () { echo "# $1"; }
test_bailout () { echo "Bail out!${1+:- $1}"; exit 1; }
test_bail_on_fail () { test $TSTFAILS -eq 0 || test_bailout $1; }
test_ok () {
local msg="$1"
_test_next
_test_succ
echo "ok $TSTNR - $msg"
return 0
}
test_not_ok () {
local msg="$1"
_test_next
_test_fails
echo "not ok $TSTNR - $msg"
return 1
}
test_eq () {
local v1="$1" v2="$2" msg="$3"
if [ "$v1" = "$v2" ]; then
test_ok "$v1 $msg"
else
test_not_ok "$v1 vs $v2 $msg"
fi
}
test_ne () {
local v1="$1" v2="$2" msg="$3"
if [ "$v1" != "$v2" ]; then
test_ok "$v1 $msg"
else
test_not_ok "$v1 vs $v2 $msg"
fi
}
test_lt () {
local v1=$1 v2=$2 msg="$3"
if [ "$v1" -lt "$v2" ]; then
test_ok "$v1 $msg"
else
test_not_ok "$v1 >= $v2 $msg"
fi
}
test_le () {
local v1=$1 v2=$2 msg="$3"
if [ "$v1" -le "$v2" ]; then
test_ok "$v1 $msg"
else
test_not_ok "$v1 >= $v2 $msg"
fi
}
test_gt () {
local v1=$1 v2=$2 msg="$3"
if [ "$v1" -gt "$v2" ]; then
test_ok "$v1 $msg"
else
test_not_ok "$v1 <= $v2 $msg"
fi
}
test_ge () {
local v1=$1 v2=$2 msg="$3"
if [ "$v1" -ge "$v2" ]; then
test_ok "$v1 $msg"
else
test_not_ok "$v1 <= $v2 $msg"
fi
}
test_rc () { test_eq $? $1 "$2"; }
test_failure () { test_ne $? 0 "$1"; }
test_success () { test_eq $? 0 "$1"; }
gethooks () {
ngctl msg MF: 'gethooks' \
| perl -ne '$h{$1}=1 while s/hookname="(.*?)"//; sub END {print join(":", sort keys %h)."\n"}'
}
countmacs () {
local hookname=${1:-'[^"]*'}
ngctl msg MF: 'gethooks' \
| perl -ne 'sub BEGIN {$c=0} $c += $1 while s/hookname="'$hookname'" hookid=\d+ maccnt=(\d+)//; sub END {print "$c\n"}'
}
randomedge () {
local edge="out$(seq 0 1 $HOOKS | sort -R | head -1)"
test $edge = 'out0' \
&& echo default \
|| echo $edge
}
genmac () {
echo "00:00:00:00:$(printf "%02x" $1):$(printf "%02x" $2)"
}
################################################################################
### Start ######################################################################
################################################################################
test_title "Setting up system..."
load_modules netgraph ng_socket ng_ether ng_macfilter ng_one2many
eth=$(find_iface)
test_comment "Using $eth..."
test_title "Configuring netgraph nodes..."
configure_nodes
trap 'exit 99' 1 2 3 13 14 15
trap 'cleanup' EXIT
created_hooks=$(gethooks)
rc=0
test_cnt
################################################################################
### Tests ######################################################################
################################################################################
################################################################################
test_title "Test: Duplicate default hook"
ngctl connect MF: O2M: default many99 2>/dev/null
test_failure "duplicate connect of default hook"
################################################################################
test_title "Test: Add and remove hooks"
ngctl connect MF: O2M: xxx1 many$(($HOOKS + 1))
test_success "connect MF:xxx1 to O2M:many$(($HOOKS + 1))"
ngctl connect MF: O2M: xxx2 many$(($HOOKS + 2))
test_success "connect MF:xxx2 to O2M:many$(($HOOKS + 2))"
ngctl connect MF: O2M: xxx3 many$(($HOOKS + 3))
test_success "connect MF:xxx3 to O2M:many$(($HOOKS + 3))"
hooks=$(gethooks)
test_eq $created_hooks:xxx1:xxx2:xxx3 $hooks 'hooks after adding xxx1-3'
ngctl rmhook MF: xxx1
test_success "rmhook MF:xxx$i"
hooks=$(gethooks)
test_eq $created_hooks:xxx2:xxx3 $hooks 'hooks after removing xxx1'
ngctl rmhook MF: xxx2
test_success "rmhook MF:xxx$i"
hooks=$(gethooks)
test_eq $created_hooks:xxx3 $hooks 'hooks after removing xxx2'
ngctl rmhook MF: xxx3
test_success "rmhook MF:xxx$i"
hooks=$(gethooks)
test_eq $created_hooks $hooks 'hooks after removing xxx3'
test_bail_on_fail
################################################################################
test_title "Test: Add many hooks"
added_hooks=""
for i in $(seq 10 1 $HOOKSADD); do
added_hooks="$added_hooks:xxx$i"
ngctl connect MF: O2M: xxx$i many$(($HOOKS + $i))
done
hooks=$(gethooks)
test_eq $created_hooks$added_hooks $hooks 'hooks after adding many hooks'
for h in $(echo $added_hooks | perl -ne 'chomp; %h=map { $_=>1 } split /:/; print "$_\n" for grep {$_} keys %h'); do
ngctl rmhook MF: $h
done
hooks=$(gethooks)
test_eq $created_hooks $hooks 'hooks after adding many hooks'
test_bail_on_fail
################################################################################
test_title "Test: Adding many MACs..."
I=1
for i in $(seq $ITERATIONS | sort -R); do
test_comment "Iteration $I/$iterations..."
for j in $(seq 0 1 $SUBITERATIONS); do
test $i = 2 && edge='out2' || edge='out1'
ether=$(genmac $j $i)
ngctl msg MF: 'direct' "{ hookname=\"$edge\" ether=$ether }"
done
I=$(($I + 1))
done
n=$(countmacs out1)
n2=$(( ( $ITERATIONS - 1 ) * ( $SUBITERATIONS + 1 ) ))
test_eq $n $n2 'MACs in table for out1'
n=$(countmacs out2)
n2=$(( 1 * ( $SUBITERATIONS + 1 ) ))
test_eq $n $n2 'MACs in table for out2'
n=$(countmacs out3)
n2=0
test_eq $n $n2 'MACs in table for out3'
test_bail_on_fail
################################################################################
test_title "Test: Changing hooks for MACs..."
for i in $(seq $ITERATIONS); do
edge='out3'
ether=$(genmac 0 $i)
ngctl msg MF: 'direct' "{ hookname=\"$edge\" ether=$ether }"
done
n=$(countmacs out1)
n2=$(( ( $ITERATIONS - 1 ) * ( $SUBITERATIONS + 1 - 1 ) ))
test_eq $n $n2 'MACs in table for out1'
n=$(countmacs out2)
n2=$(( 1 * ( $SUBITERATIONS + 1 - 1 ) ))
test_eq $n $n2 'MACs in table for out2'
n=$(countmacs out3)
n2=$ITERATIONS
test_eq $n $n2 'MACs in table for out3'
test_bail_on_fail
################################################################################
test_title "Test: Removing all MACs one by one..."
I=1
for i in $(seq $ITERATIONS | sort -R); do
test_comment "Iteration $I/$iterations..."
for j in $(seq 0 1 $SUBITERATIONS | sort -R); do
edge="default"
ether=$(genmac $j $i)
ngctl msg MF: 'direct' "{ hookname=\"$edge\" ether=$ether }"
done
I=$(($I + 1))
done
n=$(countmacs)
n2=0
test_eq $n $n2 'MACs in table'
test_bail_on_fail
################################################################################
test_title "Test: Randomly adding MACs on random hooks..."
rm -f $entries_lst
for i in $(seq $ITERATIONS); do
test_comment "Iteration $i/$iterations..."
for j in $(seq 0 1 $SUBITERATIONS | sort -R); do
edge=$(randomedge)
ether=$(genmac $j $i)
ngctl msg MF: 'direct' "{ hookname=\"$edge\" ether=$ether }"
echo $ether $edge >> $entries_lst
done
n=$(countmacs)
done
n=$(countmacs out1)
n2=$(grep -c ' out1' $entries_lst)
test_eq $n $n2 'MACs in table for out1'
n=$(countmacs out2)
n2=$(grep -c ' out2' $entries_lst)
test_eq $n $n2 'MACs in table for out2'
n=$(countmacs out3)
n2=$(grep -c ' out3' $entries_lst)
test_eq $n $n2 'MACs in table for out3'
test_bail_on_fail
################################################################################
test_title "Test: Randomly changing MAC assignments..."
rm -f $entries2_lst
for i in $(seq $ITERATIONS); do
test_comment "Iteration $i/$iterations..."
cat $entries_lst | while read ether edge; do
edge2=$(randomedge)
ngctl msg MF: 'direct' "{ hookname=\"$edge2\" ether=$ether }"
echo $ether $edge2 >> $entries2_lst
done
n=$(countmacs out1)
n2=$(grep -c ' out1' $entries2_lst)
test_eq $n $n2 'MACs in table for out1'
n=$(countmacs out2)
n2=$(grep -c ' out2' $entries2_lst)
test_eq $n $n2 'MACs in table for out2'
n=$(countmacs out3)
n2=$(grep -c ' out3' $entries2_lst)
test_eq $n $n2 'MACs in table for out3'
test_bail_on_fail
mv $entries2_lst $entries_lst
done
################################################################################
test_title "Test: Resetting macfilter..."
ngctl msg MF: reset
test_success "**** reset failed"
test_eq $(countmacs) 0 'MACs in table'
test_bail_on_fail
################################################################################
test_cnt
exit 0