netgraph/ng_bridge: Introduce "uplink" ports without MAC learning

The ng_bridge(4) node is designed to work in moderately small
environments. Connecting such a node to a larger network rapidly fills
the MAC table for no reason. It even become complicated to obtain data
from the gettable message, because the result is too large to
transmit.

This patch introduces, two new functionality bits on the hooks:
  - Allow or disallow MAC address learning for incoming patckets.
  - Allow or disallow sending unknown MACs through this hook.

Uplinks are characterized by denied learing while sending out
unknowns. Normal links are charaterized by allowed learning and
sending out unknowns.

Reviewed by:	kp
Approved by:	kp (mentor)
MFC after:	2 weeks
Differential Revision: https://reviews.freebsd.org/D23963
This commit is contained in:
Lutz Donnerhacke 2021-02-06 11:08:24 +01:00
parent 344f1083e1
commit f961caf218
3 changed files with 87 additions and 36 deletions

View file

@ -34,7 +34,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd October 2, 2019
.Dd February 6, 2021
.Dt NG_BRIDGE 4
.Os
.Sh NAME
@ -92,6 +92,23 @@ To connect the host machine to a bridged network, simply connect the
hook of an
.Xr ng_ether 4
node to the bridge node.
.Pp
Instead of naming a hook
.Dv linkX
the hook might be also named
.Dv uplinkX .
The node does not learn MAC addresses on uplink hooks, which keeps
the internal address table small.
This way it is desirable to connect the
.Dv lower
hook of an
.Xr ng_ether 4
node to an
.Dv uplink
hook of the bridge, and ignore the complexity of the outside world.
Frames with unknown MACs are always sent out to
.Dv uplink
hooks, so no functionality is lost.
.Sh CONTROL MESSAGES
This node type supports the generic control messages, plus the
following:
@ -141,8 +158,9 @@ The node configuration is not changed.
This command takes a four byte link number as an argument and
returns a
.Dv "struct ng_bridge_link_stats"
containing statistics for the corresponding link, which must be
currently connected:
containing statistics for the corresponding
.Dv link ,
which must be currently connected:
.Bd -literal -offset 0n
/* Statistics structure (one for each link) */
struct ng_bridge_link_stats {
@ -162,6 +180,12 @@ struct ng_bridge_link_stats {
uint64_t memoryFailures; /* times couldn't get mem or mbuf */
};
.Ed
.Pp
Negative numbers refer to the
.Dv uplink
hooks.
So querying for -7 will get the statistics for hook
.Dv uplink7 .
.It Dv NGM_BRIDGE_CLR_STATS Pq Ic clrstats
This command takes a four byte link number as an argument and
clears the statistics for that link.

View file

@ -91,6 +91,8 @@ static MALLOC_DEFINE(M_NETGRAPH_BRIDGE, "netgraph_bridge",
struct ng_bridge_link {
hook_p hook; /* netgraph hook */
u_int16_t loopCount; /* loop ignore timer */
unsigned int learnMac : 1, /* autolearn macs */
sendUnknown : 1;/* send unknown macs out */
struct ng_bridge_link_stats stats; /* link stats */
};
@ -334,38 +336,49 @@ static int
ng_bridge_newhook(node_p node, hook_p hook, const char *name)
{
const priv_p priv = NG_NODE_PRIVATE(node);
char linkName[NG_HOOKSIZ];
u_int32_t linkNum;
link_p link;
const char *prefix = NG_BRIDGE_HOOK_LINK_PREFIX;
bool isUplink;
/* Check for a link hook */
if (strlen(name) > strlen(NG_BRIDGE_HOOK_LINK_PREFIX)) {
char linkName[NG_HOOKSIZ];
u_int32_t linkNum;
link_p link;
if (strlen(name) <= strlen(prefix))
return (EINVAL); /* Unknown hook name */
/* primitive parsing */
linkNum = strtoul(name + strlen(NG_BRIDGE_HOOK_LINK_PREFIX),
NULL, 10);
/* validation by comparing against the reconstucted name */
snprintf(linkName, sizeof(linkName),
"%s%u", NG_BRIDGE_HOOK_LINK_PREFIX,
linkNum);
if (strcmp(linkName, name) != 0)
return (EINVAL);
if(NG_PEER_NODE(hook) == node)
return (ELOOP);
isUplink = (name[0] == 'u');
if (isUplink)
prefix = NG_BRIDGE_HOOK_UPLINK_PREFIX;
link = malloc(sizeof(*link), M_NETGRAPH_BRIDGE,
M_WAITOK|M_ZERO);
if (link == NULL)
return (ENOMEM);
link->hook = hook;
NG_HOOK_SET_PRIVATE(hook, link);
priv->numLinks++;
return (0);
/* primitive parsing */
linkNum = strtoul(name + strlen(prefix), NULL, 10);
/* validation by comparing against the reconstucted name */
snprintf(linkName, sizeof(linkName), "%s%u", prefix, linkNum);
if (strcmp(linkName, name) != 0)
return (EINVAL);
if (linkNum == 0 && isUplink)
return (EINVAL);
if(NG_PEER_NODE(hook) == node)
return (ELOOP);
link = malloc(sizeof(*link), M_NETGRAPH_BRIDGE, M_ZERO);
if (link == NULL)
return (ENOMEM);
link->hook = hook;
if (isUplink) {
link->learnMac = 0;
link->sendUnknown = 1;
} else {
link->learnMac = 1;
link->sendUnknown = 1;
}
/* Unknown hook name */
return (EINVAL);
NG_HOOK_SET_PRIVATE(hook, link);
priv->numLinks++;
return (0);
}
/*
@ -438,6 +451,11 @@ ng_bridge_rcvmsg(node_p node, item_p item, hook_p lasthook)
i = 0;
for (bucket = 0; bucket < priv->numBuckets; bucket++) {
SLIST_FOREACH(hent, &priv->tab[bucket], next) {
const char *name = NG_HOOK_NAME(hent->host.link->hook);
const char *prefix = name[0] == 'u' ?
NG_BRIDGE_HOOK_UPLINK_PREFIX :
NG_BRIDGE_HOOK_LINK_PREFIX;
memcpy(ary->hosts[i].addr,
hent->host.addr,
sizeof(ary->hosts[i].addr));
@ -445,9 +463,7 @@ ng_bridge_rcvmsg(node_p node, item_p item, hook_p lasthook)
ary->hosts[i].staleness =
hent->host.staleness;
ary->hosts[i].linkNum = strtol(
NG_HOOK_NAME(hent->host.link->hook) +
strlen(NG_BRIDGE_HOOK_LINK_PREFIX),
NULL, 10);
name + strlen(prefix), NULL, 10);
i++;
}
}
@ -506,15 +522,20 @@ ng_bridge_rcvmsg(node_p node, item_p item, hook_p lasthook)
hook_p hook;
link_p link;
char linkName[NG_HOOKSIZ];
int linkNum;
/* Get link number */
if (msg->header.arglen != sizeof(u_int32_t)) {
error = EINVAL;
break;
}
snprintf(linkName, sizeof(linkName),
"%s%u", NG_BRIDGE_HOOK_LINK_PREFIX,
*((u_int32_t *)msg->data));
linkNum = *((int32_t *)msg->data);
if (linkNum < 0)
snprintf(linkName, sizeof(linkName),
"%s%u", NG_BRIDGE_HOOK_UPLINK_PREFIX, -linkNum);
else
snprintf(linkName, sizeof(linkName),
"%s%u", NG_BRIDGE_HOOK_LINK_PREFIX, linkNum);
if ((hook = ng_findhook(node, linkName)) == NULL) {
error = ENOTCONN;
@ -609,6 +630,10 @@ ng_bridge_send_ctx(hook_p dst, void *arg)
return (1);
}
/* Skip sending unknowns to undesired links */
if (!ctx->manycast && !destLink->sendUnknown)
return (1);
if (ctx->foundFirst == NULL) {
/*
* This is the first usable link we have found.
@ -750,7 +775,7 @@ ng_bridge_rcvdata(hook_p hook, item_p item)
host->link = ctx.incoming;
host->age = 0;
}
} else {
} else if (ctx.incoming->learnMac) {
if (!ng_bridge_put(priv, eh->ether_shost, ctx.incoming)) {
ctx.incoming->stats.memoryFailures++;
NG_FREE_ITEM(item);

View file

@ -64,6 +64,8 @@
/* Hook names */
#define NG_BRIDGE_HOOK_LINK_PREFIX "link" /* append decimal integer */
#define NG_BRIDGE_HOOK_LINK_FMT "link%d" /* for use with printf(3) */
#define NG_BRIDGE_HOOK_UPLINK_PREFIX "uplink" /* append decimal integer */
#define NG_BRIDGE_HOOK_UPLINK_FMT "uplink%d" /* for use with printf(3) */
/* Node configuration structure */
struct ng_bridge_config {