Index: share/man/man4/Makefile =================================================================== --- share/man/man4/Makefile +++ share/man/man4/Makefile @@ -353,6 +353,7 @@ ng_l2cap.4 \ ng_l2tp.4 \ ng_lmi.4 \ + ng_macfilter.4 \ ng_mppc.4 \ ng_nat.4 \ ng_netflow.4 \ Index: share/man/man4/ng_macfilter.4 =================================================================== --- /dev/null +++ share/man/man4/ng_macfilter.4 @@ -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_int32_t packets_in; /* packets in from downstream */ + u_int64_t bytes_in; /* bytes in from upstream */ + u_int32_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 + /-------[combiner]---------\\ + | + / \\ +[em0] | [tagger] + \\ / + | + \\-----[macfilter]------/ +.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. Index: sys/conf/files =================================================================== --- sys/conf/files +++ sys/conf/files @@ -4290,6 +4290,7 @@ 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 Index: sys/modules/netgraph/Makefile =================================================================== --- sys/modules/netgraph/Makefile +++ sys/modules/netgraph/Makefile @@ -31,6 +31,7 @@ ksocket \ l2tp \ lmi \ + macfilter \ ${_mppc} \ nat \ netflow \ Index: sys/modules/netgraph/macfilter/Makefile =================================================================== --- sys/modules/netgraph/macfilter/Makefile +++ sys/modules/netgraph/macfilter/Makefile @@ -1,6 +1,8 @@ -# $FreeBSD: head/sys/modules/netgraph/tag/Makefile 159979 2006-06-27 12:45:28Z glebius $ +# $FreeBSD$ -KMOD= ng_tag -SRCS= ng_tag.c +KMOD= ng_macfilter +SRCS= ng_macfilter.c .include + +#CFLAGS+= -DNG_MACFILTER_DEBUG Index: sys/netgraph/ng_macfilter.h =================================================================== --- /dev/null +++ sys/netgraph/ng_macfilter.h @@ -0,0 +1,132 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2002 Ericsson Research & Pekka Nikander + * Copyright (c) 2020 Nick Hibma + * 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_uint32_type }, \ + { "bytes_in", &ng_parse_uint64_type }, \ + { "packets_out", &ng_parse_uint32_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_ */ Index: sys/netgraph/ng_macfilter.c =================================================================== --- /dev/null +++ sys/netgraph/ng_macfilter.c @@ -0,0 +1,882 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2002 Ericsson Research & Pekka Nikander + * Copyright (c) 2020 Nick Hibma + * 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 +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include + +#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__) +#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] +/* These printfs cost space and time, so enable them only when really needed. */ +#else +#define DEBUG(a...) +#endif + +/* + * 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) +{ + 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) +{ + 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(macfilter_p mfp, struct mbuf **mp, hook_p *next_hook) +{ + struct ether_header *ether_header = mtod((*mp), 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 ((*mp)->m_len > ETHER_HDR_LEN) + mf_macs[i].bytes_out += (*mp)->m_len - ETHER_HDR_LEN; + } + + 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(macfilter_p mfp, struct mbuf **mp, hook_p *next_hook) +{ + struct ether_header *ether_header = mtod((*mp), 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 ((*mp)->m_len > ETHER_HDR_LEN) + mf_macs[i].bytes_in += (*mp)->m_len - ETHER_HDR_LEN; + + hookid = mf_macs[i].hookid; + } + + 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); + +#ifdef NG_MACFILTER_DEBUG + static int getclr_macs_count = 0; /* avoid spamming kernel logs */ + if (msg->header.cmd != NGM_MACFILTER_GETCLR_MACS + || ++getclr_macs_count <= 10) { + DEBUG("typecookie=%d cmd=%d", + msg->header.typecookie, msg->header.cmd); + } +#endif + + mtx_lock(&mfp->mtx); + + switch (msg->header.typecookie) { + case NGM_MACFILTER_COOKIE: + switch (msg->header.cmd) { + + case NGM_MACFILTER_RESET: + macfilter_reset(mfp); +#ifdef NG_MACFILTER_DEBUG + getclr_macs_count = 0; +#endif + 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); +#ifdef NG_MACFILTER_DEBUG + getclr_macs_count = 0; +#endif + break; + + case NGM_MACFILTER_GET_HOOKS: + for (hookid = 0; hookid < mfp->mf_upper_cnt; hookid++) + if (mfp->mf_upper[hookid] != NULL) + n++; + DEBUG("%d vs %d", n, mfp->mf_upper_cnt); + 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); + } + } + DEBUG("&ngm_hooks->hooks[0] %p, ngm_hooks %p, n %d", &ngm_hooks->hooks[0], ngm_hooks, ngm_hooks->n); + 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 */ + + if (hook == mfp->mf_ether_hook) + error = macfilter_ether_input(mfp, &m, &next_hook); + else if (mfp->mf_ether_hook != NULL) + error = macfilter_ether_output(mfp, &m, &next_hook); + + if (next_hook == NULL) { + NG_FREE_ITEM(item); + return (0); + } + +#ifdef NG_MACFILTER_DEBUG_RECVDATA + struct ether_header *ether_header = mtod(m, struct ether_header *); + u_char *ether = (hook == mfp->mf_ether_hook? ether_header->ether_shost : ether_header->ether_dhost); + + mtx_lock(&mfp->mtx); + + u_int i; + int found = macfilter_find_mac(mfp, ether, &i); + if (found) { + DEBUG("ether=" MAC_FMT " %d: bytes: %s->%s", + MAC_S_ARGS(ether), m->m_len - ETHER_HDR_LEN, + NG_HOOK_NAME(hook), NG_HOOK_NAME(next_hook)); + } + + mtx_unlock(&mfp->mtx); +#endif + + 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); +} Index: tests/sys/Makefile =================================================================== --- tests/sys/Makefile +++ tests/sys/Makefile @@ -20,6 +20,7 @@ TESTS_SUBDIRS+= mac TESTS_SUBDIRS+= mqueue TESTS_SUBDIRS+= net +TESTS_SUBDIRS+= netgraph TESTS_SUBDIRS+= netinet TESTS_SUBDIRS+= netinet6 TESTS_SUBDIRS+= netipsec Index: tests/sys/netgraph/Makefile =================================================================== --- /dev/null +++ tests/sys/netgraph/Makefile @@ -0,0 +1,12 @@ +# $FreeBSD$ + +PACKAGE= tests + +TESTSDIR= ${TESTSBASE}/sys/netgraph +BINDIR= ${TESTSDIR} + +TAP_TESTS_SH+= ng_macfilter_test.sh + +TEST_METADATA.runtests+= required_user="root" + +.include Index: tests/sys/netgraph/ng_macfilter_test.sh =================================================================== --- /dev/null +++ tests/sys/netgraph/ng_macfilter_test.sh @@ -0,0 +1,397 @@ +#!/bin/sh + +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 + 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