[BACK]Return to if_bridge.c CVS log [TXT][DIR] Up to [local] / src / sys / net

File: [local] / src / sys / net / if_bridge.c (download)

Revision 1.370, Sun Apr 14 20:46:27 2024 UTC (8 weeks, 1 day ago) by bluhm
Branch: MAIN
CVS Tags: HEAD
Changes since 1.369: +2 -2 lines

Run raw IP input in parallel.

Running raw IPv4 input with shared net lock in parallel is less
complex than UDP.  Especially there is no socket splicing.

New ip_deliver() may run with shared or exclusive net lock.  The
last parameter indicates the mode.  If is is running with shared
netlock and encounters a protocol that needs exclusive lock, the
packet is queued.  Old ip_ours() always queued the packet.  Now it
calls ip_deliver() with shared net lock, and if that cannot handle
the packet completely, the packet is queued and later processed
with exclusive net lock.

In case of an IPv6 header chain, that switches from shared to
exclusive processing, the next protocol and mbuf offset are stored
in a mbuf tag.

OK mvs@

/*	$OpenBSD: if_bridge.c,v 1.370 2024/04/14 20:46:27 bluhm Exp $	*/

/*
 * Copyright (c) 1999, 2000 Jason L. Wright (jason@thought.net)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * Effort sponsored in part by the Defense Advanced Research Projects
 * Agency (DARPA) and Air Force Research Laboratory, Air Force
 * Materiel Command, USAF, under agreement number F30602-01-2-0537.
 *
 */

#include "bpfilter.h"
#include "gif.h"
#include "pf.h"
#include "carp.h"
#include "vlan.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/kernel.h>

#include <net/if.h>
#include <net/if_types.h>
#include <net/if_llc.h>
#include <net/netisr.h>
#include <net/route.h>

#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_var.h>
#include <netinet/if_ether.h>
#include <netinet/ip_icmp.h>

#ifdef IPSEC
#include <netinet/ip_ipsp.h>
#include <net/if_enc.h>
#endif

#ifdef INET6
#include <netinet6/in6_var.h>
#include <netinet/ip6.h>
#include <netinet6/ip6_var.h>
#endif

#if NPF > 0
#include <net/pfvar.h>
#define	BRIDGE_IN	PF_IN
#define	BRIDGE_OUT	PF_OUT
#else
#define	BRIDGE_IN	0
#define	BRIDGE_OUT	1
#endif

#if NBPFILTER > 0
#include <net/bpf.h>
#endif

#if NCARP > 0
#include <netinet/ip_carp.h>
#endif

#if NVLAN > 0
#include <net/if_vlan_var.h>
#endif

#include <net/if_bridge.h>

/*
 * Maximum number of addresses to cache
 */
#ifndef	BRIDGE_RTABLE_MAX
#define	BRIDGE_RTABLE_MAX	100
#endif

/*
 * Timeout (in seconds) for entries learned dynamically
 */
#ifndef	BRIDGE_RTABLE_TIMEOUT
#define	BRIDGE_RTABLE_TIMEOUT	240
#endif

void	bridgeattach(int);
int	bridge_ioctl(struct ifnet *, u_long, caddr_t);
void	bridge_ifdetach(void *);
void	bridge_spandetach(void *);
int	bridge_ifremove(struct bridge_iflist *);
void	bridge_spanremove(struct bridge_iflist *);
struct mbuf *
	bridge_input(struct ifnet *, struct mbuf *, uint64_t, void *);
void	bridge_process(struct ifnet *, struct mbuf *);
void	bridgeintr_frame(struct ifnet *, struct ifnet *, struct mbuf *);
void	bridge_bifgetstp(struct bridge_softc *, struct bridge_iflist *,
	    struct ifbreq *);
void	bridge_broadcast(struct bridge_softc *, struct ifnet *,
    struct ether_header *, struct mbuf *);
int	bridge_localbroadcast(struct ifnet *, struct ether_header *,
    struct mbuf *);
void	bridge_span(struct ifnet *, struct mbuf *);
void	bridge_stop(struct bridge_softc *);
void	bridge_init(struct bridge_softc *);
int	bridge_bifconf(struct bridge_softc *, struct ifbifconf *);
int bridge_blocknonip(struct ether_header *, struct mbuf *);
void	bridge_ifinput(struct ifnet *, struct mbuf *);
int	bridge_dummy_output(struct ifnet *, struct mbuf *, struct sockaddr *,
    struct rtentry *);
void	bridge_send_icmp_err(struct ifnet *, struct ether_header *,
    struct mbuf *, int, struct llc *, int, int, int);
int	bridge_ifenqueue(struct ifnet *, struct ifnet *, struct mbuf *);
struct mbuf *bridge_ip(struct ifnet *, int, struct ifnet *,
    struct ether_header *, struct mbuf *);
#ifdef IPSEC
int bridge_ipsec(struct ifnet *, struct ether_header *, int, struct llc *,
    int, int, int, struct mbuf *);
#endif
int     bridge_clone_create(struct if_clone *, int);
int	bridge_clone_destroy(struct ifnet *);
void	bridge_take(void *);
void	bridge_rele(void *);

#define	ETHERADDR_IS_IP_MCAST(a) \
	/* struct etheraddr *a;	*/				\
	((a)->ether_addr_octet[0] == 0x01 &&			\
	 (a)->ether_addr_octet[1] == 0x00 &&			\
	 (a)->ether_addr_octet[2] == 0x5e)

struct niqueue bridgeintrq = NIQUEUE_INITIALIZER(1024, NETISR_BRIDGE);

struct if_clone bridge_cloner =
    IF_CLONE_INITIALIZER("bridge", bridge_clone_create, bridge_clone_destroy);

const struct ether_brport bridge_brport = {
	bridge_input,
	bridge_take,
	bridge_rele,
	NULL,
};

void
bridgeattach(int n)
{
	if_clone_attach(&bridge_cloner);
}

int
bridge_clone_create(struct if_clone *ifc, int unit)
{
	struct bridge_softc *sc;
	struct ifnet *ifp;
	int i;

	sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK|M_ZERO);
	sc->sc_stp = bstp_create();
	if (!sc->sc_stp) {
		free(sc, M_DEVBUF, sizeof *sc);
		return (ENOMEM);
	}

	sc->sc_brtmax = BRIDGE_RTABLE_MAX;
	sc->sc_brttimeout = BRIDGE_RTABLE_TIMEOUT;
	timeout_set(&sc->sc_brtimeout, bridge_rtage, sc);
	SMR_SLIST_INIT(&sc->sc_iflist);
	SMR_SLIST_INIT(&sc->sc_spanlist);
	mtx_init(&sc->sc_mtx, IPL_MPFLOOR);
	for (i = 0; i < BRIDGE_RTABLE_SIZE; i++)
		LIST_INIT(&sc->sc_rts[i]);
	arc4random_buf(&sc->sc_hashkey, sizeof(sc->sc_hashkey));
	ifp = &sc->sc_if;
	snprintf(ifp->if_xname, sizeof ifp->if_xname, "%s%d", ifc->ifc_name,
	    unit);
	ifp->if_softc = sc;
	ifp->if_mtu = ETHERMTU;
	ifp->if_ioctl = bridge_ioctl;
	ifp->if_output = bridge_dummy_output;
	ifp->if_xflags = IFXF_CLONED;
	ifp->if_start = NULL;
	ifp->if_type = IFT_BRIDGE;
	ifp->if_hdrlen = ETHER_HDR_LEN;

	if_attach(ifp);
	if_alloc_sadl(ifp);

#if NBPFILTER > 0
	bpfattach(&sc->sc_if.if_bpf, ifp,
	    DLT_EN10MB, ETHER_HDR_LEN);
#endif

	return (0);
}

int
bridge_dummy_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst,
    struct rtentry *rt)
{
	m_freem(m);
	return (EAFNOSUPPORT);
}

int
bridge_clone_destroy(struct ifnet *ifp)
{
	struct bridge_softc *sc = ifp->if_softc;
	struct bridge_iflist *bif;

	/*
	 * bridge(4) detach hook doesn't need the NET_LOCK(), worst the
	 * use of smr_barrier() while holding the lock might lead to a
	 * deadlock situation.
	 */
	NET_ASSERT_UNLOCKED();

	bridge_stop(sc);
	bridge_rtflush(sc, IFBF_FLUSHALL);
	while ((bif = SMR_SLIST_FIRST_LOCKED(&sc->sc_iflist)) != NULL)
		bridge_ifremove(bif);
	while ((bif = SMR_SLIST_FIRST_LOCKED(&sc->sc_spanlist)) != NULL)
		bridge_spanremove(bif);

	bstp_destroy(sc->sc_stp);

	if_detach(ifp);

	free(sc, M_DEVBUF, sizeof *sc);
	return (0);
}

int
bridge_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
{
	struct bridge_softc *sc = (struct bridge_softc *)ifp->if_softc;
	struct ifbreq *req = (struct ifbreq *)data;
	struct ifbropreq *brop = (struct ifbropreq *)data;
	struct ifnet *ifs;
	struct bridge_iflist *bif;
	struct bstp_port *bp;
	struct bstp_state *bs = sc->sc_stp;
	int error = 0;

	/*
	 * bridge(4) data structure aren't protected by the NET_LOCK().
	 * Idealy it shouldn't be taken before calling `ifp->if_ioctl'
	 * but we aren't there yet.  Media ioctl run without netlock.
	 */
	switch (cmd) {
	case SIOCSIFMEDIA:
	case SIOCGIFMEDIA:
		return (ENOTTY);
	}
	NET_UNLOCK();

	switch (cmd) {
	case SIOCBRDGADD:
	/* bridge(4) does not distinguish between routing/forwarding ports */
	case SIOCBRDGADDL:
		if ((error = suser(curproc)) != 0)
			break;

		ifs = if_unit(req->ifbr_ifsname);
		if (ifs == NULL) {			/* no such interface */
			error = ENOENT;
			break;
		}
		if (ifs->if_type != IFT_ETHER) {
			if_put(ifs);
			error = EINVAL;
			break;
		}
		if (ifs->if_bridgeidx != 0) {
			if (ifs->if_bridgeidx == ifp->if_index)
				error = EEXIST;
			else
				error = EBUSY;
			if_put(ifs);
			break;
		}

		error = ether_brport_isset(ifs);
		if (error != 0) {
			if_put(ifs);
			break;
		}

		/* If it's in the span list, it can't be a member. */
		SMR_SLIST_FOREACH_LOCKED(bif, &sc->sc_spanlist, bif_next) {
			if (bif->ifp == ifs)
				break;
		}
		if (bif != NULL) {
			if_put(ifs);
			error = EBUSY;
			break;
		}

		bif = malloc(sizeof(*bif), M_DEVBUF, M_NOWAIT|M_ZERO);
		if (bif == NULL) {
			if_put(ifs);
			error = ENOMEM;
			break;
		}

		NET_LOCK();
		error = ifpromisc(ifs, 1);
		NET_UNLOCK();
		if (error != 0) {
			if_put(ifs);
			free(bif, M_DEVBUF, sizeof(*bif));
			break;
		}

		/*
		 * XXX If the NET_LOCK() or ifpromisc() calls above
		 * had to sleep, then something else could have come
		 * along and taken over ifs while the kernel lock was
		 * released.
		 */

		NET_LOCK();
		ifsetlro(ifs, 0);
		NET_UNLOCK();

		bif->bridge_sc = sc;
		bif->ifp = ifs;
		bif->bif_flags = IFBIF_LEARNING | IFBIF_DISCOVER;
		SIMPLEQ_INIT(&bif->bif_brlin);
		SIMPLEQ_INIT(&bif->bif_brlout);
		ifs->if_bridgeidx = ifp->if_index;
		task_set(&bif->bif_dtask, bridge_ifdetach, bif);
		if_detachhook_add(ifs, &bif->bif_dtask);
		ether_brport_set(bif->ifp, &bridge_brport);
		SMR_SLIST_INSERT_HEAD_LOCKED(&sc->sc_iflist, bif, bif_next);
		break;
	case SIOCBRDGDEL:
		if ((error = suser(curproc)) != 0)
			break;
		error = bridge_findbif(sc, req->ifbr_ifsname, &bif);
		if (error != 0)
			break;
		bridge_ifremove(bif);
		break;
	case SIOCBRDGIFS:
		error = bridge_bifconf(sc, (struct ifbifconf *)data);
		break;
	case SIOCBRDGADDS:
		if ((error = suser(curproc)) != 0)
			break;
		ifs = if_unit(req->ifbr_ifsname);
		if (ifs == NULL) {			/* no such interface */
			error = ENOENT;
			break;
		}
		if (ifs->if_type != IFT_ETHER) {
			if_put(ifs);
			error = EINVAL;
			break;
		}
		if (ifs->if_bridgeidx != 0) {
			if (ifs->if_bridgeidx == ifp->if_index)
				error = EEXIST;
			else
				error = EBUSY;
			if_put(ifs);
			break;
		}
		SMR_SLIST_FOREACH_LOCKED(bif, &sc->sc_spanlist, bif_next) {
			if (bif->ifp == ifs)
				break;
		}
		if (bif != NULL) {
			if_put(ifs);
			error = EEXIST;
			break;
		}
		bif = malloc(sizeof(*bif), M_DEVBUF, M_NOWAIT|M_ZERO);
		if (bif == NULL) {
			if_put(ifs);
			error = ENOMEM;
			break;
		}

		NET_LOCK();
		ifsetlro(ifs, 0);
		NET_UNLOCK();

		bif->bridge_sc = sc;
		bif->ifp = ifs;
		bif->bif_flags = IFBIF_SPAN;
		SIMPLEQ_INIT(&bif->bif_brlin);
		SIMPLEQ_INIT(&bif->bif_brlout);
		task_set(&bif->bif_dtask, bridge_spandetach, bif);
		if_detachhook_add(ifs, &bif->bif_dtask);
		SMR_SLIST_INSERT_HEAD_LOCKED(&sc->sc_spanlist, bif, bif_next);
		break;
	case SIOCBRDGDELS:
		if ((error = suser(curproc)) != 0)
			break;
		ifs = if_unit(req->ifbr_ifsname);
		if (ifs == NULL) {
			error = ENOENT;
			break;
		}
		SMR_SLIST_FOREACH_LOCKED(bif, &sc->sc_spanlist, bif_next) {
			if (bif->ifp == ifs)
				break;
		}
		if_put(ifs);
		if (bif == NULL) {
			error = ESRCH;
			break;
		}
		bridge_spanremove(bif);
		break;
	case SIOCBRDGGIFFLGS:
		error = bridge_findbif(sc, req->ifbr_ifsname, &bif);
		if (error != 0)
			break;
		req->ifbr_ifsflags = bif->bif_flags;
		req->ifbr_portno = bif->ifp->if_index & 0xfff;
		req->ifbr_protected = bif->bif_protected;
		if (bif->bif_flags & IFBIF_STP)
			bridge_bifgetstp(sc, bif, req);
		break;
	case SIOCBRDGSIFFLGS:
		if (req->ifbr_ifsflags & IFBIF_RO_MASK) {
			error = EINVAL;
			break;
		}
		if ((error = suser(curproc)) != 0)
			break;
		error = bridge_findbif(sc, req->ifbr_ifsname, &bif);
		if (error != 0)
			break;
		if (req->ifbr_ifsflags & IFBIF_STP) {
			if ((bif->bif_flags & IFBIF_STP) == 0) {
				/* Enable STP */
				if ((bif->bif_stp = bstp_add(sc->sc_stp,
				    bif->ifp)) == NULL) {
					error = ENOMEM;
					break;
				}
			} else {
				/* Update STP flags */
				bstp_ifsflags(bif->bif_stp, req->ifbr_ifsflags);
			}
		} else if (bif->bif_flags & IFBIF_STP) {
			bstp_delete(bif->bif_stp);
			bif->bif_stp = NULL;
		}
		bif->bif_flags = req->ifbr_ifsflags;
		break;
	case SIOCSIFFLAGS:
		if ((ifp->if_flags & IFF_UP) == IFF_UP)
			bridge_init(sc);

		if ((ifp->if_flags & IFF_UP) == 0)
			bridge_stop(sc);

		break;
	case SIOCBRDGGPARAM:
		if ((bp = bs->bs_root_port) == NULL)
			brop->ifbop_root_port = 0;
		else
			brop->ifbop_root_port = bp->bp_ifindex;
		brop->ifbop_maxage = bs->bs_bridge_max_age >> 8;
		brop->ifbop_hellotime = bs->bs_bridge_htime >> 8;
		brop->ifbop_fwddelay = bs->bs_bridge_fdelay >> 8;
		brop->ifbop_holdcount = bs->bs_txholdcount;
		brop->ifbop_priority = bs->bs_bridge_priority;
		brop->ifbop_protocol = bs->bs_protover;
		brop->ifbop_root_bridge = bs->bs_root_pv.pv_root_id;
		brop->ifbop_root_path_cost = bs->bs_root_pv.pv_cost;
		brop->ifbop_root_port = bs->bs_root_pv.pv_port_id;
		brop->ifbop_desg_bridge = bs->bs_root_pv.pv_dbridge_id;
		brop->ifbop_last_tc_time.tv_sec = bs->bs_last_tc_time.tv_sec;
		brop->ifbop_last_tc_time.tv_usec = bs->bs_last_tc_time.tv_usec;
		break;
	case SIOCBRDGSIFPROT:
		error = bridge_findbif(sc, req->ifbr_ifsname, &bif);
		if (error != 0)
			break;
		bif->bif_protected = req->ifbr_protected;
		break;
	case SIOCBRDGRTS:
	case SIOCBRDGGCACHE:
	case SIOCBRDGGPRI:
	case SIOCBRDGGMA:
	case SIOCBRDGGHT:
	case SIOCBRDGGFD:
	case SIOCBRDGGTO:
	case SIOCBRDGGRL:
		break;
	case SIOCBRDGFLUSH:
	case SIOCBRDGSADDR:
	case SIOCBRDGDADDR:
	case SIOCBRDGSCACHE:
	case SIOCBRDGSTO:
	case SIOCBRDGARL:
	case SIOCBRDGFRL:
	case SIOCBRDGSPRI:
	case SIOCBRDGSFD:
	case SIOCBRDGSMA:
	case SIOCBRDGSHT:
	case SIOCBRDGSTXHC:
	case SIOCBRDGSPROTO:
	case SIOCBRDGSIFPRIO:
	case SIOCBRDGSIFCOST:
		error = suser(curproc);
		break;
	default:
		error = ENOTTY;
		break;
	}

	if (!error)
		error = bridgectl_ioctl(ifp, cmd, data);

	if (!error)
		error = bstp_ioctl(ifp, cmd, data);

	NET_LOCK();
	return (error);
}

/* Detach an interface from a bridge.  */
int
bridge_ifremove(struct bridge_iflist *bif)
{
	struct bridge_softc *sc = bif->bridge_sc;
	int error;

	SMR_SLIST_REMOVE_LOCKED(&sc->sc_iflist, bif, bridge_iflist, bif_next);
	if_detachhook_del(bif->ifp, &bif->bif_dtask);
	ether_brport_clr(bif->ifp);

	smr_barrier();

	if (bif->bif_flags & IFBIF_STP) {
		bstp_delete(bif->bif_stp);
		bif->bif_stp = NULL;
	}

	bif->ifp->if_bridgeidx = 0;
	NET_LOCK();
	error = ifpromisc(bif->ifp, 0);
	NET_UNLOCK();

	bridge_rtdelete(sc, bif->ifp, 0);
	bridge_flushrule(bif);

	if_put(bif->ifp);
	bif->ifp = NULL;
	free(bif, M_DEVBUF, sizeof(*bif));

	return (error);
}

void
bridge_spanremove(struct bridge_iflist *bif)
{
	struct bridge_softc *sc = bif->bridge_sc;

	SMR_SLIST_REMOVE_LOCKED(&sc->sc_spanlist, bif, bridge_iflist, bif_next);
	if_detachhook_del(bif->ifp, &bif->bif_dtask);

	smr_barrier();

	if_put(bif->ifp);
	bif->ifp = NULL;
	free(bif, M_DEVBUF, sizeof(*bif));
}

void
bridge_ifdetach(void *xbif)
{
	struct bridge_iflist *bif = xbif;

	/*
	 * bridge(4) detach hook doesn't need the NET_LOCK(), worst the
	 * use of smr_barrier() while holding the lock might lead to a
	 * deadlock situation.
	 */
	NET_UNLOCK();
	bridge_ifremove(bif);
	NET_LOCK();
}

void
bridge_spandetach(void *xbif)
{
	struct bridge_iflist *bif = xbif;

	/*
	 * bridge(4) detach hook doesn't need the NET_LOCK(), worst the
	 * use of smr_barrier() while holding the lock might lead to a
	 * deadlock situation.
	 */
	NET_UNLOCK();
	bridge_spanremove(bif);
	NET_LOCK();
}

void
bridge_bifgetstp(struct bridge_softc *sc, struct bridge_iflist *bif,
    struct ifbreq *breq)
{
	struct bstp_state *bs = sc->sc_stp;
	struct bstp_port *bp = bif->bif_stp;

	breq->ifbr_state = bstp_getstate(bs, bp);
	breq->ifbr_priority = bp->bp_priority;
	breq->ifbr_path_cost = bp->bp_path_cost;
	breq->ifbr_proto = bp->bp_protover;
	breq->ifbr_role = bp->bp_role;
	breq->ifbr_stpflags = bp->bp_flags;
	breq->ifbr_fwd_trans = bp->bp_forward_transitions;
	breq->ifbr_root_bridge = bs->bs_root_pv.pv_root_id;
	breq->ifbr_root_cost = bs->bs_root_pv.pv_cost;
	breq->ifbr_root_port = bs->bs_root_pv.pv_port_id;
	breq->ifbr_desg_bridge = bs->bs_root_pv.pv_dbridge_id;
	breq->ifbr_desg_port = bs->bs_root_pv.pv_dport_id;

	/* Copy STP state options as flags */
	if (bp->bp_operedge)
		breq->ifbr_ifsflags |= IFBIF_BSTP_EDGE;
	if (bp->bp_flags & BSTP_PORT_AUTOEDGE)
		breq->ifbr_ifsflags |= IFBIF_BSTP_AUTOEDGE;
	if (bp->bp_ptp_link)
		breq->ifbr_ifsflags |= IFBIF_BSTP_PTP;
	if (bp->bp_flags & BSTP_PORT_AUTOPTP)
		breq->ifbr_ifsflags |= IFBIF_BSTP_AUTOPTP;
}

int
bridge_bifconf(struct bridge_softc *sc, struct ifbifconf *bifc)
{
	struct bridge_iflist *bif;
	u_int32_t total = 0, i = 0;
	int error = 0;
	struct ifbreq *breq, *breqs = NULL;

	SMR_SLIST_FOREACH_LOCKED(bif, &sc->sc_iflist, bif_next)
		total++;

	SMR_SLIST_FOREACH_LOCKED(bif, &sc->sc_spanlist, bif_next)
		total++;

	if (bifc->ifbic_len == 0) {
		i = total;
		goto done;
	}

	breqs = mallocarray(total, sizeof(*breqs), M_TEMP, M_NOWAIT|M_ZERO);
	if (breqs == NULL)
		goto done;

	SMR_SLIST_FOREACH_LOCKED(bif, &sc->sc_iflist, bif_next) {
		if (bifc->ifbic_len < (i + 1) * sizeof(*breqs))
			break;
		breq = &breqs[i];
		strlcpy(breq->ifbr_name, sc->sc_if.if_xname, IFNAMSIZ);
		strlcpy(breq->ifbr_ifsname, bif->ifp->if_xname, IFNAMSIZ);
		breq->ifbr_ifsflags = bif->bif_flags;
		breq->ifbr_portno = bif->ifp->if_index & 0xfff;
		breq->ifbr_protected = bif->bif_protected;
		if (bif->bif_flags & IFBIF_STP)
			bridge_bifgetstp(sc, bif, breq);
		i++;
	}
	SMR_SLIST_FOREACH_LOCKED(bif, &sc->sc_spanlist, bif_next) {
		if (bifc->ifbic_len < (i + 1) * sizeof(*breqs))
			break;
		breq = &breqs[i];
		strlcpy(breq->ifbr_name, sc->sc_if.if_xname, IFNAMSIZ);
		strlcpy(breq->ifbr_ifsname, bif->ifp->if_xname, IFNAMSIZ);
		breq->ifbr_ifsflags = bif->bif_flags | IFBIF_SPAN;
		breq->ifbr_portno = bif->ifp->if_index & 0xfff;
		i++;
	}

	error = copyout(breqs, bifc->ifbic_req, i * sizeof(*breqs));
done:
	free(breqs, M_TEMP, total * sizeof(*breq));
	bifc->ifbic_len = i * sizeof(*breq);
	return (error);
}

int
bridge_findbif(struct bridge_softc *sc, const char *name,
    struct bridge_iflist **rbif)
{
	struct ifnet *ifp;
	struct bridge_iflist *bif;
	int error = 0;

	KERNEL_ASSERT_LOCKED();

	if ((ifp = if_unit(name)) == NULL)
		return (ENOENT);

	if (ifp->if_bridgeidx != sc->sc_if.if_index) {
		error = ESRCH;
		goto put;
	}

	SMR_SLIST_FOREACH_LOCKED(bif, &sc->sc_iflist, bif_next) {
		if (bif->ifp == ifp)
			break;
	}

	if (bif == NULL) {
		error = ENOENT;
		goto put;
	}

	*rbif = bif;
put:
	if_put(ifp);

	return (error);
}

struct bridge_iflist *
bridge_getbif(struct ifnet *ifp)
{
	struct bridge_iflist *bif;
	struct bridge_softc *sc;
	struct ifnet *bifp;

	KERNEL_ASSERT_LOCKED();

	bifp = if_get(ifp->if_bridgeidx);
	if (bifp == NULL)
		return (NULL);

	sc = bifp->if_softc;
	SMR_SLIST_FOREACH_LOCKED(bif, &sc->sc_iflist, bif_next) {
		if (bif->ifp == ifp)
			break;
	}

	if_put(bifp);

	return (bif);
}

void
bridge_init(struct bridge_softc *sc)
{
	struct ifnet *ifp = &sc->sc_if;

	if (ISSET(ifp->if_flags, IFF_RUNNING))
		return;

	bstp_enable(sc->sc_stp, ifp->if_index);

	if (sc->sc_brttimeout != 0)
		timeout_add_sec(&sc->sc_brtimeout, sc->sc_brttimeout);

	SET(ifp->if_flags, IFF_RUNNING);
}

/*
 * Stop the bridge and deallocate the routing table.
 */
void
bridge_stop(struct bridge_softc *sc)
{
	struct ifnet *ifp = &sc->sc_if;

	if (!ISSET(ifp->if_flags, IFF_RUNNING))
		return;

	CLR(ifp->if_flags, IFF_RUNNING);

	bstp_disable(sc->sc_stp);

	timeout_del_barrier(&sc->sc_brtimeout);

	bridge_rtflush(sc, IFBF_FLUSHDYN);
}

/*
 * Send output from the bridge.  The mbuf has the ethernet header
 * already attached.  We must enqueue or free the mbuf before exiting.
 */
int
bridge_enqueue(struct ifnet *ifp, struct mbuf *m)
{
	struct ifnet *brifp;
	struct ether_header *eh;
	struct ifnet *dst_if = NULL;
	unsigned int dst_ifidx = 0;
#if NBPFILTER > 0
	caddr_t if_bpf;
#endif
	int error = 0;

	if (m->m_len < sizeof(*eh)) {
		m = m_pullup(m, sizeof(*eh));
		if (m == NULL)
			return (ENOBUFS);
	}

	/* ifp must be a member interface of the bridge. */
	brifp = if_get(ifp->if_bridgeidx);
	if (brifp == NULL) {
		m_freem(m);
		return (EINVAL);
	}

	/*
	 * If bridge is down, but original output interface is up,
	 * go ahead and send out that interface.  Otherwise the packet
	 * is dropped below.
	 */
	if (!ISSET(brifp->if_flags, IFF_RUNNING)) {
		/* Loop prevention. */
		m->m_flags |= M_PROTO1;
		error = if_enqueue(ifp, m);
		if_put(brifp);
		return (error);
	}

#if NBPFILTER > 0
	if_bpf = brifp->if_bpf;
	if (if_bpf)
		bpf_mtap(if_bpf, m, BPF_DIRECTION_OUT);
#endif
	ifp->if_opackets++;
	ifp->if_obytes += m->m_pkthdr.len;

	bridge_span(brifp, m);

	eh = mtod(m, struct ether_header *);
	if (!ETHER_IS_MULTICAST(eh->ether_dhost)) {
		struct ether_addr *dst;

		dst = (struct ether_addr *)&eh->ether_dhost[0];
		dst_ifidx = bridge_rtlookup(brifp, dst, m);
	}

	/*
	 * If the packet is a broadcast or we don't know a better way to
	 * get there, send to all interfaces.
	 */
	if (dst_ifidx == 0) {
		struct bridge_softc *sc = brifp->if_softc;
		struct bridge_iflist *bif;
		struct mbuf *mc;

		smr_read_enter();
		SMR_SLIST_FOREACH(bif, &sc->sc_iflist, bif_next) {
			dst_if = bif->ifp;
			if ((dst_if->if_flags & IFF_RUNNING) == 0)
				continue;

			/*
			 * If this is not the original output interface,
			 * and the interface is participating in spanning
			 * tree, make sure the port is in a state that
			 * allows forwarding.
			 */
			if (dst_if != ifp &&
			    (bif->bif_flags & IFBIF_STP) &&
			    (bif->bif_state == BSTP_IFSTATE_DISCARDING))
				continue;
			if ((bif->bif_flags & IFBIF_DISCOVER) == 0 &&
			    (m->m_flags & (M_BCAST | M_MCAST)) == 0)
				continue;

			if (bridge_filterrule(&bif->bif_brlout, eh, m) ==
			    BRL_ACTION_BLOCK)
				continue;

			mc = m_dup_pkt(m, ETHER_ALIGN, M_NOWAIT);
			if (mc == NULL) {
				brifp->if_oerrors++;
				continue;
			}

			error = bridge_ifenqueue(brifp, dst_if, mc);
			if (error)
				continue;
		}
		smr_read_leave();
		m_freem(m);
		goto out;
	}

	dst_if = if_get(dst_ifidx);
	if ((dst_if == NULL) || !ISSET(dst_if->if_flags, IFF_RUNNING)) {
		m_freem(m);
		if_put(dst_if);
		error = ENETDOWN;
		goto out;
	}

	bridge_ifenqueue(brifp, dst_if, m);
	if_put(dst_if);
out:
	if_put(brifp);
	return (error);
}

/*
 * Loop through each bridge interface and process their input queues.
 */
void
bridgeintr(void)
{
	struct mbuf_list ml;
	struct mbuf *m;
	struct ifnet *ifp;

	niq_delist(&bridgeintrq, &ml);
	if (ml_empty(&ml))
		return;

	KERNEL_LOCK();
	while ((m = ml_dequeue(&ml)) != NULL) {

		ifp = if_get(m->m_pkthdr.ph_ifidx);
		if (ifp == NULL) {
			m_freem(m);
			continue;
		}

		bridge_process(ifp, m);

		if_put(ifp);
	}
	KERNEL_UNLOCK();
}

/*
 * Process a single frame.  Frame must be freed or queued before returning.
 */
void
bridgeintr_frame(struct ifnet *brifp, struct ifnet *src_if, struct mbuf *m)
{
	struct bridge_softc *sc = brifp->if_softc;
	struct ifnet *dst_if = NULL;
	struct bridge_iflist *bif;
	struct ether_addr *dst, *src;
	struct ether_header eh;
	unsigned int dst_ifidx;
	u_int32_t protected;
	int len;


	sc->sc_if.if_ipackets++;
	sc->sc_if.if_ibytes += m->m_pkthdr.len;

	bif = bridge_getbif(src_if);
	KASSERT(bif != NULL);

	m_copydata(m, 0, ETHER_HDR_LEN, &eh);
	dst = (struct ether_addr *)&eh.ether_dhost[0];
	src = (struct ether_addr *)&eh.ether_shost[0];

	/*
	 * If interface is learning, and if source address
	 * is not broadcast or multicast, record its address.
	 */
	if ((bif->bif_flags & IFBIF_LEARNING) &&
	    !ETHER_IS_MULTICAST(eh.ether_shost) &&
	    !ETHER_IS_ANYADDR(eh.ether_shost))
		bridge_rtupdate(sc, src, src_if, 0, IFBAF_DYNAMIC, m);

	if ((bif->bif_flags & IFBIF_STP) &&
	    (bif->bif_state == BSTP_IFSTATE_LEARNING)) {
		m_freem(m);
		return;
	}

	/*
	 * At this point, the port either doesn't participate in stp or
	 * it's in the forwarding state
	 */

	/*
	 * If packet is unicast, destined for someone on "this"
	 * side of the bridge, drop it.
	 */
	if (!ETHER_IS_MULTICAST(eh.ether_dhost)) {
		dst_ifidx = bridge_rtlookup(brifp, dst, NULL);
		if (dst_ifidx == src_if->if_index) {
			m_freem(m);
			return;
		}
	} else {
		if (ETHER_IS_BROADCAST(eh.ether_dhost))
			m->m_flags |= M_BCAST;
		else
			m->m_flags |= M_MCAST;
	}

	/*
	 * Multicast packets get handled a little differently:
	 * If interface is:
	 *	-link0,-link1	(default) Forward all multicast
	 *			as broadcast.
	 *	-link0,link1	Drop non-IP multicast, forward
	 *			as broadcast IP multicast.
	 *	link0,-link1	Drop IP multicast, forward as
	 *			broadcast non-IP multicast.
	 *	link0,link1	Drop all multicast.
	 */
	if (m->m_flags & M_MCAST) {
		if ((sc->sc_if.if_flags &
		    (IFF_LINK0 | IFF_LINK1)) ==
		    (IFF_LINK0 | IFF_LINK1)) {
			m_freem(m);
			return;
		}
		if (sc->sc_if.if_flags & IFF_LINK0 &&
		    ETHERADDR_IS_IP_MCAST(dst)) {
			m_freem(m);
			return;
		}
		if (sc->sc_if.if_flags & IFF_LINK1 &&
		    !ETHERADDR_IS_IP_MCAST(dst)) {
			m_freem(m);
			return;
		}
	}

	if (bif->bif_flags & IFBIF_BLOCKNONIP && bridge_blocknonip(&eh, m)) {
		m_freem(m);
		return;
	}

	if (bridge_filterrule(&bif->bif_brlin, &eh, m) == BRL_ACTION_BLOCK) {
		m_freem(m);
		return;
	}
	m = bridge_ip(&sc->sc_if, BRIDGE_IN, src_if, &eh, m);
	if (m == NULL)
		return;
	/*
	 * If the packet is a multicast or broadcast OR if we don't
	 * know any better, forward it to all interfaces.
	 */
	if ((m->m_flags & (M_BCAST | M_MCAST)) || dst_ifidx == 0) {
		sc->sc_if.if_imcasts++;
		bridge_broadcast(sc, src_if, &eh, m);
		return;
	}
	protected = bif->bif_protected;

	dst_if = if_get(dst_ifidx);
	if (dst_if == NULL)
		goto bad;

	/*
	 * At this point, we're dealing with a unicast frame going to a
	 * different interface
	 */
	if (!ISSET(dst_if->if_flags, IFF_RUNNING))
		goto bad;
	bif = bridge_getbif(dst_if);
	if ((bif == NULL) || ((bif->bif_flags & IFBIF_STP) &&
	    (bif->bif_state == BSTP_IFSTATE_DISCARDING)))
		goto bad;
	/*
	 * Do not transmit if both ports are part of the same protected
	 * domain.
	 */
	if (protected != 0 && (protected & bif->bif_protected))
		goto bad;
	if (bridge_filterrule(&bif->bif_brlout, &eh, m) == BRL_ACTION_BLOCK)
		goto bad;
	m = bridge_ip(&sc->sc_if, BRIDGE_OUT, dst_if, &eh, m);
	if (m == NULL)
		goto bad;

	len = m->m_pkthdr.len;
#if NVLAN > 0
	if ((m->m_flags & M_VLANTAG) &&
	    (dst_if->if_capabilities & IFCAP_VLAN_HWTAGGING) == 0)
		len += ETHER_VLAN_ENCAP_LEN;
#endif
	if ((len - ETHER_HDR_LEN) > dst_if->if_mtu)
		bridge_fragment(&sc->sc_if, dst_if, &eh, m);
	else {
		bridge_ifenqueue(&sc->sc_if, dst_if, m);
	}
	m = NULL;
bad:
	if_put(dst_if);
	m_freem(m);
}

/*
 * Return 1 if `ena' belongs to `bif', 0 otherwise.
 */
int
bridge_ourether(struct ifnet *ifp, uint8_t *ena)
{
	struct arpcom *ac = (struct arpcom *)ifp;

	if (memcmp(ac->ac_enaddr, ena, ETHER_ADDR_LEN) == 0)
		return (1);

#if NCARP > 0
	if (carp_ourether(ifp, ena))
		return (1);
#endif

	return (0);
}

/*
 * Receive input from an interface.  Queue the packet for bridging if its
 * not for us, and schedule an interrupt.
 */
struct mbuf *
bridge_input(struct ifnet *ifp, struct mbuf *m, uint64_t dst, void *null)
{
	KASSERT(m->m_flags & M_PKTHDR);

	if (m->m_flags & M_PROTO1) {
		m->m_flags &= ~M_PROTO1;
		return (m);
	}

	niq_enqueue(&bridgeintrq, m);

	return (NULL);
}

void
bridge_process(struct ifnet *ifp, struct mbuf *m)
{
	struct ifnet *brifp;
	struct bridge_softc *sc;
	struct bridge_iflist *bif = NULL, *bif0 = NULL;
	struct ether_header *eh;
	struct mbuf *mc;
#if NBPFILTER > 0
	caddr_t if_bpf;
#endif

	KERNEL_ASSERT_LOCKED();

	brifp = if_get(ifp->if_bridgeidx);
	if ((brifp == NULL) || !ISSET(brifp->if_flags, IFF_RUNNING))
		goto reenqueue;

	if (m->m_pkthdr.len < sizeof(*eh))
		goto bad;

#if NVLAN > 0
	/*
	 * If the underlying interface removed the VLAN header itself,
	 * add it back.
	 */
	if (ISSET(m->m_flags, M_VLANTAG)) {
		m = vlan_inject(m, ETHERTYPE_VLAN, m->m_pkthdr.ether_vtag);
		if (m == NULL)
			goto bad;
	}
#endif

#if NBPFILTER > 0
	if_bpf = brifp->if_bpf;
	if (if_bpf)
		bpf_mtap_ether(if_bpf, m, BPF_DIRECTION_IN);
#endif

	eh = mtod(m, struct ether_header *);

	sc = brifp->if_softc;
	SMR_SLIST_FOREACH_LOCKED(bif, &sc->sc_iflist, bif_next) {
		struct arpcom *ac = (struct arpcom *)bif->ifp;
		if (memcmp(ac->ac_enaddr, eh->ether_shost, ETHER_ADDR_LEN) == 0)
			goto bad;
		if (bif->ifp == ifp)
			bif0 = bif;
	}
	if (bif0 == NULL)
		goto reenqueue;

	bridge_span(brifp, m);

	if (ETHER_IS_MULTICAST(eh->ether_dhost)) {
		/*
		 * Reserved destination MAC addresses (01:80:C2:00:00:0x)
		 * should not be forwarded to bridge members according to
		 * section 7.12.6 of the 802.1D-2004 specification.  The
		 * STP destination address (as stored in bstp_etheraddr)
		 * is the first of these.
		 */
		if (memcmp(eh->ether_dhost, bstp_etheraddr,
		    ETHER_ADDR_LEN - 1) == 0) {
			if (eh->ether_dhost[ETHER_ADDR_LEN - 1] == 0) {
				/* STP traffic */
				m = bstp_input(sc->sc_stp, bif0->bif_stp, eh,
				    m);
				if (m == NULL)
					goto bad;
			} else if (eh->ether_dhost[ETHER_ADDR_LEN - 1] <= 0xf)
				goto bad;
		}

		/*
		 * No need to process frames for ifs in the discarding state
		 */
		if ((bif0->bif_flags & IFBIF_STP) &&
		    (bif0->bif_state == BSTP_IFSTATE_DISCARDING))
			goto reenqueue;

		mc = m_dup_pkt(m, ETHER_ALIGN, M_NOWAIT);
		if (mc == NULL)
			goto reenqueue;

		bridge_ifinput(ifp, mc);

		bridgeintr_frame(brifp, ifp, m);
		if_put(brifp);
		return;
	}

	/*
	 * Unicast, make sure it's not for us.
	 */
	if (bridge_ourether(bif0->ifp, eh->ether_dhost)) {
		bif = bif0;
	} else {
		SMR_SLIST_FOREACH_LOCKED(bif, &sc->sc_iflist, bif_next) {
			if (bif->ifp == ifp)
				continue;
			if (bridge_ourether(bif->ifp, eh->ether_dhost))
				break;
		}
	}
	if (bif != NULL) {
		if (bif0->bif_flags & IFBIF_LEARNING)
			bridge_rtupdate(sc,
			    (struct ether_addr *)&eh->ether_shost,
			    ifp, 0, IFBAF_DYNAMIC, m);
		if (bridge_filterrule(&bif0->bif_brlin, eh, m) ==
		    BRL_ACTION_BLOCK) {
			goto bad;
		}

		/* Count for the bridge */
		brifp->if_ipackets++;
		brifp->if_ibytes += m->m_pkthdr.len;

		ifp = bif->ifp;
		goto reenqueue;
	}

	bridgeintr_frame(brifp, ifp, m);
	if_put(brifp);
	return;

reenqueue:
	bridge_ifinput(ifp, m);
	m = NULL;
bad:
	m_freem(m);
	if_put(brifp);
}

/*
 * Send a frame to all interfaces that are members of the bridge
 * (except the one it came in on).
 */
void
bridge_broadcast(struct bridge_softc *sc, struct ifnet *ifp,
    struct ether_header *eh, struct mbuf *m)
{
	struct bridge_iflist *bif;
	struct mbuf *mc;
	struct ifnet *dst_if;
	int len, used = 0;
	u_int32_t protected;

	bif = bridge_getbif(ifp);
	KASSERT(bif != NULL);
	protected = bif->bif_protected;

	SMR_SLIST_FOREACH_LOCKED(bif, &sc->sc_iflist, bif_next) {
		dst_if = bif->ifp;

		if ((dst_if->if_flags & IFF_RUNNING) == 0)
			continue;

		if ((bif->bif_flags & IFBIF_STP) &&
		    (bif->bif_state == BSTP_IFSTATE_DISCARDING))
			continue;

		if ((bif->bif_flags & IFBIF_DISCOVER) == 0 &&
		    (m->m_flags & (M_BCAST | M_MCAST)) == 0)
			continue;

		/* Drop non-IP frames if the appropriate flag is set. */
		if (bif->bif_flags & IFBIF_BLOCKNONIP &&
		    bridge_blocknonip(eh, m))
			continue;

		/*
		 * Do not transmit if both ports are part of the same
		 * protected domain.
		 */
		if (protected != 0 && (protected & bif->bif_protected))
			continue;

		if (bridge_filterrule(&bif->bif_brlout, eh, m) ==
		    BRL_ACTION_BLOCK)
			continue;

		/*
		 * Don't retransmit out of the same interface where
		 * the packet was received from.
		 */
		if (dst_if->if_index == ifp->if_index)
			continue;

		if (bridge_localbroadcast(dst_if, eh, m))
			sc->sc_if.if_oerrors++;

		/* If last one, reuse the passed-in mbuf */
		if (SMR_SLIST_NEXT_LOCKED(bif, bif_next) == NULL) {
			mc = m;
			used = 1;
		} else {
			mc = m_dup_pkt(m, ETHER_ALIGN, M_NOWAIT);
			if (mc == NULL) {
				sc->sc_if.if_oerrors++;
				continue;
			}
		}

		mc = bridge_ip(&sc->sc_if, BRIDGE_OUT, dst_if, eh, mc);
		if (mc == NULL)
			continue;

		len = mc->m_pkthdr.len;
#if NVLAN > 0
		if ((mc->m_flags & M_VLANTAG) &&
		    (dst_if->if_capabilities & IFCAP_VLAN_HWTAGGING) == 0)
			len += ETHER_VLAN_ENCAP_LEN;
#endif
		if ((len - ETHER_HDR_LEN) > dst_if->if_mtu)
			bridge_fragment(&sc->sc_if, dst_if, eh, mc);
		else {
			bridge_ifenqueue(&sc->sc_if, dst_if, mc);
		}
	}

	if (!used)
		m_freem(m);
}

int
bridge_localbroadcast(struct ifnet *ifp, struct ether_header *eh,
    struct mbuf *m)
{
	struct mbuf *m1;
	u_int16_t etype;

	/*
	 * quick optimisation, don't send packets up the stack if no
	 * corresponding address has been specified.
	 */
	etype = ntohs(eh->ether_type);
	if (!(m->m_flags & M_VLANTAG) && etype == ETHERTYPE_IP) {
		struct ifaddr *ifa;
		TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) {
			if (ifa->ifa_addr->sa_family == AF_INET)
				break;
		}
		if (ifa == NULL)
			return (0);
	}

	m1 = m_dup_pkt(m, ETHER_ALIGN, M_NOWAIT);
	if (m1 == NULL)
		return (1);

#if NPF > 0
	pf_pkt_addr_changed(m1);
#endif	/* NPF */

	bridge_ifinput(ifp, m1);

	return (0);
}

void
bridge_span(struct ifnet *brifp, struct mbuf *m)
{
	struct bridge_softc *sc = brifp->if_softc;
	struct bridge_iflist *bif;
	struct ifnet *ifp;
	struct mbuf *mc;
	int error;

	smr_read_enter();
	SMR_SLIST_FOREACH(bif, &sc->sc_spanlist, bif_next) {
		ifp = bif->ifp;

		if ((ifp->if_flags & IFF_RUNNING) == 0)
			continue;

		mc = m_copym(m, 0, M_COPYALL, M_DONTWAIT);
		if (mc == NULL) {
			brifp->if_oerrors++;
			continue;
		}

		error = bridge_ifenqueue(brifp, ifp, mc);
		if (error)
			continue;
	}
	smr_read_leave();
}

/*
 * Block non-ip frames:
 * Returns 0 if frame is ip, and 1 if it should be dropped.
 */
int
bridge_blocknonip(struct ether_header *eh, struct mbuf *m)
{
	struct llc llc;
	u_int16_t etype;

	if (m->m_pkthdr.len < ETHER_HDR_LEN)
		return (1);

#if NVLAN > 0
	if (m->m_flags & M_VLANTAG)
		return (1);
#endif

	etype = ntohs(eh->ether_type);
	switch (etype) {
	case ETHERTYPE_ARP:
	case ETHERTYPE_REVARP:
	case ETHERTYPE_IP:
	case ETHERTYPE_IPV6:
		return (0);
	}

	if (etype > ETHERMTU)
		return (1);

	if (m->m_pkthdr.len <
	    (ETHER_HDR_LEN + LLC_SNAPFRAMELEN))
		return (1);

	m_copydata(m, ETHER_HDR_LEN, LLC_SNAPFRAMELEN, &llc);

	etype = ntohs(llc.llc_snap.ether_type);
	if (llc.llc_dsap == LLC_SNAP_LSAP &&
	    llc.llc_ssap == LLC_SNAP_LSAP &&
	    llc.llc_control == LLC_UI &&
	    llc.llc_snap.org_code[0] == 0 &&
	    llc.llc_snap.org_code[1] == 0 &&
	    llc.llc_snap.org_code[2] == 0 &&
	    (etype == ETHERTYPE_ARP || etype == ETHERTYPE_REVARP ||
	    etype == ETHERTYPE_IP || etype == ETHERTYPE_IPV6)) {
		return (0);
	}

	return (1);
}

#ifdef IPSEC
int
bridge_ipsec(struct ifnet *ifp, struct ether_header *eh, int hassnap,
    struct llc *llc, int dir, int af, int hlen, struct mbuf *m)
{
	union sockaddr_union dst;
	struct tdb *tdb;
	u_int32_t spi;
	u_int16_t cpi;
	int error, off, prot;
	u_int8_t proto = 0;
	struct ip *ip;
#ifdef INET6
	struct ip6_hdr *ip6;
#endif /* INET6 */
#if NPF > 0
	struct ifnet *encif;
#endif

	if (dir == BRIDGE_IN) {
		switch (af) {
		case AF_INET:
			if (m->m_pkthdr.len - hlen < 2 * sizeof(u_int32_t))
				goto skiplookup;

			ip = mtod(m, struct ip *);
			proto = ip->ip_p;
			off = offsetof(struct ip, ip_p);

			if (proto != IPPROTO_ESP && proto != IPPROTO_AH &&
			    proto != IPPROTO_IPCOMP)
				goto skiplookup;

			bzero(&dst, sizeof(union sockaddr_union));
			dst.sa.sa_family = AF_INET;
			dst.sin.sin_len = sizeof(struct sockaddr_in);
			m_copydata(m, offsetof(struct ip, ip_dst),
			    sizeof(struct in_addr), &dst.sin.sin_addr);

			break;
#ifdef INET6
		case AF_INET6:
			if (m->m_pkthdr.len - hlen < 2 * sizeof(u_int32_t))
				goto skiplookup;

			ip6 = mtod(m, struct ip6_hdr *);

			/* XXX We should chase down the header chain */
			proto = ip6->ip6_nxt;
			off = offsetof(struct ip6_hdr, ip6_nxt);

			if (proto != IPPROTO_ESP && proto != IPPROTO_AH &&
			    proto != IPPROTO_IPCOMP)
				goto skiplookup;

			bzero(&dst, sizeof(union sockaddr_union));
			dst.sa.sa_family = AF_INET6;
			dst.sin6.sin6_len = sizeof(struct sockaddr_in6);
			m_copydata(m, offsetof(struct ip6_hdr, ip6_dst),
			    sizeof(struct in6_addr), &dst.sin6.sin6_addr);

			break;
#endif /* INET6 */
		default:
			return (0);
		}

		switch (proto) {
		case IPPROTO_ESP:
			m_copydata(m, hlen, sizeof(u_int32_t), &spi);
			break;
		case IPPROTO_AH:
			m_copydata(m, hlen + sizeof(u_int32_t),
			    sizeof(u_int32_t), &spi);
			break;
		case IPPROTO_IPCOMP:
			m_copydata(m, hlen + sizeof(u_int16_t),
			    sizeof(u_int16_t), &cpi);
			spi = htonl(ntohs(cpi));
			break;
		}

		NET_ASSERT_LOCKED();

		tdb = gettdb(ifp->if_rdomain, spi, &dst, proto);
		if (tdb != NULL && (tdb->tdb_flags & TDBF_INVALID) == 0 &&
		    tdb->tdb_xform != NULL) {
			if (tdb->tdb_first_use == 0) {
				tdb->tdb_first_use = gettime();
				if (tdb->tdb_flags & TDBF_FIRSTUSE) {
					if (timeout_add_sec(
					    &tdb->tdb_first_tmo,
					    tdb->tdb_exp_first_use))
						tdb_ref(tdb);
				}
				if (tdb->tdb_flags & TDBF_SOFT_FIRSTUSE) {
					if (timeout_add_sec(
					    &tdb->tdb_sfirst_tmo,
					    tdb->tdb_soft_first_use))
						tdb_ref(tdb);
				}
			}

			prot = (*(tdb->tdb_xform->xf_input))(&m, tdb, hlen,
			    off);
			tdb_unref(tdb);
			if (prot != IPPROTO_DONE)
				ip_deliver(&m, &hlen, prot, af, 0);
			return (1);
		} else {
			tdb_unref(tdb);
 skiplookup:
			/* XXX do an input policy lookup */
			return (0);
		}
	} else { /* Outgoing from the bridge. */
		error = ipsp_spd_lookup(m, af, hlen, IPSP_DIRECTION_OUT,
		    NULL, NULL, &tdb, NULL);
		if (error == 0 && tdb != NULL) {
			/*
			 * We don't need to do loop detection, the
			 * bridge will do that for us.
			 */
#if NPF > 0
			if ((encif = enc_getif(tdb->tdb_rdomain,
			    tdb->tdb_tap)) == NULL ||
			    pf_test(af, dir, encif, &m) != PF_PASS) {
				m_freem(m);
				tdb_unref(tdb);
				return (1);
			}
			if (m == NULL) {
				tdb_unref(tdb);
				return (1);
			}
			if (af == AF_INET)
				in_proto_cksum_out(m, encif);
#ifdef INET6
			else if (af == AF_INET6)
				in6_proto_cksum_out(m, encif);
#endif /* INET6 */
#endif /* NPF */

			ip = mtod(m, struct ip *);
			if ((af == AF_INET) &&
			    ip_mtudisc && (ip->ip_off & htons(IP_DF)) &&
			    tdb->tdb_mtu && ntohs(ip->ip_len) > tdb->tdb_mtu &&
			    tdb->tdb_mtutimeout > gettime()) {
				bridge_send_icmp_err(ifp, eh, m,
				    hassnap, llc, tdb->tdb_mtu,
				    ICMP_UNREACH, ICMP_UNREACH_NEEDFRAG);
			} else {
				KERNEL_LOCK();
				error = ipsp_process_packet(m, tdb, af, 0);
				KERNEL_UNLOCK();
			}
			tdb_unref(tdb);
			return (1);
		} else
			return (0);
	}

	return (0);
}
#endif /* IPSEC */

/*
 * Filter IP packets by peeking into the ethernet frame.  This violates
 * the ISO model, but allows us to act as a IP filter at the data link
 * layer.  As a result, most of this code will look familiar to those
 * who've read net/if_ethersubr.c and netinet/ip_input.c
 */
struct mbuf *
bridge_ip(struct ifnet *brifp, int dir, struct ifnet *ifp,
    struct ether_header *eh, struct mbuf *m)
{
	struct llc llc;
	int hassnap = 0;
	struct ip *ip;
	int hlen;
	u_int16_t etype;

#if NVLAN > 0
	if (m->m_flags & M_VLANTAG)
		return (m);
#endif

	etype = ntohs(eh->ether_type);

	if (etype != ETHERTYPE_IP && etype != ETHERTYPE_IPV6) {
		if (etype > ETHERMTU ||
		    m->m_pkthdr.len < (LLC_SNAPFRAMELEN +
		    ETHER_HDR_LEN))
			return (m);

		m_copydata(m, ETHER_HDR_LEN, LLC_SNAPFRAMELEN, &llc);

		if (llc.llc_dsap != LLC_SNAP_LSAP ||
		    llc.llc_ssap != LLC_SNAP_LSAP ||
		    llc.llc_control != LLC_UI ||
		    llc.llc_snap.org_code[0] ||
		    llc.llc_snap.org_code[1] ||
		    llc.llc_snap.org_code[2])
			return (m);

		etype = ntohs(llc.llc_snap.ether_type);
		if (etype != ETHERTYPE_IP && etype != ETHERTYPE_IPV6)
			return (m);
		hassnap = 1;
	}

	m_adj(m, ETHER_HDR_LEN);
	if (hassnap)
		m_adj(m, LLC_SNAPFRAMELEN);

	switch (etype) {

	case ETHERTYPE_IP:
		m = ipv4_check(ifp, m);
		if (m == NULL)
			return (NULL);

		ip = mtod(m, struct ip *);
		hlen = ip->ip_hl << 2;

#ifdef IPSEC
		if ((brifp->if_flags & IFF_LINK2) == IFF_LINK2 &&
		    bridge_ipsec(ifp, eh, hassnap, &llc, dir, AF_INET, hlen, m))
			return (NULL);
#endif /* IPSEC */
#if NPF > 0
		/* Finally, we get to filter the packet! */
		if (pf_test(AF_INET, dir, ifp, &m) != PF_PASS)
			goto dropit;
		if (m == NULL)
			goto dropit;
#endif /* NPF > 0 */

		/* Rebuild the IP header */
		if (m->m_len < hlen && ((m = m_pullup(m, hlen)) == NULL))
			return (NULL);
		if (m->m_len < sizeof(struct ip))
			goto dropit;
		in_hdr_cksum_out(m, ifp);
		in_proto_cksum_out(m, ifp);

#if NPF > 0
		if (dir == BRIDGE_IN &&
		    m->m_pkthdr.pf.flags & PF_TAG_DIVERTED) {
			m_resethdr(m);
			m->m_pkthdr.ph_ifidx = ifp->if_index;
			m->m_pkthdr.ph_rtableid = ifp->if_rdomain;
			ipv4_input(ifp, m);
			return (NULL);
		}
#endif /* NPF > 0 */

		break;

#ifdef INET6
	case ETHERTYPE_IPV6:
		m = ipv6_check(ifp, m);
		if (m == NULL)
			return (NULL);

#ifdef IPSEC
		hlen = sizeof(struct ip6_hdr);

		if ((brifp->if_flags & IFF_LINK2) == IFF_LINK2 &&
		    bridge_ipsec(ifp, eh, hassnap, &llc, dir, AF_INET6, hlen,
		    m))
			return (NULL);
#endif /* IPSEC */

#if NPF > 0
		if (pf_test(AF_INET6, dir, ifp, &m) != PF_PASS)
			goto dropit;
		if (m == NULL)
			return (NULL);
#endif /* NPF > 0 */
		in6_proto_cksum_out(m, ifp);

#if NPF > 0
		if (dir == BRIDGE_IN &&
		    m->m_pkthdr.pf.flags & PF_TAG_DIVERTED) {
			m_resethdr(m);
			m->m_pkthdr.ph_ifidx = ifp->if_index;
			m->m_pkthdr.ph_rtableid = ifp->if_rdomain;
			ipv6_input(ifp, m);
			return (NULL);
		}
#endif /* NPF > 0 */

		break;
#endif /* INET6 */

	default:
		goto dropit;
		break;
	}

	/* Reattach SNAP header */
	if (hassnap) {
		M_PREPEND(m, LLC_SNAPFRAMELEN, M_DONTWAIT);
		if (m == NULL)
			goto dropit;
		bcopy(&llc, mtod(m, caddr_t), LLC_SNAPFRAMELEN);
	}

	/* Reattach ethernet header */
	M_PREPEND(m, sizeof(*eh), M_DONTWAIT);
	if (m == NULL)
		goto dropit;
	bcopy(eh, mtod(m, caddr_t), sizeof(*eh));

	return (m);

dropit:
	m_freem(m);
	return (NULL);
}

void
bridge_fragment(struct ifnet *brifp, struct ifnet *ifp, struct ether_header *eh,
    struct mbuf *m)
{
	struct llc llc;
	struct mbuf_list ml;
	int error = 0;
	int hassnap = 0;
	u_int16_t etype;
	struct ip *ip;

	etype = ntohs(eh->ether_type);
#if NVLAN > 0
	if ((m->m_flags & M_VLANTAG) || etype == ETHERTYPE_VLAN ||
	    etype == ETHERTYPE_QINQ) {
		int len = m->m_pkthdr.len;

		if (m->m_flags & M_VLANTAG)
			len += ETHER_VLAN_ENCAP_LEN;
		if ((ifp->if_capabilities & IFCAP_VLAN_MTU) &&
		    (len - sizeof(struct ether_vlan_header) <= ifp->if_mtu)) {
			bridge_ifenqueue(brifp, ifp, m);
			return;
		}
		goto dropit;
	}
#endif
	if (etype != ETHERTYPE_IP) {
		if (etype > ETHERMTU ||
		    m->m_pkthdr.len < (LLC_SNAPFRAMELEN +
		    ETHER_HDR_LEN))
			goto dropit;

		m_copydata(m, ETHER_HDR_LEN, LLC_SNAPFRAMELEN, &llc);

		if (llc.llc_dsap != LLC_SNAP_LSAP ||
		    llc.llc_ssap != LLC_SNAP_LSAP ||
		    llc.llc_control != LLC_UI ||
		    llc.llc_snap.org_code[0] ||
		    llc.llc_snap.org_code[1] ||
		    llc.llc_snap.org_code[2] ||
		    llc.llc_snap.ether_type != htons(ETHERTYPE_IP))
			goto dropit;

		hassnap = 1;
	}

	m_adj(m, ETHER_HDR_LEN);
	if (hassnap)
		m_adj(m, LLC_SNAPFRAMELEN);

	if (m->m_len < sizeof(struct ip) &&
	    (m = m_pullup(m, sizeof(struct ip))) == NULL)
		goto dropit;
	ip = mtod(m, struct ip *);

	/* Respect IP_DF, return a ICMP_UNREACH_NEEDFRAG. */
	if (ip->ip_off & htons(IP_DF)) {
		bridge_send_icmp_err(ifp, eh, m, hassnap, &llc,
		    ifp->if_mtu, ICMP_UNREACH, ICMP_UNREACH_NEEDFRAG);
		return;
	}

	error = ip_fragment(m, &ml, ifp, ifp->if_mtu);
	if (error)
		return;

	while ((m = ml_dequeue(&ml)) != NULL) {
		if (hassnap) {
			M_PREPEND(m, LLC_SNAPFRAMELEN, M_DONTWAIT);
			if (m == NULL) {
				error = ENOBUFS;
				break;
			}
			bcopy(&llc, mtod(m, caddr_t), LLC_SNAPFRAMELEN);
		}
		M_PREPEND(m, sizeof(*eh), M_DONTWAIT);
		if (m == NULL) {
			error = ENOBUFS;
			break;
		}
		bcopy(eh, mtod(m, caddr_t), sizeof(*eh));
		error = bridge_ifenqueue(brifp, ifp, m);
		if (error)
			break;
	}
	if (error)
		ml_purge(&ml);
	else
		ipstat_inc(ips_fragmented);

	return;
 dropit:
	m_freem(m);
}

int
bridge_ifenqueue(struct ifnet *brifp, struct ifnet *ifp, struct mbuf *m)
{
	int error, len;

	/* Loop prevention. */
	m->m_flags |= M_PROTO1;

	len = m->m_pkthdr.len;

	error = if_enqueue(ifp, m);
	if (error) {
		brifp->if_oerrors++;
		return (error);
	}

	brifp->if_opackets++;
	brifp->if_obytes += len;

	return (0);
}

void
bridge_ifinput(struct ifnet *ifp, struct mbuf *m)
{
	struct mbuf_list ml = MBUF_LIST_INITIALIZER();

	m->m_flags |= M_PROTO1;

	ml_enqueue(&ml, m);
	if_input(ifp, &ml);
}

void
bridge_send_icmp_err(struct ifnet *ifp,
    struct ether_header *eh, struct mbuf *n, int hassnap, struct llc *llc,
    int mtu, int type, int code)
{
	struct ip *ip;
	struct icmp *icp;
	struct in_addr t;
	struct mbuf *m, *n2;
	int hlen;
	u_int8_t ether_tmp[ETHER_ADDR_LEN];

	n2 = m_copym(n, 0, M_COPYALL, M_DONTWAIT);
	if (!n2) {
		m_freem(n);
		return;
	}
	m = icmp_do_error(n, type, code, 0, mtu);
	if (m == NULL) {
		m_freem(n2);
		return;
	}

	n = n2;

	ip = mtod(m, struct ip *);
	hlen = ip->ip_hl << 2;
	t = ip->ip_dst;
	ip->ip_dst = ip->ip_src;
	ip->ip_src = t;

	m->m_data += hlen;
	m->m_len -= hlen;
	icp = mtod(m, struct icmp *);
	icp->icmp_cksum = 0;
	icp->icmp_cksum = in_cksum(m, ntohs(ip->ip_len) - hlen);
	m->m_data -= hlen;
	m->m_len += hlen;

	ip->ip_v = IPVERSION;
	ip->ip_off &= htons(IP_DF);
	ip->ip_id = htons(ip_randomid());
	ip->ip_ttl = MAXTTL;
	in_hdr_cksum_out(m, NULL);

	/* Swap ethernet addresses */
	bcopy(&eh->ether_dhost, &ether_tmp, sizeof(ether_tmp));
	bcopy(&eh->ether_shost, &eh->ether_dhost, sizeof(ether_tmp));
	bcopy(&ether_tmp, &eh->ether_shost, sizeof(ether_tmp));

	/* Reattach SNAP header */
	if (hassnap) {
		M_PREPEND(m, LLC_SNAPFRAMELEN, M_DONTWAIT);
		if (m == NULL)
			goto dropit;
		bcopy(llc, mtod(m, caddr_t), LLC_SNAPFRAMELEN);
	}

	/* Reattach ethernet header */
	M_PREPEND(m, sizeof(*eh), M_DONTWAIT);
	if (m == NULL)
		goto dropit;
	bcopy(eh, mtod(m, caddr_t), sizeof(*eh));

	bridge_enqueue(ifp, m);
	m_freem(n);
	return;

 dropit:
	m_freem(n);
}

void
bridge_take(void *unused)
{
	return;
}

void
bridge_rele(void *unused)
{
	return;
}