Page MenuHomeFreeBSD

D58732.id.diff
No OneTemporary

D58732.id.diff

diff --git a/sys/net/if_bridge.c b/sys/net/if_bridge.c
--- a/sys/net/if_bridge.c
+++ b/sys/net/if_bridge.c
@@ -313,12 +313,14 @@
CK_LIST_ENTRY(bridge_mdb_member) bmm_next;
struct bridge_iflist *bmm_bif;
unsigned long bmm_expire;
+ struct epoch_context bmm_epoch_ctx;
};
struct bridge_mdb_entry {
CK_LIST_ENTRY(bridge_mdb_entry) bmd_next;
struct bridge_mdb_key bmd_key;
CK_LIST_HEAD(, bridge_mdb_member) bmd_members;
+ struct epoch_context bmd_epoch_ctx;
};
/*
@@ -431,6 +433,8 @@
static int bridge_mdb_igmp_v3_report(struct bridge_softc *,
struct bridge_iflist *, struct mbuf *, int, int,
ether_vlanid_t);
+static bool bridge_mdb_forward(struct bridge_softc *, struct bridge_iflist *,
+ struct mbuf *);
#endif
static int bridge_rtnode_addr_cmp(const uint8_t *, const uint8_t *);
@@ -1066,7 +1070,7 @@
{
struct bridge_mdb_entry *bmd;
- BRIDGE_RT_LOCK_ASSERT(sc);
+ BRIDGE_RT_LOCK_OR_NET_EPOCH_ASSERT(sc);
CK_LIST_FOREACH(bmd, &sc->sc_mdb, bmd_next) {
if (bridge_mdb_key_equal(&bmd->bmd_key, key))
@@ -1090,6 +1094,30 @@
return (NULL);
}
+/*
+ * MDB nodes are read by bridge_mdb_lookup()/bridge_mdb_forward() under the
+ * net epoch without holding BRIDGE_RT_LOCK. Unlink them synchronously under
+ * the lock, but defer the free until the epoch ends so a concurrent reader
+ * cannot dereference freed memory. This mirrors bridge_rtnode_destroy().
+ */
+static void
+bridge_mdb_destroy_member_cb(struct epoch_context *ctx)
+{
+ struct bridge_mdb_member *bmm;
+
+ bmm = __containerof(ctx, struct bridge_mdb_member, bmm_epoch_ctx);
+ free(bmm, M_DEVBUF);
+}
+
+static void
+bridge_mdb_destroy_entry_cb(struct epoch_context *ctx)
+{
+ struct bridge_mdb_entry *bmd;
+
+ bmd = __containerof(ctx, struct bridge_mdb_entry, bmd_epoch_ctx);
+ free(bmd, M_DEVBUF);
+}
+
static void
bridge_mdb_destroy_entry(struct bridge_softc *sc, struct bridge_mdb_entry *bmd)
{
@@ -1100,12 +1128,12 @@
while ((bmm = CK_LIST_FIRST(&bmd->bmd_members)) != NULL) {
CK_LIST_REMOVE(bmm, bmm_next);
sc->sc_mdbmembercnt--;
- free(bmm, M_DEVBUF);
+ NET_EPOCH_CALL(bridge_mdb_destroy_member_cb, &bmm->bmm_epoch_ctx);
}
CK_LIST_REMOVE(bmd, bmd_next);
sc->sc_mdbcnt--;
- free(bmd, M_DEVBUF);
+ NET_EPOCH_CALL(bridge_mdb_destroy_entry_cb, &bmd->bmd_epoch_ctx);
}
static bool
@@ -1116,14 +1144,14 @@
CK_LIST_REMOVE(bmm, bmm_next);
sc->sc_mdbmembercnt--;
- free(bmm, M_DEVBUF);
+ NET_EPOCH_CALL(bridge_mdb_destroy_member_cb, &bmm->bmm_epoch_ctx);
if (!CK_LIST_EMPTY(&bmd->bmd_members))
return (false);
CK_LIST_REMOVE(bmd, bmd_next);
sc->sc_mdbcnt--;
- free(bmd, M_DEVBUF);
+ NET_EPOCH_CALL(bridge_mdb_destroy_entry_cb, &bmd->bmd_epoch_ctx);
return (true);
}
@@ -1352,6 +1380,173 @@
return (0);
}
+
+/*
+ * bridge_mdb_forward:
+ *
+ * Look up an IPv4 multicast destination in the MDB and forward only to
+ * member interfaces that have a learned listener for the group. Returns
+ * true if the MDB handled the packet (caller must not flood); returns
+ * false if the group is unknown and the caller should fall back to
+ * flooding. Consumes m on a true return.
+ */
+static bool
+bridge_mdb_forward(struct bridge_softc *sc, struct bridge_iflist *sbif,
+ struct mbuf *m)
+{
+ struct bridge_mdb_entry *bmd;
+ struct bridge_mdb_member *bmm, *last;
+ struct bridge_mdb_key key;
+ struct ether_header *eh;
+ struct ether_vlan_header evh;
+ struct ip ip;
+ struct ifnet *src_if, *dst_if;
+ ether_vlanid_t vlan;
+ uint16_t ether_type;
+ struct mbuf *mc;
+ int i, ipoff;
+ bool used;
+
+ NET_EPOCH_ASSERT();
+
+ if (m->m_pkthdr.len < ETHER_HDR_LEN)
+ return (false);
+
+ eh = mtod(m, struct ether_header *);
+
+ /* IPv4 multicast Ethernet addresses are 01:00:5e:xx:xx:xx. */
+ if (eh->ether_dhost[0] != 0x01 || eh->ether_dhost[1] != 0x00 ||
+ eh->ether_dhost[2] != 0x5e)
+ return (false);
+
+ vlan = VLANTAGOF(m);
+ ether_type = ntohs(eh->ether_type);
+ ipoff = ETHER_HDR_LEN;
+
+ if (ether_type == ETHERTYPE_VLAN || ether_type == ETHERTYPE_QINQ) {
+ if (m->m_pkthdr.len < (int)sizeof(evh))
+ return (false);
+ m_copydata(m, 0, sizeof(evh), (caddr_t)&evh);
+ if ((m->m_flags & M_VLANTAG) == 0)
+ vlan = EVL_VLANOFTAG(ntohs(evh.evl_tag));
+ ether_type = ntohs(evh.evl_proto);
+ ipoff = sizeof(evh);
+ }
+
+ if (ether_type != ETHERTYPE_IP)
+ return (false);
+
+ if (m->m_pkthdr.len < ipoff + (int)sizeof(ip))
+ return (false);
+ m_copydata(m, ipoff, sizeof(ip), (caddr_t)&ip);
+
+ if (!IN_MULTICAST(ntohl(ip.ip_dst.s_addr)))
+ return (false);
+
+ if (bridge_mdb_key_init(&key, AF_INET, &ip.ip_dst, vlan) != 0)
+ return (false);
+
+ /*
+ * Look up the MDB entry under epoch. bridge_mdb_lookup accepts
+ * either BRIDGE_RT_LOCK or an active net epoch for readers.
+ */
+ bmd = bridge_mdb_lookup(sc, &key);
+ if (bmd == NULL)
+ return (false);
+
+ /*
+ * Run the bridge-level output filter once before replicating.
+ * Mirror the runfilt=1 path in bridge_broadcast.
+ */
+ if (PFIL_HOOKED_OUT_46) {
+ if (bridge_pfil(&m, sc->sc_ifp, NULL, PFIL_OUT) != 0)
+ return (true);
+ if (m == NULL)
+ return (true);
+ }
+
+ src_if = m->m_pkthdr.rcvif;
+
+ /*
+ * Walk the member list once to find the last eligible interface so
+ * the original mbuf can be handed to it without an extra m_dup.
+ */
+ last = NULL;
+ CK_LIST_FOREACH(bmm, &bmd->bmd_members, bmm_next) {
+ struct bridge_iflist *dbif = bmm->bmm_bif;
+
+ if (dbif->bif_ifp == src_if)
+ continue;
+ if (sbif != NULL &&
+ (sbif->bif_flags & dbif->bif_flags & IFBIF_PRIVATE))
+ continue;
+ if ((dbif->bif_flags & IFBIF_STP) &&
+ dbif->bif_stp.bp_state == BSTP_IFSTATE_DISCARDING)
+ continue;
+ if ((dbif->bif_ifp->if_drv_flags & IFF_DRV_RUNNING) == 0)
+ continue;
+ last = bmm;
+ }
+
+ if (last == NULL) {
+ /*
+ * Group is known but every learned port is either the source
+ * or ineligible. Drop to suppress unneeded replication.
+ */
+ m_freem(m);
+ return (true);
+ }
+
+ used = false;
+ CK_LIST_FOREACH(bmm, &bmd->bmd_members, bmm_next) {
+ struct bridge_iflist *dbif = bmm->bmm_bif;
+
+ dst_if = dbif->bif_ifp;
+ if (dst_if == src_if)
+ continue;
+ if (sbif != NULL &&
+ (sbif->bif_flags & dbif->bif_flags & IFBIF_PRIVATE))
+ continue;
+ if ((dbif->bif_flags & IFBIF_STP) &&
+ dbif->bif_stp.bp_state == BSTP_IFSTATE_DISCARDING)
+ continue;
+ if ((dst_if->if_drv_flags & IFF_DRV_RUNNING) == 0)
+ continue;
+
+ if (bmm == last) {
+ mc = m;
+ used = true;
+ } else {
+ mc = m_dup(m, M_NOWAIT);
+ if (mc == NULL) {
+ if_inc_counter(sc->sc_ifp, IFCOUNTER_OERRORS, 1);
+ continue;
+ }
+ }
+
+ if (PFIL_HOOKED_OUT_46) {
+ if (!used) {
+ i = min(mc->m_pkthdr.len, max_protohdr);
+ mc = m_copyup(mc, i, ETHER_ALIGN);
+ if (mc == NULL) {
+ if_inc_counter(sc->sc_ifp,
+ IFCOUNTER_OERRORS, 1);
+ continue;
+ }
+ }
+ if (bridge_pfil(&mc, NULL, dst_if, PFIL_OUT) != 0)
+ continue;
+ if (mc == NULL)
+ continue;
+ }
+
+ bridge_enqueue(sc, dst_if, mc);
+ }
+ if (!used)
+ m_freem(m);
+
+ return (true);
+}
#endif
static void
@@ -3316,6 +3511,10 @@
}
if (dst_if == NULL) {
+#ifdef INET
+ if (bridge_mdb_forward(sc, sbif, m))
+ return;
+#endif
bridge_broadcast(sc, src_if, m, 1);
return;
}
diff --git a/tests/sys/net/if_bridge_test.sh b/tests/sys/net/if_bridge_test.sh
--- a/tests/sys/net/if_bridge_test.sh
+++ b/tests/sys/net/if_bridge_test.sh
@@ -917,6 +917,93 @@
vnet_cleanup
}
+atf_test_case "mdb_selective_forward" "cleanup"
+mdb_selective_forward_head()
+{
+ atf_set descr 'bridge MDB prunes learned IPv4 multicast from idle ports'
+ atf_set require.user root
+ atf_set require.progs "cc tcpdump timeout"
+}
+
+mdb_selective_forward_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ bridge_mdb_vnet_topology_create
+
+ # Compile a small helper that joins a multicast group and holds the
+ # socket open. VNET jails share the host filesystem, so a binary
+ # written to /tmp on the host is reachable via jexec without copying.
+ cat > /tmp/mcast_join_bridge_test.c << 'CSRC'
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+int main(int argc, char **argv)
+{
+ struct ip_mreq mreq;
+ int s, on = 1, secs;
+ if (argc != 4) return 1;
+ secs = atoi(argv[3]);
+ s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (s < 0) return 1;
+ setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+ memset(&mreq, 0, sizeof(mreq));
+ mreq.imr_multiaddr.s_addr = inet_addr(argv[1]);
+ mreq.imr_interface.s_addr = inet_addr(argv[2]);
+ if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+ &mreq, sizeof(mreq)) < 0)
+ return 1;
+ sleep(secs);
+ return 0;
+}
+CSRC
+ cc -o /tmp/mcast_join_bridge_test /tmp/mcast_join_bridge_test.c || \
+ atf_skip "could not compile mcast_join helper"
+
+ # Join 239.1.1.1 from the listener jail. The kernel sends an IGMP
+ # membership report which the bridge learns via bridge_mdb_input().
+ jexec mdbl /tmp/mcast_join_bridge_test 239.1.1.1 192.0.2.2 30 &
+ joiner_pid=$!
+ # Allow time for the IGMP report to traverse the bridge and be learned.
+ sleep 2
+
+ jexec mdbs route add -net 224.0.0.0/4 \
+ -interface ${mdb_sender_epair}b >/dev/null 2>&1 || true
+
+ # Arm tcpdump on the idle port. If the MDB is working, no multicast
+ # for 239.1.1.1 should arrive on the idle port.
+ capture="${PWD}/mdb_selective_forward.tcpdump"
+ rm -f "${capture}"
+ jexec mdbi sh -c "timeout 4 tcpdump --immediate-mode -n \
+ -i ${mdb_idle_epair}b -c 1 \
+ 'icmp and dst host 239.1.1.1' > ${capture} 2>&1" &
+ tcpdump_pid=$!
+ sleep 1
+
+ jexec mdbs ping -c 1 -t 1 239.1.1.1 >/dev/null 2>&1 || true
+ wait ${tcpdump_pid}
+ idle_status=$?
+
+ kill ${joiner_pid} 2>/dev/null || true
+
+ # idle_status 0 means tcpdump captured a packet on the idle port,
+ # which means MDB-based pruning is not working.
+ if [ ${idle_status} -eq 0 ]; then
+ cat "${capture}"
+ atf_fail "idle port received multicast that MDB should have pruned"
+ fi
+}
+
+mdb_selective_forward_cleanup()
+{
+ rm -f /tmp/mcast_join_bridge_test /tmp/mcast_join_bridge_test.c
+ vnet_cleanup
+}
+
atf_test_case "member_ifaddrs_enabled" "cleanup"
member_ifaddrs_enabled_head()
{
@@ -1602,6 +1689,7 @@
atf_add_test_case "group_fwd_mask"
atf_add_test_case "mdb_vnet_topology"
atf_add_test_case "mdb_unknown_multicast_flood"
+ atf_add_test_case "mdb_selective_forward"
atf_add_test_case "member_ifaddrs_enabled"
atf_add_test_case "member_ifaddrs_disabled"
atf_add_test_case "member_ifaddrs_vlan"

File Metadata

Mime Type
text/plain
Expires
Fri, Aug 21, 12:19 AM (3 h, 51 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
37015413
Default Alt Text
D58732.id.diff (10 KB)

Event Timeline