bridge: Add support for emulated netmap mode

if_bridge receives packets via a special interface, if_bridge_input,
rather than by if_input.  Thus, netmap's usual hooking of ifnet routines
does not work as expected.  Instead, modify bridge_input() to pass
packets directly to netmap when it is enabled.  This applies to both
locally delivered packets and forwarded packets.

When a netmap application transmits a packet by writing it to the host
TX ring, the mbuf chain is passed to if_input, which ordinarily points
to ether_input().  However, when transmitting via if_bridge,
bridge_input() needs to see the packet again in order to decide whether
to deliver or forward.  Thus, introduce a new protocol flag,
M_BRIDGE_INJECT, which 1) causes the packet to be passed to
bridge_input() again after Ethernet processing, and 2) avoids passing
the packet back to netmap.  The source MAC address of the packet is used
to determine the original "receiving" interface.

Reviewed by:	vmaffione
MFC after:	2 months
Sponsored by:	Zenarmor
Sponsored by:	OPNsense
Sponsored by:	Klara, Inc.
Differential Revision:	https://reviews.freebsd.org/D38066
This commit is contained in:
Mark Johnston 2023-04-10 11:18:25 -04:00 committed by Franco Fichtner
parent 6429ff037e
commit eebd4b140f
5 changed files with 136 additions and 11 deletions

View File

@ -410,6 +410,29 @@ interface and not to the bridge members.
Enabling
.Va net.link.bridge.pfil_local_phys
will let you do the additional filtering on the physical interface.
.Sh NETMAP
.Xr netmap 4
applications may open a bridge interface in emulated mode.
The netmap application will receive all packets which arrive from member
interfaces.
In particular, packets which would otherwise be forwarded to another
member interface will be received by the netmap application.
.Pp
When the
.Xr netmap 4
application transmits a packet to the host stack via the bridge interface,
.Nm
receive it and attempts to determine its
.Ql source
interface by looking up the source MAC address in the interface's learning
tables.
Packets for which no matching source interface is found are dropped and the
input error counter is incremented.
If a matching source interface is found,
.Nm
treats the packet as though it was received from the corresponding interface
and handles it normally without passing the packet back to
.Xr netmap 4 .
.Sh EXAMPLES
The following when placed in the file
.Pa /etc/rc.conf
@ -484,6 +507,7 @@ ifconfig bridge0 addm fxp0 addm gif0 up
.Xr gif 4 ,
.Xr ipf 4 ,
.Xr ipfw 4 ,
.Xr netmap 4 ,
.Xr pf 4 ,
.Xr ifconfig 8
.Sh HISTORY

View File

@ -41,6 +41,7 @@
* Ethernet-specific mbuf flags.
*/
#define M_HASFCS M_PROTO5 /* FCS included at end of frame */
#define M_BRIDGE_INJECT M_PROTO6 /* if_bridge-injected frame */
/*
* Ethernet CRC32 polynomials (big- and little-endian versions).

View File

@ -274,6 +274,8 @@ struct bridge_softc {
uint32_t sc_brtexceeded; /* # of cache drops */
struct ifnet *sc_ifaddr; /* member mac copied from */
struct ether_addr sc_defaddr; /* Default MAC address */
void (*sc_if_input) /* Saved copy of if_input */
(struct ifnet *, struct mbuf *);
struct epoch_context sc_epoch_ctx;
};
@ -304,6 +306,7 @@ static int bridge_altq_transmit(if_t, struct mbuf *);
#endif
static void bridge_qflush(struct ifnet *);
static struct mbuf *bridge_input(struct ifnet *, struct mbuf *);
static void bridge_inject(struct ifnet *, struct mbuf *);
static int bridge_output(struct ifnet *, struct mbuf *, struct sockaddr *,
struct rtentry *);
static int bridge_enqueue(struct bridge_softc *, struct ifnet *,
@ -753,6 +756,15 @@ bridge_clone_create(struct if_clone *ifc, int unit, caddr_t params)
#ifdef VIMAGE
ifp->if_reassign = bridge_reassign;
#endif
sc->sc_if_input = ifp->if_input; /* ether_input */
ifp->if_input = bridge_inject;
/*
* Allow BRIDGE_INPUT() to pass in packets originating from the bridge
* itself via bridge_inject(). This is required for netmap but
* otherwise has no effect.
*/
ifp->if_bridge_input = bridge_input;
BRIDGE_LIST_LOCK();
LIST_INSERT_HEAD(&V_bridge_list, sc, sc_list);
@ -2317,6 +2329,19 @@ bridge_forward(struct bridge_softc *sc, struct bridge_iflist *sbif,
sbif->bif_stp.bp_state == BSTP_IFSTATE_LEARNING)
goto drop;
#ifdef DEV_NETMAP
/*
* Hand the packet to netmap only if it wasn't injected by netmap
* itself.
*/
if ((m->m_flags & M_BRIDGE_INJECT) == 0 &&
(if_getcapenable(ifp) & IFCAP_NETMAP) != 0) {
ifp->if_input(ifp, m);
return;
}
m->m_flags &= ~M_BRIDGE_INJECT;
#endif
/*
* At this point, the port either doesn't participate
* in spanning tree or it is in the forwarding state.
@ -2423,7 +2448,7 @@ drop:
static struct mbuf *
bridge_input(struct ifnet *ifp, struct mbuf *m)
{
struct bridge_softc *sc = ifp->if_bridge;
struct bridge_softc *sc;
struct bridge_iflist *bif, *bif2;
struct ifnet *bifp;
struct ether_header *eh;
@ -2433,12 +2458,32 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)
NET_EPOCH_ASSERT();
if ((sc->sc_ifp->if_drv_flags & IFF_DRV_RUNNING) == 0)
return (m);
bifp = sc->sc_ifp;
eh = mtod(m, struct ether_header *);
vlan = VLANTAGOF(m);
sc = ifp->if_bridge;
if (sc == NULL) {
/*
* This packet originated from the bridge itself, so it must
* have been transmitted by netmap. Derive the "source"
* interface from the source address and drop the packet if the
* source address isn't known.
*/
KASSERT((m->m_flags & M_BRIDGE_INJECT) != 0,
("%s: ifnet %p missing a bridge softc", __func__, ifp));
sc = if_getsoftc(ifp);
ifp = bridge_rtlookup(sc, eh->ether_shost, vlan);
if (ifp == NULL) {
if_inc_counter(sc->sc_ifp, IFCOUNTER_IERRORS, 1);
m_freem(m);
return (NULL);
}
m->m_pkthdr.rcvif = ifp;
}
bifp = sc->sc_ifp;
if ((bifp->if_drv_flags & IFF_DRV_RUNNING) == 0)
return (m);
/*
* Implement support for bridge monitoring. If this flag has been
* set on this interface, discard the packet once we push it through
@ -2458,8 +2503,6 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)
return (m);
}
eh = mtod(m, struct ether_header *);
bridge_span(sc, m);
if (m->m_flags & (M_BCAST|M_MCAST)) {
@ -2488,6 +2531,18 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)
/* Perform the bridge forwarding function with the copy. */
bridge_forward(sc, bif, mc);
#ifdef DEV_NETMAP
/*
* If netmap is enabled and has not already seen this packet,
* then it will be consumed by bridge_forward().
*/
if ((if_getcapenable(bifp) & IFCAP_NETMAP) != 0 &&
(m->m_flags & M_BRIDGE_INJECT) == 0) {
m_freem(m);
return (NULL);
}
#endif
/*
* Reinject the mbuf as arriving on the bridge so we have a
* chance at claiming multicast packets. We can not loop back
@ -2504,7 +2559,8 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)
}
if (mc2 != NULL) {
mc2->m_pkthdr.rcvif = bifp;
(*bifp->if_input)(bifp, mc2);
mc2->m_flags &= ~M_BRIDGE_INJECT;
sc->sc_if_input(bifp, mc2);
}
/* Return the original packet for local processing. */
@ -2532,6 +2588,18 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)
#define PFIL_HOOKED_INET6 false
#endif
#ifdef DEV_NETMAP
#define GRAB_FOR_NETMAP(ifp, m) do { \
if ((if_getcapenable(ifp) & IFCAP_NETMAP) != 0 && \
((m)->m_flags & M_BRIDGE_INJECT) == 0) { \
(ifp)->if_input(ifp, m); \
return (NULL); \
} \
} while (0)
#else
#define GRAB_FOR_NETMAP(ifp, m)
#endif
#define GRAB_OUR_PACKETS(iface) \
if ((iface)->if_type == IFT_GIF) \
continue; \
@ -2554,7 +2622,9 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)
/* It's passing over or to the bridge, locally. */ \
ETHER_BPF_MTAP(bifp, m); \
if_inc_counter(bifp, IFCOUNTER_IPACKETS, 1); \
if_inc_counter(bifp, IFCOUNTER_IBYTES, m->m_pkthdr.len); \
if_inc_counter(bifp, IFCOUNTER_IBYTES, m->m_pkthdr.len);\
/* Hand the packet over to netmap if necessary. */ \
GRAB_FOR_NETMAP(bifp, m); \
/* Filter on the physical interface. */ \
if (V_pfil_local_phys && (PFIL_HOOKED_IN(V_inet_pfil_head) || \
PFIL_HOOKED_INET6)) { \
@ -2597,6 +2667,7 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)
#undef CARP_CHECK_WE_ARE_DST
#undef CARP_CHECK_WE_ARE_SRC
#undef PFIL_HOOKED_INET6
#undef GRAB_FOR_NETMAP
#undef GRAB_OUR_PACKETS
/* Perform the bridge forwarding function. */
@ -2605,6 +2676,28 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)
return (NULL);
}
/*
* Inject a packet back into the host ethernet stack. This will generally only
* be used by netmap when an application writes to the host TX ring. The
* M_BRIDGE_INJECT flag ensures that the packet is re-routed to the bridge
* interface after ethernet processing.
*/
static void
bridge_inject(struct ifnet *ifp, struct mbuf *m)
{
struct bridge_softc *sc;
KASSERT((if_getcapenable(ifp) & IFCAP_NETMAP) != 0,
("%s: iface %s is not running in netmap mode",
__func__, if_name(ifp)));
KASSERT((m->m_flags & M_BRIDGE_INJECT) == 0,
("%s: mbuf %p has M_BRIDGE_INJECT set", __func__, m));
m->m_flags |= M_BRIDGE_INJECT;
sc = if_getsoftc(ifp);
sc->sc_if_input(ifp, m);
}
/*
* bridge_broadcast:
*

View File

@ -305,8 +305,10 @@ struct ifbpstpconf {
KASSERT((_ifp)->if_bridge_input != NULL, \
("%s: if_bridge not loaded!", __func__)); \
_m = (*(_ifp)->if_bridge_input)(_ifp, _m); \
if (_m != NULL) \
if (_m != NULL) { \
_ifp = _m->m_pkthdr.rcvif; \
m->m_flags &= ~M_BRIDGE_INJECT; \
} \
} while (0)
#define BRIDGE_OUTPUT(_ifp, _m, _err) do { \

View File

@ -663,10 +663,15 @@ ether_input_internal(struct ifnet *ifp, struct mbuf *m)
/*
* Allow if_bridge(4) to claim this frame.
*
* The BRIDGE_INPUT() macro will update ifp if the bridge changed it
* and the frame should be delivered locally.
*
* If M_BRIDGE_INJECT is set, the packet was received directly by the
* bridge via netmap, so "ifp" is the bridge itself and the packet
* should be re-examined.
*/
if (ifp->if_bridge != NULL) {
if (ifp->if_bridge != NULL || (m->m_flags & M_BRIDGE_INJECT) != 0) {
m->m_flags &= ~M_PROMISC;
BRIDGE_INPUT(ifp, m);
if (m == NULL) {