2030 lines
45 KiB
C
2030 lines
45 KiB
C
/* $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, ð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;
|
|
}
|