#include "bpfilter.h"
#include "pf.h"
#include "vlan.h"
#include <sys/param.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/systm.h>
#include <sys/syslog.h>
#include <sys/percpu.h>
#include <sys/smr.h>
#include <sys/task.h>
#include <net/if.h>
#include <net/if_types.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <net/if_bridge.h>
#if NBPFILTER > 0
#include <net/bpf.h>
#endif
#if NPF > 0
#include <net/pfvar.h>
#endif
#if NVLAN > 0
#include <net/if_vlan_var.h>
#endif
#define TPMR_NUM_PORTS 2
struct tpmr_softc;
struct tpmr_port {
struct ifnet *p_ifp0;
int (*p_ioctl)(struct ifnet *, u_long, caddr_t);
int (*p_output)(struct ifnet *, struct mbuf *, struct sockaddr *,
struct rtentry *);
struct task p_ltask;
struct task p_dtask;
struct tpmr_softc *p_tpmr;
unsigned int p_slot;
struct refcnt p_refcnt;
struct cpumem *p_percpu;
struct ether_port p_brport;
};
struct tpmr_softc {
struct ifnet sc_if;
unsigned int sc_dead;
struct tpmr_port *sc_ports[TPMR_NUM_PORTS];
unsigned int sc_nports;
};
#define DPRINTF(_sc, fmt...) do { \
if (ISSET((_sc)->sc_if.if_flags, IFF_DEBUG)) \
printf(fmt); \
} while (0)
static int tpmr_clone_create(struct if_clone *, int);
static int tpmr_clone_destroy(struct ifnet *);
static int tpmr_ioctl(struct ifnet *, u_long, caddr_t);
static int tpmr_enqueue(struct ifnet *, struct mbuf *);
static int tpmr_output(struct ifnet *, struct mbuf *, struct sockaddr *,
struct rtentry *);
static void tpmr_start(struct ifqueue *);
static int tpmr_up(struct tpmr_softc *);
static int tpmr_down(struct tpmr_softc *);
static int tpmr_iff(struct tpmr_softc *);
static void tpmr_p_linkch(void *);
static void tpmr_p_detach(void *);
static int tpmr_p_ioctl(struct ifnet *, u_long, caddr_t);
static int tpmr_p_output(struct ifnet *, struct mbuf *,
struct sockaddr *, struct rtentry *);
static void tpmr_p_dtor(struct tpmr_softc *, struct tpmr_port *,
const char *);
static int tpmr_add_port(struct tpmr_softc *,
const struct ifbreq *);
static int tpmr_del_port(struct tpmr_softc *,
const struct ifbreq *);
static int tpmr_port_list(struct tpmr_softc *, struct ifbifconf *);
static void tpmr_p_take(struct tpmr_port *);
static void tpmr_p_rele(struct tpmr_port *);
static void *tpmr_cpu_take(void *);
static void tpmr_cpu_rele(void *, void *);
static struct if_clone tpmr_cloner =
IF_CLONE_INITIALIZER("tpmr", tpmr_clone_create, tpmr_clone_destroy);
void
tpmrattach(int count)
{
if_clone_attach(&tpmr_cloner);
}
static int
tpmr_clone_create(struct if_clone *ifc, int unit)
{
struct tpmr_softc *sc;
struct ifnet *ifp;
sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK|M_ZERO|M_CANFAIL);
if (sc == NULL)
return (ENOMEM);
ifp = &sc->sc_if;
snprintf(ifp->if_xname, sizeof(ifp->if_xname), "%s%d",
ifc->ifc_name, unit);
ifp->if_softc = sc;
ifp->if_type = IFT_BRIDGE;
ifp->if_hardmtu = ETHER_MAX_HARDMTU_LEN;
ifp->if_mtu = 0;
ifp->if_addrlen = ETHER_ADDR_LEN;
ifp->if_hdrlen = ETHER_HDR_LEN;
ifp->if_ioctl = tpmr_ioctl;
ifp->if_output = tpmr_output;
ifp->if_enqueue = tpmr_enqueue;
ifp->if_qstart = tpmr_start;
ifp->if_flags = IFF_POINTOPOINT;
ifp->if_xflags = IFXF_CLONED | IFXF_MPSAFE;
ifp->if_link_state = LINK_STATE_DOWN;
if_counters_alloc(ifp);
if_attach(ifp);
if_alloc_sadl(ifp);
#if NBPFILTER > 0
bpfattach(&ifp->if_bpf, ifp, DLT_EN10MB, ETHER_HDR_LEN);
#endif
ifp->if_llprio = IFQ_MAXPRIO;
return (0);
}
static int
tpmr_clone_destroy(struct ifnet *ifp)
{
struct tpmr_softc *sc = ifp->if_softc;
unsigned int i;
NET_LOCK();
sc->sc_dead = 1;
if (ISSET(ifp->if_flags, IFF_RUNNING))
tpmr_down(sc);
NET_UNLOCK();
if_detach(ifp);
NET_LOCK();
for (i = 0; i < nitems(sc->sc_ports); i++) {
struct tpmr_port *p = SMR_PTR_GET_LOCKED(&sc->sc_ports[i]);
if (p == NULL)
continue;
tpmr_p_dtor(sc, p, "destroy");
}
NET_UNLOCK();
free(sc, M_DEVBUF, sizeof(*sc));
return (0);
}
static int
tpmr_vlan_filter(const struct mbuf *m)
{
const struct ether_header *eh;
#if NVLAN > 0
if (ISSET(m->m_flags, M_VLANTAG))
return (1);
#endif
eh = mtod(m, struct ether_header *);
switch (ntohs(eh->ether_type)) {
case ETHERTYPE_VLAN:
case ETHERTYPE_QINQ:
return (1);
default:
break;
}
return (0);
}
static int
tpmr_8021q_filter(const struct mbuf *m, uint64_t dst)
{
if (ETH64_IS_8021_RSVD(dst)) {
switch (dst & 0xf) {
case 0x01:
case 0x02:
case 0x04:
case 0x0e:
return (1);
default:
break;
}
}
return (0);
}
#if NPF > 0
struct tpmr_pf_ip_family {
sa_family_t af;
struct mbuf *(*ip_check)(struct ifnet *, struct mbuf *);
void (*ip_input)(struct ifnet *, struct mbuf *,
struct netstack *);
};
static const struct tpmr_pf_ip_family tpmr_pf_ipv4 = {
.af = AF_INET,
.ip_check = ipv4_check,
.ip_input = ipv4_input,
};
#ifdef INET6
static const struct tpmr_pf_ip_family tpmr_pf_ipv6 = {
.af = AF_INET6,
.ip_check = ipv6_check,
.ip_input = ipv6_input,
};
#endif
static struct mbuf *
tpmr_pf(struct ifnet *ifp0, int dir, struct mbuf *m, struct netstack *ns)
{
struct ether_header *eh, copy;
const struct tpmr_pf_ip_family *fam;
#if NVLAN > 0
if (ISSET(m->m_flags, M_VLANTAG))
return (m);
#endif
eh = mtod(m, struct ether_header *);
switch (ntohs(eh->ether_type)) {
case ETHERTYPE_IP:
fam = &tpmr_pf_ipv4;
break;
#ifdef INET6
case ETHERTYPE_IPV6:
fam = &tpmr_pf_ipv6;
break;
#endif
default:
return (m);
}
copy = *eh;
m_adj(m, sizeof(*eh));
if (dir == PF_IN) {
m = (*fam->ip_check)(ifp0, m);
if (m == NULL)
return (NULL);
}
if (pf_test(fam->af, dir, ifp0, &m) != PF_PASS) {
m_freem(m);
return (NULL);
}
if (m == NULL)
return (NULL);
if (dir == PF_IN && ISSET(m->m_pkthdr.pf.flags, PF_TAG_DIVERTED)) {
pf_mbuf_unlink_state_key(m);
pf_mbuf_unlink_inpcb(m);
if_input_proto(ifp0, m, fam->ip_input, ns);
return (NULL);
}
m = m_prepend(m, sizeof(*eh), M_DONTWAIT);
if (m == NULL)
return (NULL);
eh = mtod(m, struct ether_header *);
*eh = copy;
return (m);
}
#endif
static struct mbuf *
tpmr_input(struct ifnet *ifp0, struct mbuf *m, uint64_t dst, void *brport,
struct netstack *ns)
{
struct tpmr_port *p = brport;
struct tpmr_softc *sc = p->p_tpmr;
struct ifnet *ifp = &sc->sc_if;
struct ifnet *ifpn;
unsigned int iff;
struct tpmr_port *pn;
void *pnref;
int len;
#if NBPFILTER > 0
caddr_t if_bpf;
#endif
iff = READ_ONCE(ifp->if_flags);
if (!ISSET(iff, IFF_RUNNING))
goto drop;
if (!ISSET(iff, IFF_LINK2) &&
tpmr_vlan_filter(m))
goto drop;
if (!ISSET(iff, IFF_LINK0) &&
tpmr_8021q_filter(m, dst))
goto drop;
#if NPF > 0
if (!ISSET(iff, IFF_LINK1) &&
(m = tpmr_pf(ifp0, PF_IN, m, ns)) == NULL)
return (NULL);
#endif
len = m->m_pkthdr.len;
counters_pkt(ifp->if_counters, ifc_ipackets, ifc_ibytes, len);
#if NBPFILTER > 0
if_bpf = READ_ONCE(ifp->if_bpf);
if (if_bpf) {
if (bpf_mtap_ether(if_bpf, m, 0))
goto drop;
}
#endif
smr_read_enter();
pn = SMR_PTR_GET(&sc->sc_ports[!p->p_slot]);
if (pn != NULL)
pnref = tpmr_cpu_take(pn);
smr_read_leave();
if (pn == NULL)
goto drop;
ifpn = pn->p_ifp0;
#if NPF > 0
if (!ISSET(iff, IFF_LINK1) &&
(m = tpmr_pf(ifpn, PF_OUT, m, ns)) == NULL)
goto rele;
#endif
m = ether_offload_ifcap(ifpn, m);
if (m == NULL) {
counters_inc(ifp->if_counters, ifc_oerrors);
goto rele;
}
if (if_enqueue(ifpn, m) != 0) {
counters_inc(ifp->if_counters, ifc_oerrors);
goto rele;
}
counters_pkt(ifp->if_counters, ifc_opackets, ifc_obytes, len);
rele:
tpmr_cpu_rele(pnref, pn);
return (NULL);
drop:
m_freem(m);
return (NULL);
}
static int
tpmr_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst,
struct rtentry *rt)
{
m_freem(m);
return (ENODEV);
}
static int
tpmr_enqueue(struct ifnet *ifp, struct mbuf *m)
{
m_freem(m);
return (ENODEV);
}
static void
tpmr_start(struct ifqueue *ifq)
{
ifq_purge(ifq);
}
static int
tpmr_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
{
struct tpmr_softc *sc = ifp->if_softc;
int error = 0;
if (sc->sc_dead)
return (ENXIO);
switch (cmd) {
case SIOCSIFFLAGS:
if (ISSET(ifp->if_flags, IFF_UP)) {
if (!ISSET(ifp->if_flags, IFF_RUNNING))
error = tpmr_up(sc);
} else {
if (ISSET(ifp->if_flags, IFF_RUNNING))
error = tpmr_down(sc);
}
break;
case SIOCBRDGADD:
error = suser(curproc);
if (error != 0)
break;
error = tpmr_add_port(sc, (struct ifbreq *)data);
break;
case SIOCBRDGDEL:
error = suser(curproc);
if (error != 0)
break;
error = tpmr_del_port(sc, (struct ifbreq *)data);
break;
case SIOCBRDGIFS:
error = tpmr_port_list(sc, (struct ifbifconf *)data);
break;
case SIOCBRDGGRL:
((struct ifbrlconf *)data)->ifbrl_len = 0;
break;
default:
error = ENOTTY;
break;
}
if (error == ENETRESET)
error = tpmr_iff(sc);
return (error);
}
static int
tpmr_add_port(struct tpmr_softc *sc, const struct ifbreq *req)
{
struct ifnet *ifp = &sc->sc_if;
struct ifnet *ifp0;
struct tpmr_port **pp;
struct tpmr_port *p;
struct refcnt *r;
struct cpumem_iter cmi;
int i;
int error;
NET_ASSERT_LOCKED();
if (sc->sc_nports >= nitems(sc->sc_ports))
return (ENOSPC);
ifp0 = if_unit(req->ifbr_ifsname);
if (ifp0 == NULL)
return (EINVAL);
if (ifp0->if_type != IFT_ETHER) {
error = EPROTONOSUPPORT;
goto put;
}
error = ether_brport_isset(ifp0);
if (error != 0)
goto put;
p = malloc(sizeof(*p), M_DEVBUF, M_WAITOK|M_ZERO|M_CANFAIL);
if (p == NULL) {
error = ENOMEM;
goto put;
}
refcnt_init(&p->p_refcnt);
p->p_percpu = cpumem_malloc(sizeof(*r), M_DEVBUF);
CPUMEM_FOREACH(r, &cmi, p->p_percpu) {
tpmr_p_take(p);
refcnt_init(r);
}
ifsetlro(ifp0, 0);
p->p_ifp0 = ifp0;
p->p_tpmr = sc;
p->p_ioctl = ifp0->if_ioctl;
p->p_output = ifp0->if_output;
error = ifpromisc(ifp0, 1);
if (error != 0)
goto free;
error = ether_brport_isset(ifp0);
if (error != 0)
goto unpromisc;
task_set(&p->p_ltask, tpmr_p_linkch, p);
if_linkstatehook_add(ifp0, &p->p_ltask);
task_set(&p->p_dtask, tpmr_p_detach, p);
if_detachhook_add(ifp0, &p->p_dtask);
p->p_brport.ep_input = tpmr_input;
p->p_brport.ep_port_take = tpmr_cpu_take;
p->p_brport.ep_port_rele = tpmr_cpu_rele;
p->p_brport.ep_port = p;
DPRINTF(sc, "%s %s trunkport: creating port\n",
ifp->if_xname, ifp0->if_xname);
for (i = 0; i < nitems(sc->sc_ports); i++) {
pp = &sc->sc_ports[i];
if (SMR_PTR_GET_LOCKED(pp) == NULL)
break;
}
sc->sc_nports++;
p->p_slot = i;
ether_brport_set(ifp0, &p->p_brport);
ifp0->if_ioctl = tpmr_p_ioctl;
ifp0->if_output = tpmr_p_output;
SMR_PTR_SET_LOCKED(pp, p);
tpmr_p_linkch(p);
return (0);
unpromisc:
ifpromisc(ifp0, 0);
free:
cpumem_free(p->p_percpu, M_DEVBUF, sizeof(*r));
free(p, M_DEVBUF, sizeof(*p));
put:
if_put(ifp0);
return (error);
}
static struct tpmr_port *
tpmr_trunkport(struct tpmr_softc *sc, const char *name)
{
unsigned int i;
for (i = 0; i < nitems(sc->sc_ports); i++) {
struct tpmr_port *p = SMR_PTR_GET_LOCKED(&sc->sc_ports[i]);
if (p == NULL)
continue;
if (strcmp(p->p_ifp0->if_xname, name) == 0)
return (p);
}
return (NULL);
}
static int
tpmr_del_port(struct tpmr_softc *sc, const struct ifbreq *req)
{
struct tpmr_port *p;
NET_ASSERT_LOCKED();
p = tpmr_trunkport(sc, req->ifbr_ifsname);
if (p == NULL)
return (EINVAL);
tpmr_p_dtor(sc, p, "del");
return (0);
}
static int
tpmr_port_list(struct tpmr_softc *sc, struct ifbifconf *bifc)
{
struct tpmr_port *p;
struct ifbreq breq;
int i = 0, total = nitems(sc->sc_ports), n = 0, error = 0;
NET_ASSERT_LOCKED();
if (bifc->ifbic_len == 0) {
n = total;
goto done;
}
for (i = 0; i < total; i++) {
memset(&breq, 0, sizeof(breq));
if (bifc->ifbic_len < sizeof(breq))
break;
p = SMR_PTR_GET_LOCKED(&sc->sc_ports[i]);
if (p == NULL)
continue;
strlcpy(breq.ifbr_ifsname, p->p_ifp0->if_xname, IFNAMSIZ);
breq.ifbr_ifsflags = IFBIF_SPAN;
strlcpy(breq.ifbr_name, sc->sc_if.if_xname, IFNAMSIZ);
if ((error = copyout(&breq, bifc->ifbic_req + n,
sizeof(breq))) != 0)
goto done;
bifc->ifbic_len -= sizeof(breq);
n++;
}
done:
bifc->ifbic_len = n * sizeof(breq);
return (error);
}
static int
tpmr_p_ioctl(struct ifnet *ifp0, u_long cmd, caddr_t data)
{
const struct ether_port *ep = ether_brport_get_locked(ifp0);
struct tpmr_port *p;
int error = 0;
KASSERTMSG(ep != NULL,
"%s: %s called without an ether_brport set",
ifp0->if_xname, __func__);
KASSERTMSG(ep->ep_input == tpmr_input,
"%s: %s called, but ep_input seems wrong (%p != tpmr_input())",
ifp0->if_xname, __func__, ep->ep_input);
p = ep->ep_port;
switch (cmd) {
case SIOCSIFADDR:
error = EBUSY;
break;
default:
error = (*p->p_ioctl)(ifp0, cmd, data);
break;
}
return (error);
}
static int
tpmr_p_output(struct ifnet *ifp0, struct mbuf *m, struct sockaddr *dst,
struct rtentry *rt)
{
int (*p_output)(struct ifnet *, struct mbuf *, struct sockaddr *,
struct rtentry *) = NULL;
const struct ether_port *ep;
if ((m_tag_find(m, PACKET_TAG_DLT, NULL) == NULL)) {
m_freem(m);
return (EBUSY);
}
smr_read_enter();
ep = ether_brport_get(ifp0);
if (ep != NULL && ep->ep_input == tpmr_input) {
struct tpmr_port *p = ep->ep_port;
p_output = p->p_output;
}
smr_read_leave();
if (p_output == NULL) {
m_freem(m);
return (ENXIO);
}
return ((*p_output)(ifp0, m, dst, rt));
}
static void
tpmr_p_take(struct tpmr_port *p)
{
refcnt_take(&p->p_refcnt);
}
static void
tpmr_p_rele(struct tpmr_port *p)
{
if (refcnt_rele(&p->p_refcnt)) {
if_put(p->p_ifp0);
cpumem_free(p->p_percpu, M_DEVBUF, sizeof(struct refcnt));
free(p, M_DEVBUF, sizeof(*p));
}
}
static void *
tpmr_cpu_take(void *port)
{
struct tpmr_port *p = port;
struct refcnt *r;
r = cpumem_enter(p->p_percpu);
refcnt_take(r);
cpumem_leave(p->p_percpu, r);
return (r);
}
static void
tpmr_cpu_rele(void *ref, void *port)
{
struct refcnt *r = ref;
if (refcnt_rele(r))
tpmr_p_rele(port);
}
static void
tpmr_p_dtor(struct tpmr_softc *sc, struct tpmr_port *p, const char *op)
{
struct ifnet *ifp = &sc->sc_if;
struct ifnet *ifp0 = p->p_ifp0;
struct refcnt *r;
struct cpumem_iter cmi;
DPRINTF(sc, "%s %s: destroying port\n",
ifp->if_xname, ifp0->if_xname);
ifp0->if_ioctl = p->p_ioctl;
ifp0->if_output = p->p_output;
ether_brport_clr(ifp0);
sc->sc_nports--;
SMR_PTR_SET_LOCKED(&sc->sc_ports[p->p_slot], NULL);
if (ifpromisc(ifp0, 0) != 0) {
log(LOG_WARNING, "%s %s: unable to disable promisc\n",
ifp->if_xname, ifp0->if_xname);
}
if_detachhook_del(ifp0, &p->p_dtask);
if_linkstatehook_del(ifp0, &p->p_ltask);
smr_barrier();
CPUMEM_FOREACH(r, &cmi, p->p_percpu)
tpmr_cpu_rele(r, p);
tpmr_p_rele(p);
if (ifp->if_link_state != LINK_STATE_DOWN) {
ifp->if_link_state = LINK_STATE_DOWN;
if_link_state_change(ifp);
}
}
static void
tpmr_p_detach(void *arg)
{
struct tpmr_port *p = arg;
struct tpmr_softc *sc = p->p_tpmr;
tpmr_p_dtor(sc, p, "detach");
NET_ASSERT_LOCKED();
}
static int
tpmr_p_active(struct tpmr_port *p)
{
struct ifnet *ifp0 = p->p_ifp0;
return (ISSET(ifp0->if_flags, IFF_RUNNING) &&
LINK_STATE_IS_UP(ifp0->if_link_state));
}
static void
tpmr_p_linkch(void *arg)
{
struct tpmr_port *p = arg;
struct tpmr_softc *sc = p->p_tpmr;
struct ifnet *ifp = &sc->sc_if;
struct tpmr_port *np;
u_char link_state = LINK_STATE_FULL_DUPLEX;
NET_ASSERT_LOCKED();
if (!tpmr_p_active(p))
link_state = LINK_STATE_DOWN;
np = SMR_PTR_GET_LOCKED(&sc->sc_ports[!p->p_slot]);
if (np == NULL || !tpmr_p_active(np))
link_state = LINK_STATE_DOWN;
if (ifp->if_link_state != link_state) {
ifp->if_link_state = link_state;
if_link_state_change(ifp);
}
}
static int
tpmr_up(struct tpmr_softc *sc)
{
struct ifnet *ifp = &sc->sc_if;
NET_ASSERT_LOCKED();
SET(ifp->if_flags, IFF_RUNNING);
return (0);
}
static int
tpmr_iff(struct tpmr_softc *sc)
{
return (0);
}
static int
tpmr_down(struct tpmr_softc *sc)
{
struct ifnet *ifp = &sc->sc_if;
NET_ASSERT_LOCKED();
CLR(ifp->if_flags, IFF_RUNNING);
return (0);
}