Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F170815546
D59350.id185786.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
10 KB
Referenced Files
None
Subscribers
None
D59350.id185786.diff
View Options
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,19 @@
return (error);
}
+ /*
+ * Record the hook this source was last sent out. Unlocked
+ * during shared access; a lost store costs at most one
+ * missed or spurious reflected-transmit drop.
+ */
+ 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 +881,27 @@
/* 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, like the other
+ * shared-access stores (xmitLink, xmitAge, floodAge).
*/
if (__predict_false(host->staleness > 0))
host->staleness = 0;
+ /*
+ * Our own transmit coming back: the source was recently sent
+ * out this hook or flooded out every hook. Drop it without
+ * moving the host or muting 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 +964,10 @@
counter_u64_add(ctx.incoming->stats.recvUnknown, 1);
}
+ /* A flood can reflect back on any hook */
+ 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 +1117,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 +1227,14 @@
*hptr = SLIST_NEXT(host, next);
free(host, M_NETGRAPH_BRIDGE);
priv->numHosts--;
- } else
+ } else {
+ /* forget the freed link */
+ if (host->xmitLink == link) {
+ host->xmitLink = NULL;
+ host->xmitAge = 0xffff;
+ }
hptr = &SLIST_NEXT(host, next);
+ }
}
}
}
@@ -1230,6 +1287,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++;
}
diff --git a/tests/sys/netgraph/bridge.c b/tests/sys/netgraph/bridge.c
--- a/tests/sys/netgraph/bridge.c
+++ b/tests/sys/netgraph/bridge.c
@@ -34,6 +34,8 @@
#include <atf-c.h>
#include <errno.h>
#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
#include <net/ethernet.h>
#include <netinet/in.h>
@@ -50,6 +52,20 @@
int cnt;
};
+static void get_stats(char const *source, struct ng_mesg *msg, void *ctx);
+struct getstats
+{
+ u_int32_t tok;
+ struct ng_bridge_link_stats st;
+};
+
+static void get_hostlink(char const *source, struct ng_mesg *msg, void *ctx);
+struct gethost
+{
+ u_int32_t tok;
+ char hook[NG_HOOKSIZ];
+};
+
struct frame4
{
struct ether_header eh;
@@ -192,6 +208,7 @@
ATF_TC_BODY(loop, dummy)
{
ng_counter_t r;
+ struct getstats rs;
int i;
ng_init();
@@ -223,6 +240,12 @@
ng_handle_events(50, &r);
ATF_CHECK(r[0] == 0 && r[1] == 1);
+ /* host is learned now: flood again so both bridges record it */
+ ng_counter_clear(r);
+ ng_send_data("a", &msg4, sizeof(msg4));
+ ng_handle_events(50, &r);
+ ATF_CHECK(r[0] == 0 && r[1] == 1);
+
/*-
* Closed loop, DANGEROUS!
*
@@ -235,18 +258,98 @@
ng_counter_clear(r);
msg4.eh.ether_shost[5] = 1;
ng_errors(PASS);
+ errno = 0;
ng_send_data("a", &msg4, sizeof(msg4));
- ATF_CHECK_ERRNO(ELOOP, errno != 0); /* loop might be detected */
+ ATF_CHECK(errno == 0); /* reflected transmit, not a loop */
ng_errors(FAIL);
for (i = 0; i < 10; i++) /* don't run forever */
if (!ng_handle_event(50, &r))
break;
ATF_CHECK(r[0] == 0 && r[1] == 1);
+ /* dropped on the returning hooks, nothing muted */
+ ng_register_msg(get_stats);
+ rs.tok = ng_send_msg("bridge1:", "getstats 12");
+ ng_handle_events(50, &rs);
+ ATF_CHECK(rs.st.loopDrops == 1 && rs.st.loopDetects == 0);
+ rs.tok = ng_send_msg("bridge2:", "getstats 12");
+ ng_handle_events(50, &rs);
+ ATF_CHECK(rs.st.loopDrops == 1 && rs.st.loopDetects == 0);
+
ng_shutdown("bridge1:");
ng_shutdown("bridge2:");
}
+ATF_TC(reflect);
+ATF_TC_HEAD(reflect, conf)
+{
+ atf_tc_set_md_var(conf, "require.user", "root");
+}
+
+ATF_TC_BODY(reflect, dummy)
+{
+ ng_counter_t r;
+ struct getstats rs;
+ struct gethost rh;
+
+ ng_init();
+ ng_errors(PASS);
+ ng_shutdown("bridge:");
+ ng_errors(FAIL);
+
+ ng_mkpeer(".", "a", "bridge", "link0");
+ ng_name("a", "bridge");
+ ng_connect(".", "b", "bridge:", "link1");
+ ng_connect(".", "c", "bridge:", "link2");
+
+ ng_register_data("a", get_data0);
+ ng_register_data("b", get_data1);
+ ng_register_data("c", get_data2);
+
+ /* learn host 1 on link0, then flood again so it is recorded */
+ msg4.eh.ether_shost[5] = 1;
+ msg4.eh.ether_dhost[5] = 0;
+ ng_counter_clear(r);
+ ng_send_data("a", &msg4, sizeof(msg4));
+ ng_handle_events(50, &r);
+ ATF_CHECK(r[0] == 0 && r[1] == 1 && r[2] == 1);
+ ng_counter_clear(r);
+ ng_send_data("a", &msg4, sizeof(msg4));
+ ng_handle_events(50, &r);
+ ATF_CHECK(r[0] == 0 && r[1] == 1 && r[2] == 1);
+
+ /* reflect it back on link1: dropped, no move, no mute */
+ ng_counter_clear(r);
+ errno = 0;
+ ng_send_data("b", &msg4, sizeof(msg4));
+ ATF_CHECK(errno == 0);
+ ng_handle_events(50, &r);
+ ATF_CHECK(r[0] == 0 && r[1] == 0 && r[2] == 0);
+
+ ng_register_msg(get_stats);
+ rs.tok = ng_send_msg("bridge:", "getstats 1");
+ ng_handle_events(50, &rs);
+ ATF_CHECK(rs.st.loopDrops == 1 && rs.st.loopDetects == 0);
+
+ ng_register_msg(get_hostlink);
+ rh.tok = ng_send_msg("bridge:", "gettable");
+ ng_handle_events(50, &rh);
+ ATF_CHECK(strcmp(rh.hook, "link0") == 0);
+
+ /* past minStableAge the same frame is a genuine move */
+ sleep(3);
+ ng_counter_clear(r);
+ ng_send_data("b", &msg4, sizeof(msg4));
+ ng_handle_events(50, &r);
+ ATF_CHECK(r[0] == 1 && r[1] == 0 && r[2] == 1);
+
+ rh.tok = ng_send_msg("bridge:", "gettable");
+ ng_handle_events(50, &rh);
+ ATF_CHECK(strcmp(rh.hook, "link1") == 0);
+
+ ng_shutdown("bridge:");
+}
+
ATF_TC(many_unicasts);
ATF_TC_HEAD(many_unicasts, conf)
{
@@ -611,6 +714,7 @@
{
ATF_TP_ADD_TC(bridge, basic);
ATF_TP_ADD_TC(bridge, loop);
+ ATF_TP_ADD_TC(bridge, reflect);
ATF_TP_ADD_TC(bridge, persistence);
ATF_TP_ADD_TC(bridge, many_unicasts);
ATF_TP_ADD_TC(bridge, many_broadcasts);
@@ -630,3 +734,31 @@
if (rm->tok == msg->header.token)
rm->cnt = gt->numHosts;
}
+
+static void
+get_stats(char const *source, struct ng_mesg *msg, void *ctx)
+{
+ struct getstats *rs = ctx;
+
+ fprintf(stderr, "Response from %s to query %d\n", source, msg->header.token);
+ if (rs->tok == msg->header.token)
+ memcpy(&rs->st, msg->data, sizeof(rs->st));
+}
+
+/* hook of the host msg4 is sent from, or "" if unknown */
+static void
+get_hostlink(char const *source, struct ng_mesg *msg, void *ctx)
+{
+ struct gethost *rh = ctx;
+ struct ng_bridge_host_ary *gt = (void *)msg->data;
+ u_int32_t i;
+
+ fprintf(stderr, "Response from %s to query %d\n", source, msg->header.token);
+ if (rh->tok != msg->header.token)
+ return;
+ rh->hook[0] = '\0';
+ for (i = 0; i < gt->numHosts; i++)
+ if (memcmp(gt->hosts[i].addr, msg4.eh.ether_shost,
+ ETHER_ADDR_LEN) == 0)
+ strlcpy(rh->hook, gt->hosts[i].hook, sizeof(rh->hook));
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Sep 7, 7:40 PM (2 h, 31 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
38351741
Default Alt Text
D59350.id185786.diff (10 KB)
Attached To
Mode
D59350: ng_bridge(4): do not move a host on a reflected transmit
Attached
Detach File
Event Timeline
Log In to Comment