Page MenuHomeFreeBSD

D59350.id185732.diff
No OneTemporary

D59350.id185732.diff

diff --git a/share/man/man4/ng_bridge.4 b/share/man/man4/ng_bridge.4
--- a/share/man/man4/ng_bridge.4
+++ b/share/man/man4/ng_bridge.4
@@ -32,7 +32,7 @@
.\"
.\" Author: Archie Cobbs <archie@FreeBSD.org>
.\"
-.Dd August 31, 2026
+.Dd September 3, 2026
.Dt NG_BRIDGE 4
.Os
.Sh NAME
@@ -69,6 +69,14 @@
.Pp
A looped back link will be temporarily muted, i.e., all traffic
received on that link is ignored.
+.Pp
+If a packet is received on a hook that this node recently used to
+transmit that source address, it is treated as a reflected transmit
+and is dropped without moving the host and without muting the hook.
+Promiscuous receive on
+.Xr ng_ether 4
+.Ar lower
+is enough to produce that reflection.
.Sh IPFW PROCESSING
Processing of IP packets via the
.Xr ipfirewall 4
diff --git a/sys/netgraph/ng_bridge.c b/sys/netgraph/ng_bridge.c
--- a/sys/netgraph/ng_bridge.c
+++ b/sys/netgraph/ng_bridge.c
@@ -41,7 +41,10 @@
* each of its connected hooks, or links. A simple loop detection
* algorithm is included which disables a link for priv->conf.loopTimeout
* seconds when a host is seen to have jumped from one link to
- * another within priv->conf.minStableAge seconds.
+ * another within priv->conf.minStableAge seconds. A packet received
+ * on a hook this node recently used to transmit that source is
+ * treated as a reflected transmit, not a move, and is dropped
+ * without muting the hook.
*
* We keep a hashtable that maps Ethernet addresses to host info,
* which is contained in struct ng_bridge_host's. These structures
@@ -134,8 +137,11 @@
struct ng_bridge_host {
u_char addr[6]; /* ethernet address */
link_p link; /* link where addr can be found */
+ link_cp xmitLink; /* last hook we sent this source out */
u_int16_t age; /* seconds ago entry was created */
u_int16_t staleness; /* seconds ago host last heard from */
+ u_int16_t xmitAge; /* seconds since that send */
+ u_int16_t floodAge; /* seconds since flooded this source */
SLIST_ENTRY(ng_bridge_host) next; /* next entry in bucket */
};
@@ -715,9 +721,19 @@
*/
static inline int
ng_bridge_send_data(link_cp dst, int manycast, struct mbuf *m, item_p item) {
+ const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(dst->hook));
+ struct ng_bridge_host *host;
+ u_char shost[ETHER_ADDR_LEN];
+ int have_shost = 0;
int error = 0;
size_t len = m->m_pkthdr.len;
+ if (m->m_len >= ETHER_HDR_LEN) {
+ bcopy(mtod(m, struct ether_header *)->ether_shost,
+ shost, ETHER_ADDR_LEN);
+ have_shost = 1;
+ }
+
if(item != NULL)
NG_FWD_NEW_DATA(error, item, dst->hook, m);
else
@@ -734,6 +750,20 @@
return (error);
}
+ /*
+ * Remember the hook we last sent this source out. Written
+ * during shared access: the stores are pointer-sized or
+ * smaller, and a race costs at most one missed or spurious
+ * reflected-transmit drop within minStableAge.
+ */
+ if (have_shost) {
+ host = ng_bridge_get(priv, shost);
+ if (host != NULL) {
+ host->xmitLink = dst;
+ host->xmitAge = 0;
+ }
+ }
+
counter_u64_add(dst->stats.xmitPackets, 1);
counter_u64_add(dst->stats.xmitOctets, len);
switch (manycast) {
@@ -852,12 +882,30 @@
/* Look up packet's source Ethernet address in hashtable */
if ((host = ng_bridge_get(priv, eh->ether_shost)) != NULL)
/* Update time since last heard from this host.
- * This is safe without locking, because it's
- * the only operation during shared access.
+ * This is safe without locking: the writes done
+ * during shared access are this reset, the floodAge
+ * reset below, and the xmitLink/xmitAge record in
+ * ng_bridge_send_data().
*/
if (__predict_false(host->staleness > 0))
host->staleness = 0;
+ /*
+ * Packet received on a hook we recently used to transmit
+ * this source, or the source was recently flooded out every
+ * hook: our own transmit coming back. Do not move the host
+ * and do not mute the hook.
+ */
+ if (host != NULL && host->link != ctx.incoming &&
+ ((host->xmitLink == ctx.incoming &&
+ host->xmitAge <= priv->conf.minStableAge) ||
+ host->floodAge <= priv->conf.minStableAge)) {
+ counter_u64_add(ctx.incoming->stats.loopDrops, 1);
+ NG_FREE_ITEM(item);
+ NG_FREE_M(ctx.m);
+ return (0);
+ }
+
/*
* learnMac is 0 on uplink: neither insert a new host nor
* move an existing one from packets received there.
@@ -920,6 +968,14 @@
counter_u64_add(ctx.incoming->stats.recvUnknown, 1);
}
+ /*
+ * Every other hook is about to get a copy, so a reflection
+ * can return on any of them; xmitLink alone cannot record
+ * that. Mark the source as recently flooded.
+ */
+ if ((host = ng_bridge_get(priv, eh->ether_shost)) != NULL)
+ host->floodAge = 0;
+
/* Distribute unknown, multicast, broadcast pkts to all other links */
NG_NODE_FOREACH_HOOK(node, ng_bridge_send_ctx, &ctx);
@@ -1069,8 +1125,11 @@
return (ENOMEM);
bcopy(addr, host->addr, ETHER_ADDR_LEN);
host->link = link;
+ host->xmitLink = NULL;
host->staleness = 0;
host->age = 0;
+ host->xmitAge = 0xffff;
+ host->floodAge = 0xffff;
/* Add new element to hash bucket */
SLIST_INSERT_HEAD(&priv->tab[bucket], host, next);
@@ -1176,8 +1235,17 @@
*hptr = SLIST_NEXT(host, next);
free(host, M_NETGRAPH_BRIDGE);
priv->numHosts--;
- } else
+ } else {
+ /*
+ * The link is about to be freed; a
+ * reconnect could reuse the address.
+ */
+ if (host->xmitLink == link) {
+ host->xmitLink = NULL;
+ host->xmitAge = 0xffff;
+ }
hptr = &SLIST_NEXT(host, next);
+ }
}
}
}
@@ -1230,6 +1298,10 @@
} else {
if (host->age < 0xffff)
host->age++;
+ if (host->xmitAge < 0xffff)
+ host->xmitAge++;
+ if (host->floodAge < 0xffff)
+ host->floodAge++;
hptr = &SLIST_NEXT(host, next);
counter++;
}

File Metadata

Mime Type
text/plain
Expires
Sat, Sep 12, 9:05 PM (57 m, 30 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
38819100
Default Alt Text
D59350.id185732.diff (5 KB)

Event Timeline