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 @@ -2715,7 +2715,6 @@ struct ifnet *src_if, *dst_if, *ifp; struct ether_header *eh; uint8_t *dst; - int error; ether_vlanid_t vlan; NET_EPOCH_ASSERT(); @@ -2734,18 +2733,6 @@ eh = mtod(m, struct ether_header *); dst = eh->ether_dhost; - /* If the interface is learning, record the address. */ - if (sbif->bif_flags & IFBIF_LEARNING) { - error = bridge_rtupdate(sc, eh->ether_shost, vlan, - sbif, 0, IFBAF_DYNAMIC); - /* - * If the interface has addresses limits then deny any source - * that is not in the cache. - */ - if (error && sbif->bif_addrmax) - goto drop; - } - if ((sbif->bif_flags & IFBIF_STP) != 0 && sbif->bif_stp.bp_state == BSTP_IFSTATE_LEARNING) goto drop; @@ -2856,6 +2843,175 @@ m_freem(m); } +/* + * Handle a frame which is destined for the host. If a non-NULL mbuf is + * returned, it should be passed back to ether_input(). + */ +static struct mbuf * +bridge_handle_for_host(struct bridge_softc *sc, struct ifnet *ifp, + struct mbuf *m) +{ + struct ifnet *bifp; + + NET_EPOCH_ASSERT(); + + bifp = sc->sc_ifp; + + /* Tap the frame on the bridge interface. */ + ETHER_BPF_MTAP(bifp, m); + + if_inc_counter(bifp, IFCOUNTER_IPACKETS, 1); + if_inc_counter(bifp, IFCOUNTER_IBYTES, m->m_pkthdr.len); + +#ifdef DEV_NETMAP + /* Hand the packet over to netmap if necessary. */ + if ((if_getcapenable(ifp) & IFCAP_NETMAP) != 0 && + ((m)->m_flags & M_BRIDGE_INJECT) == 0) { + (ifp)->if_input(ifp, m); + return (NULL); + } +#endif + + /* + * If the frame has a VLAN tag, it's intended for an SVI, so pass it to + * vlan_input() to handle. + * + * If there's no VLAN trunk configured, we can't handle this frame + * locally, so just drop it. + */ + if (VLANTAGOF(m) != 0) { + if (bifp->if_vlantrunk != NULL) + (*vlan_input_p)(ifp, m); + else + m_freem(m); + + return (NULL); + } + + /* + * Return the frame to ether_input(). + */ + return (m); +} + +/* + * Given an mbuf and a bridge member interface, determine if this frame should + * be handled locally by comparing the destination Ethernet address of the frame + * to the interface's Ethernet address. If the frame is destined for that + * interface, pass it to bridge_handle_for_host() and return true. + * + * Note that the interface passed here is *not* (necessarily) the interface we + * received the frame on. + */ +static bool +bridge_try_local_if(struct bridge_softc *sc, struct ifnet *iface, + struct mbuf **mp) +{ + struct ether_header *eh; + + NET_EPOCH_ASSERT(); + + /* + * Frames destined for a gif(4) interface are never considered local. + * XXX: Why not? (This was the behaviour of bridge before we added + * member_ifaddrs, so it's probably correct, and will become moot + * once member_ifaddrs is removed.) + */ + if (iface->if_type == IFT_GIF) + return (false); + + eh = mtod(*mp, struct ether_header *); + + /* + * If the destination address is the interface's address, this frame + * is for us. Also check CARP's destination address. + */ + if (memcmp(IF_LLADDR(iface), eh->ether_dhost, ETHER_ADDR_LEN) == 0 +#if defined(INET) || defined(INET6) + || (iface->if_carp && (*carp_forus_p)(iface, eh->ether_dhost)) +#endif + ) { + /* + * rcvif will have been previously set to the interface this + * frame was actually received on. Change it to the interface + * we matched here, because IP expects that the rcvif of an + * incoming packet will match the interface the address is + * assigned to. + * + * This is required in two cases: + * + * - When the IP address is assigned to the bridge; + * + * - When the IP address is assigned to a bridge member, but + * the frame was received on a different member. + * (Only if member_ifaddrs=1.) + */ + (*mp)->m_pkthdr.rcvif = iface; + + *mp = bridge_handle_for_host(sc, iface, *mp); + return (true); + } + + /* + * If the source address is the interface's address, this frame was + * sent by us, so drop it. + */ + if (memcmp(IF_LLADDR(iface), eh->ether_shost, ETHER_ADDR_LEN) == 0 +#if defined(INET) || defined(INET6) + || (iface->if_carp && (*carp_forus_p)(iface, eh->ether_shost)) +#endif + ) { + m_freem(*mp); + *mp = NULL; + return (true); + } + + return (false); +} + +/* + * Try to handle this as a local packet by checking all the interfaces it + * might be local for. The behaviour depends on how member_ifaddrs is set: + * + * - If it's enabled, check the bridge itself, then the interface we + * received the frame on (to optimise the common case), then all other + * bridge members. This means that a frame with the destination address + * of member A can arrive on member B and still be considered local, + * which is the legacy behaviour of bridge before member_ifaddrs. + * + * - If member_ifaddrs is disabled, only check the bridge interface; any + * other frames will not be considered local. + * + * Once member_ifaddrs is removed, this function can be removed too. + */ +static bool +bridge_try_local(struct bridge_softc *sc, struct ifnet *iface, + struct mbuf **mp) +{ + struct bridge_iflist *bif; + + /* Check the bridge interface itself first. */ + if (bridge_try_local_if(sc, sc->sc_ifp, mp)) + return (true); + + /* If member_ifaddrs is disabled, that's it; it's not local. */ + if (!V_member_ifaddrs) + return (false); + + /* Check the interface the frame arrived on. */ + if (bridge_try_local_if(sc, iface, mp)) + return (true); + + /* Check the other bridge members. */ + CK_LIST_FOREACH(bif, &sc->sc_iflist, bif_next) { + if (bridge_try_local_if(sc, bif->bif_ifp, mp)) + return (true); + } + + /* It's not local. */ + return (false); +} + /* * bridge_input: * @@ -2866,15 +3022,27 @@ bridge_input(struct ifnet *ifp, struct mbuf *m) { struct bridge_softc *sc = NULL; - struct bridge_iflist *bif, *bif2; + struct bridge_iflist *bif; struct ifnet *bifp; struct ether_header *eh; struct mbuf *mc, *mc2; ether_vlanid_t vlan; - int error; NET_EPOCH_ASSERT(); + vlan = VLANTAGOF(m); + + /* + * If this frame has a VLAN tag, and it was not received on a bridge + * interface, and the receiving interface has a vlan(4) trunk, then + * it is is destined for vlan(4), not for us. This means if vlan(4) + * and bridge(4) are configured on the same interface, vlan(4) is + * preferred, which is what users typically expect. + */ + if (vlan != DOT1Q_VID_NULL && ifp->if_type != IFT_BRIDGE && + ifp->if_vlantrunk != NULL) + return (m); + /* We need the Ethernet header later, so make sure we have it now. */ if (m->m_len < ETHER_HDR_LEN) { m = m_pullup(m, ETHER_HDR_LEN); @@ -2886,7 +3054,6 @@ } eh = mtod(m, struct ether_header *); - vlan = VLANTAGOF(m); bif = ifp->if_bridge; if (bif) @@ -2908,11 +3075,43 @@ m_freem(m); return (NULL); } + bif = ifp->if_bridge; m->m_pkthdr.rcvif = ifp; } + + KASSERT(bif != NULL, + ("%s: no bif for packet from %s", __func__, ifp->if_xname)); + bifp = sc->sc_ifp; - if ((bifp->if_drv_flags & IFF_DRV_RUNNING) == 0) - return (m); + + /* If the bridge is down, discard all incoming frames. */ + if ((bifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { + m_freem(m); + return (NULL); + } + + /* Do VLAN filtering. */ + if (!bridge_vfilter_in(bif, m)) { + if_inc_counter(sc->sc_ifp, IFCOUNTER_IERRORS, 1); + m_freem(m); + return (NULL); + } + /* bridge_vfilter_in() may add a tag */ + vlan = VLANTAGOF(m); + + /* + * Filter on the physical interface. Do this early so that we don't + * do any other processing (like address learning) for filtered + * packets. + */ + if (V_pfil_local_phys && PFIL_HOOKED_IN_46) { + int error = bridge_pfil(&m, NULL, ifp, PFIL_IN); + if (error != 0 || m == NULL) { + if (m != NULL) + m_freem(m); + return (NULL); + } + } /* * Implement support for bridge monitoring. If this flag has been @@ -2929,17 +3128,30 @@ return (NULL); } - /* Do VLAN filtering. */ - if (!bridge_vfilter_in(bif, m)) { - if_inc_counter(sc->sc_ifp, IFCOUNTER_IERRORS, 1); - m_freem(m); - return (NULL); - } - /* bridge_vfilter_in() may add a tag */ - vlan = VLANTAGOF(m); - + /* Pass this frame to our span ports */ bridge_span(sc, m); + /* Learn the source address of this frame */ + if (bif->bif_flags & IFBIF_LEARNING) { + int error; + + error = bridge_rtupdate(sc, eh->ether_shost, vlan, bif, 0, + IFBAF_DYNAMIC); + + /* + * Ignore errors, unless the error was caused by the port + * exceeding its address limit, in which case the frame should + * be dropped because this is a security policy violation. + */ + if (error && bif->bif_addrmax) { + m_freem(m); + return (NULL); + } + } + + /* + * Handle broadcast and multicast frames. + */ if (m->m_flags & (M_BCAST|M_MCAST)) { /* Tap off 802.1D packets; they do not get forwarded. */ if (memcmp(eh->ether_dhost, bstp_etheraddr, @@ -2950,7 +3162,8 @@ if ((bif->bif_flags & IFBIF_STP) && bif->bif_stp.bp_state == BSTP_IFSTATE_DISCARDING) { - return (m); + m_freem(m); + return (NULL); } /* @@ -3004,112 +3217,13 @@ if ((bif->bif_flags & IFBIF_STP) && bif->bif_stp.bp_state == BSTP_IFSTATE_DISCARDING) { - return (m); - } - -#if defined(INET) || defined(INET6) -#define CARP_CHECK_WE_ARE_DST(iface) \ - ((iface)->if_carp && (*carp_forus_p)((iface), eh->ether_dhost)) -#define CARP_CHECK_WE_ARE_SRC(iface) \ - ((iface)->if_carp && (*carp_forus_p)((iface), eh->ether_shost)) -#else -#define CARP_CHECK_WE_ARE_DST(iface) false -#define CARP_CHECK_WE_ARE_SRC(iface) 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; \ - /* It is destined for us. */ \ - if (memcmp(IF_LLADDR(iface), eh->ether_dhost, ETHER_ADDR_LEN) == 0 || \ - CARP_CHECK_WE_ARE_DST(iface)) { \ - if (bif->bif_flags & IFBIF_LEARNING) { \ - error = bridge_rtupdate(sc, eh->ether_shost, \ - vlan, bif, 0, IFBAF_DYNAMIC); \ - if (error && bif->bif_addrmax) { \ - m_freem(m); \ - return (NULL); \ - } \ - } \ - m->m_pkthdr.rcvif = iface; \ - if ((iface) == ifp) { \ - /* Skip bridge processing... src == dest */ \ - return (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);\ - /* 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_46) { \ - if (bridge_pfil(&m, NULL, ifp, \ - PFIL_IN) != 0 || m == NULL) { \ - return (NULL); \ - } \ - } \ - if ((iface) != bifp) \ - ETHER_BPF_MTAP(iface, m); \ - /* Pass tagged packets to if_vlan, if it's loaded */ \ - if (VLANTAGOF(m) != 0) { \ - if (bifp->if_vlantrunk == NULL) { \ - m_freem(m); \ - return (NULL); \ - } \ - (*vlan_input_p)(bifp, m); \ - return (NULL); \ - } \ - return (m); \ - } \ - \ - /* We just received a packet that we sent out. */ \ - if (memcmp(IF_LLADDR(iface), eh->ether_shost, ETHER_ADDR_LEN) == 0 || \ - CARP_CHECK_WE_ARE_SRC(iface)) { \ - m_freem(m); \ - return (NULL); \ - } - - /* - * Unicast. Make sure it's not for the bridge. - */ - do { GRAB_OUR_PACKETS(bifp) } while (0); - - /* - * Check the interface the packet arrived on. For tagged frames, - * we need to do this even if member_ifaddrs is disabled because - * vlan(4) might need to handle the traffic. - */ - if (V_member_ifaddrs || (vlan && ifp->if_vlantrunk)) - do { GRAB_OUR_PACKETS(ifp) } while (0); - - /* - * We only need to check other members interface if member_ifaddrs - * is enabled; otherwise we should have never traffic destined for - * a member's lladdr. - */ - if (V_member_ifaddrs) { - CK_LIST_FOREACH(bif2, &sc->sc_iflist, bif_next) { - GRAB_OUR_PACKETS(bif2->bif_ifp) - } + m_freem(m); + return (NULL); } -#undef CARP_CHECK_WE_ARE_DST -#undef CARP_CHECK_WE_ARE_SRC -#undef GRAB_FOR_NETMAP -#undef GRAB_OUR_PACKETS + /* See if this packet should be handled locally. */ + if (bridge_try_local(sc, ifp, &m)) + return (m); /* Perform the bridge forwarding function. */ bridge_forward(sc, bif, m); @@ -3118,10 +3232,17 @@ } /* - * 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. + * Inject a packet back into the host ethernet stack. This is the if_input + * handler for bridge. + * + * We end up here in two cases: + * + * - With 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. + * + * - With a vlan(4) SVI on a bridge; this is how vlan(4) sends us input + * frames. */ static void bridge_inject(struct ifnet *ifp, struct mbuf *m) @@ -3130,9 +3251,9 @@ if (ifp->if_type == IFT_L2VLAN) { /* - * vlan(4) gives us the vlan ifnet, so we need to get the - * bridge softc to get a pointer to ether_input to send the - * packet to. + * This frame came from vlan(4). ifp is the vlan ifnet, so we + * need to get the bridge softc to get a pointer to ether_input + * to send the frame to. */ struct ifnet *bifp = NULL; @@ -3152,6 +3273,7 @@ return; } + /* If it's not a vlan(4) then this is from netmap */ KASSERT((if_getcapenable(ifp) & IFCAP_NETMAP) != 0, ("%s: iface %s is not running in netmap mode", __func__, if_name(ifp))); 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 @@ -788,7 +788,7 @@ vnet_mkjail one ${ep}b jexec one sysctl net.link.bridge.member_ifaddrs=1 jexec one ifconfig ${ep}b inet 192.0.2.2/24 up - jexec one ifconfig bridge0 create addm ${ep}b + jexec one ifconfig bridge0 create addm ${ep}b up atf_check -s exit:0 -o ignore ping -c3 -t1 192.0.2.2 }