/* $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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IPSEC #include #include #endif #ifdef INET6 #include #include #include #endif #if NPF > 0 #include #define BRIDGE_IN PF_IN #define BRIDGE_OUT PF_OUT #else #define BRIDGE_IN 0 #define BRIDGE_OUT 1 #endif #if NBPFILTER > 0 #include #endif #if NCARP > 0 #include #endif #if NVLAN > 0 #include #endif #include /* * 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, ðer_tmp, sizeof(ether_tmp)); bcopy(&eh->ether_shost, &eh->ether_dhost, sizeof(ether_tmp)); bcopy(ðer_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; }