Fix for if_start() and pfil_hook() being called from hardware interrupt

context (reported on various mailing-lists, and part of PR kern/41114,
causing panic in pf(4) and possibly ipf(4) when BRIDGE_IPF is used).
Defer bridge_forward() to a software interrupt; bridge_input() enqueues
mbufs to ifp->if_snd which is handled in bridge_forward().
This commit is contained in:
bouyer 2009-04-04 10:00:23 +00:00
parent 481ff5ace4
commit 1fd1b49685
2 changed files with 145 additions and 103 deletions

View File

@ -1,4 +1,4 @@
/* $NetBSD: if_bridge.c,v 1.64 2009/01/18 10:28:55 mrg Exp $ */
/* $NetBSD: if_bridge.c,v 1.65 2009/04/04 10:00:23 bouyer Exp $ */
/*
* Copyright 2001 Wasabi Systems, Inc.
@ -80,7 +80,7 @@
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: if_bridge.c,v 1.64 2009/01/18 10:28:55 mrg Exp $");
__KERNEL_RCSID(0, "$NetBSD: if_bridge.c,v 1.65 2009/04/04 10:00:23 bouyer Exp $");
#include "opt_bridge_ipf.h"
#include "opt_inet.h"
@ -97,6 +97,7 @@ __KERNEL_RCSID(0, "$NetBSD: if_bridge.c,v 1.64 2009/01/18 10:28:55 mrg Exp $");
#include <sys/proc.h>
#include <sys/pool.h>
#include <sys/kauth.h>
#include <sys/cpu.h>
#if NBPFILTER > 0
#include <net/bpf.h>
@ -185,7 +186,7 @@ static int bridge_init(struct ifnet *);
static void bridge_stop(struct ifnet *, int);
static void bridge_start(struct ifnet *);
static void bridge_forward(struct bridge_softc *, struct mbuf *m);
static void bridge_forward(void *);
static void bridge_timer(void *);
@ -350,6 +351,13 @@ bridge_clone_create(struct if_clone *ifc, int unit)
sc->sc_hold_time = BSTP_DEFAULT_HOLD_TIME;
sc->sc_filter_flags = 0;
/* software interrupt to do the work */
sc->sc_softintr = softint_establish(SOFTINT_NET, bridge_forward, sc);
if (sc->sc_softintr == NULL) {
free(sc, M_DEVBUF);
return ENOMEM;
}
/* Initialize our routing table. */
bridge_rtable_init(sc);
@ -370,6 +378,7 @@ bridge_clone_create(struct if_clone *ifc, int unit)
ifp->if_addrlen = 0;
ifp->if_dlt = DLT_EN10MB;
ifp->if_hdrlen = ETHER_HDR_LEN;
IFQ_SET_READY(&ifp->if_snd);
if_attach(ifp);
@ -410,6 +419,10 @@ bridge_clone_destroy(struct ifnet *ifp)
/* Tear down the routing table. */
bridge_rtable_fini(sc);
softint_disestablish(sc->sc_softintr);
free(sc, M_DEVBUF);
return (0);
@ -1305,124 +1318,139 @@ bridge_start(struct ifnet *ifp)
* The forwarding function of the bridge.
*/
static void
bridge_forward(struct bridge_softc *sc, struct mbuf *m)
bridge_forward(void *v)
{
struct bridge_softc *sc = v;
struct mbuf *m;
struct bridge_iflist *bif;
struct ifnet *src_if, *dst_if;
struct ether_header *eh;
int s;
src_if = m->m_pkthdr.rcvif;
sc->sc_if.if_ipackets++;
sc->sc_if.if_ibytes += m->m_pkthdr.len;
/*
* Look up the bridge_iflist.
*/
bif = bridge_lookup_member_if(sc, src_if);
if (bif == NULL) {
/* Interface is not a bridge member (anymore?) */
m_freem(m);
if ((sc->sc_if.if_flags & IFF_RUNNING) == 0)
return;
}
if (bif->bif_flags & IFBIF_STP) {
switch (bif->bif_state) {
case BSTP_IFSTATE_BLOCKING:
case BSTP_IFSTATE_LISTENING:
case BSTP_IFSTATE_DISABLED:
s = splbio();
while (1) {
IFQ_POLL(&sc->sc_if.if_snd, m);
if (m == NULL)
break;
IFQ_DEQUEUE(&sc->sc_if.if_snd, m);
src_if = m->m_pkthdr.rcvif;
sc->sc_if.if_ipackets++;
sc->sc_if.if_ibytes += m->m_pkthdr.len;
/*
* Look up the bridge_iflist.
*/
bif = bridge_lookup_member_if(sc, src_if);
if (bif == NULL) {
/* Interface is not a bridge member (anymore?) */
m_freem(m);
return;
continue;
}
}
eh = mtod(m, struct ether_header *);
if (bif->bif_flags & IFBIF_STP) {
switch (bif->bif_state) {
case BSTP_IFSTATE_BLOCKING:
case BSTP_IFSTATE_LISTENING:
case BSTP_IFSTATE_DISABLED:
m_freem(m);
continue;
}
}
/*
* If the interface is learning, and the source
* address is valid and not multicast, record
* the address.
*/
if ((bif->bif_flags & IFBIF_LEARNING) != 0 &&
ETHER_IS_MULTICAST(eh->ether_shost) == 0 &&
(eh->ether_shost[0] == 0 &&
eh->ether_shost[1] == 0 &&
eh->ether_shost[2] == 0 &&
eh->ether_shost[3] == 0 &&
eh->ether_shost[4] == 0 &&
eh->ether_shost[5] == 0) == 0) {
(void) bridge_rtupdate(sc, eh->ether_shost,
src_if, 0, IFBAF_DYNAMIC);
}
eh = mtod(m, struct ether_header *);
if ((bif->bif_flags & IFBIF_STP) != 0 &&
bif->bif_state == BSTP_IFSTATE_LEARNING) {
m_freem(m);
return;
}
/*
* If the interface is learning, and the source
* address is valid and not multicast, record
* the address.
*/
if ((bif->bif_flags & IFBIF_LEARNING) != 0 &&
ETHER_IS_MULTICAST(eh->ether_shost) == 0 &&
(eh->ether_shost[0] == 0 &&
eh->ether_shost[1] == 0 &&
eh->ether_shost[2] == 0 &&
eh->ether_shost[3] == 0 &&
eh->ether_shost[4] == 0 &&
eh->ether_shost[5] == 0) == 0) {
(void) bridge_rtupdate(sc, eh->ether_shost,
src_if, 0, IFBAF_DYNAMIC);
}
/*
* At this point, the port either doesn't participate
* in spanning tree or it is in the forwarding state.
*/
/*
* If the packet is unicast, destined for someone on
* "this" side of the bridge, drop it.
*/
if ((m->m_flags & (M_BCAST|M_MCAST)) == 0) {
dst_if = bridge_rtlookup(sc, eh->ether_dhost);
if (src_if == dst_if) {
if ((bif->bif_flags & IFBIF_STP) != 0 &&
bif->bif_state == BSTP_IFSTATE_LEARNING) {
m_freem(m);
return;
continue;
}
/*
* At this point, the port either doesn't participate
* in spanning tree or it is in the forwarding state.
*/
/*
* If the packet is unicast, destined for someone on
* "this" side of the bridge, drop it.
*/
if ((m->m_flags & (M_BCAST|M_MCAST)) == 0) {
dst_if = bridge_rtlookup(sc, eh->ether_dhost);
if (src_if == dst_if) {
m_freem(m);
continue;
}
} else {
/* ...forward it to all interfaces. */
sc->sc_if.if_imcasts++;
dst_if = NULL;
}
} else {
/* ...forward it to all interfaces. */
sc->sc_if.if_imcasts++;
dst_if = NULL;
}
#ifdef PFIL_HOOKS
if (pfil_run_hooks(&sc->sc_if.if_pfil, &m,
m->m_pkthdr.rcvif, PFIL_IN) != 0) {
if (m != NULL)
m_freem(m);
return;
}
if (m == NULL)
return;
if (pfil_run_hooks(&sc->sc_if.if_pfil, &m,
m->m_pkthdr.rcvif, PFIL_IN) != 0) {
if (m != NULL)
m_freem(m);
continue;
}
if (m == NULL)
continue;
#endif /* PFIL_HOOKS */
if (dst_if == NULL) {
bridge_broadcast(sc, src_if, m);
return;
}
/*
* At this point, we're dealing with a unicast frame
* going to a different interface.
*/
if ((dst_if->if_flags & IFF_RUNNING) == 0) {
m_freem(m);
return;
}
bif = bridge_lookup_member_if(sc, dst_if);
if (bif == NULL) {
/* Not a member of the bridge (anymore?) */
m_freem(m);
return;
}
if (bif->bif_flags & IFBIF_STP) {
switch (bif->bif_state) {
case BSTP_IFSTATE_DISABLED:
case BSTP_IFSTATE_BLOCKING:
m_freem(m);
return;
if (dst_if == NULL) {
bridge_broadcast(sc, src_if, m);
continue;
}
}
bridge_enqueue(sc, dst_if, m, 1);
/*
* At this point, we're dealing with a unicast frame
* going to a different interface.
*/
if ((dst_if->if_flags & IFF_RUNNING) == 0) {
m_freem(m);
continue;
}
bif = bridge_lookup_member_if(sc, dst_if);
if (bif == NULL) {
/* Not a member of the bridge (anymore?) */
m_freem(m);
continue;
}
if (bif->bif_flags & IFBIF_STP) {
switch (bif->bif_state) {
case BSTP_IFSTATE_DISABLED:
case BSTP_IFSTATE_BLOCKING:
m_freem(m);
continue;
}
}
bridge_enqueue(sc, dst_if, m, 1);
}
splx(s);
}
/*
@ -1430,6 +1458,7 @@ bridge_forward(struct bridge_softc *sc, struct mbuf *m)
*
* Receive input from a member interface. Queue the packet for
* bridging if it is not for us.
* should be called at splbio()
*/
struct mbuf *
bridge_input(struct ifnet *ifp, struct mbuf *m)
@ -1471,12 +1500,17 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)
* for bridge processing; return the original packet for
* local processing.
*/
if (IF_QFULL(&sc->sc_if.if_snd)) {
IF_DROP(&sc->sc_if.if_snd);
return (m);
}
mc = m_dup(m, 0, M_COPYALL, M_NOWAIT);
if (mc == NULL)
return (m);
/* Perform the bridge forwarding function with the copy. */
bridge_forward(sc, mc);
IF_ENQUEUE(&sc->sc_if.if_snd, mc);
softint_schedule(sc->sc_softintr);
/* Return the original packet for local processing. */
return (m);
@ -1524,7 +1558,13 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)
}
/* Perform the bridge forwarding function. */
bridge_forward(sc, m);
if (IF_QFULL(&sc->sc_if.if_snd)) {
IF_DROP(&sc->sc_if.if_snd);
m_freem(m);
return (NULL);
}
IF_ENQUEUE(&sc->sc_if.if_snd, m);
softint_schedule(sc->sc_softintr);
return (NULL);
}
@ -2010,6 +2050,7 @@ bridge_ipf(void *arg, struct mbuf **mp, struct ifnet *ifp, int dir)
/*
* Check basic packet sanity and run IPF through pfil.
*/
KASSERT(!cpu_intr_p());
switch (ether_type)
{
case ETHERTYPE_IP :

View File

@ -1,4 +1,4 @@
/* $NetBSD: if_bridgevar.h,v 1.13 2009/01/18 10:28:55 mrg Exp $ */
/* $NetBSD: if_bridgevar.h,v 1.14 2009/04/04 10:00:23 bouyer Exp $ */
/*
* Copyright 2001 Wasabi Systems, Inc.
@ -301,6 +301,7 @@ struct bridge_softc {
LIST_HEAD(, bridge_rtnode) sc_rtlist; /* list version of above */
uint32_t sc_rthash_key; /* key for hash */
uint32_t sc_filter_flags; /* ipf and flags */
void *sc_softintr;
};
extern const uint8_t bstp_etheraddr[];