File: [local] / sys / net / if_bridge.c (download)
Revision 1.1.1.1 (vendor branch), Tue Mar 4 16:16:22 2008 UTC (16 years, 6 months ago) by nbrk
Branch: OPENBSD_4_2_BASE, MAIN
CVS Tags: jornada-partial-support-wip, HEAD Changes since 1.1: +0 -0 lines
Import of OpenBSD 4.2 release kernel tree with initial code to support
Jornada 720/728, StrongARM 1110-based handheld PC.
At this point kernel roots on NFS and boots into vfs_mountroot() and traps.
What is supported:
- glass console, Jornada framebuffer (jfb) works in 16bpp direct color mode
(needs some palette tweaks for non black/white/blue colors, i think)
- saic, SA11x0 interrupt controller (needs cleanup)
- sacom, SA11x0 UART (supported only as boot console for now)
- SA11x0 GPIO controller fully supported (but can't handle multiple interrupt
handlers on one gpio pin)
- sassp, SSP port on SA11x0 that attaches spibus
- Jornada microcontroller (jmcu) to control kbd, battery, etc throught
the SPI bus (wskbd attaches on jmcu, but not tested)
- tod functions seem work
- initial code for SA-1111 (chip companion) : this is TODO
Next important steps, i think:
- gpio and intc on sa1111
- pcmcia support for sa11x0 (and sa1111 help logic)
- REAL root on nfs when we have PCMCIA support (we may use any of supported pccard NICs)
- root on wd0! (using already supported PCMCIA-ATA)
|
/* $OpenBSD: if_bridge.c,v 1.164 2007/05/28 17:16:39 henning 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 <sys/param.h>
#include <sys/proc.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/errno.h>
#include <sys/kernel.h>
#include <machine/cpu.h>
#include <net/if.h>
#include <net/if_types.h>
#include <net/if_llc.h>
#include <net/route.h>
#include <net/netisr.h>
/* for arc4random() */
#include <dev/rndvar.h>
#ifdef INET
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/in_var.h>
#include <netinet/ip.h>
#include <netinet/ip_var.h>
#include <netinet/if_ether.h>
#include <netinet/ip_icmp.h>
#endif
#ifdef IPSEC
#include <netinet/ip_ipsp.h>
#include <net/if_enc.h>
#endif
#ifdef INET6
#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
#include <net/if_vlan_var.h>
#if NCARP > 0
#include <netinet/ip_carp.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_start(struct ifnet *);
void bridgeintr_frame(struct bridge_softc *, struct mbuf *);
void bridge_broadcast(struct bridge_softc *, struct ifnet *,
struct ether_header *, struct mbuf *);
void bridge_span(struct bridge_softc *, struct ether_header *,
struct mbuf *);
void bridge_stop(struct bridge_softc *);
void bridge_init(struct bridge_softc *);
int bridge_bifconf(struct bridge_softc *, struct ifbifconf *);
void bridge_timer(void *);
int bridge_rtfind(struct bridge_softc *, struct ifbaconf *);
void bridge_rtage(struct bridge_softc *);
void bridge_rttrim(struct bridge_softc *);
int bridge_rtdaddr(struct bridge_softc *, struct ether_addr *);
int bridge_rtflush(struct bridge_softc *, int);
struct ifnet * bridge_rtupdate(struct bridge_softc *,
struct ether_addr *, struct ifnet *ifp, int, u_int8_t);
struct ifnet * bridge_rtlookup(struct bridge_softc *,
struct ether_addr *);
u_int32_t bridge_hash(struct bridge_softc *, struct ether_addr *);
int bridge_blocknonip(struct ether_header *, struct mbuf *);
int bridge_addrule(struct bridge_iflist *,
struct ifbrlreq *, int out);
int bridge_flushrule(struct bridge_iflist *);
int bridge_brlconf(struct bridge_softc *, struct ifbrlconf *);
u_int8_t bridge_filterrule(struct brl_head *, struct ether_header *,
struct mbuf *);
#if NPF > 0
struct mbuf *bridge_filter(struct bridge_softc *, int, struct ifnet *,
struct ether_header *, struct mbuf *m);
#endif
int bridge_ifenqueue(struct bridge_softc *, struct ifnet *, struct mbuf *);
void bridge_fragment(struct bridge_softc *, struct ifnet *,
struct ether_header *, struct mbuf *);
#ifdef INET
void bridge_send_icmp_err(struct bridge_softc *, struct ifnet *,
struct ether_header *, struct mbuf *, int, struct llc *, int, int, int);
#endif
#ifdef IPSEC
int bridge_ipsec(struct bridge_softc *, struct ifnet *,
struct ether_header *, int, struct llc *,
int, int, int, struct mbuf *);
#define ICMP_DEFLEN MHLEN
#endif
int bridge_clone_create(struct if_clone *, int);
int bridge_clone_destroy(struct ifnet *ifp);
int bridge_delete(struct bridge_softc *, struct bridge_iflist *);
#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)
LIST_HEAD(, bridge_softc) bridge_list;
struct if_clone bridge_cloner =
IF_CLONE_INITIALIZER("bridge", bridge_clone_create, bridge_clone_destroy);
/* ARGSUSED */
void
bridgeattach(int n)
{
LIST_INIT(&bridge_list);
if_clone_attach(&bridge_cloner);
bstp_attach(n);
}
int
bridge_clone_create(struct if_clone *ifc, int unit)
{
struct bridge_softc *sc;
struct ifnet *ifp;
int i, s;
sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT);
if (!sc)
return (ENOMEM);
bzero(sc, sizeof(*sc));
sc->sc_stp = bstp_create(&sc->sc_if);
if (!sc->sc_stp) {
free(sc, M_DEVBUF);
return (ENOMEM);
}
sc->sc_brtmax = BRIDGE_RTABLE_MAX;
sc->sc_brttimeout = BRIDGE_RTABLE_TIMEOUT;
timeout_set(&sc->sc_brtimeout, bridge_timer, sc);
LIST_INIT(&sc->sc_iflist);
LIST_INIT(&sc->sc_spanlist);
for (i = 0; i < BRIDGE_RTABLE_SIZE; i++)
LIST_INIT(&sc->sc_rts[i]);
sc->sc_hashkey = arc4random();
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_output;
ifp->if_start = bridge_start;
ifp->if_type = IFT_BRIDGE;
ifp->if_hdrlen = ETHER_HDR_LEN;
IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen);
IFQ_SET_READY(&ifp->if_snd);
if_attach(ifp);
if_alloc_sadl(ifp);
#if NBPFILTER > 0
bpfattach(&sc->sc_if.if_bpf, ifp,
DLT_EN10MB, ETHER_HDR_LEN);
#endif
s = splnet();
LIST_INSERT_HEAD(&bridge_list, sc, sc_list);
splx(s);
return (0);
}
int
bridge_clone_destroy(struct ifnet *ifp)
{
struct bridge_softc *sc = ifp->if_softc;
struct bridge_iflist *bif;
int s;
bridge_stop(sc);
bridge_rtflush(sc, IFBF_FLUSHALL);
while ((bif = LIST_FIRST(&sc->sc_iflist)) != NULL)
bridge_delete(sc, bif);
while ((bif = LIST_FIRST(&sc->sc_spanlist)) != NULL) {
LIST_REMOVE(bif, next);
free(bif, M_DEVBUF);
}
s = splnet();
LIST_REMOVE(sc, sc_list);
splx(s);
bstp_destroy(sc->sc_stp);
if_detach(ifp);
free(sc, M_DEVBUF);
return (0);
}
int
bridge_delete(struct bridge_softc *sc, struct bridge_iflist *p)
{
int error;
if (p->bif_flags & IFBIF_STP)
bstp_delete(p->bif_stp);
p->ifp->if_bridge = NULL;
error = ifpromisc(p->ifp, 0);
LIST_REMOVE(p, next);
bridge_rtdelete(sc, p->ifp, 0);
bridge_flushrule(p);
free(p, M_DEVBUF);
return (error);
}
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 ifbareq *bareq = (struct ifbareq *)data;
struct ifbrparam *bparam = (struct ifbrparam *)data;
struct ifbrlreq *brlreq = (struct ifbrlreq *)data;
struct ifbropreq *brop = (struct ifbropreq *)data;
struct ifnet *ifs;
struct bridge_iflist *p;
struct bstp_port *bp;
struct bstp_state *bs = sc->sc_stp;
int error = 0, s;
s = splnet();
switch (cmd) {
case SIOCBRDGADD:
if ((error = suser(curproc, 0)) != 0)
break;
ifs = ifunit(req->ifbr_ifsname);
if (ifs == NULL) { /* no such interface */
error = ENOENT;
break;
}
if (ifs->if_bridge == (caddr_t)sc) {
error = EEXIST;
break;
}
if (ifs->if_bridge != NULL) {
error = EBUSY;
break;
}
/* If it's in the span list, it can't be a member. */
LIST_FOREACH(p, &sc->sc_spanlist, next)
if (p->ifp == ifs)
break;
if (p != LIST_END(&sc->sc_spanlist)) {
error = EBUSY;
break;
}
if (ifs->if_type == IFT_ETHER) {
if ((ifs->if_flags & IFF_UP) == 0) {
struct ifreq ifreq;
/*
* Bring interface up long enough to set
* promiscuous flag, then shut it down again.
*/
strlcpy(ifreq.ifr_name, req->ifbr_ifsname,
IFNAMSIZ);
ifs->if_flags |= IFF_UP;
ifreq.ifr_flags = ifs->if_flags;
error = (*ifs->if_ioctl)(ifs, SIOCSIFFLAGS,
(caddr_t)&ifreq);
if (error != 0)
break;
error = ifpromisc(ifs, 1);
if (error != 0)
break;
strlcpy(ifreq.ifr_name, req->ifbr_ifsname,
IFNAMSIZ);
ifs->if_flags &= ~IFF_UP;
ifreq.ifr_flags = ifs->if_flags;
error = (*ifs->if_ioctl)(ifs, SIOCSIFFLAGS,
(caddr_t)&ifreq);
if (error != 0) {
ifpromisc(ifs, 0);
break;
}
} else {
error = ifpromisc(ifs, 1);
if (error != 0)
break;
}
}
#if NGIF > 0
else if (ifs->if_type == IFT_GIF) {
/* Nothing needed */
}
#endif /* NGIF */
else {
error = EINVAL;
break;
}
p = (struct bridge_iflist *)malloc(
sizeof(struct bridge_iflist), M_DEVBUF, M_NOWAIT);
if (p == NULL) {
if (ifs->if_type == IFT_ETHER)
ifpromisc(ifs, 0);
error = ENOMEM;
break;
}
bzero(p, sizeof(struct bridge_iflist));
p->ifp = ifs;
p->bif_flags = IFBIF_LEARNING | IFBIF_DISCOVER;
SIMPLEQ_INIT(&p->bif_brlin);
SIMPLEQ_INIT(&p->bif_brlout);
ifs->if_bridge = (caddr_t)sc;
LIST_INSERT_HEAD(&sc->sc_iflist, p, next);
break;
case SIOCBRDGDEL:
if ((error = suser(curproc, 0)) != 0)
break;
LIST_FOREACH(p, &sc->sc_iflist, next) {
if (strncmp(p->ifp->if_xname, req->ifbr_ifsname,
sizeof(p->ifp->if_xname)) == 0) {
error = bridge_delete(sc, p);
p = NULL;
break;
}
}
if (p != NULL && p == LIST_END(&sc->sc_iflist)) {
error = ENOENT;
break;
}
break;
case SIOCBRDGIFS:
error = bridge_bifconf(sc, (struct ifbifconf *)data);
break;
case SIOCBRDGADDS:
if ((error = suser(curproc, 0)) != 0)
break;
ifs = ifunit(req->ifbr_ifsname);
if (ifs == NULL) { /* no such interface */
error = ENOENT;
break;
}
if (ifs->if_bridge == (caddr_t)sc) {
error = EEXIST;
break;
}
if (ifs->if_bridge != NULL) {
error = EBUSY;
break;
}
LIST_FOREACH(p, &sc->sc_spanlist, next) {
if (p->ifp == ifs)
break;
}
if (p != LIST_END(&sc->sc_spanlist)) {
error = EBUSY;
break;
}
p = (struct bridge_iflist *)malloc(
sizeof(struct bridge_iflist), M_DEVBUF, M_NOWAIT);
if (p == NULL) {
error = ENOMEM;
break;
}
bzero(p, sizeof(struct bridge_iflist));
p->ifp = ifs;
p->bif_flags = IFBIF_SPAN;
SIMPLEQ_INIT(&p->bif_brlin);
SIMPLEQ_INIT(&p->bif_brlout);
LIST_INSERT_HEAD(&sc->sc_spanlist, p, next);
break;
case SIOCBRDGDELS:
if ((error = suser(curproc, 0)) != 0)
break;
LIST_FOREACH(p, &sc->sc_spanlist, next) {
if (strncmp(p->ifp->if_xname, req->ifbr_ifsname,
sizeof(p->ifp->if_xname)) == 0) {
LIST_REMOVE(p, next);
free(p, M_DEVBUF);
break;
}
}
if (p == LIST_END(&sc->sc_spanlist)) {
error = ENOENT;
break;
}
break;
case SIOCBRDGGIFFLGS:
ifs = ifunit(req->ifbr_ifsname);
if (ifs == NULL) {
error = ENOENT;
break;
}
if ((caddr_t)sc != ifs->if_bridge) {
error = ESRCH;
break;
}
LIST_FOREACH(p, &sc->sc_iflist, next) {
if (p->ifp == ifs)
break;
}
if (p == LIST_END(&sc->sc_iflist)) {
error = ESRCH;
break;
}
req->ifbr_ifsflags = p->bif_flags;
req->ifbr_portno = p->ifp->if_index & 0xfff;
if (p->bif_flags & IFBIF_STP) {
bp = p->bif_stp;
req->ifbr_state = bstp_getstate(bs, bp);
req->ifbr_priority = bp->bp_priority;
req->ifbr_path_cost = bp->bp_path_cost;
req->ifbr_proto = bp->bp_protover;
req->ifbr_role = bp->bp_role;
req->ifbr_stpflags = bp->bp_flags;
req->ifbr_fwd_trans = bp->bp_forward_transitions;
req->ifbr_desg_bridge = bp->bp_desg_pv.pv_dbridge_id;
req->ifbr_desg_port = bp->bp_desg_pv.pv_dport_id;
req->ifbr_root_bridge = bp->bp_desg_pv.pv_root_id;
req->ifbr_root_cost = bp->bp_desg_pv.pv_cost;
req->ifbr_root_port = bp->bp_desg_pv.pv_port_id;
/* Copy STP state options as flags */
if (bp->bp_operedge)
req->ifbr_ifsflags |= IFBIF_BSTP_EDGE;
if (bp->bp_flags & BSTP_PORT_AUTOEDGE)
req->ifbr_ifsflags |= IFBIF_BSTP_AUTOEDGE;
if (bp->bp_ptp_link)
req->ifbr_ifsflags |= IFBIF_BSTP_PTP;
if (bp->bp_flags & BSTP_PORT_AUTOPTP)
req->ifbr_ifsflags |= IFBIF_BSTP_AUTOPTP;
}
break;
case SIOCBRDGSIFFLGS:
if ((error = suser(curproc, 0)) != 0)
break;
ifs = ifunit(req->ifbr_ifsname);
if (ifs == NULL) {
error = ENOENT;
break;
}
if ((caddr_t)sc != ifs->if_bridge) {
error = ESRCH;
break;
}
LIST_FOREACH(p, &sc->sc_iflist, next) {
if (p->ifp == ifs)
break;
}
if (p == LIST_END(&sc->sc_iflist)) {
error = ESRCH;
break;
}
if (req->ifbr_ifsflags & IFBIF_RO_MASK) {
error = EINVAL;
break;
}
if (req->ifbr_ifsflags & IFBIF_STP) {
if ((p->bif_flags & IFBIF_STP) == 0) {
/* Enable STP */
if ((p->bif_stp = bstp_add(sc->sc_stp,
p->ifp)) == NULL) {
error = ENOMEM;
break;
}
} else {
/* Update STP flags */
bstp_ifsflags(p->bif_stp, req->ifbr_ifsflags);
}
} else if (p->bif_flags & IFBIF_STP) {
bstp_delete(p->bif_stp);
p->bif_stp = NULL;
}
p->bif_flags = req->ifbr_ifsflags;
break;
case SIOCBRDGRTS:
error = bridge_rtfind(sc, (struct ifbaconf *)data);
break;
case SIOCBRDGFLUSH:
if ((error = suser(curproc, 0)) != 0)
break;
error = bridge_rtflush(sc, req->ifbr_ifsflags);
break;
case SIOCBRDGSADDR:
if ((error = suser(curproc, 0)) != 0)
break;
ifs = ifunit(bareq->ifba_ifsname);
if (ifs == NULL) { /* no such interface */
error = ENOENT;
break;
}
if (ifs->if_bridge == NULL ||
ifs->if_bridge != (caddr_t)sc) {
error = ESRCH;
break;
}
ifs = bridge_rtupdate(sc, &bareq->ifba_dst, ifs, 1,
bareq->ifba_flags);
if (ifs == NULL)
error = ENOMEM;
break;
case SIOCBRDGDADDR:
if ((error = suser(curproc, 0)) != 0)
break;
error = bridge_rtdaddr(sc, &bareq->ifba_dst);
break;
case SIOCBRDGGCACHE:
bparam->ifbrp_csize = sc->sc_brtmax;
break;
case SIOCBRDGSCACHE:
if ((error = suser(curproc, 0)) != 0)
break;
sc->sc_brtmax = bparam->ifbrp_csize;
bridge_rttrim(sc);
break;
case SIOCBRDGSTO:
if ((error = suser(curproc, 0)) != 0)
break;
if (bparam->ifbrp_ctime < 0 ||
bparam->ifbrp_ctime > INT_MAX / hz) {
error = EINVAL;
break;
}
sc->sc_brttimeout = bparam->ifbrp_ctime;
timeout_del(&sc->sc_brtimeout);
if (bparam->ifbrp_ctime != 0)
timeout_add(&sc->sc_brtimeout, sc->sc_brttimeout * hz);
break;
case SIOCBRDGGTO:
bparam->ifbrp_ctime = sc->sc_brttimeout;
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 SIOCBRDGARL:
if ((error = suser(curproc, 0)) != 0)
break;
ifs = ifunit(brlreq->ifbr_ifsname);
if (ifs == NULL) {
error = ENOENT;
break;
}
if (ifs->if_bridge == NULL ||
ifs->if_bridge != (caddr_t)sc) {
error = ESRCH;
break;
}
LIST_FOREACH(p, &sc->sc_iflist, next) {
if (p->ifp == ifs)
break;
}
if (p == LIST_END(&sc->sc_iflist)) {
error = ESRCH;
break;
}
if ((brlreq->ifbr_action != BRL_ACTION_BLOCK &&
brlreq->ifbr_action != BRL_ACTION_PASS) ||
(brlreq->ifbr_flags & (BRL_FLAG_IN|BRL_FLAG_OUT)) == 0) {
error = EINVAL;
break;
}
if (brlreq->ifbr_flags & BRL_FLAG_IN) {
error = bridge_addrule(p, brlreq, 0);
if (error)
break;
}
if (brlreq->ifbr_flags & BRL_FLAG_OUT) {
error = bridge_addrule(p, brlreq, 1);
if (error)
break;
}
break;
case SIOCBRDGFRL:
if ((error = suser(curproc, 0)) != 0)
break;
ifs = ifunit(brlreq->ifbr_ifsname);
if (ifs == NULL) {
error = ENOENT;
break;
}
if (ifs->if_bridge == NULL ||
ifs->if_bridge != (caddr_t)sc) {
error = ESRCH;
break;
}
LIST_FOREACH(p, &sc->sc_iflist, next) {
if (p->ifp == ifs)
break;
}
if (p == LIST_END(&sc->sc_iflist)) {
error = ESRCH;
break;
}
error = bridge_flushrule(p);
break;
case SIOCBRDGGRL:
error = bridge_brlconf(sc, (struct ifbrlconf *)data);
break;
case SIOCBRDGGPARAM:
if ((bp = bs->bs_root_port) == NULL)
brop->ifbop_root_port = 0;
else
brop->ifbop_root_port = bp->bp_ifp->if_index;
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 SIOCBRDGGPRI:
case SIOCBRDGGMA:
case SIOCBRDGGHT:
case SIOCBRDGGFD:
break;
case SIOCBRDGSPRI:
case SIOCBRDGSFD:
case SIOCBRDGSMA:
case SIOCBRDGSHT:
case SIOCBRDGSTXHC:
case SIOCBRDGSPROTO:
case SIOCBRDGSIFPRIO:
case SIOCBRDGSIFCOST:
error = suser(curproc, 0);
break;
default:
error = EINVAL;
break;
}
if (!error)
error = bstp_ioctl(ifp, cmd, data);
splx(s);
return (error);
}
/* Detach an interface from a bridge. */
void
bridge_ifdetach(struct ifnet *ifp)
{
struct bridge_softc *sc = (struct bridge_softc *)ifp->if_bridge;
struct bridge_iflist *bif;
LIST_FOREACH(bif, &sc->sc_iflist, next)
if (bif->ifp == ifp) {
LIST_REMOVE(bif, next);
bridge_rtdelete(sc, ifp, 0);
bridge_flushrule(bif);
free(bif, M_DEVBUF);
ifp->if_bridge = NULL;
break;
}
}
void
bridge_update(struct ifnet *ifp, struct ether_addr *ea, int delete)
{
struct bridge_softc *sc = (struct bridge_softc *)ifp->if_bridge;
struct bridge_iflist *bif;
u_int8_t *addr;
addr = (u_int8_t *)ea;
LIST_FOREACH(bif, &sc->sc_iflist, next)
if (bif->ifp == ifp) {
/*
* Update the bridge interface if it is in
* the learning state.
*/
if ((bif->bif_flags & IFBIF_LEARNING) &&
(ETHER_IS_MULTICAST(addr) == 0) &&
!(addr[0] == 0 && addr[1] == 0 && addr[2] == 0 &&
addr[3] == 0 && addr[4] == 0 && addr[5] == 0)) {
/* Care must be taken with spanning tree */
if ((bif->bif_flags & IFBIF_STP) &&
(bif->bif_state == BSTP_IFSTATE_DISCARDING))
return;
/* Delete the address from the bridge */
bridge_rtdaddr(sc, ea);
if (!delete) {
/* Update the bridge table */
bridge_rtupdate(sc, ea, ifp, 0,
IFBAF_DYNAMIC);
}
}
return;
}
}
int
bridge_bifconf(struct bridge_softc *sc, struct ifbifconf *bifc)
{
struct bridge_iflist *p;
struct bstp_port *bp;
struct bstp_state *bs = sc->sc_stp;
u_int32_t total = 0, i = 0;
int error = 0;
struct ifbreq *breq = NULL;
LIST_FOREACH(p, &sc->sc_iflist, next)
total++;
LIST_FOREACH(p, &sc->sc_spanlist, next)
total++;
if (bifc->ifbic_len == 0) {
i = total;
goto done;
}
if ((breq = (struct ifbreq *)
malloc(sizeof(*breq), M_DEVBUF, M_NOWAIT)) == NULL)
goto done;
LIST_FOREACH(p, &sc->sc_iflist, next) {
bzero(breq, sizeof(*breq));
if (bifc->ifbic_len < sizeof(*breq))
break;
strlcpy(breq->ifbr_name, sc->sc_if.if_xname, IFNAMSIZ);
strlcpy(breq->ifbr_ifsname, p->ifp->if_xname, IFNAMSIZ);
breq->ifbr_ifsflags = p->bif_flags;
breq->ifbr_portno = p->ifp->if_index & 0xfff;
if (p->bif_flags & IFBIF_STP) {
bp = p->bif_stp;
breq->ifbr_state = bstp_getstate(sc->sc_stp, 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;
}
error = copyout((caddr_t)breq,
(caddr_t)(bifc->ifbic_req + i), sizeof(*breq));
if (error)
goto done;
i++;
bifc->ifbic_len -= sizeof(*breq);
}
LIST_FOREACH(p, &sc->sc_spanlist, next) {
bzero(breq, sizeof(*breq));
if (bifc->ifbic_len < sizeof(*breq))
break;
strlcpy(breq->ifbr_name, sc->sc_if.if_xname, IFNAMSIZ);
strlcpy(breq->ifbr_ifsname, p->ifp->if_xname, IFNAMSIZ);
breq->ifbr_ifsflags = p->bif_flags | IFBIF_SPAN;
breq->ifbr_portno = p->ifp->if_index & 0xfff;
error = copyout((caddr_t)breq,
(caddr_t)(bifc->ifbic_req + i), sizeof(*breq));
if (error)
goto done;
i++;
bifc->ifbic_len -= sizeof(*breq);
}
done:
if (breq != NULL)
free(breq, M_DEVBUF);
bifc->ifbic_len = i * sizeof(*breq);
return (error);
}
int
bridge_brlconf(struct bridge_softc *sc, struct ifbrlconf *bc)
{
struct ifnet *ifp;
struct bridge_iflist *ifl;
struct brl_node *n;
struct ifbrlreq req;
int error = 0;
u_int32_t i = 0, total = 0;
ifp = ifunit(bc->ifbrl_ifsname);
if (ifp == NULL)
return (ENOENT);
if (ifp->if_bridge == NULL || ifp->if_bridge != (caddr_t)sc)
return (ESRCH);
LIST_FOREACH(ifl, &sc->sc_iflist, next) {
if (ifl->ifp == ifp)
break;
}
if (ifl == LIST_END(&sc->sc_iflist))
return (ESRCH);
SIMPLEQ_FOREACH(n, &ifl->bif_brlin, brl_next) {
total++;
}
SIMPLEQ_FOREACH(n, &ifl->bif_brlout, brl_next) {
total++;
}
if (bc->ifbrl_len == 0) {
i = total;
goto done;
}
SIMPLEQ_FOREACH(n, &ifl->bif_brlin, brl_next) {
bzero(&req, sizeof req);
if (bc->ifbrl_len < sizeof(req))
goto done;
strlcpy(req.ifbr_name, sc->sc_if.if_xname, IFNAMSIZ);
strlcpy(req.ifbr_ifsname, ifl->ifp->if_xname, IFNAMSIZ);
req.ifbr_action = n->brl_action;
req.ifbr_flags = n->brl_flags;
req.ifbr_src = n->brl_src;
req.ifbr_dst = n->brl_dst;
#if NPF > 0
req.ifbr_tagname[0] = '\0';
if (n->brl_tag)
pf_tag2tagname(n->brl_tag, req.ifbr_tagname);
#endif
error = copyout((caddr_t)&req,
(caddr_t)(bc->ifbrl_buf + (i * sizeof(req))), sizeof(req));
if (error)
goto done;
i++;
bc->ifbrl_len -= sizeof(req);
}
SIMPLEQ_FOREACH(n, &ifl->bif_brlout, brl_next) {
bzero(&req, sizeof req);
if (bc->ifbrl_len < sizeof(req))
goto done;
strlcpy(req.ifbr_name, sc->sc_if.if_xname, IFNAMSIZ);
strlcpy(req.ifbr_ifsname, ifl->ifp->if_xname, IFNAMSIZ);
req.ifbr_action = n->brl_action;
req.ifbr_flags = n->brl_flags;
req.ifbr_src = n->brl_src;
req.ifbr_dst = n->brl_dst;
#if NPF > 0
req.ifbr_tagname[0] = '\0';
if (n->brl_tag)
pf_tag2tagname(n->brl_tag, req.ifbr_tagname);
#endif
error = copyout((caddr_t)&req,
(caddr_t)(bc->ifbrl_buf + (i * sizeof(req))), sizeof(req));
if (error)
goto done;
i++;
bc->ifbrl_len -= sizeof(req);
}
done:
bc->ifbrl_len = i * sizeof(req);
return (error);
}
void
bridge_init(struct bridge_softc *sc)
{
struct ifnet *ifp = &sc->sc_if;
if ((ifp->if_flags & IFF_RUNNING) == IFF_RUNNING)
return;
ifp->if_flags |= IFF_RUNNING;
bstp_initialization(sc->sc_stp);
if (sc->sc_brttimeout != 0)
timeout_add(&sc->sc_brtimeout, sc->sc_brttimeout * hz);
}
/*
* Stop the bridge and deallocate the routing table.
*/
void
bridge_stop(struct bridge_softc *sc)
{
struct ifnet *ifp = &sc->sc_if;
/*
* If we're not running, there's nothing to do.
*/
if ((ifp->if_flags & IFF_RUNNING) == 0)
return;
timeout_del(&sc->sc_brtimeout);
bridge_rtflush(sc, IFBF_FLUSHDYN);
ifp->if_flags &= ~IFF_RUNNING;
}
/*
* Send output from the bridge. The mbuf has the ethernet header
* already attached. We must enqueue or free the mbuf before exiting.
*/
int
bridge_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *sa,
struct rtentry *rt)
{
struct ether_header *eh;
struct ifnet *dst_if;
struct ether_addr *src, *dst;
struct bridge_softc *sc;
int s, error, len;
#ifdef IPSEC
struct m_tag *mtag;
#endif /* IPSEC */
/* ifp must be a member interface of the bridge. */
sc = (struct bridge_softc *)ifp->if_bridge;
if (sc == NULL) {
m_freem(m);
return (EINVAL);
}
if (m->m_len < sizeof(*eh)) {
m = m_pullup(m, sizeof(*eh));
if (m == NULL)
return (ENOBUFS);
}
eh = mtod(m, struct ether_header *);
dst = (struct ether_addr *)&eh->ether_dhost[0];
src = (struct ether_addr *)&eh->ether_shost[0];
s = splnet();
/*
* If bridge is down, but original output interface is up,
* go ahead and send out that interface. Otherwise the packet
* is dropped below.
*/
if ((sc->sc_if.if_flags & IFF_RUNNING) == 0) {
dst_if = ifp;
goto sendunicast;
}
/*
* If the packet is a broadcast or we don't know a better way to
* get there, send to all interfaces.
*/
dst_if = bridge_rtlookup(sc, dst);
if (dst_if == NULL || ETHER_IS_MULTICAST(eh->ether_dhost)) {
struct bridge_iflist *p;
struct mbuf *mc;
int used = 0;
#ifdef IPSEC
/*
* Don't send out the packet if IPsec is needed, and
* notify IPsec to do its own crypto for now.
*/
if ((mtag = m_tag_find(m, PACKET_TAG_IPSEC_OUT_CRYPTO_NEEDED,
NULL)) != NULL) {
ipsp_skipcrypto_unmark((struct tdb_ident *)(mtag + 1));
m_freem(m);
splx(s);
return (0);
}
#endif /* IPSEC */
/* Catch packets that need TCP/UDP/IP hardware checksumming */
if (m->m_pkthdr.csum_flags & M_IPV4_CSUM_OUT ||
m->m_pkthdr.csum_flags & M_TCPV4_CSUM_OUT ||
m->m_pkthdr.csum_flags & M_UDPV4_CSUM_OUT) {
m_freem(m);
splx(s);
return (0);
}
bridge_span(sc, NULL, m);
LIST_FOREACH(p, &sc->sc_iflist, next) {
dst_if = p->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 &&
(p->bif_flags & IFBIF_STP) &&
(p->bif_state == BSTP_IFSTATE_DISCARDING))
continue;
if ((p->bif_flags & IFBIF_DISCOVER) == 0 &&
(m->m_flags & (M_BCAST | M_MCAST)) == 0)
continue;
#ifdef ALTQ
if (ALTQ_IS_ENABLED(&dst_if->if_snd) == 0)
#endif
if (IF_QFULL(&dst_if->if_snd)) {
IF_DROP(&dst_if->if_snd);
sc->sc_if.if_oerrors++;
continue;
}
if (LIST_NEXT(p, next) == LIST_END(&sc->sc_iflist)) {
used = 1;
mc = m;
} else {
struct mbuf *m1, *m2, *mx;
m1 = m_copym2(m, 0, ETHER_HDR_LEN,
M_DONTWAIT);
if (m1 == NULL) {
sc->sc_if.if_oerrors++;
continue;
}
m2 = m_copym2(m, ETHER_HDR_LEN,
M_COPYALL, M_DONTWAIT);
if (m2 == NULL) {
m_freem(m1);
sc->sc_if.if_oerrors++;
continue;
}
for (mx = m1; mx->m_next != NULL; mx = mx->m_next)
/*EMPTY*/;
mx->m_next = m2;
if (m1->m_flags & M_PKTHDR) {
len = 0;
for (mx = m1; mx != NULL; mx = mx->m_next)
len += mx->m_len;
m1->m_pkthdr.len = len;
}
mc = m1;
}
error = bridge_ifenqueue(sc, dst_if, mc);
if (error)
continue;
}
if (!used)
m_freem(m);
splx(s);
return (0);
}
sendunicast:
bridge_span(sc, NULL, m);
if ((dst_if->if_flags & IFF_RUNNING) == 0) {
m_freem(m);
splx(s);
return (ENETDOWN);
}
bridge_ifenqueue(sc, dst_if, m);
splx(s);
return (0);
}
/*
* Start output on the bridge. This function should never be called.
*/
void
bridge_start(struct ifnet *ifp)
{
}
/*
* Loop through each bridge interface and process their input queues.
*/
void
bridgeintr(void)
{
struct bridge_softc *sc;
struct mbuf *m;
int s;
LIST_FOREACH(sc, &bridge_list, sc_list) {
while (sc->sc_if.if_snd.ifq_head) {
s = splnet();
IF_DEQUEUE(&sc->sc_if.if_snd, m);
splx(s);
if (m == NULL)
break;
bridgeintr_frame(sc, m);
}
}
}
/*
* Process a single frame. Frame must be freed or queued before returning.
*/
void
bridgeintr_frame(struct bridge_softc *sc, struct mbuf *m)
{
int s, len;
struct ifnet *src_if, *dst_if;
struct bridge_iflist *ifl;
struct ether_addr *dst, *src;
struct ether_header eh;
if ((sc->sc_if.if_flags & IFF_RUNNING) == 0) {
m_freem(m);
return;
}
src_if = m->m_pkthdr.rcvif;
#if NBPFILTER > 0
if (sc->sc_if.if_bpf)
bpf_mtap(sc->sc_if.if_bpf, m, BPF_DIRECTION_IN);
#endif
sc->sc_if.if_ipackets++;
sc->sc_if.if_ibytes += m->m_pkthdr.len;
LIST_FOREACH(ifl, &sc->sc_iflist, next)
if (ifl->ifp == src_if)
break;
if (ifl == LIST_END(&sc->sc_iflist)) {
m_freem(m);
return;
}
if ((ifl->bif_flags & IFBIF_STP) &&
(ifl->bif_state == BSTP_IFSTATE_DISCARDING)) {
m_freem(m);
return;
}
if (m->m_pkthdr.len < sizeof(eh)) {
m_freem(m);
return;
}
m_copydata(m, 0, ETHER_HDR_LEN, (caddr_t)&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 ((ifl->bif_flags & IFBIF_LEARNING) &&
(eh.ether_shost[0] & 1) == 0 &&
!(eh.ether_shost[0] == 0 && eh.ether_shost[1] == 0 &&
eh.ether_shost[2] == 0 && eh.ether_shost[3] == 0 &&
eh.ether_shost[4] == 0 && eh.ether_shost[5] == 0))
bridge_rtupdate(sc, src, src_if, 0, IFBAF_DYNAMIC);
if ((ifl->bif_flags & IFBIF_STP) &&
(ifl->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 ((m->m_flags & (M_BCAST | M_MCAST)) == 0) {
dst_if = bridge_rtlookup(sc, dst);
if (dst_if == src_if) {
m_freem(m);
return;
}
} else
dst_if = NULL;
/*
* 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 (ifl->bif_flags & IFBIF_BLOCKNONIP && bridge_blocknonip(&eh, m)) {
m_freem(m);
return;
}
if (bridge_filterrule(&ifl->bif_brlin, &eh, m) == BRL_ACTION_BLOCK) {
m_freem(m);
return;
}
#if NPF > 0
m = bridge_filter(sc, BRIDGE_IN, src_if, &eh, m);
if (m == NULL)
return;
#endif
/*
* 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_if == NULL) {
sc->sc_if.if_imcasts++;
s = splnet();
bridge_broadcast(sc, src_if, &eh, m);
splx(s);
return;
}
/*
* At this point, we're dealing with a unicast frame going to a
* different interface
*/
if ((dst_if->if_flags & IFF_RUNNING) == 0) {
m_freem(m);
return;
}
LIST_FOREACH(ifl, &sc->sc_iflist, next) {
if (ifl->ifp == dst_if)
break;
}
if (ifl == LIST_END(&sc->sc_iflist)) {
m_freem(m);
return;
}
if ((ifl->bif_flags & IFBIF_STP) &&
(ifl->bif_state == BSTP_IFSTATE_DISCARDING)) {
m_freem(m);
return;
}
if (bridge_filterrule(&ifl->bif_brlout, &eh, m) == BRL_ACTION_BLOCK) {
m_freem(m);
return;
}
#if NPF > 0
m = bridge_filter(sc, BRIDGE_OUT, dst_if, &eh, m);
if (m == NULL)
return;
#endif
len = m->m_pkthdr.len;
if ((len - ETHER_HDR_LEN) > dst_if->if_mtu)
bridge_fragment(sc, dst_if, &eh, m);
else {
s = splnet();
bridge_ifenqueue(sc, dst_if, m);
splx(s);
}
}
/*
* 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 ether_header *eh, struct mbuf *m)
{
struct bridge_softc *sc;
int s;
struct bridge_iflist *ifl, *srcifl;
struct arpcom *ac;
struct mbuf *mc;
/*
* Make sure this interface is a bridge member.
*/
if (ifp == NULL || ifp->if_bridge == NULL || m == NULL)
return (m);
if ((m->m_flags & M_PKTHDR) == 0)
panic("bridge_input(): no HDR");
m->m_flags &= ~M_PROTO1; /* Loop prevention */
sc = (struct bridge_softc *)ifp->if_bridge;
if ((sc->sc_if.if_flags & IFF_RUNNING) == 0)
return (m);
LIST_FOREACH(ifl, &sc->sc_iflist, next) {
if (ifl->ifp == ifp)
break;
}
if (ifl == LIST_END(&sc->sc_iflist))
return (m);
bridge_span(sc, eh, m);
if (m->m_flags & (M_BCAST | M_MCAST)) {
/* Tap off 802.1D packets, they do not get forwarded */
if (bcmp(eh->ether_dhost, bstp_etheraddr, ETHER_ADDR_LEN) == 0) {
m = bstp_input(sc->sc_stp, ifl->bif_stp, eh, m);
if (m == NULL)
return (NULL);
}
/*
* No need to queue frames for ifs in the discarding state
*/
if ((ifl->bif_flags & IFBIF_STP) &&
(ifl->bif_state == BSTP_IFSTATE_DISCARDING))
return (m);
/*
* make a copy of 'm' with 'eh' tacked on to the
* beginning. Return 'm' for local processing
* and enqueue the copy. Schedule netisr.
*/
mc = m_copym2(m, 0, M_COPYALL, M_NOWAIT);
if (mc == NULL)
return (m);
M_PREPEND(mc, ETHER_HDR_LEN, M_DONTWAIT);
if (mc == NULL)
return (m);
bcopy(eh, mtod(mc, caddr_t), ETHER_HDR_LEN);
s = splnet();
if (IF_QFULL(&sc->sc_if.if_snd)) {
m_freem(mc);
splx(s);
return (m);
}
IF_ENQUEUE(&sc->sc_if.if_snd, mc);
splx(s);
schednetisr(NETISR_BRIDGE);
if (ifp->if_type == IFT_GIF) {
LIST_FOREACH(ifl, &sc->sc_iflist, next) {
if (ifl->ifp->if_type == IFT_ETHER)
break;
}
if (ifl != LIST_END(&sc->sc_iflist)) {
m->m_flags |= M_PROTO1;
m->m_pkthdr.rcvif = ifl->ifp;
ether_input(ifl->ifp, eh, m);
m = NULL;
}
}
return (m);
}
/*
* No need to queue frames for ifs in the discarding state
*/
if ((ifl->bif_flags & IFBIF_STP) &&
(ifl->bif_state == BSTP_IFSTATE_DISCARDING))
return (m);
/*
* Unicast, make sure it's not for us.
*/
srcifl = ifl;
LIST_FOREACH(ifl, &sc->sc_iflist, next) {
if (ifl->ifp->if_type != IFT_ETHER)
continue;
ac = (struct arpcom *)ifl->ifp;
if (bcmp(ac->ac_enaddr, eh->ether_dhost, ETHER_ADDR_LEN) == 0
#if NCARP > 0
|| (ifl->ifp->if_carp && carp_ourether(ifl->ifp->if_carp,
eh, IFT_ETHER, 0) != NULL)
#endif
) {
if (srcifl->bif_flags & IFBIF_LEARNING)
bridge_rtupdate(sc,
(struct ether_addr *)&eh->ether_shost,
ifp, 0, IFBAF_DYNAMIC);
if (bridge_filterrule(&srcifl->bif_brlin, eh, m) ==
BRL_ACTION_BLOCK) {
m_freem(m);
return (NULL);
}
m->m_pkthdr.rcvif = ifl->ifp;
if (ifp->if_type == IFT_GIF) {
m->m_flags |= M_PROTO1;
ether_input(ifl->ifp, eh, m);
m = NULL;
}
return (m);
}
if (bcmp(ac->ac_enaddr, eh->ether_shost, ETHER_ADDR_LEN) == 0
#if NCARP > 0
|| (ifl->ifp->if_carp && carp_ourether(ifl->ifp->if_carp,
eh, IFT_ETHER, 1) != NULL)
#endif
) {
m_freem(m);
return (NULL);
}
}
M_PREPEND(m, ETHER_HDR_LEN, M_DONTWAIT);
if (m == NULL)
return (NULL);
bcopy(eh, mtod(m, caddr_t), ETHER_HDR_LEN);
s = splnet();
if (IF_QFULL(&sc->sc_if.if_snd)) {
m_freem(m);
splx(s);
return (NULL);
}
IF_ENQUEUE(&sc->sc_if.if_snd, m);
splx(s);
schednetisr(NETISR_BRIDGE);
return (NULL);
}
/*
* Send a frame to all interfaces that are members of the bridge
* (except the one it came in on). This code assumes that it is
* running at splnet or higher.
*/
void
bridge_broadcast(struct bridge_softc *sc, struct ifnet *ifp,
struct ether_header *eh, struct mbuf *m)
{
struct bridge_iflist *p;
struct mbuf *mc;
struct ifnet *dst_if;
int len = m->m_pkthdr.len, used = 0;
splassert(IPL_NET);
LIST_FOREACH(p, &sc->sc_iflist, next) {
/*
* Don't retransmit out of the same interface where
* the packet was received from.
*/
dst_if = p->ifp;
if (dst_if->if_index == ifp->if_index)
continue;
if ((p->bif_flags & IFBIF_STP) &&
(p->bif_state == BSTP_IFSTATE_DISCARDING))
continue;
if ((p->bif_flags & IFBIF_DISCOVER) == 0 &&
(m->m_flags & (M_BCAST | M_MCAST)) == 0)
continue;
if ((dst_if->if_flags & IFF_RUNNING) == 0)
continue;
#ifdef ALTQ
if (ALTQ_IS_ENABLED(&dst_if->if_snd) == 0)
#endif
if (IF_QFULL(&dst_if->if_snd)) {
IF_DROP(&dst_if->if_snd);
sc->sc_if.if_oerrors++;
continue;
}
/* Drop non-IP frames if the appropriate flag is set. */
if (p->bif_flags & IFBIF_BLOCKNONIP &&
bridge_blocknonip(eh, m))
continue;
if (bridge_filterrule(&p->bif_brlout, eh, m) == BRL_ACTION_BLOCK)
continue;
/* If last one, reuse the passed-in mbuf */
if (LIST_NEXT(p, next) == LIST_END(&sc->sc_iflist)) {
mc = m;
used = 1;
} else {
struct mbuf *m1, *m2, *mx;
m1 = m_copym2(m, 0, ETHER_HDR_LEN,
M_DONTWAIT);
if (m1 == NULL) {
sc->sc_if.if_oerrors++;
continue;
}
m2 = m_copym2(m, ETHER_HDR_LEN,
M_COPYALL, M_DONTWAIT);
if (m2 == NULL) {
m_freem(m1);
sc->sc_if.if_oerrors++;
continue;
}
for (mx = m1; mx->m_next != NULL; mx = mx->m_next)
/*EMPTY*/;
mx->m_next = m2;
if (m1->m_flags & M_PKTHDR) {
int len = 0;
for (mx = m1; mx != NULL; mx = mx->m_next)
len += mx->m_len;
m1->m_pkthdr.len = len;
}
mc = m1;
}
#if NPF > 0
mc = bridge_filter(sc, BRIDGE_OUT, dst_if, eh, mc);
if (mc == NULL)
continue;
#endif
if ((len - ETHER_HDR_LEN) > dst_if->if_mtu)
bridge_fragment(sc, dst_if, eh, mc);
else {
bridge_ifenqueue(sc, dst_if, mc);
}
}
if (!used)
m_freem(m);
}
void
bridge_span(struct bridge_softc *sc, struct ether_header *eh,
struct mbuf *morig)
{
struct bridge_iflist *p;
struct ifnet *ifp;
struct mbuf *mc, *m;
int error;
if (LIST_EMPTY(&sc->sc_spanlist))
return;
m = m_copym2(morig, 0, M_COPYALL, M_NOWAIT);
if (m == NULL)
return;
if (eh != NULL) {
M_PREPEND(m, ETHER_HDR_LEN, M_DONTWAIT);
if (m == NULL)
return;
bcopy(eh, mtod(m, caddr_t), ETHER_HDR_LEN);
}
LIST_FOREACH(p, &sc->sc_spanlist, next) {
ifp = p->ifp;
if ((ifp->if_flags & IFF_RUNNING) == 0)
continue;
#ifdef ALTQ
if (ALTQ_IS_ENABLED(&ifp->if_snd) == 0)
#endif
if (IF_QFULL(&ifp->if_snd)) {
IF_DROP(&ifp->if_snd);
sc->sc_if.if_oerrors++;
continue;
}
mc = m_copym(m, 0, M_COPYALL, M_DONTWAIT);
if (mc == NULL) {
sc->sc_if.if_oerrors++;
continue;
}
error = bridge_ifenqueue(sc, ifp, mc);
if (error)
continue;
}
m_freem(m);
}
struct ifnet *
bridge_rtupdate(struct bridge_softc *sc, struct ether_addr *ea,
struct ifnet *ifp, int setflags, u_int8_t flags)
{
struct bridge_rtnode *p, *q;
u_int32_t h;
int dir;
h = bridge_hash(sc, ea);
p = LIST_FIRST(&sc->sc_rts[h]);
if (p == LIST_END(&sc->sc_rts[h])) {
if (sc->sc_brtcnt >= sc->sc_brtmax)
goto done;
p = (struct bridge_rtnode *)malloc(
sizeof(struct bridge_rtnode), M_DEVBUF, M_NOWAIT);
if (p == NULL)
goto done;
bcopy(ea, &p->brt_addr, sizeof(p->brt_addr));
p->brt_if = ifp;
p->brt_age = 1;
if (setflags)
p->brt_flags = flags;
else
p->brt_flags = IFBAF_DYNAMIC;
LIST_INSERT_HEAD(&sc->sc_rts[h], p, brt_next);
sc->sc_brtcnt++;
goto want;
}
do {
q = p;
p = LIST_NEXT(p, brt_next);
dir = memcmp(ea, &q->brt_addr, sizeof(q->brt_addr));
if (dir == 0) {
if (setflags) {
q->brt_if = ifp;
q->brt_flags = flags;
} else if (!(q->brt_flags & IFBAF_STATIC))
q->brt_if = ifp;
if (q->brt_if == ifp)
q->brt_age = 1;
ifp = q->brt_if;
goto want;
}
if (dir > 0) {
if (sc->sc_brtcnt >= sc->sc_brtmax)
goto done;
p = (struct bridge_rtnode *)malloc(
sizeof(struct bridge_rtnode), M_DEVBUF, M_NOWAIT);
if (p == NULL)
goto done;
bcopy(ea, &p->brt_addr, sizeof(p->brt_addr));
p->brt_if = ifp;
p->brt_age = 1;
if (setflags)
p->brt_flags = flags;
else
p->brt_flags = IFBAF_DYNAMIC;
LIST_INSERT_BEFORE(q, p, brt_next);
sc->sc_brtcnt++;
goto want;
}
if (p == LIST_END(&sc->sc_rts[h])) {
if (sc->sc_brtcnt >= sc->sc_brtmax)
goto done;
p = (struct bridge_rtnode *)malloc(
sizeof(struct bridge_rtnode), M_DEVBUF, M_NOWAIT);
if (p == NULL)
goto done;
bcopy(ea, &p->brt_addr, sizeof(p->brt_addr));
p->brt_if = ifp;
p->brt_age = 1;
if (setflags)
p->brt_flags = flags;
else
p->brt_flags = IFBAF_DYNAMIC;
LIST_INSERT_AFTER(q, p, brt_next);
sc->sc_brtcnt++;
goto want;
}
} while (p != LIST_END(&sc->sc_rts[h]));
done:
ifp = NULL;
want:
return (ifp);
}
struct ifnet *
bridge_rtlookup(struct bridge_softc *sc, struct ether_addr *ea)
{
struct bridge_rtnode *p;
u_int32_t h;
int dir;
h = bridge_hash(sc, ea);
LIST_FOREACH(p, &sc->sc_rts[h], brt_next) {
dir = memcmp(ea, &p->brt_addr, sizeof(p->brt_addr));
if (dir == 0)
return (p->brt_if);
if (dir > 0)
goto fail;
}
fail:
return (NULL);
}
/*
* The following hash function is adapted from 'Hash Functions' by Bob Jenkins
* ("Algorithm Alley", Dr. Dobbs Journal, September 1997).
* "You may use this code any way you wish, private, educational, or
* commercial. It's free."
*/
#define mix(a,b,c) \
do { \
a -= b; a -= c; a ^= (c >> 13); \
b -= c; b -= a; b ^= (a << 8); \
c -= a; c -= b; c ^= (b >> 13); \
a -= b; a -= c; a ^= (c >> 12); \
b -= c; b -= a; b ^= (a << 16); \
c -= a; c -= b; c ^= (b >> 5); \
a -= b; a -= c; a ^= (c >> 3); \
b -= c; b -= a; b ^= (a << 10); \
c -= a; c -= b; c ^= (b >> 15); \
} while (0)
u_int32_t
bridge_hash(struct bridge_softc *sc, struct ether_addr *addr)
{
u_int32_t a = 0x9e3779b9, b = 0x9e3779b9, c = sc->sc_hashkey;
b += addr->ether_addr_octet[5] << 8;
b += addr->ether_addr_octet[4];
a += addr->ether_addr_octet[3] << 24;
a += addr->ether_addr_octet[2] << 16;
a += addr->ether_addr_octet[1] << 8;
a += addr->ether_addr_octet[0];
mix(a, b, c);
return (c & BRIDGE_RTABLE_MASK);
}
/*
* Trim the routing table so that we've got a number of routes
* less than or equal to the maximum.
*/
void
bridge_rttrim(struct bridge_softc *sc)
{
struct bridge_rtnode *n, *p;
int i;
/*
* Make sure we have to trim the address table
*/
if (sc->sc_brtcnt <= sc->sc_brtmax)
return;
/*
* Force an aging cycle, this might trim enough addresses.
*/
bridge_rtage(sc);
if (sc->sc_brtcnt <= sc->sc_brtmax)
return;
for (i = 0; i < BRIDGE_RTABLE_SIZE; i++) {
n = LIST_FIRST(&sc->sc_rts[i]);
while (n != LIST_END(&sc->sc_rts[i])) {
p = LIST_NEXT(n, brt_next);
if ((n->brt_flags & IFBAF_TYPEMASK) == IFBAF_DYNAMIC) {
LIST_REMOVE(n, brt_next);
sc->sc_brtcnt--;
free(n, M_DEVBUF);
n = p;
if (sc->sc_brtcnt <= sc->sc_brtmax)
return;
}
}
}
}
void
bridge_timer(void *vsc)
{
struct bridge_softc *sc = vsc;
int s;
s = splsoftnet();
bridge_rtage(sc);
splx(s);
}
/*
* Perform an aging cycle
*/
void
bridge_rtage(struct bridge_softc *sc)
{
struct bridge_rtnode *n, *p;
int i;
for (i = 0; i < BRIDGE_RTABLE_SIZE; i++) {
n = LIST_FIRST(&sc->sc_rts[i]);
while (n != LIST_END(&sc->sc_rts[i])) {
if ((n->brt_flags & IFBAF_TYPEMASK) == IFBAF_STATIC) {
n->brt_age = !n->brt_age;
if (n->brt_age)
n->brt_age = 0;
n = LIST_NEXT(n, brt_next);
} else if (n->brt_age) {
n->brt_age = 0;
n = LIST_NEXT(n, brt_next);
} else {
p = LIST_NEXT(n, brt_next);
LIST_REMOVE(n, brt_next);
sc->sc_brtcnt--;
free(n, M_DEVBUF);
n = p;
}
}
}
if (sc->sc_brttimeout != 0)
timeout_add(&sc->sc_brtimeout, sc->sc_brttimeout * hz);
}
void
bridge_rtagenode(struct ifnet *ifp, int age)
{
struct bridge_softc *sc = (struct bridge_softc *)ifp->if_bridge;
struct bridge_rtnode *n;
int i;
if (sc == NULL)
return;
/*
* If the age is zero then flush, otherwise set all the expiry times to
* age for the interface
*/
if (age == 0)
bridge_rtdelete(sc, ifp, 1);
else {
for (i = 0; i < BRIDGE_RTABLE_SIZE; i++) {
n = LIST_FIRST(&sc->sc_rts[i]);
while (n != LIST_END(&sc->sc_rts[i])) {
/* Cap the expiry time to 'age' */
if (n->brt_if == ifp &&
n->brt_age > time_uptime + age &&
(n->brt_flags & IFBAF_TYPEMASK) == IFBAF_DYNAMIC)
n->brt_age = time_uptime + age;
}
}
}
}
/*
* Remove all dynamic addresses from the cache
*/
int
bridge_rtflush(struct bridge_softc *sc, int full)
{
int i;
struct bridge_rtnode *p, *n;
for (i = 0; i < BRIDGE_RTABLE_SIZE; i++) {
n = LIST_FIRST(&sc->sc_rts[i]);
while (n != LIST_END(&sc->sc_rts[i])) {
if (full ||
(n->brt_flags & IFBAF_TYPEMASK) == IFBAF_DYNAMIC) {
p = LIST_NEXT(n, brt_next);
LIST_REMOVE(n, brt_next);
sc->sc_brtcnt--;
free(n, M_DEVBUF);
n = p;
} else
n = LIST_NEXT(n, brt_next);
}
}
return (0);
}
/*
* Remove an address from the cache
*/
int
bridge_rtdaddr(struct bridge_softc *sc, struct ether_addr *ea)
{
int h;
struct bridge_rtnode *p;
h = bridge_hash(sc, ea);
LIST_FOREACH(p, &sc->sc_rts[h], brt_next) {
if (bcmp(ea, &p->brt_addr, sizeof(p->brt_addr)) == 0) {
LIST_REMOVE(p, brt_next);
sc->sc_brtcnt--;
free(p, M_DEVBUF);
return (0);
}
}
return (ENOENT);
}
/*
* Delete routes to a specific interface member.
*/
void
bridge_rtdelete(struct bridge_softc *sc, struct ifnet *ifp, int dynonly)
{
int i;
struct bridge_rtnode *n, *p;
/*
* Loop through all of the hash buckets and traverse each
* chain looking for routes to this interface.
*/
for (i = 0; i < BRIDGE_RTABLE_SIZE; i++) {
n = LIST_FIRST(&sc->sc_rts[i]);
while (n != LIST_END(&sc->sc_rts[i])) {
if (n->brt_if != ifp) {
/* Not ours */
n = LIST_NEXT(n, brt_next);
continue;
}
if (dynonly &&
(n->brt_flags & IFBAF_TYPEMASK) != IFBAF_DYNAMIC) {
/* only deleting dynamics */
n = LIST_NEXT(n, brt_next);
continue;
}
p = LIST_NEXT(n, brt_next);
LIST_REMOVE(n, brt_next);
sc->sc_brtcnt--;
free(n, M_DEVBUF);
n = p;
}
}
}
/*
* Gather all of the routes for this interface.
*/
int
bridge_rtfind(struct bridge_softc *sc, struct ifbaconf *baconf)
{
int i, error = 0, onlycnt = 0;
u_int32_t cnt = 0;
struct bridge_rtnode *n;
struct ifbareq bareq;
if (baconf->ifbac_len == 0)
onlycnt = 1;
for (i = 0, cnt = 0; i < BRIDGE_RTABLE_SIZE; i++) {
LIST_FOREACH(n, &sc->sc_rts[i], brt_next) {
if (!onlycnt) {
if (baconf->ifbac_len < sizeof(struct ifbareq))
goto done;
bcopy(sc->sc_if.if_xname, bareq.ifba_name,
sizeof(bareq.ifba_name));
bcopy(n->brt_if->if_xname, bareq.ifba_ifsname,
sizeof(bareq.ifba_ifsname));
bcopy(&n->brt_addr, &bareq.ifba_dst,
sizeof(bareq.ifba_dst));
bareq.ifba_age = n->brt_age;
bareq.ifba_flags = n->brt_flags;
error = copyout((caddr_t)&bareq,
(caddr_t)(baconf->ifbac_req + cnt), sizeof(bareq));
if (error)
goto done;
baconf->ifbac_len -= sizeof(struct ifbareq);
}
cnt++;
}
}
done:
baconf->ifbac_len = cnt * sizeof(struct ifbareq);
return (error);
}
/*
* 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);
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,
(caddr_t)&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);
}
u_int8_t
bridge_filterrule(struct brl_head *h, struct ether_header *eh, struct mbuf *m)
{
struct brl_node *n;
u_int8_t flags;
SIMPLEQ_FOREACH(n, h, brl_next) {
flags = n->brl_flags & (BRL_FLAG_SRCVALID|BRL_FLAG_DSTVALID);
if (flags == 0)
goto return_action;
if (flags == (BRL_FLAG_SRCVALID|BRL_FLAG_DSTVALID)) {
if (bcmp(eh->ether_shost, &n->brl_src, ETHER_ADDR_LEN))
continue;
if (bcmp(eh->ether_dhost, &n->brl_dst, ETHER_ADDR_LEN))
continue;
goto return_action;
}
if (flags == BRL_FLAG_SRCVALID) {
if (bcmp(eh->ether_shost, &n->brl_src, ETHER_ADDR_LEN))
continue;
goto return_action;
}
if (flags == BRL_FLAG_DSTVALID) {
if (bcmp(eh->ether_dhost, &n->brl_dst, ETHER_ADDR_LEN))
continue;
goto return_action;
}
}
return (BRL_ACTION_PASS);
return_action:
#if NPF > 0
pf_tag_packet(m, n->brl_tag, -1);
#endif
return (n->brl_action);
}
int
bridge_addrule(struct bridge_iflist *bif, struct ifbrlreq *req, int out)
{
struct brl_node *n;
n = (struct brl_node *)malloc(sizeof(struct brl_node), M_DEVBUF, M_NOWAIT);
if (n == NULL)
return (ENOMEM);
bcopy(&req->ifbr_src, &n->brl_src, sizeof(struct ether_addr));
bcopy(&req->ifbr_dst, &n->brl_dst, sizeof(struct ether_addr));
n->brl_action = req->ifbr_action;
n->brl_flags = req->ifbr_flags;
#if NPF > 0
if (req->ifbr_tagname[0])
n->brl_tag = pf_tagname2tag(req->ifbr_tagname);
else
n->brl_tag = 0;
#endif
if (out) {
n->brl_flags &= ~BRL_FLAG_IN;
n->brl_flags |= BRL_FLAG_OUT;
SIMPLEQ_INSERT_TAIL(&bif->bif_brlout, n, brl_next);
} else {
n->brl_flags &= ~BRL_FLAG_OUT;
n->brl_flags |= BRL_FLAG_IN;
SIMPLEQ_INSERT_TAIL(&bif->bif_brlin, n, brl_next);
}
return (0);
}
int
bridge_flushrule(struct bridge_iflist *bif)
{
struct brl_node *p;
while (!SIMPLEQ_EMPTY(&bif->bif_brlin)) {
p = SIMPLEQ_FIRST(&bif->bif_brlin);
SIMPLEQ_REMOVE_HEAD(&bif->bif_brlin, brl_next);
#if NPF > 0
pf_tag_unref(p->brl_tag);
#endif
free(p, M_DEVBUF);
}
while (!SIMPLEQ_EMPTY(&bif->bif_brlout)) {
p = SIMPLEQ_FIRST(&bif->bif_brlout);
SIMPLEQ_REMOVE_HEAD(&bif->bif_brlout, brl_next);
#if NPF > 0
pf_tag_unref(p->brl_tag);
#endif
free(p, M_DEVBUF);
}
return (0);
}
#ifdef IPSEC
int
bridge_ipsec(struct bridge_softc *sc, 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 timeval tv;
struct tdb *tdb;
u_int32_t spi;
u_int16_t cpi;
int error, off, s;
u_int8_t proto = 0;
#ifdef INET
struct ip *ip;
#endif /* INET */
#ifdef INET6
struct ip6_hdr *ip6;
#endif /* INET6 */
if (dir == BRIDGE_IN) {
switch (af) {
#ifdef INET
case AF_INET:
if (m->m_pkthdr.len - hlen < 2 * sizeof(u_int32_t))
break;
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),
(caddr_t)&dst.sin.sin_addr);
if (ip->ip_p == IPPROTO_ESP)
m_copydata(m, hlen, sizeof(u_int32_t),
(caddr_t)&spi);
else if (ip->ip_p == IPPROTO_AH)
m_copydata(m, hlen + sizeof(u_int32_t),
sizeof(u_int32_t), (caddr_t)&spi);
else if (ip->ip_p == IPPROTO_IPCOMP) {
m_copydata(m, hlen + sizeof(u_int16_t),
sizeof(u_int16_t), (caddr_t)&cpi);
spi = ntohl(htons(cpi));
}
break;
#endif /* INET */
#ifdef INET6
case AF_INET6:
if (m->m_pkthdr.len - hlen < 2 * sizeof(u_int32_t))
break;
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_nxt),
sizeof(struct in6_addr),
(caddr_t)&dst.sin6.sin6_addr);
if (proto == IPPROTO_ESP)
m_copydata(m, hlen, sizeof(u_int32_t),
(caddr_t)&spi);
else if (proto == IPPROTO_AH)
m_copydata(m, hlen + sizeof(u_int32_t),
sizeof(u_int32_t), (caddr_t)&spi);
else if (proto == IPPROTO_IPCOMP) {
m_copydata(m, hlen + sizeof(u_int16_t),
sizeof(u_int16_t), (caddr_t)&cpi);
spi = ntohl(htons(cpi));
}
break;
#endif /* INET6 */
default:
return (0);
}
if (proto == 0)
goto skiplookup;
s = spltdb();
tdb = gettdb(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 = time_second;
tv.tv_usec = 0;
/* Check for wrap-around. */
if (tdb->tdb_exp_first_use + tdb->tdb_first_use
< tdb->tdb_first_use)
tv.tv_sec = ((unsigned long)-1) / 2;
else
tv.tv_sec = tdb->tdb_exp_first_use +
tdb->tdb_first_use;
if (tdb->tdb_flags & TDBF_FIRSTUSE)
timeout_add(&tdb->tdb_first_tmo,
hzto(&tv));
/* Check for wrap-around. */
if (tdb->tdb_first_use +
tdb->tdb_soft_first_use
< tdb->tdb_first_use)
tv.tv_sec = ((unsigned long)-1) / 2;
else
tv.tv_sec = tdb->tdb_first_use +
tdb->tdb_soft_first_use;
if (tdb->tdb_flags & TDBF_SOFT_FIRSTUSE)
timeout_add(&tdb->tdb_sfirst_tmo,
hzto(&tv));
}
(*(tdb->tdb_xform->xf_input))(m, tdb, hlen, off);
splx(s);
return (1);
} else {
splx(s);
skiplookup:
/* XXX do an input policy lookup */
return (0);
}
} else { /* Outgoing from the bridge. */
tdb = ipsp_spd_lookup(m, af, hlen, &error,
IPSP_DIRECTION_OUT, NULL, NULL);
if (tdb != NULL) {
/*
* We don't need to do loop detection, the
* bridge will do that for us.
*/
#if NPF > 0
switch (af) {
#ifdef INET
case AF_INET:
if (pf_test(dir, &encif[0].sc_if,
&m, NULL) != PF_PASS) {
m_freem(m);
return (1);
}
break;
#endif /* INET */
#ifdef INET6
case AF_INET6:
if (pf_test6(dir, &encif[0].sc_if,
&m, NULL) != PF_PASS) {
m_freem(m);
return (1);
}
break;
#endif /* INET6 */
}
if (m == NULL)
return (1);
#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 > time_second)
bridge_send_icmp_err(sc, ifp, eh, m,
hassnap, llc, tdb->tdb_mtu,
ICMP_UNREACH, ICMP_UNREACH_NEEDFRAG);
else
error = ipsp_process_packet(m, tdb, af, 0);
return (1);
} else
return (0);
}
return (0);
}
#endif /* IPSEC */
#if NPF > 0
/*
* 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_filter(struct bridge_softc *sc, 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;
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, (caddr_t)&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:
if (m->m_pkthdr.len < sizeof(struct ip))
goto dropit;
/* Copy minimal header, and drop invalids */
if (m->m_len < sizeof(struct ip) &&
(m = m_pullup(m, sizeof(struct ip))) == NULL) {
ipstat.ips_toosmall++;
return (NULL);
}
ip = mtod(m, struct ip *);
if (ip->ip_v != IPVERSION) {
ipstat.ips_badvers++;
goto dropit;
}
hlen = ip->ip_hl << 2; /* get whole header length */
if (hlen < sizeof(struct ip)) {
ipstat.ips_badhlen++;
goto dropit;
}
if (hlen > m->m_len) {
if ((m = m_pullup(m, hlen)) == NULL) {
ipstat.ips_badhlen++;
return (NULL);
}
ip = mtod(m, struct ip *);
}
if ((ip->ip_sum = in_cksum(m, hlen)) != 0) {
ipstat.ips_badsum++;
goto dropit;
}
if (ntohs(ip->ip_len) < hlen)
goto dropit;
if (m->m_pkthdr.len < ntohs(ip->ip_len))
goto dropit;
if (m->m_pkthdr.len > ntohs(ip->ip_len)) {
if (m->m_len == m->m_pkthdr.len) {
m->m_len = ntohs(ip->ip_len);
m->m_pkthdr.len = ntohs(ip->ip_len);
} else
m_adj(m, ntohs(ip->ip_len) - m->m_pkthdr.len);
}
#ifdef IPSEC
if ((sc->sc_if.if_flags & IFF_LINK2) == IFF_LINK2 &&
bridge_ipsec(sc, ifp, eh, hassnap, &llc,
dir, AF_INET, hlen, m))
return (NULL);
#endif /* IPSEC */
/* Finally, we get to filter the packet! */
m->m_pkthdr.rcvif = ifp;
if (pf_test(dir, ifp, &m, eh) != PF_PASS)
goto dropit;
if (m == NULL)
goto dropit;
/* 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;
ip = mtod(m, struct ip *);
ip->ip_sum = 0;
ip->ip_sum = in_cksum(m, hlen);
break;
#ifdef INET6
case ETHERTYPE_IPV6: {
struct ip6_hdr *ip6;
if (m->m_len < sizeof(struct ip6_hdr)) {
if ((m = m_pullup(m, sizeof(struct ip6_hdr)))
== NULL) {
ip6stat.ip6s_toosmall++;
return (NULL);
}
}
ip6 = mtod(m, struct ip6_hdr *);
if ((ip6->ip6_vfc & IPV6_VERSION_MASK) != IPV6_VERSION) {
ip6stat.ip6s_badvers++;
in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_hdrerr);
goto dropit;
}
#ifdef IPSEC
hlen = sizeof(struct ip6_hdr);
if ((sc->sc_if.if_flags & IFF_LINK2) == IFF_LINK2 &&
bridge_ipsec(sc, ifp, eh, hassnap, &llc,
dir, AF_INET6, hlen, m))
return (NULL);
#endif /* IPSEC */
if (pf_test6(dir, ifp, &m, eh) != PF_PASS)
goto dropit;
if (m == NULL)
return (NULL);
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:
if (m != NULL)
m_freem(m);
return (NULL);
}
#endif /* NPF > 0 */
void
bridge_fragment(struct bridge_softc *sc, struct ifnet *ifp,
struct ether_header *eh, struct mbuf *m)
{
struct llc llc;
struct mbuf *m0;
int s, len, error = 0;
int hassnap = 0;
#ifdef INET
u_int16_t etype;
struct ip *ip;
#endif
#ifndef INET
goto dropit;
#else
etype = ntohs(eh->ether_type);
if (etype == ETHERTYPE_VLAN &&
(ifp->if_capabilities & IFCAP_VLAN_MTU) &&
((m->m_pkthdr.len - sizeof(struct ether_vlan_header)) <=
ifp->if_mtu)) {
s = splnet();
bridge_ifenqueue(sc, ifp, m);
splx(s);
return;
}
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, (caddr_t)&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(sc, ifp, eh, m, hassnap, &llc,
ifp->if_mtu, ICMP_UNREACH, ICMP_UNREACH_NEEDFRAG);
return;
}
error = ip_fragment(m, ifp, ifp->if_mtu);
if (error) {
m = NULL;
goto dropit;
}
for (; m; m = m0) {
m0 = m->m_nextpkt;
m->m_nextpkt = NULL;
if (error == 0) {
if (hassnap) {
M_PREPEND(m, LLC_SNAPFRAMELEN, M_DONTWAIT);
if (m == NULL) {
error = ENOBUFS;
continue;
}
bcopy(&llc, mtod(m, caddr_t),
LLC_SNAPFRAMELEN);
}
M_PREPEND(m, sizeof(*eh), M_DONTWAIT);
if (m == NULL) {
error = ENOBUFS;
continue;
}
len = m->m_pkthdr.len;
bcopy(eh, mtod(m, caddr_t), sizeof(*eh));
s = splnet();
error = bridge_ifenqueue(sc, ifp, m);
if (error) {
splx(s);
continue;
}
splx(s);
} else
m_freem(m);
}
if (error == 0)
ipstat.ips_fragmented++;
return;
#endif /* INET */
dropit:
if (m != NULL)
m_freem(m);
}
int
bridge_ifenqueue(struct bridge_softc *sc, struct ifnet *ifp, struct mbuf *m)
{
int error, len;
short mflags;
#if NGIF > 0
/* Packet needs etherip encapsulation. */
if (ifp->if_type == IFT_GIF)
m->m_flags |= M_PROTO1;
#endif
len = m->m_pkthdr.len;
mflags = m->m_flags;
IFQ_ENQUEUE(&ifp->if_snd, m, NULL, error);
if (error) {
sc->sc_if.if_oerrors++;
return (error);
}
sc->sc_if.if_opackets++;
sc->sc_if.if_obytes += len;
ifp->if_obytes += len;
if (mflags & M_MCAST)
ifp->if_omcasts++;
if ((ifp->if_flags & IFF_OACTIVE) == 0)
(*ifp->if_start)(ifp);
return (0);
}
#ifdef INET
void
bridge_send_icmp_err(struct bridge_softc *sc, 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;
ip->ip_sum = 0;
ip->ip_sum = in_cksum(m, hlen);
/* 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_output(ifp, m, NULL, NULL);
m_freem(n);
return;
dropit:
m_freem(n);
}
#endif