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:
parent
c02e6ca7c2
commit
d862b165a6
@ -421,6 +421,29 @@ interface and not to the bridge members.
|
|||||||
Enabling
|
Enabling
|
||||||
.Va net.link.bridge.pfil_local_phys
|
.Va net.link.bridge.pfil_local_phys
|
||||||
will let you do the additional filtering on the physical interface.
|
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
|
.Sh EXAMPLES
|
||||||
The following when placed in the file
|
The following when placed in the file
|
||||||
.Pa /etc/rc.conf
|
.Pa /etc/rc.conf
|
||||||
@ -495,6 +518,7 @@ ifconfig bridge0 addm fxp0 addm gif0 up
|
|||||||
.Xr gif 4 ,
|
.Xr gif 4 ,
|
||||||
.Xr ipf 4 ,
|
.Xr ipf 4 ,
|
||||||
.Xr ipfw 4 ,
|
.Xr ipfw 4 ,
|
||||||
|
.Xr netmap 4 ,
|
||||||
.Xr pf 4 ,
|
.Xr pf 4 ,
|
||||||
.Xr ifconfig 8
|
.Xr ifconfig 8
|
||||||
.Sh HISTORY
|
.Sh HISTORY
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
* Ethernet-specific mbuf flags.
|
* Ethernet-specific mbuf flags.
|
||||||
*/
|
*/
|
||||||
#define M_HASFCS M_PROTO5 /* FCS included at end of frame */
|
#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).
|
* Ethernet CRC32 polynomials (big- and little-endian versions).
|
||||||
|
@ -267,6 +267,7 @@ struct bridge_softc {
|
|||||||
uint32_t sc_brtexceeded; /* # of cache drops */
|
uint32_t sc_brtexceeded; /* # of cache drops */
|
||||||
struct ifnet *sc_ifaddr; /* member mac copied from */
|
struct ifnet *sc_ifaddr; /* member mac copied from */
|
||||||
struct ether_addr sc_defaddr; /* Default MAC address */
|
struct ether_addr sc_defaddr; /* Default MAC address */
|
||||||
|
if_input_fn_t sc_if_input; /* Saved copy of if_input */
|
||||||
struct epoch_context sc_epoch_ctx;
|
struct epoch_context sc_epoch_ctx;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -298,6 +299,7 @@ static int bridge_altq_transmit(if_t, struct mbuf *);
|
|||||||
#endif
|
#endif
|
||||||
static void bridge_qflush(struct ifnet *);
|
static void bridge_qflush(struct ifnet *);
|
||||||
static struct mbuf *bridge_input(struct ifnet *, struct mbuf *);
|
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 *,
|
static int bridge_output(struct ifnet *, struct mbuf *, struct sockaddr *,
|
||||||
struct rtentry *);
|
struct rtentry *);
|
||||||
static int bridge_enqueue(struct bridge_softc *, struct ifnet *,
|
static int bridge_enqueue(struct bridge_softc *, struct ifnet *,
|
||||||
@ -768,6 +770,15 @@ bridge_clone_create(struct if_clone *ifc, char *name, size_t len,
|
|||||||
#ifdef VIMAGE
|
#ifdef VIMAGE
|
||||||
ifp->if_reassign = bridge_reassign;
|
ifp->if_reassign = bridge_reassign;
|
||||||
#endif
|
#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();
|
BRIDGE_LIST_LOCK();
|
||||||
LIST_INSERT_HEAD(&V_bridge_list, sc, sc_list);
|
LIST_INSERT_HEAD(&V_bridge_list, sc, sc_list);
|
||||||
@ -2355,6 +2366,19 @@ bridge_forward(struct bridge_softc *sc, struct bridge_iflist *sbif,
|
|||||||
sbif->bif_stp.bp_state == BSTP_IFSTATE_LEARNING)
|
sbif->bif_stp.bp_state == BSTP_IFSTATE_LEARNING)
|
||||||
goto drop;
|
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
|
* At this point, the port either doesn't participate
|
||||||
* in spanning tree or it is in the forwarding state.
|
* in spanning tree or it is in the forwarding state.
|
||||||
@ -2461,7 +2485,7 @@ bridge_forward(struct bridge_softc *sc, struct bridge_iflist *sbif,
|
|||||||
static struct mbuf *
|
static struct mbuf *
|
||||||
bridge_input(struct ifnet *ifp, struct mbuf *m)
|
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 bridge_iflist *bif, *bif2;
|
||||||
struct ifnet *bifp;
|
struct ifnet *bifp;
|
||||||
struct ether_header *eh;
|
struct ether_header *eh;
|
||||||
@ -2471,12 +2495,32 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)
|
|||||||
|
|
||||||
NET_EPOCH_ASSERT();
|
NET_EPOCH_ASSERT();
|
||||||
|
|
||||||
if ((sc->sc_ifp->if_drv_flags & IFF_DRV_RUNNING) == 0)
|
eh = mtod(m, struct ether_header *);
|
||||||
return (m);
|
|
||||||
|
|
||||||
bifp = sc->sc_ifp;
|
|
||||||
vlan = VLANTAGOF(m);
|
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
|
* Implement support for bridge monitoring. If this flag has been
|
||||||
* set on this interface, discard the packet once we push it through
|
* set on this interface, discard the packet once we push it through
|
||||||
@ -2496,8 +2540,6 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)
|
|||||||
return (m);
|
return (m);
|
||||||
}
|
}
|
||||||
|
|
||||||
eh = mtod(m, struct ether_header *);
|
|
||||||
|
|
||||||
bridge_span(sc, m);
|
bridge_span(sc, m);
|
||||||
|
|
||||||
if (m->m_flags & (M_BCAST|M_MCAST)) {
|
if (m->m_flags & (M_BCAST|M_MCAST)) {
|
||||||
@ -2526,6 +2568,18 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)
|
|||||||
/* Perform the bridge forwarding function with the copy. */
|
/* Perform the bridge forwarding function with the copy. */
|
||||||
bridge_forward(sc, bif, mc);
|
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
|
* Reinject the mbuf as arriving on the bridge so we have a
|
||||||
* chance at claiming multicast packets. We can not loop back
|
* chance at claiming multicast packets. We can not loop back
|
||||||
@ -2542,7 +2596,8 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)
|
|||||||
}
|
}
|
||||||
if (mc2 != NULL) {
|
if (mc2 != NULL) {
|
||||||
mc2->m_pkthdr.rcvif = bifp;
|
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. */
|
/* Return the original packet for local processing. */
|
||||||
@ -2570,6 +2625,18 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)
|
|||||||
#define PFIL_HOOKED_INET6 false
|
#define PFIL_HOOKED_INET6 false
|
||||||
#endif
|
#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) \
|
#define GRAB_OUR_PACKETS(iface) \
|
||||||
if ((iface)->if_type == IFT_GIF) \
|
if ((iface)->if_type == IFT_GIF) \
|
||||||
continue; \
|
continue; \
|
||||||
@ -2593,6 +2660,8 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)
|
|||||||
ETHER_BPF_MTAP(bifp, m); \
|
ETHER_BPF_MTAP(bifp, m); \
|
||||||
if_inc_counter(bifp, IFCOUNTER_IPACKETS, 1); \
|
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. */ \
|
/* Filter on the physical interface. */ \
|
||||||
if (V_pfil_local_phys && (PFIL_HOOKED_IN(V_inet_pfil_head) || \
|
if (V_pfil_local_phys && (PFIL_HOOKED_IN(V_inet_pfil_head) || \
|
||||||
PFIL_HOOKED_INET6)) { \
|
PFIL_HOOKED_INET6)) { \
|
||||||
@ -2635,6 +2704,7 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)
|
|||||||
#undef CARP_CHECK_WE_ARE_DST
|
#undef CARP_CHECK_WE_ARE_DST
|
||||||
#undef CARP_CHECK_WE_ARE_SRC
|
#undef CARP_CHECK_WE_ARE_SRC
|
||||||
#undef PFIL_HOOKED_INET6
|
#undef PFIL_HOOKED_INET6
|
||||||
|
#undef GRAB_FOR_NETMAP
|
||||||
#undef GRAB_OUR_PACKETS
|
#undef GRAB_OUR_PACKETS
|
||||||
|
|
||||||
/* Perform the bridge forwarding function. */
|
/* Perform the bridge forwarding function. */
|
||||||
@ -2643,6 +2713,28 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)
|
|||||||
return (NULL);
|
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:
|
* bridge_broadcast:
|
||||||
*
|
*
|
||||||
|
@ -309,8 +309,10 @@ struct ifbpstpconf {
|
|||||||
KASSERT((_ifp)->if_bridge_input != NULL, \
|
KASSERT((_ifp)->if_bridge_input != NULL, \
|
||||||
("%s: if_bridge not loaded!", __func__)); \
|
("%s: if_bridge not loaded!", __func__)); \
|
||||||
_m = (*(_ifp)->if_bridge_input)(_ifp, _m); \
|
_m = (*(_ifp)->if_bridge_input)(_ifp, _m); \
|
||||||
if (_m != NULL) \
|
if (_m != NULL) { \
|
||||||
_ifp = _m->m_pkthdr.rcvif; \
|
_ifp = _m->m_pkthdr.rcvif; \
|
||||||
|
m->m_flags &= ~M_BRIDGE_INJECT; \
|
||||||
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
#define BRIDGE_OUTPUT(_ifp, _m, _err) do { \
|
#define BRIDGE_OUTPUT(_ifp, _m, _err) do { \
|
||||||
|
@ -667,10 +667,15 @@ ether_input_internal(struct ifnet *ifp, struct mbuf *m)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Allow if_bridge(4) to claim this frame.
|
* Allow if_bridge(4) to claim this frame.
|
||||||
|
*
|
||||||
* The BRIDGE_INPUT() macro will update ifp if the bridge changed it
|
* The BRIDGE_INPUT() macro will update ifp if the bridge changed it
|
||||||
* and the frame should be delivered locally.
|
* 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;
|
m->m_flags &= ~M_PROMISC;
|
||||||
BRIDGE_INPUT(ifp, m);
|
BRIDGE_INPUT(ifp, m);
|
||||||
if (m == NULL) {
|
if (m == NULL) {
|
||||||
|
Loading…
Reference in New Issue
Block a user