/* $OpenBSD: if_bpe.c,v 1.13 2020/07/22 08:38:51 dlg Exp $ */
/*
* Copyright (c) 2018 David Gwynne <dlg@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "bpfilter.h"
#include "pf.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/timeout.h>
#include <sys/pool.h>
#include <sys/tree.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <net/if_types.h>
#include <net/rtable.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
/* for bridge stuff */
#include <net/if_bridge.h>
#if NBPFILTER > 0
#include <net/bpf.h>
#endif
#include <net/if_bpe.h>
#define PBB_ITAG_ISID 0x00ffffff
#define PBB_ITAG_ISID_MIN 0x00000000
#define PBB_ITAG_ISID_MAX 0x00ffffff
#define PBB_ITAG_RES2 0x03000000 /* must be zero on input */
#define PBB_ITAG_RES1 0x04000000 /* ignore on input */
#define PBB_ITAG_UCA 0x08000000
#define PBB_ITAG_DEI 0x10000000
#define PBB_ITAG_PCP_SHIFT 29
#define PBB_ITAG_PCP_MASK (0x7U << PBB_ITAG_PCP_SHIFT)
#define BPE_BRIDGE_AGE_TMO 100 /* seconds */
struct bpe_key {
int k_if;
uint32_t k_isid;
RBT_ENTRY(bpe_tunnel) k_entry;
};
RBT_HEAD(bpe_tree, bpe_key);
static inline int bpe_cmp(const struct bpe_key *, const struct bpe_key *);
RBT_PROTOTYPE(bpe_tree, bpe_key, k_entry, bpe_cmp);
RBT_GENERATE(bpe_tree, bpe_key, k_entry, bpe_cmp);
struct bpe_entry {
struct ether_addr be_c_da; /* customer address - must be first */
struct ether_addr be_b_da; /* bridge address */
unsigned int be_type;
#define BPE_ENTRY_DYNAMIC 0
#define BPE_ENTRY_STATIC 1
struct refcnt be_refs;
time_t be_age;
RBT_ENTRY(bpe_entry) be_entry;
};
RBT_HEAD(bpe_map, bpe_entry);
static inline int bpe_entry_cmp(const struct bpe_entry *,
const struct bpe_entry *);
RBT_PROTOTYPE(bpe_map, bpe_entry, be_entry, bpe_entry_cmp);
RBT_GENERATE(bpe_map, bpe_entry, be_entry, bpe_entry_cmp);
struct bpe_softc {
struct bpe_key sc_key; /* must be first */
struct arpcom sc_ac;
int sc_txhprio;
int sc_rxhprio;
uint8_t sc_group[ETHER_ADDR_LEN];
struct task sc_ltask;
struct task sc_dtask;
struct bpe_map sc_bridge_map;
struct rwlock sc_bridge_lock;
unsigned int sc_bridge_num;
unsigned int sc_bridge_max;
int sc_bridge_tmo; /* seconds */
struct timeout sc_bridge_age;
};
void bpeattach(int);
static int bpe_clone_create(struct if_clone *, int);
static int bpe_clone_destroy(struct ifnet *);
static void bpe_start(struct ifnet *);
static int bpe_ioctl(struct ifnet *, u_long, caddr_t);
static int bpe_media_get(struct bpe_softc *, struct ifreq *);
static int bpe_up(struct bpe_softc *);
static int bpe_down(struct bpe_softc *);
static int bpe_multi(struct bpe_softc *, struct ifnet *, u_long);
static int bpe_set_vnetid(struct bpe_softc *, const struct ifreq *);
static void bpe_set_group(struct bpe_softc *, uint32_t);
static int bpe_set_parent(struct bpe_softc *, const struct if_parent *);
static int bpe_get_parent(struct bpe_softc *, struct if_parent *);
static int bpe_del_parent(struct bpe_softc *);
static void bpe_link_hook(void *);
static void bpe_link_state(struct bpe_softc *, u_char, uint64_t);
static void bpe_detach_hook(void *);
static void bpe_input_map(struct bpe_softc *,
const uint8_t *, const uint8_t *);
static void bpe_bridge_age(void *);
static struct if_clone bpe_cloner =
IF_CLONE_INITIALIZER("bpe", bpe_clone_create, bpe_clone_destroy);
static struct bpe_tree bpe_interfaces = RBT_INITIALIZER();
static struct rwlock bpe_lock = RWLOCK_INITIALIZER("bpeifs");
static struct pool bpe_entry_pool;
void
bpeattach(int count)
{
if_clone_attach(&bpe_cloner);
}
static int
bpe_clone_create(struct if_clone *ifc, int unit)
{
struct bpe_softc *sc;
struct ifnet *ifp;
if (bpe_entry_pool.pr_size == 0) {
pool_init(&bpe_entry_pool, sizeof(struct bpe_entry), 0,
IPL_NONE, 0, "bpepl", NULL);
}
sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK|M_ZERO);
ifp = &sc->sc_ac.ac_if;
snprintf(ifp->if_xname, sizeof(ifp->if_xname), "%s%d",
ifc->ifc_name, unit);
sc->sc_key.k_if = 0;
sc->sc_key.k_isid = 0;
bpe_set_group(sc, 0);
sc->sc_txhprio = IF_HDRPRIO_PACKET;
sc->sc_rxhprio = IF_HDRPRIO_OUTER;
task_set(&sc->sc_ltask, bpe_link_hook, sc);
task_set(&sc->sc_dtask, bpe_detach_hook, sc);
rw_init(&sc->sc_bridge_lock, "bpebr");
RBT_INIT(bpe_map, &sc->sc_bridge_map);
sc->sc_bridge_num = 0;
sc->sc_bridge_max = 100; /* XXX */
sc->sc_bridge_tmo = 240;
timeout_set_proc(&sc->sc_bridge_age, bpe_bridge_age, sc);
ifp->if_softc = sc;
ifp->if_hardmtu = ETHER_MAX_HARDMTU_LEN;
ifp->if_ioctl = bpe_ioctl;
ifp->if_start = bpe_start;
ifp->if_flags = IFF_BROADCAST | IFF_MULTICAST;
ifp->if_xflags = IFXF_CLONED;
ifq_set_maxlen(&ifp->if_snd, IFQ_MAXLEN);
ether_fakeaddr(ifp);
if_counters_alloc(ifp);
if_attach(ifp);
ether_ifattach(ifp);
return (0);
}
static int
bpe_clone_destroy(struct ifnet *ifp)
{
struct bpe_softc *sc = ifp->if_softc;
NET_LOCK();
if (ISSET(ifp->if_flags, IFF_RUNNING))
bpe_down(sc);
NET_UNLOCK();
ether_ifdetach(ifp);
if_detach(ifp);
free(sc, M_DEVBUF, sizeof(*sc));
return (0);
}
static inline int
bpe_entry_valid(struct bpe_softc *sc, const struct bpe_entry *be)
{
time_t diff;
if (be == NULL)
return (0);
if (be->be_type == BPE_ENTRY_STATIC)
return (1);
diff = getuptime() - be->be_age;
if (diff < sc->sc_bridge_tmo)
return (1);
return (0);
}
static void
bpe_start(struct ifnet *ifp)
{
struct bpe_softc *sc = ifp->if_softc;
struct ifnet *ifp0;
struct mbuf *m0, *m;
struct ether_header *ceh;
struct ether_header *beh;
uint32_t itag, *itagp;
int hlen = sizeof(*beh) + sizeof(*itagp);
#if NBPFILTER > 0
caddr_t if_bpf;
#endif
int txprio;
uint8_t prio;
ifp0 = if_get(sc->sc_key.k_if);
if (ifp0 == NULL || !ISSET(ifp0->if_flags, IFF_RUNNING)) {
ifq_purge(&ifp->if_snd);
goto done;
}
txprio = sc->sc_txhprio;
while ((m0 = ifq_dequeue(&ifp->if_snd)) != NULL) {
#if NBPFILTER > 0
if_bpf = ifp->if_bpf;
if (if_bpf)
bpf_mtap_ether(if_bpf, m0, BPF_DIRECTION_OUT);
#endif
ceh = mtod(m0, struct ether_header *);
/* force prepend of a whole mbuf because of alignment */
m = m_get(M_DONTWAIT, m0->m_type);
if (m == NULL) {
m_freem(m0);
continue;
}
M_MOVE_PKTHDR(m, m0);
m->m_next = m0;
m_align(m, 0);
m->m_len = 0;
m = m_prepend(m, hlen, M_DONTWAIT);
if (m == NULL)
continue;
beh = mtod(m, struct ether_header *);
if (ETHER_IS_BROADCAST(ceh->ether_dhost)) {
memcpy(beh->ether_dhost, sc->sc_group,
sizeof(beh->ether_dhost));
} else {
struct bpe_entry *be;
rw_enter_read(&sc->sc_bridge_lock);
be = RBT_FIND(bpe_map, &sc->sc_bridge_map,
(struct bpe_entry *)ceh->ether_dhost);
if (bpe_entry_valid(sc, be)) {
memcpy(beh->ether_dhost, &be->be_b_da,
sizeof(beh->ether_dhost));
} else {
/* "flood" to unknown hosts */
memcpy(beh->ether_dhost, sc->sc_group,
sizeof(beh->ether_dhost));
}
rw_exit_read(&sc->sc_bridge_lock);
}
memcpy(beh->ether_shost, ((struct arpcom *)ifp0)->ac_enaddr,
sizeof(beh->ether_shost));
beh->ether_type = htons(ETHERTYPE_PBB);
prio = (txprio == IF_HDRPRIO_PACKET) ?
m->m_pkthdr.pf.prio : txprio;
itag = sc->sc_key.k_isid;
itag |= prio << PBB_ITAG_PCP_SHIFT;
itagp = (uint32_t *)(beh + 1);
htobem32(itagp, itag);
if_enqueue(ifp0, m);
}
done:
if_put(ifp0);
}
static void
bpe_bridge_age(void *arg)
{
struct bpe_softc *sc = arg;
struct bpe_entry *be, *nbe;
time_t diff;
timeout_add_sec(&sc->sc_bridge_age, BPE_BRIDGE_AGE_TMO);
rw_enter_write(&sc->sc_bridge_lock);
RBT_FOREACH_SAFE(be, bpe_map, &sc->sc_bridge_map, nbe) {
if (be->be_type != BPE_ENTRY_DYNAMIC)
continue;
diff = getuptime() - be->be_age;
if (diff < sc->sc_bridge_tmo)
continue;
sc->sc_bridge_num--;
RBT_REMOVE(bpe_map, &sc->sc_bridge_map, be);
if (refcnt_rele(&be->be_refs))
pool_put(&bpe_entry_pool, be);
}
rw_exit_write(&sc->sc_bridge_lock);
}
static int
bpe_rtfind(struct bpe_softc *sc, struct ifbaconf *baconf)
{
struct ifnet *ifp = &sc->sc_ac.ac_if;
struct bpe_entry *be;
struct ifbareq bareq;
caddr_t uaddr, end;
int error;
time_t age;
struct sockaddr_dl *sdl;
if (baconf->ifbac_len == 0) {
/* single read is atomic */
baconf->ifbac_len = sc->sc_bridge_num * sizeof(bareq);
return (0);
}
uaddr = baconf->ifbac_buf;
end = uaddr + baconf->ifbac_len;
rw_enter_read(&sc->sc_bridge_lock);
RBT_FOREACH(be, bpe_map, &sc->sc_bridge_map) {
if (uaddr >= end)
break;
memcpy(bareq.ifba_name, ifp->if_xname,
sizeof(bareq.ifba_name));
memcpy(bareq.ifba_ifsname, ifp->if_xname,
sizeof(bareq.ifba_ifsname));
memcpy(&bareq.ifba_dst, &be->be_c_da,
sizeof(bareq.ifba_dst));
memset(&bareq.ifba_dstsa, 0, sizeof(bareq.ifba_dstsa));
bzero(&bareq.ifba_dstsa, sizeof(bareq.ifba_dstsa));
sdl = (struct sockaddr_dl *)&bareq.ifba_dstsa;
sdl->sdl_len = sizeof(sdl);
sdl->sdl_family = AF_LINK;
sdl->sdl_index = 0;
sdl->sdl_type = IFT_ETHER;
sdl->sdl_nlen = 0;
sdl->sdl_alen = sizeof(be->be_b_da);
CTASSERT(sizeof(sdl->sdl_data) >= sizeof(be->be_b_da));
memcpy(sdl->sdl_data, &be->be_b_da, sizeof(be->be_b_da));
switch (be->be_type) {
case BPE_ENTRY_DYNAMIC:
age = getuptime() - be->be_age;
bareq.ifba_age = MIN(age, 0xff);
bareq.ifba_flags = IFBAF_DYNAMIC;
break;
case BPE_ENTRY_STATIC:
bareq.ifba_age = 0;
bareq.ifba_flags = IFBAF_STATIC;
break;
}
error = copyout(&bareq, uaddr, sizeof(bareq));
if (error != 0) {
rw_exit_read(&sc->sc_bridge_lock);
return (error);
}
uaddr += sizeof(bareq);
}
baconf->ifbac_len = sc->sc_bridge_num * sizeof(bareq);
rw_exit_read(&sc->sc_bridge_lock);
return (0);
}
static void
bpe_flush_map(struct bpe_softc *sc, uint32_t flags)
{
struct bpe_entry *be, *nbe;
rw_enter_write(&sc->sc_bridge_lock);
RBT_FOREACH_SAFE(be, bpe_map, &sc->sc_bridge_map, nbe) {
if (flags == IFBF_FLUSHDYN &&
be->be_type != BPE_ENTRY_DYNAMIC)
continue;
RBT_REMOVE(bpe_map, &sc->sc_bridge_map, be);
if (refcnt_rele(&be->be_refs))
pool_put(&bpe_entry_pool, be);
}
rw_exit_write(&sc->sc_bridge_lock);
}
static int
bpe_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
{
struct bpe_softc *sc = ifp->if_softc;
struct ifreq *ifr = (struct ifreq *)data;
struct ifbrparam *bparam = (struct ifbrparam *)data;
int error = 0;
switch (cmd) {
case SIOCSIFFLAGS:
if (ISSET(ifp->if_flags, IFF_UP)) {
if (!ISSET(ifp->if_flags, IFF_RUNNING))
error = bpe_up(sc);
else
error = 0;
} else {
if (ISSET(ifp->if_flags, IFF_RUNNING))
error = bpe_down(sc);
}
break;
case SIOCSVNETID:
error = bpe_set_vnetid(sc, ifr);
break;
case SIOCGVNETID:
ifr->ifr_vnetid = sc->sc_key.k_isid;
break;
case SIOCSIFPARENT:
error = bpe_set_parent(sc, (struct if_parent *)data);
break;
case SIOCGIFPARENT:
error = bpe_get_parent(sc, (struct if_parent *)data);
break;
case SIOCDIFPARENT:
error = bpe_del_parent(sc);
break;
case SIOCSTXHPRIO:
error = if_txhprio_l2_check(ifr->ifr_hdrprio);
if (error != 0)
break;
sc->sc_txhprio = ifr->ifr_hdrprio;
break;
case SIOCGTXHPRIO:
ifr->ifr_hdrprio = sc->sc_txhprio;
break;
case SIOCSRXHPRIO:
error = if_rxhprio_l2_check(ifr->ifr_hdrprio);
if (error != 0)
break;
sc->sc_rxhprio = ifr->ifr_hdrprio;
break;
case SIOCGRXHPRIO:
ifr->ifr_hdrprio = sc->sc_rxhprio;
break;
case SIOCGIFMEDIA:
error = bpe_media_get(sc, ifr);
break;
case SIOCBRDGSCACHE:
error = suser(curproc);
if (error != 0)
break;
if (bparam->ifbrp_csize < 1) {
error = EINVAL;
break;
}
/* commit */
sc->sc_bridge_max = bparam->ifbrp_csize;
break;
case SIOCBRDGGCACHE:
bparam->ifbrp_csize = sc->sc_bridge_max;
break;
case SIOCBRDGSTO:
error = suser(curproc);
if (error != 0)
break;
if (bparam->ifbrp_ctime < 8 ||
bparam->ifbrp_ctime > 3600) {
error = EINVAL;
break;
}
sc->sc_bridge_tmo = bparam->ifbrp_ctime;
break;
case SIOCBRDGGTO:
bparam->ifbrp_ctime = sc->sc_bridge_tmo;
break;
case SIOCBRDGRTS:
error = bpe_rtfind(sc, (struct ifbaconf *)data);
break;
case SIOCBRDGFLUSH:
error = suser(curproc);
if (error != 0)
break;
bpe_flush_map(sc,
((struct ifbreq *)data)->ifbr_ifsflags);
break;
default:
error = ether_ioctl(ifp, &sc->sc_ac, cmd, data);
break;
}
return (error);
}
static int
bpe_media_get(struct bpe_softc *sc, struct ifreq *ifr)
{
struct ifnet *ifp0;
int error;
ifp0 = if_get(sc->sc_key.k_if);
if (ifp0 != NULL)
error = (*ifp0->if_ioctl)(ifp0, SIOCGIFMEDIA, (caddr_t)ifr);
else
error = ENOTTY;
if_put(ifp0);
return (error);
}
static int
bpe_up(struct bpe_softc *sc)
{
struct ifnet *ifp = &sc->sc_ac.ac_if;
struct ifnet *ifp0;
struct bpe_softc *osc;
int error = 0;
u_int hardmtu;
u_int hlen = sizeof(struct ether_header) + sizeof(uint32_t);
KASSERT(!ISSET(ifp->if_flags, IFF_RUNNING));
NET_ASSERT_LOCKED();
ifp0 = if_get(sc->sc_key.k_if);
if (ifp0 == NULL)
return (ENXIO);
/* check again if bpe will work on top of the parent */
if (ifp0->if_type != IFT_ETHER) {
error = EPROTONOSUPPORT;
goto put;
}
hardmtu = ifp0->if_hardmtu;
if (hardmtu < hlen) {
error = ENOBUFS;
goto put;
}
hardmtu -= hlen;
if (ifp->if_mtu > hardmtu) {
error = ENOBUFS;
goto put;
}
/* parent is fine, let's prepare the bpe to handle packets */
ifp->if_hardmtu = hardmtu;
SET(ifp->if_flags, ifp0->if_flags & IFF_SIMPLEX);
/* commit the interface */
error = rw_enter(&bpe_lock, RW_WRITE | RW_INTR);
if (error != 0)
goto scrub;
osc = (struct bpe_softc *)RBT_INSERT(bpe_tree, &bpe_interfaces,
(struct bpe_key *)sc);
rw_exit(&bpe_lock);
if (osc != NULL) {
error = EADDRINUSE;
goto scrub;
}
if (bpe_multi(sc, ifp0, SIOCADDMULTI) != 0) {
error = ENOTCONN;
goto remove;
}
/* Register callback for physical link state changes */
if_linkstatehook_add(ifp0, &sc->sc_ltask);
/* Register callback if parent wants to unregister */
if_detachhook_add(ifp0, &sc->sc_dtask);
/* we're running now */
SET(ifp->if_flags, IFF_RUNNING);
bpe_link_state(sc, ifp0->if_link_state, ifp0->if_baudrate);
if_put(ifp0);
timeout_add_sec(&sc->sc_bridge_age, BPE_BRIDGE_AGE_TMO);
return (0);
remove:
rw_enter(&bpe_lock, RW_WRITE);
RBT_REMOVE(bpe_tree, &bpe_interfaces, (struct bpe_key *)sc);
rw_exit(&bpe_lock);
scrub:
CLR(ifp->if_flags, IFF_SIMPLEX);
ifp->if_hardmtu = 0xffff;
put:
if_put(ifp0);
return (error);
}
static int
bpe_down(struct bpe_softc *sc)
{
struct ifnet *ifp = &sc->sc_ac.ac_if;
struct ifnet *ifp0;
NET_ASSERT_LOCKED();
CLR(ifp->if_flags, IFF_RUNNING);
ifp0 = if_get(sc->sc_key.k_if);
if (ifp0 != NULL) {
if_detachhook_del(ifp0, &sc->sc_dtask);
if_linkstatehook_del(ifp0, &sc->sc_ltask);
bpe_multi(sc, ifp0, SIOCDELMULTI);
}
if_put(ifp0);
rw_enter(&bpe_lock, RW_WRITE);
RBT_REMOVE(bpe_tree, &bpe_interfaces, (struct bpe_key *)sc);
rw_exit(&bpe_lock);
CLR(ifp->if_flags, IFF_SIMPLEX);
ifp->if_hardmtu = 0xffff;
return (0);
}
static int
bpe_multi(struct bpe_softc *sc, struct ifnet *ifp0, u_long cmd)
{
struct ifreq ifr;
struct sockaddr *sa;
/* make it convincing */
CTASSERT(sizeof(ifr.ifr_name) == sizeof(ifp0->if_xname));
memcpy(ifr.ifr_name, ifp0->if_xname, sizeof(ifr.ifr_name));
sa = &ifr.ifr_addr;
CTASSERT(sizeof(sa->sa_data) >= sizeof(sc->sc_group));
sa->sa_family = AF_UNSPEC;
memcpy(sa->sa_data, sc->sc_group, sizeof(sc->sc_group));
return ((*ifp0->if_ioctl)(ifp0, cmd, (caddr_t)&ifr));
}
static void
bpe_set_group(struct bpe_softc *sc, uint32_t isid)
{
uint8_t *group = sc->sc_group;
group[0] = 0x01;
group[1] = 0x1e;
group[2] = 0x83;
group[3] = isid >> 16;
group[4] = isid >> 8;
group[5] = isid >> 0;
}
static int
bpe_set_vnetid(struct bpe_softc *sc, const struct ifreq *ifr)
{
struct ifnet *ifp = &sc->sc_ac.ac_if;
uint32_t isid;
if (ifr->ifr_vnetid < PBB_ITAG_ISID_MIN ||
ifr->ifr_vnetid > PBB_ITAG_ISID_MAX)
return (EINVAL);
isid = ifr->ifr_vnetid;
if (isid == sc->sc_key.k_isid)
return (0);
if (ISSET(ifp->if_flags, IFF_RUNNING))
return (EBUSY);
/* commit */
sc->sc_key.k_isid = isid;
bpe_set_group(sc, isid);
bpe_flush_map(sc, IFBF_FLUSHALL);
return (0);
}
static int
bpe_set_parent(struct bpe_softc *sc, const struct if_parent *p)
{
struct ifnet *ifp = &sc->sc_ac.ac_if;
struct ifnet *ifp0;
ifp0 = ifunit(p->ifp_parent); /* doesn't need an if_put */
if (ifp0 == NULL)
return (ENXIO);
if (ifp0->if_type != IFT_ETHER)
return (ENXIO);
if (ifp0->if_index == sc->sc_key.k_if)
return (0);
if (ISSET(ifp->if_flags, IFF_RUNNING))
return (EBUSY);
/* commit */
sc->sc_key.k_if = ifp0->if_index;
bpe_flush_map(sc, IFBF_FLUSHALL);
return (0);
}
static int
bpe_get_parent(struct bpe_softc *sc, struct if_parent *p)
{
struct ifnet *ifp0;
int error = 0;
ifp0 = if_get(sc->sc_key.k_if);
if (ifp0 == NULL)
error = EADDRNOTAVAIL;
else
memcpy(p->ifp_parent, ifp0->if_xname, sizeof(p->ifp_parent));
if_put(ifp0);
return (error);
}
static int
bpe_del_parent(struct bpe_softc *sc)
{
struct ifnet *ifp = &sc->sc_ac.ac_if;
if (ISSET(ifp->if_flags, IFF_RUNNING))
return (EBUSY);
/* commit */
sc->sc_key.k_if = 0;
bpe_flush_map(sc, IFBF_FLUSHALL);
return (0);
}
static inline struct bpe_softc *
bpe_find(struct ifnet *ifp0, uint32_t isid)
{
struct bpe_key k = { .k_if = ifp0->if_index, .k_isid = isid };
struct bpe_softc *sc;
rw_enter_read(&bpe_lock);
sc = (struct bpe_softc *)RBT_FIND(bpe_tree, &bpe_interfaces, &k);
rw_exit_read(&bpe_lock);
return (sc);
}
static void
bpe_input_map(struct bpe_softc *sc, const uint8_t *ba, const uint8_t *ca)
{
struct bpe_entry *be;
int new = 0;
if (ETHER_IS_MULTICAST(ca))
return;
/* remember where it came from */
rw_enter_read(&sc->sc_bridge_lock);
be = RBT_FIND(bpe_map, &sc->sc_bridge_map, (struct bpe_entry *)ca);
if (be == NULL)
new = 1;
else {
be->be_age = getuptime(); /* only a little bit racy */
if (be->be_type != BPE_ENTRY_DYNAMIC ||
ETHER_IS_EQ(ba, &be->be_b_da))
be = NULL;
else
refcnt_take(&be->be_refs);
}
rw_exit_read(&sc->sc_bridge_lock);
if (new) {
struct bpe_entry *obe;
unsigned int num;
be = pool_get(&bpe_entry_pool, PR_NOWAIT);
if (be == NULL) {
/* oh well */
return;
}
memcpy(&be->be_c_da, ca, sizeof(be->be_c_da));
memcpy(&be->be_b_da, ba, sizeof(be->be_b_da));
be->be_type = BPE_ENTRY_DYNAMIC;
refcnt_init(&be->be_refs);
be->be_age = getuptime();
rw_enter_write(&sc->sc_bridge_lock);
num = sc->sc_bridge_num;
if (++num > sc->sc_bridge_max)
obe = be;
else {
/* try and give the ref to the map */
obe = RBT_INSERT(bpe_map, &sc->sc_bridge_map, be);
if (obe == NULL) {
/* count the insert */
sc->sc_bridge_num = num;
}
}
rw_exit_write(&sc->sc_bridge_lock);
if (obe != NULL)
pool_put(&bpe_entry_pool, obe);
} else if (be != NULL) {
rw_enter_write(&sc->sc_bridge_lock);
memcpy(&be->be_b_da, ba, sizeof(be->be_b_da));
rw_exit_write(&sc->sc_bridge_lock);
if (refcnt_rele(&be->be_refs)) {
/* ioctl may have deleted the entry */
pool_put(&bpe_entry_pool, be);
}
}
}
void
bpe_input(struct ifnet *ifp0, struct mbuf *m)
{
struct bpe_softc *sc;
struct ifnet *ifp;
struct ether_header *beh, *ceh;
uint32_t *itagp, itag;
unsigned int hlen = sizeof(*beh) + sizeof(*itagp) + sizeof(*ceh);
struct mbuf *n;
int off;
int prio;
if (m->m_len < hlen) {
m = m_pullup(m, hlen);
if (m == NULL) {
/* pbb short ++ */
return;
}
}
beh = mtod(m, struct ether_header *);
itagp = (uint32_t *)(beh + 1);
itag = bemtoh32(itagp);
if (itag & PBB_ITAG_RES2) {
/* dropped by res2 ++ */
goto drop;
}
sc = bpe_find(ifp0, itag & PBB_ITAG_ISID);
if (sc == NULL) {
/* no interface found */
goto drop;
}
ceh = (struct ether_header *)(itagp + 1);
bpe_input_map(sc, beh->ether_shost, ceh->ether_shost);
m_adj(m, sizeof(*beh) + sizeof(*itagp));
n = m_getptr(m, sizeof(*ceh), &off);
if (n == NULL) {
/* no data ++ */
goto drop;
}
if (!ALIGNED_POINTER(mtod(n, caddr_t) + off, uint32_t)) {
/* unaligned ++ */
n = m_dup_pkt(m, ETHER_ALIGN, M_NOWAIT);
m_freem(m);
if (n == NULL)
return;
m = n;
}
ifp = &sc->sc_ac.ac_if;
prio = sc->sc_rxhprio;
switch (prio) {
case IF_HDRPRIO_PACKET:
break;
case IF_HDRPRIO_OUTER:
m->m_pkthdr.pf.prio = (itag & PBB_ITAG_PCP_MASK) >>
PBB_ITAG_PCP_SHIFT;
break;
default:
m->m_pkthdr.pf.prio = prio;
break;
}
m->m_flags &= ~(M_BCAST|M_MCAST);
m->m_pkthdr.ph_ifidx = ifp->if_index;
m->m_pkthdr.ph_rtableid = ifp->if_rdomain;
#if NPF > 0
pf_pkt_addr_changed(m);
#endif
if_vinput(ifp, m);
return;
drop:
m_freem(m);
}
void
bpe_detach_hook(void *arg)
{
struct bpe_softc *sc = arg;
struct ifnet *ifp = &sc->sc_ac.ac_if;
if (ISSET(ifp->if_flags, IFF_RUNNING)) {
bpe_down(sc);
CLR(ifp->if_flags, IFF_UP);
}
sc->sc_key.k_if = 0;
}
static void
bpe_link_hook(void *arg)
{
struct bpe_softc *sc = arg;
struct ifnet *ifp0;
u_char link = LINK_STATE_DOWN;
uint64_t baud = 0;
ifp0 = if_get(sc->sc_key.k_if);
if (ifp0 != NULL) {
link = ifp0->if_link_state;
baud = ifp0->if_baudrate;
}
if_put(ifp0);
bpe_link_state(sc, link, baud);
}
void
bpe_link_state(struct bpe_softc *sc, u_char link, uint64_t baud)
{
struct ifnet *ifp = &sc->sc_ac.ac_if;
if (ifp->if_link_state == link)
return;
ifp->if_link_state = link;
ifp->if_baudrate = baud;
if_link_state_change(ifp);
}
static inline int
bpe_cmp(const struct bpe_key *a, const struct bpe_key *b)
{
if (a->k_if > b->k_if)
return (1);
if (a->k_if < b->k_if)
return (-1);
if (a->k_isid > b->k_isid)
return (1);
if (a->k_isid < b->k_isid)
return (-1);
return (0);
}
static inline int
bpe_entry_cmp(const struct bpe_entry *a, const struct bpe_entry *b)
{
return memcmp(&a->be_c_da, &b->be_c_da, sizeof(a->be_c_da));
}