Skip to content

Commit

Permalink
bridge: Add support for emulated netmap mode
Browse files Browse the repository at this point in the history
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
  • Loading branch information
markjdb authored and fichtner committed Apr 11, 2023
1 parent 6429ff0 commit eebd4b1
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 10 deletions.
24 changes: 24 additions & 0 deletions share/man/man4/bridge.4
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions sys/net/ethernet.h
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
109 changes: 101 additions & 8 deletions sys/net/if_bridge.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

Expand Down Expand Up @@ -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 *,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -2423,7 +2448,7 @@ bridge_forward(struct bridge_softc *sc, struct bridge_iflist *sbif,
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;
Expand All @@ -2433,11 +2458,31 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)

NET_EPOCH_ASSERT();

if ((sc->sc_ifp->if_drv_flags & IFF_DRV_RUNNING) == 0)
return (m);
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;
vlan = VLANTAGOF(m);
if ((bifp->if_drv_flags & IFF_DRV_RUNNING) == 0)
return (m);

/*
* Implement support for bridge monitoring. If this flag has been
Expand All @@ -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)) {
Expand Down Expand Up @@ -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
Expand All @@ -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. */
Expand Down Expand Up @@ -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; \
Expand All @@ -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)) { \
Expand Down Expand Up @@ -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. */
Expand All @@ -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:
*
Expand Down
4 changes: 3 additions & 1 deletion sys/net/if_bridgevar.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 { \
Expand Down
7 changes: 6 additions & 1 deletion sys/net/if_ethersubr.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit eebd4b1

Please sign in to comment.