netgraph/ng_bridge: learn MACs via control message

Add a new control message to move ethernet addresses to a given link
in ng_bridge(4). Send this message instead of doing the work directly.
This decouples the read-only activity from the modification under a
more strict writer lock.

Decoupling the work is a prerequisite for multithreaded operation.

Approved by:	manpages (bcr), kp (earlier version)
MFC:		3 weeks
Differential Revision:	https://reviews.freebsd.org/D28516
This commit is contained in:
Lutz Donnerhacke 2021-05-04 22:14:59 +02:00
parent 36be84b966
commit b1bd44732d
3 changed files with 91 additions and 10 deletions

View File

@ -34,7 +34,7 @@
.\" .\"
.\" $FreeBSD$ .\" $FreeBSD$
.\" .\"
.Dd February 6, 2021 .Dd February 17, 2021
.Dt NG_BRIDGE 4 .Dt NG_BRIDGE 4
.Os .Os
.Sh NAME .Sh NAME
@ -214,6 +214,25 @@ Returns the current host mapping table used to direct packets, in a
.Vt "struct ng_bridge_host_ary" . .Vt "struct ng_bridge_host_ary" .
.It Dv NGM_BRIDGE_SET_PERSISTENT Pq Ar setpersistent .It Dv NGM_BRIDGE_SET_PERSISTENT Pq Ar setpersistent
This command sets the persistent flag on the node, and takes no arguments. This command sets the persistent flag on the node, and takes no arguments.
.It Dv NGM_BRIDGE_MOVE_HOST Pq Ar movehost
This command takes a
.Vt "struct ng_bridge_move_host"
as an argument.
It assigns the MAC
.Va addr
to the
.Va hook ,
which must not be assigned yet.
If the
.Va hook
is the empty string, the incoming hook of the control message is
used as fallback.
.Bd -literal -offset 0n
struct ng_bridge_move_host {
u_char addr[ETHER_ADDR_LEN]; /* ethernet address */
char hook[NG_HOOKSIZ]; /* link where addr can be found */
};
.Ed
.El .El
.Sh SHUTDOWN .Sh SHUTDOWN
This node shuts down upon receipt of a This node shuts down upon receipt of a

View File

@ -234,6 +234,13 @@ static const struct ng_parse_type ng_bridge_stats_type = {
&ng_parse_struct_type, &ng_parse_struct_type,
&ng_bridge_stats_type_fields &ng_bridge_stats_type_fields
}; };
/* Parse type for struct ng_bridge_move_host */
static const struct ng_parse_struct_field ng_bridge_move_host_type_fields[]
= NG_BRIDGE_MOVE_HOST_TYPE_INFO(&ng_parse_enaddr_type);
static const struct ng_parse_type ng_bridge_move_host_type = {
&ng_parse_struct_type,
&ng_bridge_move_host_type_fields
};
/* List of commands and how to convert arguments to/from ASCII */ /* List of commands and how to convert arguments to/from ASCII */
static const struct ng_cmdlist ng_bridge_cmdlist[] = { static const struct ng_cmdlist ng_bridge_cmdlist[] = {
@ -293,6 +300,13 @@ static const struct ng_cmdlist ng_bridge_cmdlist[] = {
NULL, NULL,
NULL NULL
}, },
{
NGM_BRIDGE_COOKIE,
NGM_BRIDGE_MOVE_HOST,
"movehost",
&ng_bridge_move_host_type,
NULL
},
{ 0 } { 0 }
}; };
@ -601,6 +615,32 @@ ng_bridge_rcvmsg(node_p node, item_p item, hook_p lasthook)
priv->persistent = 1; priv->persistent = 1;
break; break;
} }
case NGM_BRIDGE_MOVE_HOST:
{
struct ng_bridge_move_host *mh;
hook_p hook;
struct ng_bridge_host *host;
if (msg->header.arglen < sizeof(*mh)) {
error = EINVAL;
break;
}
mh = (struct ng_bridge_move_host *)msg->data;
hook = (mh->hook[0] == 0)
? lasthook
: ng_findhook(node, mh->hook);
if (hook == NULL) {
error = ENOENT;
break;
}
host = ng_bridge_get(priv, mh->addr);
if (host != NULL) {
error = EADDRINUSE;
break;
}
error = ng_bridge_put(priv, mh->addr, NG_HOOK_PRIVATE(hook));
break;
}
default: default:
error = EINVAL; error = EINVAL;
break; break;
@ -809,12 +849,26 @@ ng_bridge_rcvdata(hook_p hook, item_p item)
host->age = 0; host->age = 0;
} }
} else if (ctx.incoming->learnMac) { } else if (ctx.incoming->learnMac) {
if (!ng_bridge_put(priv, eh->ether_shost, ctx.incoming)) { struct ng_mesg *msg;
struct ng_bridge_move_host *mh;
int error = 0;
NG_MKMESSAGE(msg, NGM_BRIDGE_COOKIE, NGM_BRIDGE_MOVE_HOST,
sizeof(*mh), M_NOWAIT);
if (msg == NULL) {
counter_u64_add(ctx.incoming->stats.memoryFailures, 1); counter_u64_add(ctx.incoming->stats.memoryFailures, 1);
NG_FREE_ITEM(item); NG_FREE_ITEM(item);
NG_FREE_M(ctx.m); NG_FREE_M(ctx.m);
return (ENOMEM); return (ENOMEM);
} }
mh = (struct ng_bridge_move_host *)msg->data;
strncpy(mh->hook, NG_HOOK_NAME(ctx.incoming->hook),
sizeof(mh->hook));
memcpy(mh->addr, eh->ether_shost, sizeof(mh->addr));
NG_SEND_MSG_ID(error, node, msg, NG_NODE_ID(node),
NG_NODE_ID(node));
if (error)
counter_u64_add(ctx.incoming->stats.memoryFailures, 1);
} }
/* Run packet through ipfw processing, if enabled */ /* Run packet through ipfw processing, if enabled */
@ -959,8 +1013,7 @@ ng_bridge_get(priv_cp priv, const u_char *addr)
/* /*
* Add a new host entry to the table. This assumes the host doesn't * Add a new host entry to the table. This assumes the host doesn't
* already exist in the table. Returns 1 on success, 0 if there * already exist in the table. Returns 0 on success.
* was a memory allocation failure.
*/ */
static int static int
ng_bridge_put(priv_p priv, const u_char *addr, link_p link) ng_bridge_put(priv_p priv, const u_char *addr, link_p link)
@ -970,16 +1023,14 @@ ng_bridge_put(priv_p priv, const u_char *addr, link_p link)
#ifdef INVARIANTS #ifdef INVARIANTS
/* Assert that entry does not already exist in hashtable */ /* Assert that entry does not already exist in hashtable */
SLIST_FOREACH(host, &priv->tab[bucket], next) { KASSERT(ng_bridge_get(priv, addr) == NULL,
KASSERT(!ETHER_EQUAL(host->addr, addr),
("%s: entry %6D exists in table", __func__, addr, ":")); ("%s: entry %6D exists in table", __func__, addr, ":"));
}
#endif #endif
/* Allocate and initialize new hashtable entry */ /* Allocate and initialize new hashtable entry */
host = malloc(sizeof(*host), M_NETGRAPH_BRIDGE, M_NOWAIT); host = malloc(sizeof(*host), M_NETGRAPH_BRIDGE, M_NOWAIT);
if (host == NULL) if (host == NULL)
return (0); return (ENOMEM);
bcopy(addr, host->addr, ETHER_ADDR_LEN); bcopy(addr, host->addr, ETHER_ADDR_LEN);
host->link = link; host->link = link;
host->staleness = 0; host->staleness = 0;
@ -991,7 +1042,7 @@ ng_bridge_put(priv_p priv, const u_char *addr, link_p link)
/* Resize table if necessary */ /* Resize table if necessary */
ng_bridge_rehash(priv); ng_bridge_rehash(priv);
return (1); return (0);
} }
/* /*

View File

@ -140,6 +140,16 @@ struct ng_bridge_host_ary {
{ NULL } \ { NULL } \
} }
struct ng_bridge_move_host {
u_char addr[ETHER_ADDR_LEN]; /* ethernet address */
char hook[NG_HOOKSIZ]; /* link where addr can be found */
};
/* Keep this in sync with the above structure definition */
#define NG_BRIDGE_MOVE_HOST_TYPE_INFO(entype) { \
{ "addr", (entype) }, \
{ "hook", &ng_parse_hookbuf_type }, \
}
/* Netgraph control messages */ /* Netgraph control messages */
enum { enum {
NGM_BRIDGE_SET_CONFIG = 1, /* set node configuration */ NGM_BRIDGE_SET_CONFIG = 1, /* set node configuration */
@ -150,6 +160,7 @@ enum {
NGM_BRIDGE_GETCLR_STATS, /* atomically get & clear link stats */ NGM_BRIDGE_GETCLR_STATS, /* atomically get & clear link stats */
NGM_BRIDGE_GET_TABLE, /* get link table */ NGM_BRIDGE_GET_TABLE, /* get link table */
NGM_BRIDGE_SET_PERSISTENT, /* set persistent mode */ NGM_BRIDGE_SET_PERSISTENT, /* set persistent mode */
NGM_BRIDGE_MOVE_HOST, /* move a host to a link */
}; };
#endif /* _NETGRAPH_NG_BRIDGE_H_ */ #endif /* _NETGRAPH_NG_BRIDGE_H_ */