2520 lines
52 KiB
C
2520 lines
52 KiB
C
/* $OpenBSD: if_veb.c,v 1.36 2024/08/05 17:47:29 bluhm Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 2021 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 "vlan.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/mbuf.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/sockio.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/syslog.h>
|
|
#include <sys/rwlock.h>
|
|
#include <sys/percpu.h>
|
|
#include <sys/smr.h>
|
|
#include <sys/task.h>
|
|
#include <sys/pool.h>
|
|
|
|
#include <net/if.h>
|
|
#include <net/if_dl.h>
|
|
#include <net/if_types.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <netinet/ip.h>
|
|
#include <netinet/if_ether.h>
|
|
|
|
#ifdef INET6
|
|
#include <netinet6/in6_var.h>
|
|
#include <netinet/ip6.h>
|
|
#endif
|
|
|
|
#if 0 && defined(IPSEC)
|
|
/*
|
|
* IPsec handling is disabled in veb until getting and using tdbs is mpsafe.
|
|
*/
|
|
#include <netinet/ip_ipsp.h>
|
|
#include <net/if_enc.h>
|
|
#endif
|
|
|
|
#include <net/if_bridge.h>
|
|
#include <net/if_etherbridge.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
|
|
|
|
/* SIOCBRDGIFFLGS, SIOCBRDGIFFLGS */
|
|
#define VEB_IFBIF_FLAGS (IFBIF_LEARNING|IFBIF_DISCOVER|IFBIF_BLOCKNONIP)
|
|
|
|
struct veb_rule {
|
|
TAILQ_ENTRY(veb_rule) vr_entry;
|
|
SMR_TAILQ_ENTRY(veb_rule) vr_lentry[2];
|
|
|
|
uint16_t vr_flags;
|
|
#define VEB_R_F_IN (1U << 0)
|
|
#define VEB_R_F_OUT (1U << 1)
|
|
#define VEB_R_F_SRC (1U << 2)
|
|
#define VEB_R_F_DST (1U << 3)
|
|
|
|
#define VEB_R_F_ARP (1U << 4)
|
|
#define VEB_R_F_RARP (1U << 5)
|
|
#define VEB_R_F_SHA (1U << 6)
|
|
#define VEB_R_F_SPA (1U << 7)
|
|
#define VEB_R_F_THA (1U << 8)
|
|
#define VEB_R_F_TPA (1U << 9)
|
|
uint16_t vr_arp_op;
|
|
|
|
uint64_t vr_src;
|
|
uint64_t vr_dst;
|
|
struct ether_addr vr_arp_sha;
|
|
struct ether_addr vr_arp_tha;
|
|
struct in_addr vr_arp_spa;
|
|
struct in_addr vr_arp_tpa;
|
|
|
|
unsigned int vr_action;
|
|
#define VEB_R_MATCH 0
|
|
#define VEB_R_PASS 1
|
|
#define VEB_R_BLOCK 2
|
|
|
|
int vr_pftag;
|
|
};
|
|
|
|
TAILQ_HEAD(veb_rules, veb_rule);
|
|
SMR_TAILQ_HEAD(veb_rule_list, veb_rule);
|
|
|
|
struct veb_softc;
|
|
|
|
struct veb_port {
|
|
struct ifnet *p_ifp0;
|
|
struct refcnt p_refs;
|
|
|
|
int (*p_enqueue)(struct ifnet *, struct mbuf *);
|
|
|
|
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 veb_softc *p_veb;
|
|
|
|
struct ether_brport p_brport;
|
|
|
|
unsigned int p_link_state;
|
|
unsigned int p_bif_flags;
|
|
uint32_t p_protected;
|
|
|
|
struct veb_rules p_vrl;
|
|
unsigned int p_nvrl;
|
|
struct veb_rule_list p_vr_list[2];
|
|
#define VEB_RULE_LIST_OUT 0
|
|
#define VEB_RULE_LIST_IN 1
|
|
};
|
|
|
|
struct veb_ports {
|
|
struct refcnt m_refs;
|
|
unsigned int m_count;
|
|
|
|
/* followed by an array of veb_port pointers */
|
|
};
|
|
|
|
struct veb_softc {
|
|
struct ifnet sc_if;
|
|
unsigned int sc_dead;
|
|
|
|
struct etherbridge sc_eb;
|
|
|
|
struct rwlock sc_rule_lock;
|
|
struct veb_ports *sc_ports;
|
|
struct veb_ports *sc_spans;
|
|
};
|
|
|
|
#define DPRINTF(_sc, fmt...) do { \
|
|
if (ISSET((_sc)->sc_if.if_flags, IFF_DEBUG)) \
|
|
printf(fmt); \
|
|
} while (0)
|
|
|
|
static int veb_clone_create(struct if_clone *, int);
|
|
static int veb_clone_destroy(struct ifnet *);
|
|
|
|
static int veb_ioctl(struct ifnet *, u_long, caddr_t);
|
|
static void veb_input(struct ifnet *, struct mbuf *);
|
|
static int veb_enqueue(struct ifnet *, struct mbuf *);
|
|
static int veb_output(struct ifnet *, struct mbuf *, struct sockaddr *,
|
|
struct rtentry *);
|
|
static void veb_start(struct ifqueue *);
|
|
|
|
static int veb_up(struct veb_softc *);
|
|
static int veb_down(struct veb_softc *);
|
|
static int veb_iff(struct veb_softc *);
|
|
|
|
static void veb_p_linkch(void *);
|
|
static void veb_p_detach(void *);
|
|
static int veb_p_ioctl(struct ifnet *, u_long, caddr_t);
|
|
static int veb_p_output(struct ifnet *, struct mbuf *,
|
|
struct sockaddr *, struct rtentry *);
|
|
|
|
static inline size_t
|
|
veb_ports_size(unsigned int n)
|
|
{
|
|
/* use of _ALIGN is inspired by CMSGs */
|
|
return _ALIGN(sizeof(struct veb_ports)) +
|
|
n * sizeof(struct veb_port *);
|
|
}
|
|
|
|
static inline struct veb_port **
|
|
veb_ports_array(struct veb_ports *m)
|
|
{
|
|
return (struct veb_port **)((caddr_t)m + _ALIGN(sizeof(*m)));
|
|
}
|
|
|
|
static inline void veb_ports_free(struct veb_ports *);
|
|
|
|
static void veb_p_unlink(struct veb_softc *, struct veb_port *);
|
|
static void veb_p_fini(struct veb_port *);
|
|
static void veb_p_dtor(struct veb_softc *, struct veb_port *);
|
|
static int veb_add_port(struct veb_softc *,
|
|
const struct ifbreq *, unsigned int);
|
|
static int veb_del_port(struct veb_softc *,
|
|
const struct ifbreq *, unsigned int);
|
|
static int veb_port_list(struct veb_softc *, struct ifbifconf *);
|
|
static int veb_port_set_flags(struct veb_softc *, struct ifbreq *);
|
|
static int veb_port_get_flags(struct veb_softc *, struct ifbreq *);
|
|
static int veb_port_set_protected(struct veb_softc *,
|
|
const struct ifbreq *);
|
|
static int veb_add_addr(struct veb_softc *, const struct ifbareq *);
|
|
static int veb_del_addr(struct veb_softc *, const struct ifbareq *);
|
|
|
|
static int veb_rule_add(struct veb_softc *, const struct ifbrlreq *);
|
|
static int veb_rule_list_flush(struct veb_softc *,
|
|
const struct ifbrlreq *);
|
|
static void veb_rule_list_free(struct veb_rule *);
|
|
static int veb_rule_list_get(struct veb_softc *, struct ifbrlconf *);
|
|
|
|
static int veb_eb_port_cmp(void *, void *, void *);
|
|
static void *veb_eb_port_take(void *, void *);
|
|
static void veb_eb_port_rele(void *, void *);
|
|
static size_t veb_eb_port_ifname(void *, char *, size_t, void *);
|
|
static void veb_eb_port_sa(void *, struct sockaddr_storage *, void *);
|
|
|
|
static void veb_eb_brport_take(void *);
|
|
static void veb_eb_brport_rele(void *);
|
|
|
|
static const struct etherbridge_ops veb_etherbridge_ops = {
|
|
veb_eb_port_cmp,
|
|
veb_eb_port_take,
|
|
veb_eb_port_rele,
|
|
veb_eb_port_ifname,
|
|
veb_eb_port_sa,
|
|
};
|
|
|
|
static struct if_clone veb_cloner =
|
|
IF_CLONE_INITIALIZER("veb", veb_clone_create, veb_clone_destroy);
|
|
|
|
static struct pool veb_rule_pool;
|
|
|
|
static int vport_clone_create(struct if_clone *, int);
|
|
static int vport_clone_destroy(struct ifnet *);
|
|
|
|
struct vport_softc {
|
|
struct arpcom sc_ac;
|
|
unsigned int sc_dead;
|
|
};
|
|
|
|
static int vport_if_enqueue(struct ifnet *, struct mbuf *);
|
|
|
|
static int vport_ioctl(struct ifnet *, u_long, caddr_t);
|
|
static int vport_enqueue(struct ifnet *, struct mbuf *);
|
|
static void vport_start(struct ifqueue *);
|
|
|
|
static int vport_up(struct vport_softc *);
|
|
static int vport_down(struct vport_softc *);
|
|
static int vport_iff(struct vport_softc *);
|
|
|
|
static struct if_clone vport_cloner =
|
|
IF_CLONE_INITIALIZER("vport", vport_clone_create, vport_clone_destroy);
|
|
|
|
void
|
|
vebattach(int count)
|
|
{
|
|
if_clone_attach(&veb_cloner);
|
|
if_clone_attach(&vport_cloner);
|
|
}
|
|
|
|
static int
|
|
veb_clone_create(struct if_clone *ifc, int unit)
|
|
{
|
|
struct veb_softc *sc;
|
|
struct ifnet *ifp;
|
|
int error;
|
|
|
|
if (veb_rule_pool.pr_size == 0) {
|
|
pool_init(&veb_rule_pool, sizeof(struct veb_rule),
|
|
0, IPL_SOFTNET, 0, "vebrpl", NULL);
|
|
}
|
|
|
|
sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK|M_ZERO|M_CANFAIL);
|
|
if (sc == NULL)
|
|
return (ENOMEM);
|
|
|
|
rw_init(&sc->sc_rule_lock, "vebrlk");
|
|
sc->sc_ports = NULL;
|
|
sc->sc_spans = NULL;
|
|
|
|
ifp = &sc->sc_if;
|
|
|
|
snprintf(ifp->if_xname, IFNAMSIZ, "%s%d", ifc->ifc_name, unit);
|
|
|
|
error = etherbridge_init(&sc->sc_eb, ifp->if_xname,
|
|
&veb_etherbridge_ops, sc);
|
|
if (error != 0) {
|
|
free(sc, M_DEVBUF, sizeof(*sc));
|
|
return (error);
|
|
}
|
|
|
|
ifp->if_softc = sc;
|
|
ifp->if_type = IFT_BRIDGE;
|
|
ifp->if_hdrlen = ETHER_HDR_LEN;
|
|
ifp->if_hardmtu = ETHER_MAX_HARDMTU_LEN;
|
|
ifp->if_ioctl = veb_ioctl;
|
|
ifp->if_input = veb_input;
|
|
ifp->if_output = veb_output;
|
|
ifp->if_enqueue = veb_enqueue;
|
|
ifp->if_qstart = veb_start;
|
|
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
|
|
ifp->if_xflags = IFXF_CLONED | IFXF_MPSAFE;
|
|
|
|
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
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
veb_clone_destroy(struct ifnet *ifp)
|
|
{
|
|
struct veb_softc *sc = ifp->if_softc;
|
|
struct veb_ports *mp, *ms;
|
|
struct veb_port **ps;
|
|
struct veb_port *p;
|
|
unsigned int i;
|
|
|
|
NET_LOCK();
|
|
sc->sc_dead = 1;
|
|
|
|
if (ISSET(ifp->if_flags, IFF_RUNNING))
|
|
veb_down(sc);
|
|
NET_UNLOCK();
|
|
|
|
if_detach(ifp);
|
|
|
|
NET_LOCK();
|
|
|
|
/*
|
|
* this is an upside down version of veb_p_dtor() and
|
|
* veb_ports_destroy() to avoid a lot of malloc/free and
|
|
* smr_barrier calls if we remove ports one by one.
|
|
*/
|
|
|
|
mp = SMR_PTR_GET_LOCKED(&sc->sc_ports);
|
|
SMR_PTR_SET_LOCKED(&sc->sc_ports, NULL);
|
|
if (mp != NULL) {
|
|
ps = veb_ports_array(mp);
|
|
for (i = 0; i < mp->m_count; i++)
|
|
veb_p_unlink(sc, ps[i]);
|
|
}
|
|
|
|
ms = SMR_PTR_GET_LOCKED(&sc->sc_spans);
|
|
SMR_PTR_SET_LOCKED(&sc->sc_spans, NULL);
|
|
if (ms != NULL) {
|
|
ps = veb_ports_array(ms);
|
|
for (i = 0; i < ms->m_count; i++)
|
|
veb_p_unlink(sc, ps[i]);
|
|
}
|
|
|
|
if (mp != NULL || ms != NULL) {
|
|
smr_barrier(); /* everything everywhere all at once */
|
|
|
|
if (mp != NULL) {
|
|
refcnt_finalize(&mp->m_refs, "vebdtor");
|
|
|
|
ps = veb_ports_array(mp);
|
|
for (i = 0; i < mp->m_count; i++) {
|
|
p = ps[i];
|
|
/* the ports map holds a port ref */
|
|
refcnt_rele(&p->p_refs);
|
|
/* now we can finalize the port */
|
|
veb_p_fini(p);
|
|
}
|
|
|
|
veb_ports_free(mp);
|
|
}
|
|
if (ms != NULL) {
|
|
refcnt_finalize(&ms->m_refs, "vebdtor");
|
|
|
|
ps = veb_ports_array(ms);
|
|
for (i = 0; i < ms->m_count; i++) {
|
|
p = ps[i];
|
|
/* the ports map holds a port ref */
|
|
refcnt_rele(&p->p_refs);
|
|
/* now we can finalize the port */
|
|
veb_p_fini(p);
|
|
}
|
|
|
|
veb_ports_free(ms);
|
|
}
|
|
}
|
|
NET_UNLOCK();
|
|
|
|
etherbridge_destroy(&sc->sc_eb);
|
|
|
|
free(sc, M_DEVBUF, sizeof(*sc));
|
|
|
|
return (0);
|
|
}
|
|
|
|
static struct mbuf *
|
|
veb_span_input(struct ifnet *ifp0, struct mbuf *m, uint64_t dst, void *brport)
|
|
{
|
|
m_freem(m);
|
|
return (NULL);
|
|
}
|
|
|
|
static void
|
|
veb_span(struct veb_softc *sc, struct mbuf *m0)
|
|
{
|
|
struct veb_ports *sm;
|
|
struct veb_port **ps;
|
|
struct veb_port *p;
|
|
struct ifnet *ifp0;
|
|
struct mbuf *m;
|
|
unsigned int i;
|
|
|
|
smr_read_enter();
|
|
sm = SMR_PTR_GET(&sc->sc_spans);
|
|
if (sm != NULL)
|
|
refcnt_take(&sm->m_refs);
|
|
smr_read_leave();
|
|
if (sm == NULL)
|
|
return;
|
|
|
|
ps = veb_ports_array(sm);
|
|
for (i = 0; i < sm->m_count; i++) {
|
|
p = ps[i];
|
|
|
|
ifp0 = p->p_ifp0;
|
|
if (!ISSET(ifp0->if_flags, IFF_RUNNING))
|
|
continue;
|
|
|
|
m = m_dup_pkt(m0, max_linkhdr + ETHER_ALIGN, M_NOWAIT);
|
|
if (m == NULL) {
|
|
/* XXX count error */
|
|
continue;
|
|
}
|
|
|
|
if_enqueue(ifp0, m); /* XXX count error */
|
|
}
|
|
refcnt_rele_wake(&sm->m_refs);
|
|
}
|
|
|
|
static int
|
|
veb_ip_filter(const struct mbuf *m)
|
|
{
|
|
const struct ether_header *eh;
|
|
|
|
eh = mtod(m, struct ether_header *);
|
|
switch (ntohs(eh->ether_type)) {
|
|
case ETHERTYPE_IP:
|
|
case ETHERTYPE_ARP:
|
|
case ETHERTYPE_REVARP:
|
|
case ETHERTYPE_IPV6:
|
|
return (0);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return (1);
|
|
}
|
|
|
|
static int
|
|
veb_vlan_filter(const struct mbuf *m)
|
|
{
|
|
const struct ether_header *eh;
|
|
|
|
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
|
|
veb_rule_arp_match(const struct veb_rule *vr, struct mbuf *m)
|
|
{
|
|
struct ether_header *eh;
|
|
struct ether_arp ea;
|
|
|
|
eh = mtod(m, struct ether_header *);
|
|
|
|
if (eh->ether_type != htons(ETHERTYPE_ARP))
|
|
return (0);
|
|
if (m->m_pkthdr.len < sizeof(*eh) + sizeof(ea))
|
|
return (0);
|
|
|
|
m_copydata(m, sizeof(*eh), sizeof(ea), (caddr_t)&ea);
|
|
|
|
if (ea.arp_hrd != htons(ARPHRD_ETHER) ||
|
|
ea.arp_pro != htons(ETHERTYPE_IP) ||
|
|
ea.arp_hln != ETHER_ADDR_LEN ||
|
|
ea.arp_pln != sizeof(struct in_addr))
|
|
return (0);
|
|
|
|
if (ISSET(vr->vr_flags, VEB_R_F_ARP)) {
|
|
if (ea.arp_op != htons(ARPOP_REQUEST) &&
|
|
ea.arp_op != htons(ARPOP_REPLY))
|
|
return (0);
|
|
}
|
|
if (ISSET(vr->vr_flags, VEB_R_F_RARP)) {
|
|
if (ea.arp_op != htons(ARPOP_REVREQUEST) &&
|
|
ea.arp_op != htons(ARPOP_REVREPLY))
|
|
return (0);
|
|
}
|
|
|
|
if (vr->vr_arp_op != htons(0) && vr->vr_arp_op != ea.arp_op)
|
|
return (0);
|
|
|
|
if (ISSET(vr->vr_flags, VEB_R_F_SHA) &&
|
|
!ETHER_IS_EQ(&vr->vr_arp_sha, ea.arp_sha))
|
|
return (0);
|
|
if (ISSET(vr->vr_flags, VEB_R_F_THA) &&
|
|
!ETHER_IS_EQ(&vr->vr_arp_tha, ea.arp_tha))
|
|
return (0);
|
|
if (ISSET(vr->vr_flags, VEB_R_F_SPA) &&
|
|
memcmp(&vr->vr_arp_spa, ea.arp_spa, sizeof(vr->vr_arp_spa)) != 0)
|
|
return (0);
|
|
if (ISSET(vr->vr_flags, VEB_R_F_TPA) &&
|
|
memcmp(&vr->vr_arp_tpa, ea.arp_tpa, sizeof(vr->vr_arp_tpa)) != 0)
|
|
return (0);
|
|
|
|
return (1);
|
|
}
|
|
|
|
static int
|
|
veb_rule_list_test(struct veb_rule *vr, int dir, struct mbuf *m,
|
|
uint64_t src, uint64_t dst)
|
|
{
|
|
SMR_ASSERT_CRITICAL();
|
|
|
|
do {
|
|
if (ISSET(vr->vr_flags, VEB_R_F_ARP|VEB_R_F_RARP) &&
|
|
!veb_rule_arp_match(vr, m))
|
|
continue;
|
|
|
|
if (ISSET(vr->vr_flags, VEB_R_F_SRC) &&
|
|
vr->vr_src != src)
|
|
continue;
|
|
if (ISSET(vr->vr_flags, VEB_R_F_DST) &&
|
|
vr->vr_dst != dst)
|
|
continue;
|
|
|
|
if (vr->vr_action == VEB_R_BLOCK)
|
|
return (VEB_R_BLOCK);
|
|
#if NPF > 0
|
|
pf_tag_packet(m, vr->vr_pftag, -1);
|
|
#endif
|
|
if (vr->vr_action == VEB_R_PASS)
|
|
return (VEB_R_PASS);
|
|
} while ((vr = SMR_TAILQ_NEXT(vr, vr_lentry[dir])) != NULL);
|
|
|
|
return (VEB_R_PASS);
|
|
}
|
|
|
|
static inline int
|
|
veb_rule_filter(struct veb_port *p, int dir, struct mbuf *m,
|
|
uint64_t src, uint64_t dst)
|
|
{
|
|
struct veb_rule *vr;
|
|
int filter = VEB_R_PASS;
|
|
|
|
smr_read_enter();
|
|
vr = SMR_TAILQ_FIRST(&p->p_vr_list[dir]);
|
|
if (vr != NULL)
|
|
filter = veb_rule_list_test(vr, dir, m, src, dst);
|
|
smr_read_leave();
|
|
|
|
return (filter == VEB_R_BLOCK);
|
|
}
|
|
|
|
#if NPF > 0
|
|
struct veb_pf_ip_family {
|
|
sa_family_t af;
|
|
struct mbuf *(*ip_check)(struct ifnet *, struct mbuf *);
|
|
void (*ip_input)(struct ifnet *, struct mbuf *);
|
|
};
|
|
|
|
static const struct veb_pf_ip_family veb_pf_ipv4 = {
|
|
.af = AF_INET,
|
|
.ip_check = ipv4_check,
|
|
.ip_input = ipv4_input,
|
|
};
|
|
|
|
#ifdef INET6
|
|
static const struct veb_pf_ip_family veb_pf_ipv6 = {
|
|
.af = AF_INET6,
|
|
.ip_check = ipv6_check,
|
|
.ip_input = ipv6_input,
|
|
};
|
|
#endif
|
|
|
|
static struct mbuf *
|
|
veb_pf(struct ifnet *ifp0, int dir, struct mbuf *m)
|
|
{
|
|
struct ether_header *eh, copy;
|
|
const struct veb_pf_ip_family *fam;
|
|
int hlen;
|
|
|
|
/*
|
|
* pf runs on vport interfaces when they enter or leave the
|
|
* l3 stack, so don't confuse things (even more) by running
|
|
* pf again here. note that because of this exception the
|
|
* pf direction on vport interfaces is reversed compared to
|
|
* other veb ports.
|
|
*/
|
|
if (ifp0->if_enqueue == vport_enqueue)
|
|
return (m);
|
|
|
|
eh = mtod(m, struct ether_header *);
|
|
switch (ntohs(eh->ether_type)) {
|
|
case ETHERTYPE_IP:
|
|
fam = &veb_pf_ipv4;
|
|
break;
|
|
#ifdef INET6
|
|
case ETHERTYPE_IPV6:
|
|
fam = &veb_pf_ipv6;
|
|
break;
|
|
#endif
|
|
default:
|
|
return (m);
|
|
}
|
|
|
|
copy = *eh;
|
|
m_adj(m, sizeof(*eh));
|
|
|
|
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);
|
|
(*fam->ip_input)(ifp0, m);
|
|
return (NULL);
|
|
}
|
|
|
|
hlen = roundup(sizeof(*eh), sizeof(long));
|
|
m = m_prepend(m, hlen, M_DONTWAIT);
|
|
if (m == NULL)
|
|
return (NULL);
|
|
|
|
/* checksum? */
|
|
|
|
m_adj(m, hlen - sizeof(*eh));
|
|
eh = mtod(m, struct ether_header *);
|
|
*eh = copy;
|
|
|
|
return (m);
|
|
}
|
|
#endif /* NPF > 0 */
|
|
|
|
#if 0 && defined(IPSEC)
|
|
static struct mbuf *
|
|
veb_ipsec_proto_in(struct ifnet *ifp0, struct mbuf *m, int iphlen,
|
|
/* const */ union sockaddr_union *dst, int poff)
|
|
{
|
|
struct tdb *tdb;
|
|
uint16_t cpi;
|
|
uint32_t spi;
|
|
uint8_t proto;
|
|
|
|
/* ipsec_common_input checks for 8 bytes of input, so we do too */
|
|
if (m->m_pkthdr.len < iphlen + 2 * sizeof(u_int32_t))
|
|
return (m); /* decline */
|
|
|
|
proto = *(mtod(m, uint8_t *) + poff);
|
|
/* i'm not a huge fan of how these headers get picked at */
|
|
switch (proto) {
|
|
case IPPROTO_ESP:
|
|
m_copydata(m, iphlen, sizeof(spi), &spi);
|
|
break;
|
|
case IPPROTO_AH:
|
|
m_copydata(m, iphlen + sizeof(uint32_t), sizeof(spi), &spi);
|
|
break;
|
|
case IPPROTO_IPCOMP:
|
|
m_copydata(m, iphlen + sizeof(uint16_t), sizeof(cpi), &cpi);
|
|
spi = htonl(ntohs(cpi));
|
|
break;
|
|
default:
|
|
return (m); /* decline */
|
|
}
|
|
|
|
tdb = gettdb(m->m_pkthdr.ph_rtableid, spi, dst, proto);
|
|
if (tdb != NULL && !ISSET(tdb->tdb_flags, TDBF_INVALID) &&
|
|
tdb->tdb_xform != NULL) {
|
|
if (tdb->tdb_first_use == 0) {
|
|
tdb->tdb_first_use = gettime();
|
|
if (ISSET(tdb->tdb_flags, TDBF_FIRSTUSE)) {
|
|
timeout_add_sec(&tdb->tdb_first_tmo,
|
|
tdb->tdb_exp_first_use);
|
|
}
|
|
if (ISSET(tdb->tdb_flags, TDBF_SOFT_FIRSTUSE)) {
|
|
timeout_add_sec(&tdb->tdb_sfirst_tmo,
|
|
tdb->tdb_soft_first_use);
|
|
}
|
|
}
|
|
|
|
(*(tdb->tdb_xform->xf_input))(m, tdb, iphlen, poff);
|
|
return (NULL);
|
|
}
|
|
|
|
return (m);
|
|
}
|
|
|
|
static struct mbuf *
|
|
veb_ipsec_ipv4_in(struct ifnet *ifp0, struct mbuf *m)
|
|
{
|
|
union sockaddr_union su = {
|
|
.sin.sin_len = sizeof(su.sin),
|
|
.sin.sin_family = AF_INET,
|
|
};
|
|
struct ip *ip;
|
|
int iphlen;
|
|
|
|
if (m->m_len < sizeof(*ip)) {
|
|
m = m_pullup(m, sizeof(*ip));
|
|
if (m == NULL)
|
|
return (NULL);
|
|
}
|
|
|
|
ip = mtod(m, struct ip *);
|
|
iphlen = ip->ip_hl << 2;
|
|
if (iphlen < sizeof(*ip)) {
|
|
/* this is a weird packet, decline */
|
|
return (m);
|
|
}
|
|
|
|
su.sin.sin_addr = ip->ip_dst;
|
|
|
|
return (veb_ipsec_proto_in(ifp0, m, iphlen, &su,
|
|
offsetof(struct ip, ip_p)));
|
|
}
|
|
|
|
#ifdef INET6
|
|
static struct mbuf *
|
|
veb_ipsec_ipv6_in(struct ifnet *ifp0, struct mbuf *m)
|
|
{
|
|
union sockaddr_union su = {
|
|
.sin6.sin6_len = sizeof(su.sin6),
|
|
.sin6.sin6_family = AF_INET6,
|
|
};
|
|
struct ip6_hdr *ip6;
|
|
|
|
if (m->m_len < sizeof(*ip6)) {
|
|
m = m_pullup(m, sizeof(*ip6));
|
|
if (m == NULL)
|
|
return (NULL);
|
|
}
|
|
|
|
ip6 = mtod(m, struct ip6_hdr *);
|
|
|
|
su.sin6.sin6_addr = ip6->ip6_dst;
|
|
|
|
/* XXX scope? */
|
|
|
|
return (veb_ipsec_proto_in(ifp0, m, sizeof(*ip6), &su,
|
|
offsetof(struct ip6_hdr, ip6_nxt)));
|
|
}
|
|
#endif /* INET6 */
|
|
|
|
static struct mbuf *
|
|
veb_ipsec_in(struct ifnet *ifp0, struct mbuf *m)
|
|
{
|
|
struct mbuf *(*ipsec_ip_in)(struct ifnet *, struct mbuf *);
|
|
struct ether_header *eh, copy;
|
|
|
|
if (ifp0->if_enqueue == vport_enqueue)
|
|
return (m);
|
|
|
|
eh = mtod(m, struct ether_header *);
|
|
switch (ntohs(eh->ether_type)) {
|
|
case ETHERTYPE_IP:
|
|
ipsec_ip_in = veb_ipsec_ipv4_in;
|
|
break;
|
|
#ifdef INET6
|
|
case ETHERTYPE_IPV6:
|
|
ipsec_ip_in = veb_ipsec_ipv6_in;
|
|
break;
|
|
#endif /* INET6 */
|
|
default:
|
|
return (m);
|
|
}
|
|
|
|
copy = *eh;
|
|
m_adj(m, sizeof(*eh));
|
|
|
|
m = (*ipsec_ip_in)(ifp0, m);
|
|
if (m == NULL)
|
|
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);
|
|
}
|
|
|
|
static struct mbuf *
|
|
veb_ipsec_proto_out(struct mbuf *m, sa_family_t af, int iphlen)
|
|
{
|
|
struct tdb *tdb;
|
|
int error;
|
|
#if NPF > 0
|
|
struct ifnet *encifp;
|
|
#endif
|
|
|
|
tdb = ipsp_spd_lookup(m, af, iphlen, &error, IPSP_DIRECTION_OUT,
|
|
NULL, NULL, NULL);
|
|
if (tdb == NULL)
|
|
return (m);
|
|
|
|
#if NPF > 0
|
|
encifp = enc_getif(tdb->tdb_rdomain, tdb->tdb_tap);
|
|
if (encifp != NULL) {
|
|
if (pf_test(af, PF_OUT, encifp, &m) != PF_PASS) {
|
|
m_freem(m);
|
|
return (NULL);
|
|
}
|
|
if (m == NULL)
|
|
return (NULL);
|
|
}
|
|
#endif /* NPF > 0 */
|
|
|
|
/* XXX mtu checks */
|
|
|
|
(void)ipsp_process_packet(m, tdb, af, 0);
|
|
return (NULL);
|
|
}
|
|
|
|
static struct mbuf *
|
|
veb_ipsec_ipv4_out(struct mbuf *m)
|
|
{
|
|
struct ip *ip;
|
|
int iphlen;
|
|
|
|
if (m->m_len < sizeof(*ip)) {
|
|
m = m_pullup(m, sizeof(*ip));
|
|
if (m == NULL)
|
|
return (NULL);
|
|
}
|
|
|
|
ip = mtod(m, struct ip *);
|
|
iphlen = ip->ip_hl << 2;
|
|
if (iphlen < sizeof(*ip)) {
|
|
/* this is a weird packet, decline */
|
|
return (m);
|
|
}
|
|
|
|
return (veb_ipsec_proto_out(m, AF_INET, iphlen));
|
|
}
|
|
|
|
#ifdef INET6
|
|
static struct mbuf *
|
|
veb_ipsec_ipv6_out(struct mbuf *m)
|
|
{
|
|
return (veb_ipsec_proto_out(m, AF_INET6, sizeof(struct ip6_hdr)));
|
|
}
|
|
#endif /* INET6 */
|
|
|
|
static struct mbuf *
|
|
veb_ipsec_out(struct ifnet *ifp0, struct mbuf *m)
|
|
{
|
|
struct mbuf *(*ipsec_ip_out)(struct mbuf *);
|
|
struct ether_header *eh, copy;
|
|
|
|
if (ifp0->if_enqueue == vport_enqueue)
|
|
return (m);
|
|
|
|
eh = mtod(m, struct ether_header *);
|
|
switch (ntohs(eh->ether_type)) {
|
|
case ETHERTYPE_IP:
|
|
ipsec_ip_out = veb_ipsec_ipv4_out;
|
|
break;
|
|
#ifdef INET6
|
|
case ETHERTYPE_IPV6:
|
|
ipsec_ip_out = veb_ipsec_ipv6_out;
|
|
break;
|
|
#endif /* INET6 */
|
|
default:
|
|
return (m);
|
|
}
|
|
|
|
copy = *eh;
|
|
m_adj(m, sizeof(*eh));
|
|
|
|
m = (*ipsec_ip_out)(m);
|
|
if (m == NULL)
|
|
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 /* IPSEC */
|
|
|
|
static void
|
|
veb_broadcast(struct veb_softc *sc, struct veb_port *rp, struct mbuf *m0,
|
|
uint64_t src, uint64_t dst)
|
|
{
|
|
struct ifnet *ifp = &sc->sc_if;
|
|
struct veb_ports *pm;
|
|
struct veb_port **ps;
|
|
struct veb_port *tp;
|
|
struct ifnet *ifp0;
|
|
struct mbuf *m;
|
|
unsigned int i;
|
|
|
|
#if NPF > 0
|
|
/*
|
|
* we couldn't find a specific port to send this packet to,
|
|
* but pf should still have a chance to apply policy to it.
|
|
* let pf look at it, but use the veb interface as a proxy.
|
|
*/
|
|
if (ISSET(ifp->if_flags, IFF_LINK1) &&
|
|
(m0 = veb_pf(ifp, PF_FWD, m0)) == NULL)
|
|
return;
|
|
#endif
|
|
|
|
#if 0 && defined(IPSEC)
|
|
/* same goes for ipsec */
|
|
if (ISSET(ifp->if_flags, IFF_LINK2) &&
|
|
(m0 = veb_ipsec_out(ifp, m0)) == NULL)
|
|
return;
|
|
#endif
|
|
|
|
counters_pkt(ifp->if_counters, ifc_opackets, ifc_obytes,
|
|
m0->m_pkthdr.len);
|
|
|
|
smr_read_enter();
|
|
pm = SMR_PTR_GET(&sc->sc_ports);
|
|
if (__predict_true(pm != NULL))
|
|
refcnt_take(&pm->m_refs);
|
|
smr_read_leave();
|
|
if (__predict_false(pm == NULL))
|
|
goto done;
|
|
|
|
ps = veb_ports_array(pm);
|
|
for (i = 0; i < pm->m_count; i++) {
|
|
tp = ps[i];
|
|
|
|
if (rp == tp || (rp->p_protected & tp->p_protected)) {
|
|
/*
|
|
* don't let Ethernet packets hairpin or
|
|
* move between ports in the same protected
|
|
* domain(s).
|
|
*/
|
|
continue;
|
|
}
|
|
|
|
ifp0 = tp->p_ifp0;
|
|
if (!ISSET(ifp0->if_flags, IFF_RUNNING)) {
|
|
/* don't waste time */
|
|
continue;
|
|
}
|
|
|
|
if (!ISSET(tp->p_bif_flags, IFBIF_DISCOVER) &&
|
|
!ISSET(m0->m_flags, M_BCAST | M_MCAST)) {
|
|
/* don't flood unknown unicast */
|
|
continue;
|
|
}
|
|
|
|
if (veb_rule_filter(tp, VEB_RULE_LIST_OUT, m0, src, dst))
|
|
continue;
|
|
|
|
m = m_dup_pkt(m0, max_linkhdr + ETHER_ALIGN, M_NOWAIT);
|
|
if (m == NULL) {
|
|
/* XXX count error? */
|
|
continue;
|
|
}
|
|
|
|
(*tp->p_enqueue)(ifp0, m); /* XXX count error */
|
|
}
|
|
refcnt_rele_wake(&pm->m_refs);
|
|
|
|
done:
|
|
m_freem(m0);
|
|
}
|
|
|
|
static struct mbuf *
|
|
veb_transmit(struct veb_softc *sc, struct veb_port *rp, struct veb_port *tp,
|
|
struct mbuf *m, uint64_t src, uint64_t dst)
|
|
{
|
|
struct ifnet *ifp = &sc->sc_if;
|
|
struct ifnet *ifp0;
|
|
|
|
if (tp == NULL)
|
|
return (m);
|
|
|
|
if (rp == tp || (rp->p_protected & tp->p_protected)) {
|
|
/*
|
|
* don't let Ethernet packets hairpin or move between
|
|
* ports in the same protected domain(s).
|
|
*/
|
|
goto drop;
|
|
}
|
|
|
|
if (veb_rule_filter(tp, VEB_RULE_LIST_OUT, m, src, dst))
|
|
goto drop;
|
|
|
|
ifp0 = tp->p_ifp0;
|
|
|
|
#if 0 && defined(IPSEC)
|
|
if (ISSET(ifp->if_flags, IFF_LINK2) &&
|
|
(m = veb_ipsec_out(ifp0, m0)) == NULL)
|
|
return;
|
|
#endif
|
|
|
|
#if NPF > 0
|
|
if (ISSET(ifp->if_flags, IFF_LINK1) &&
|
|
(m = veb_pf(ifp0, PF_FWD, m)) == NULL)
|
|
return (NULL);
|
|
#endif
|
|
|
|
counters_pkt(ifp->if_counters, ifc_opackets, ifc_obytes,
|
|
m->m_pkthdr.len);
|
|
|
|
(*tp->p_enqueue)(ifp0, m); /* XXX count error */
|
|
|
|
return (NULL);
|
|
drop:
|
|
m_freem(m);
|
|
return (NULL);
|
|
}
|
|
|
|
static struct mbuf *
|
|
veb_vport_input(struct ifnet *ifp0, struct mbuf *m, uint64_t dst, void *brport)
|
|
{
|
|
return (m);
|
|
}
|
|
|
|
static struct mbuf *
|
|
veb_port_input(struct ifnet *ifp0, struct mbuf *m, uint64_t dst, void *brport)
|
|
{
|
|
struct veb_port *p = brport;
|
|
struct veb_softc *sc = p->p_veb;
|
|
struct ifnet *ifp = &sc->sc_if;
|
|
struct ether_header *eh;
|
|
uint64_t src;
|
|
#if NBPFILTER > 0
|
|
caddr_t if_bpf;
|
|
#endif
|
|
|
|
if (!ISSET(ifp->if_flags, IFF_RUNNING))
|
|
return (m);
|
|
|
|
eh = mtod(m, struct ether_header *);
|
|
src = ether_addr_to_e64((struct ether_addr *)eh->ether_shost);
|
|
|
|
/* Is this a MAC Bridge component Reserved address? */
|
|
if (ETH64_IS_8021_RSVD(dst)) {
|
|
if (!ISSET(ifp->if_flags, IFF_LINK0)) {
|
|
/*
|
|
* letting vlans through implies this is
|
|
* an s-vlan component.
|
|
*/
|
|
goto drop;
|
|
}
|
|
|
|
/* look at the last nibble of the 802.1 reserved address */
|
|
switch (dst & 0xf) {
|
|
case 0x0: /* Nearest Customer Bridge Group Address */
|
|
case 0xb: /* EDE-SS PEP (IEEE Std 802.1AEcg) */
|
|
case 0xc: /* reserved */
|
|
case 0xd: /* Provider Bridge MVRP Address */
|
|
case 0xf: /* reserved */
|
|
break;
|
|
default:
|
|
goto drop;
|
|
}
|
|
}
|
|
|
|
#if NVLAN > 0
|
|
/*
|
|
* If the underlying interface removed the VLAN header itself,
|
|
* add it back.
|
|
*/
|
|
if (ISSET(m->m_flags, M_VLANTAG)) {
|
|
m = vlan_inject(m, ETHERTYPE_VLAN, m->m_pkthdr.ether_vtag);
|
|
if (m == NULL) {
|
|
counters_inc(ifp->if_counters, ifc_ierrors);
|
|
goto drop;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
counters_pkt(ifp->if_counters, ifc_ipackets, ifc_ibytes,
|
|
m->m_pkthdr.len);
|
|
|
|
/* force packets into the one routing domain for pf */
|
|
m->m_pkthdr.ph_rtableid = ifp->if_rdomain;
|
|
|
|
#if NBPFILTER > 0
|
|
if_bpf = READ_ONCE(ifp->if_bpf);
|
|
if (if_bpf != NULL) {
|
|
if (bpf_mtap_ether(if_bpf, m, 0) != 0)
|
|
goto drop;
|
|
}
|
|
#endif
|
|
|
|
veb_span(sc, m);
|
|
|
|
if (ISSET(p->p_bif_flags, IFBIF_BLOCKNONIP) &&
|
|
veb_ip_filter(m))
|
|
goto drop;
|
|
|
|
if (!ISSET(ifp->if_flags, IFF_LINK0) &&
|
|
veb_vlan_filter(m))
|
|
goto drop;
|
|
|
|
if (veb_rule_filter(p, VEB_RULE_LIST_IN, m, src, dst))
|
|
goto drop;
|
|
|
|
#if NPF > 0
|
|
if (ISSET(ifp->if_flags, IFF_LINK1) &&
|
|
(m = veb_pf(ifp0, PF_IN, m)) == NULL)
|
|
return (NULL);
|
|
#endif
|
|
|
|
#if 0 && defined(IPSEC)
|
|
if (ISSET(ifp->if_flags, IFF_LINK2) &&
|
|
(m = veb_ipsec_in(ifp0, m)) == NULL)
|
|
return (NULL);
|
|
#endif
|
|
|
|
eh = mtod(m, struct ether_header *);
|
|
|
|
if (ISSET(p->p_bif_flags, IFBIF_LEARNING))
|
|
etherbridge_map(&sc->sc_eb, p, src);
|
|
|
|
CLR(m->m_flags, M_BCAST|M_MCAST);
|
|
|
|
if (!ETH64_IS_MULTICAST(dst)) {
|
|
struct veb_port *tp = NULL;
|
|
|
|
smr_read_enter();
|
|
tp = etherbridge_resolve(&sc->sc_eb, dst);
|
|
if (tp != NULL)
|
|
veb_eb_port_take(NULL, tp);
|
|
smr_read_leave();
|
|
if (tp != NULL) {
|
|
m = veb_transmit(sc, p, tp, m, src, dst);
|
|
veb_eb_port_rele(NULL, tp);
|
|
}
|
|
|
|
if (m == NULL)
|
|
return (NULL);
|
|
|
|
/* unknown unicast address */
|
|
} else {
|
|
SET(m->m_flags, ETH64_IS_BROADCAST(dst) ? M_BCAST : M_MCAST);
|
|
}
|
|
|
|
veb_broadcast(sc, p, m, src, dst);
|
|
return (NULL);
|
|
|
|
drop:
|
|
m_freem(m);
|
|
return (NULL);
|
|
}
|
|
|
|
static void
|
|
veb_input(struct ifnet *ifp, struct mbuf *m)
|
|
{
|
|
m_freem(m);
|
|
}
|
|
|
|
static int
|
|
veb_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst,
|
|
struct rtentry *rt)
|
|
{
|
|
m_freem(m);
|
|
return (ENODEV);
|
|
}
|
|
|
|
static int
|
|
veb_enqueue(struct ifnet *ifp, struct mbuf *m)
|
|
{
|
|
m_freem(m);
|
|
return (ENODEV);
|
|
}
|
|
|
|
static void
|
|
veb_start(struct ifqueue *ifq)
|
|
{
|
|
ifq_purge(ifq);
|
|
}
|
|
|
|
static int
|
|
veb_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
|
|
{
|
|
struct veb_softc *sc = ifp->if_softc;
|
|
struct ifbrparam *bparam = (struct ifbrparam *)data;
|
|
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 = veb_up(sc);
|
|
} else {
|
|
if (ISSET(ifp->if_flags, IFF_RUNNING))
|
|
error = veb_down(sc);
|
|
}
|
|
break;
|
|
|
|
case SIOCBRDGADD:
|
|
error = suser(curproc);
|
|
if (error != 0)
|
|
break;
|
|
|
|
error = veb_add_port(sc, (struct ifbreq *)data, 0);
|
|
break;
|
|
case SIOCBRDGADDS:
|
|
error = suser(curproc);
|
|
if (error != 0)
|
|
break;
|
|
|
|
error = veb_add_port(sc, (struct ifbreq *)data, 1);
|
|
break;
|
|
case SIOCBRDGDEL:
|
|
error = suser(curproc);
|
|
if (error != 0)
|
|
break;
|
|
|
|
error = veb_del_port(sc, (struct ifbreq *)data, 0);
|
|
break;
|
|
case SIOCBRDGDELS:
|
|
error = suser(curproc);
|
|
if (error != 0)
|
|
break;
|
|
|
|
error = veb_del_port(sc, (struct ifbreq *)data, 1);
|
|
break;
|
|
|
|
case SIOCBRDGSCACHE:
|
|
error = suser(curproc);
|
|
if (error != 0)
|
|
break;
|
|
|
|
error = etherbridge_set_max(&sc->sc_eb, bparam);
|
|
break;
|
|
case SIOCBRDGGCACHE:
|
|
error = etherbridge_get_max(&sc->sc_eb, bparam);
|
|
break;
|
|
|
|
case SIOCBRDGSTO:
|
|
error = suser(curproc);
|
|
if (error != 0)
|
|
break;
|
|
|
|
error = etherbridge_set_tmo(&sc->sc_eb, bparam);
|
|
break;
|
|
case SIOCBRDGGTO:
|
|
error = etherbridge_get_tmo(&sc->sc_eb, bparam);
|
|
break;
|
|
|
|
case SIOCBRDGRTS:
|
|
error = etherbridge_rtfind(&sc->sc_eb, (struct ifbaconf *)data);
|
|
break;
|
|
case SIOCBRDGIFS:
|
|
error = veb_port_list(sc, (struct ifbifconf *)data);
|
|
break;
|
|
case SIOCBRDGFLUSH:
|
|
etherbridge_flush(&sc->sc_eb,
|
|
((struct ifbreq *)data)->ifbr_ifsflags);
|
|
break;
|
|
case SIOCBRDGSADDR:
|
|
error = veb_add_addr(sc, (struct ifbareq *)data);
|
|
break;
|
|
case SIOCBRDGDADDR:
|
|
error = veb_del_addr(sc, (struct ifbareq *)data);
|
|
break;
|
|
|
|
case SIOCBRDGSIFPROT:
|
|
error = veb_port_set_protected(sc, (struct ifbreq *)data);
|
|
break;
|
|
|
|
case SIOCBRDGSIFFLGS:
|
|
error = veb_port_set_flags(sc, (struct ifbreq *)data);
|
|
break;
|
|
case SIOCBRDGGIFFLGS:
|
|
error = veb_port_get_flags(sc, (struct ifbreq *)data);
|
|
break;
|
|
|
|
case SIOCBRDGARL:
|
|
error = veb_rule_add(sc, (struct ifbrlreq *)data);
|
|
break;
|
|
case SIOCBRDGFRL:
|
|
error = veb_rule_list_flush(sc, (struct ifbrlreq *)data);
|
|
break;
|
|
case SIOCBRDGGRL:
|
|
error = veb_rule_list_get(sc, (struct ifbrlconf *)data);
|
|
break;
|
|
|
|
default:
|
|
error = ENOTTY;
|
|
break;
|
|
}
|
|
|
|
if (error == ENETRESET)
|
|
error = veb_iff(sc);
|
|
|
|
return (error);
|
|
}
|
|
|
|
static struct veb_ports *
|
|
veb_ports_insert(struct veb_ports *om, struct veb_port *p)
|
|
{
|
|
struct veb_ports *nm;
|
|
struct veb_port **nps, **ops;
|
|
unsigned int ocount = om != NULL ? om->m_count : 0;
|
|
unsigned int ncount = ocount + 1;
|
|
unsigned int i;
|
|
|
|
nm = malloc(veb_ports_size(ncount), M_DEVBUF, M_WAITOK|M_ZERO);
|
|
|
|
refcnt_init(&nm->m_refs);
|
|
nm->m_count = ncount;
|
|
|
|
nps = veb_ports_array(nm);
|
|
|
|
if (om != NULL) {
|
|
ops = veb_ports_array(om);
|
|
for (i = 0; i < ocount; i++) {
|
|
struct veb_port *op = ops[i];
|
|
refcnt_take(&op->p_refs);
|
|
nps[i] = op;
|
|
}
|
|
} else
|
|
i = 0;
|
|
|
|
refcnt_take(&p->p_refs);
|
|
nps[i] = p;
|
|
|
|
return (nm);
|
|
}
|
|
|
|
static struct veb_ports *
|
|
veb_ports_remove(struct veb_ports *om, struct veb_port *p)
|
|
{
|
|
struct veb_ports *nm;
|
|
struct veb_port **nps, **ops;
|
|
unsigned int ocount = om->m_count;
|
|
unsigned int ncount = ocount - 1;
|
|
unsigned int i, j;
|
|
|
|
if (ncount == 0)
|
|
return (NULL);
|
|
|
|
nm = malloc(veb_ports_size(ncount), M_DEVBUF, M_WAITOK|M_ZERO);
|
|
|
|
refcnt_init(&nm->m_refs);
|
|
nm->m_count = ncount;
|
|
|
|
nps = veb_ports_array(nm);
|
|
j = 0;
|
|
|
|
ops = veb_ports_array(om);
|
|
for (i = 0; i < ocount; i++) {
|
|
struct veb_port *op = ops[i];
|
|
if (op == p)
|
|
continue;
|
|
|
|
refcnt_take(&op->p_refs);
|
|
nps[j++] = op;
|
|
}
|
|
KASSERT(j == ncount);
|
|
|
|
return (nm);
|
|
}
|
|
|
|
static inline void
|
|
veb_ports_free(struct veb_ports *m)
|
|
{
|
|
free(m, M_DEVBUF, veb_ports_size(m->m_count));
|
|
}
|
|
|
|
static void
|
|
veb_ports_destroy(struct veb_ports *m)
|
|
{
|
|
struct veb_port **ps = veb_ports_array(m);
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < m->m_count; i++) {
|
|
struct veb_port *p = ps[i];
|
|
refcnt_rele_wake(&p->p_refs);
|
|
}
|
|
|
|
veb_ports_free(m);
|
|
}
|
|
|
|
static int
|
|
veb_add_port(struct veb_softc *sc, const struct ifbreq *req, unsigned int span)
|
|
{
|
|
struct ifnet *ifp = &sc->sc_if;
|
|
struct ifnet *ifp0;
|
|
struct veb_ports **ports_ptr;
|
|
struct veb_ports *om, *nm;
|
|
struct veb_port *p;
|
|
int isvport;
|
|
int error;
|
|
|
|
NET_ASSERT_LOCKED();
|
|
|
|
ifp0 = if_unit(req->ifbr_ifsname);
|
|
if (ifp0 == NULL)
|
|
return (EINVAL);
|
|
|
|
if (ifp0->if_type != IFT_ETHER) {
|
|
error = EPROTONOSUPPORT;
|
|
goto put;
|
|
}
|
|
|
|
if (ifp0 == ifp) {
|
|
error = EPROTONOSUPPORT;
|
|
goto put;
|
|
}
|
|
|
|
isvport = (ifp0->if_enqueue == vport_enqueue);
|
|
|
|
error = ether_brport_isset(ifp0);
|
|
if (error != 0)
|
|
goto put;
|
|
|
|
/* let's try */
|
|
|
|
p = malloc(sizeof(*p), M_DEVBUF, M_WAITOK|M_ZERO|M_CANFAIL);
|
|
if (p == NULL) {
|
|
error = ENOMEM;
|
|
goto put;
|
|
}
|
|
|
|
ifsetlro(ifp0, 0);
|
|
|
|
p->p_ifp0 = ifp0;
|
|
p->p_veb = sc;
|
|
|
|
refcnt_init(&p->p_refs);
|
|
TAILQ_INIT(&p->p_vrl);
|
|
SMR_TAILQ_INIT(&p->p_vr_list[0]);
|
|
SMR_TAILQ_INIT(&p->p_vr_list[1]);
|
|
|
|
p->p_enqueue = isvport ? vport_if_enqueue : if_enqueue;
|
|
p->p_ioctl = ifp0->if_ioctl;
|
|
p->p_output = ifp0->if_output;
|
|
|
|
if (span) {
|
|
ports_ptr = &sc->sc_spans;
|
|
|
|
if (isvport) {
|
|
error = EPROTONOSUPPORT;
|
|
goto free;
|
|
}
|
|
|
|
p->p_brport.eb_input = veb_span_input;
|
|
p->p_bif_flags = IFBIF_SPAN;
|
|
} else {
|
|
ports_ptr = &sc->sc_ports;
|
|
|
|
error = ifpromisc(ifp0, 1);
|
|
if (error != 0)
|
|
goto free;
|
|
|
|
p->p_bif_flags = IFBIF_LEARNING | IFBIF_DISCOVER;
|
|
p->p_brport.eb_input = isvport ?
|
|
veb_vport_input : veb_port_input;
|
|
}
|
|
|
|
p->p_brport.eb_port_take = veb_eb_brport_take;
|
|
p->p_brport.eb_port_rele = veb_eb_brport_rele;
|
|
|
|
om = SMR_PTR_GET_LOCKED(ports_ptr);
|
|
nm = veb_ports_insert(om, p);
|
|
|
|
/* this might have changed if we slept for malloc or ifpromisc */
|
|
error = ether_brport_isset(ifp0);
|
|
if (error != 0)
|
|
goto unpromisc;
|
|
|
|
task_set(&p->p_ltask, veb_p_linkch, p);
|
|
if_linkstatehook_add(ifp0, &p->p_ltask);
|
|
|
|
task_set(&p->p_dtask, veb_p_detach, p);
|
|
if_detachhook_add(ifp0, &p->p_dtask);
|
|
|
|
p->p_brport.eb_port = p;
|
|
|
|
/* commit */
|
|
SMR_PTR_SET_LOCKED(ports_ptr, nm);
|
|
|
|
ether_brport_set(ifp0, &p->p_brport);
|
|
if (!isvport) { /* vport is special */
|
|
ifp0->if_ioctl = veb_p_ioctl;
|
|
ifp0->if_output = veb_p_output;
|
|
}
|
|
|
|
veb_p_linkch(p);
|
|
|
|
/* clean up the old veb_ports map */
|
|
smr_barrier();
|
|
if (om != NULL) {
|
|
refcnt_finalize(&om->m_refs, "vebports");
|
|
veb_ports_destroy(om);
|
|
}
|
|
|
|
return (0);
|
|
|
|
unpromisc:
|
|
if (!span)
|
|
ifpromisc(ifp0, 0);
|
|
free:
|
|
free(p, M_DEVBUF, sizeof(*p));
|
|
put:
|
|
if_put(ifp0);
|
|
return (error);
|
|
}
|
|
|
|
static struct veb_port *
|
|
veb_trunkport(struct veb_softc *sc, const char *name, unsigned int span)
|
|
{
|
|
struct veb_ports *m;
|
|
struct veb_port **ps;
|
|
struct veb_port *p;
|
|
unsigned int i;
|
|
|
|
m = SMR_PTR_GET_LOCKED(span ? &sc->sc_spans : &sc->sc_ports);
|
|
if (m == NULL)
|
|
return (NULL);
|
|
|
|
ps = veb_ports_array(m);
|
|
for (i = 0; i < m->m_count; i++) {
|
|
p = ps[i];
|
|
|
|
if (strncmp(p->p_ifp0->if_xname, name, IFNAMSIZ) == 0)
|
|
return (p);
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
static int
|
|
veb_del_port(struct veb_softc *sc, const struct ifbreq *req, unsigned int span)
|
|
{
|
|
struct veb_port *p;
|
|
|
|
NET_ASSERT_LOCKED();
|
|
p = veb_trunkport(sc, req->ifbr_ifsname, span);
|
|
if (p == NULL)
|
|
return (EINVAL);
|
|
|
|
veb_p_dtor(sc, p);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static struct veb_port *
|
|
veb_port_get(struct veb_softc *sc, const char *name)
|
|
{
|
|
struct veb_ports *m;
|
|
struct veb_port **ps;
|
|
struct veb_port *p;
|
|
unsigned int i;
|
|
|
|
NET_ASSERT_LOCKED();
|
|
|
|
m = SMR_PTR_GET_LOCKED(&sc->sc_ports);
|
|
if (m == NULL)
|
|
return (NULL);
|
|
|
|
ps = veb_ports_array(m);
|
|
for (i = 0; i < m->m_count; i++) {
|
|
p = ps[i];
|
|
|
|
if (strncmp(p->p_ifp0->if_xname, name, IFNAMSIZ) == 0) {
|
|
refcnt_take(&p->p_refs);
|
|
return (p);
|
|
}
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
static void
|
|
veb_port_put(struct veb_softc *sc, struct veb_port *p)
|
|
{
|
|
refcnt_rele_wake(&p->p_refs);
|
|
}
|
|
|
|
static int
|
|
veb_port_set_protected(struct veb_softc *sc, const struct ifbreq *ifbr)
|
|
{
|
|
struct veb_port *p;
|
|
|
|
p = veb_port_get(sc, ifbr->ifbr_ifsname);
|
|
if (p == NULL)
|
|
return (ESRCH);
|
|
|
|
p->p_protected = ifbr->ifbr_protected;
|
|
veb_port_put(sc, p);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
veb_rule_add(struct veb_softc *sc, const struct ifbrlreq *ifbr)
|
|
{
|
|
const struct ifbrarpf *brla = &ifbr->ifbr_arpf;
|
|
struct veb_rule vr, *vrp;
|
|
struct veb_port *p;
|
|
int error;
|
|
|
|
memset(&vr, 0, sizeof(vr));
|
|
|
|
switch (ifbr->ifbr_action) {
|
|
case BRL_ACTION_BLOCK:
|
|
vr.vr_action = VEB_R_BLOCK;
|
|
break;
|
|
case BRL_ACTION_PASS:
|
|
vr.vr_action = VEB_R_PASS;
|
|
break;
|
|
/* XXX VEB_R_MATCH */
|
|
default:
|
|
return (EINVAL);
|
|
}
|
|
|
|
if (!ISSET(ifbr->ifbr_flags, BRL_FLAG_IN|BRL_FLAG_OUT))
|
|
return (EINVAL);
|
|
if (ISSET(ifbr->ifbr_flags, BRL_FLAG_IN))
|
|
SET(vr.vr_flags, VEB_R_F_IN);
|
|
if (ISSET(ifbr->ifbr_flags, BRL_FLAG_OUT))
|
|
SET(vr.vr_flags, VEB_R_F_OUT);
|
|
|
|
if (ISSET(ifbr->ifbr_flags, BRL_FLAG_SRCVALID)) {
|
|
SET(vr.vr_flags, VEB_R_F_SRC);
|
|
vr.vr_src = ether_addr_to_e64(&ifbr->ifbr_src);
|
|
}
|
|
if (ISSET(ifbr->ifbr_flags, BRL_FLAG_DSTVALID)) {
|
|
SET(vr.vr_flags, VEB_R_F_DST);
|
|
vr.vr_dst = ether_addr_to_e64(&ifbr->ifbr_dst);
|
|
}
|
|
|
|
/* ARP rule */
|
|
if (ISSET(brla->brla_flags, BRLA_ARP|BRLA_RARP)) {
|
|
if (ISSET(brla->brla_flags, BRLA_ARP))
|
|
SET(vr.vr_flags, VEB_R_F_ARP);
|
|
if (ISSET(brla->brla_flags, BRLA_RARP))
|
|
SET(vr.vr_flags, VEB_R_F_RARP);
|
|
|
|
if (ISSET(brla->brla_flags, BRLA_SHA)) {
|
|
SET(vr.vr_flags, VEB_R_F_SHA);
|
|
vr.vr_arp_sha = brla->brla_sha;
|
|
}
|
|
if (ISSET(brla->brla_flags, BRLA_THA)) {
|
|
SET(vr.vr_flags, VEB_R_F_THA);
|
|
vr.vr_arp_tha = brla->brla_tha;
|
|
}
|
|
if (ISSET(brla->brla_flags, BRLA_SPA)) {
|
|
SET(vr.vr_flags, VEB_R_F_SPA);
|
|
vr.vr_arp_spa = brla->brla_spa;
|
|
}
|
|
if (ISSET(brla->brla_flags, BRLA_TPA)) {
|
|
SET(vr.vr_flags, VEB_R_F_TPA);
|
|
vr.vr_arp_tpa = brla->brla_tpa;
|
|
}
|
|
vr.vr_arp_op = htons(brla->brla_op);
|
|
}
|
|
|
|
if (ifbr->ifbr_tagname[0] != '\0') {
|
|
#if NPF > 0
|
|
vr.vr_pftag = pf_tagname2tag((char *)ifbr->ifbr_tagname, 1);
|
|
if (vr.vr_pftag == 0)
|
|
return (ENOMEM);
|
|
#else
|
|
return (EINVAL);
|
|
#endif
|
|
}
|
|
|
|
p = veb_port_get(sc, ifbr->ifbr_ifsname);
|
|
if (p == NULL) {
|
|
error = ESRCH;
|
|
goto error;
|
|
}
|
|
|
|
vrp = pool_get(&veb_rule_pool, PR_WAITOK|PR_LIMITFAIL|PR_ZERO);
|
|
if (vrp == NULL) {
|
|
error = ENOMEM;
|
|
goto port_put;
|
|
}
|
|
|
|
*vrp = vr;
|
|
|
|
/* there's one big lock on a veb for all ports */
|
|
error = rw_enter(&sc->sc_rule_lock, RW_WRITE|RW_INTR);
|
|
if (error != 0)
|
|
goto rule_put;
|
|
|
|
TAILQ_INSERT_TAIL(&p->p_vrl, vrp, vr_entry);
|
|
p->p_nvrl++;
|
|
if (ISSET(vr.vr_flags, VEB_R_F_OUT)) {
|
|
SMR_TAILQ_INSERT_TAIL_LOCKED(&p->p_vr_list[0],
|
|
vrp, vr_lentry[0]);
|
|
}
|
|
if (ISSET(vr.vr_flags, VEB_R_F_IN)) {
|
|
SMR_TAILQ_INSERT_TAIL_LOCKED(&p->p_vr_list[1],
|
|
vrp, vr_lentry[1]);
|
|
}
|
|
|
|
rw_exit(&sc->sc_rule_lock);
|
|
veb_port_put(sc, p);
|
|
|
|
return (0);
|
|
|
|
rule_put:
|
|
pool_put(&veb_rule_pool, vrp);
|
|
port_put:
|
|
veb_port_put(sc, p);
|
|
error:
|
|
#if NPF > 0
|
|
pf_tag_unref(vr.vr_pftag);
|
|
#endif
|
|
return (error);
|
|
}
|
|
|
|
static void
|
|
veb_rule_list_free(struct veb_rule *nvr)
|
|
{
|
|
struct veb_rule *vr;
|
|
|
|
while ((vr = nvr) != NULL) {
|
|
nvr = TAILQ_NEXT(vr, vr_entry);
|
|
pool_put(&veb_rule_pool, vr);
|
|
}
|
|
}
|
|
|
|
static int
|
|
veb_rule_list_flush(struct veb_softc *sc, const struct ifbrlreq *ifbr)
|
|
{
|
|
struct veb_port *p;
|
|
struct veb_rule *vr;
|
|
int error;
|
|
|
|
p = veb_port_get(sc, ifbr->ifbr_ifsname);
|
|
if (p == NULL)
|
|
return (ESRCH);
|
|
|
|
error = rw_enter(&sc->sc_rule_lock, RW_WRITE|RW_INTR);
|
|
if (error != 0) {
|
|
veb_port_put(sc, p);
|
|
return (error);
|
|
}
|
|
|
|
/* take all the rules away */
|
|
vr = TAILQ_FIRST(&p->p_vrl);
|
|
|
|
/* reset the lists and counts of rules */
|
|
TAILQ_INIT(&p->p_vrl);
|
|
p->p_nvrl = 0;
|
|
SMR_TAILQ_INIT(&p->p_vr_list[0]);
|
|
SMR_TAILQ_INIT(&p->p_vr_list[1]);
|
|
|
|
rw_exit(&sc->sc_rule_lock);
|
|
veb_port_put(sc, p);
|
|
|
|
smr_barrier();
|
|
veb_rule_list_free(vr);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
veb_rule2ifbr(struct ifbrlreq *ifbr, const struct veb_rule *vr)
|
|
{
|
|
switch (vr->vr_action) {
|
|
case VEB_R_PASS:
|
|
ifbr->ifbr_action = BRL_ACTION_PASS;
|
|
break;
|
|
case VEB_R_BLOCK:
|
|
ifbr->ifbr_action = BRL_ACTION_BLOCK;
|
|
break;
|
|
}
|
|
|
|
if (ISSET(vr->vr_flags, VEB_R_F_IN))
|
|
SET(ifbr->ifbr_flags, BRL_FLAG_IN);
|
|
if (ISSET(vr->vr_flags, VEB_R_F_OUT))
|
|
SET(ifbr->ifbr_flags, BRL_FLAG_OUT);
|
|
|
|
if (ISSET(vr->vr_flags, VEB_R_F_SRC)) {
|
|
SET(ifbr->ifbr_flags, BRL_FLAG_SRCVALID);
|
|
ether_e64_to_addr(&ifbr->ifbr_src, vr->vr_src);
|
|
}
|
|
if (ISSET(vr->vr_flags, VEB_R_F_DST)) {
|
|
SET(ifbr->ifbr_flags, BRL_FLAG_DSTVALID);
|
|
ether_e64_to_addr(&ifbr->ifbr_dst, vr->vr_dst);
|
|
}
|
|
|
|
/* ARP rule */
|
|
if (ISSET(vr->vr_flags, VEB_R_F_ARP|VEB_R_F_RARP)) {
|
|
struct ifbrarpf *brla = &ifbr->ifbr_arpf;
|
|
|
|
if (ISSET(vr->vr_flags, VEB_R_F_ARP))
|
|
SET(brla->brla_flags, BRLA_ARP);
|
|
if (ISSET(vr->vr_flags, VEB_R_F_RARP))
|
|
SET(brla->brla_flags, BRLA_RARP);
|
|
|
|
if (ISSET(vr->vr_flags, VEB_R_F_SHA)) {
|
|
SET(brla->brla_flags, BRLA_SHA);
|
|
brla->brla_sha = vr->vr_arp_sha;
|
|
}
|
|
if (ISSET(vr->vr_flags, VEB_R_F_THA)) {
|
|
SET(brla->brla_flags, BRLA_THA);
|
|
brla->brla_tha = vr->vr_arp_tha;
|
|
}
|
|
|
|
if (ISSET(vr->vr_flags, VEB_R_F_SPA)) {
|
|
SET(brla->brla_flags, BRLA_SPA);
|
|
brla->brla_spa = vr->vr_arp_spa;
|
|
}
|
|
if (ISSET(vr->vr_flags, VEB_R_F_TPA)) {
|
|
SET(brla->brla_flags, BRLA_TPA);
|
|
brla->brla_tpa = vr->vr_arp_tpa;
|
|
}
|
|
|
|
brla->brla_op = ntohs(vr->vr_arp_op);
|
|
}
|
|
|
|
#if NPF > 0
|
|
if (vr->vr_pftag != 0)
|
|
pf_tag2tagname(vr->vr_pftag, ifbr->ifbr_tagname);
|
|
#endif
|
|
}
|
|
|
|
static int
|
|
veb_rule_list_get(struct veb_softc *sc, struct ifbrlconf *ifbrl)
|
|
{
|
|
struct veb_port *p;
|
|
struct veb_rule *vr;
|
|
struct ifbrlreq *ifbr, *ifbrs;
|
|
int error = 0;
|
|
size_t len;
|
|
|
|
p = veb_port_get(sc, ifbrl->ifbrl_ifsname);
|
|
if (p == NULL)
|
|
return (ESRCH);
|
|
|
|
len = p->p_nvrl; /* estimate */
|
|
if (ifbrl->ifbrl_len == 0 || len == 0) {
|
|
ifbrl->ifbrl_len = len * sizeof(*ifbrs);
|
|
goto port_put;
|
|
}
|
|
|
|
error = rw_enter(&sc->sc_rule_lock, RW_READ|RW_INTR);
|
|
if (error != 0)
|
|
goto port_put;
|
|
|
|
ifbrs = mallocarray(p->p_nvrl, sizeof(*ifbrs), M_TEMP,
|
|
M_WAITOK|M_CANFAIL|M_ZERO);
|
|
if (ifbrs == NULL) {
|
|
rw_exit(&sc->sc_rule_lock);
|
|
goto port_put;
|
|
}
|
|
len = p->p_nvrl * sizeof(*ifbrs);
|
|
|
|
ifbr = ifbrs;
|
|
TAILQ_FOREACH(vr, &p->p_vrl, vr_entry) {
|
|
strlcpy(ifbr->ifbr_name, sc->sc_if.if_xname, IFNAMSIZ);
|
|
strlcpy(ifbr->ifbr_ifsname, p->p_ifp0->if_xname, IFNAMSIZ);
|
|
veb_rule2ifbr(ifbr, vr);
|
|
|
|
ifbr++;
|
|
}
|
|
|
|
rw_exit(&sc->sc_rule_lock);
|
|
|
|
error = copyout(ifbrs, ifbrl->ifbrl_buf, min(len, ifbrl->ifbrl_len));
|
|
if (error == 0)
|
|
ifbrl->ifbrl_len = len;
|
|
free(ifbrs, M_TEMP, len);
|
|
|
|
port_put:
|
|
veb_port_put(sc, p);
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
veb_port_list(struct veb_softc *sc, struct ifbifconf *bifc)
|
|
{
|
|
struct ifnet *ifp = &sc->sc_if;
|
|
struct veb_ports *m;
|
|
struct veb_port **ps;
|
|
struct veb_port *p;
|
|
struct ifnet *ifp0;
|
|
struct ifbreq breq;
|
|
int n = 0, error = 0;
|
|
unsigned int i;
|
|
|
|
NET_ASSERT_LOCKED();
|
|
|
|
if (bifc->ifbic_len == 0) {
|
|
m = SMR_PTR_GET_LOCKED(&sc->sc_ports);
|
|
if (m != NULL)
|
|
n += m->m_count;
|
|
m = SMR_PTR_GET_LOCKED(&sc->sc_spans);
|
|
if (m != NULL)
|
|
n += m->m_count;
|
|
goto done;
|
|
}
|
|
|
|
m = SMR_PTR_GET_LOCKED(&sc->sc_ports);
|
|
if (m != NULL) {
|
|
ps = veb_ports_array(m);
|
|
for (i = 0; i < m->m_count; i++) {
|
|
if (bifc->ifbic_len < sizeof(breq))
|
|
break;
|
|
|
|
p = ps[i];
|
|
|
|
memset(&breq, 0, sizeof(breq));
|
|
|
|
ifp0 = p->p_ifp0;
|
|
|
|
strlcpy(breq.ifbr_name, ifp->if_xname, IFNAMSIZ);
|
|
strlcpy(breq.ifbr_ifsname, ifp0->if_xname, IFNAMSIZ);
|
|
|
|
breq.ifbr_ifsflags = p->p_bif_flags;
|
|
breq.ifbr_portno = ifp0->if_index;
|
|
breq.ifbr_protected = p->p_protected;
|
|
if ((error = copyout(&breq, bifc->ifbic_req + n,
|
|
sizeof(breq))) != 0)
|
|
goto done;
|
|
|
|
bifc->ifbic_len -= sizeof(breq);
|
|
n++;
|
|
}
|
|
}
|
|
|
|
m = SMR_PTR_GET_LOCKED(&sc->sc_spans);
|
|
if (m != NULL) {
|
|
ps = veb_ports_array(m);
|
|
for (i = 0; i < m->m_count; i++) {
|
|
if (bifc->ifbic_len < sizeof(breq))
|
|
break;
|
|
|
|
p = ps[i];
|
|
|
|
memset(&breq, 0, sizeof(breq));
|
|
|
|
strlcpy(breq.ifbr_name, ifp->if_xname, IFNAMSIZ);
|
|
strlcpy(breq.ifbr_ifsname, p->p_ifp0->if_xname,
|
|
IFNAMSIZ);
|
|
|
|
breq.ifbr_ifsflags = p->p_bif_flags;
|
|
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
|
|
veb_port_set_flags(struct veb_softc *sc, struct ifbreq *ifbr)
|
|
{
|
|
struct veb_port *p;
|
|
|
|
if (ISSET(ifbr->ifbr_ifsflags, ~VEB_IFBIF_FLAGS))
|
|
return (EINVAL);
|
|
|
|
p = veb_port_get(sc, ifbr->ifbr_ifsname);
|
|
if (p == NULL)
|
|
return (ESRCH);
|
|
|
|
p->p_bif_flags = ifbr->ifbr_ifsflags;
|
|
|
|
veb_port_put(sc, p);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
veb_port_get_flags(struct veb_softc *sc, struct ifbreq *ifbr)
|
|
{
|
|
struct veb_port *p;
|
|
|
|
p = veb_port_get(sc, ifbr->ifbr_ifsname);
|
|
if (p == NULL)
|
|
return (ESRCH);
|
|
|
|
ifbr->ifbr_ifsflags = p->p_bif_flags;
|
|
ifbr->ifbr_portno = p->p_ifp0->if_index;
|
|
ifbr->ifbr_protected = p->p_protected;
|
|
|
|
veb_port_put(sc, p);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
veb_add_addr(struct veb_softc *sc, const struct ifbareq *ifba)
|
|
{
|
|
struct veb_port *p;
|
|
int error = 0;
|
|
unsigned int type;
|
|
|
|
if (ISSET(ifba->ifba_flags, ~IFBAF_TYPEMASK))
|
|
return (EINVAL);
|
|
switch (ifba->ifba_flags & IFBAF_TYPEMASK) {
|
|
case IFBAF_DYNAMIC:
|
|
type = EBE_DYNAMIC;
|
|
break;
|
|
case IFBAF_STATIC:
|
|
type = EBE_STATIC;
|
|
break;
|
|
default:
|
|
return (EINVAL);
|
|
}
|
|
|
|
if (ifba->ifba_dstsa.ss_family != AF_UNSPEC)
|
|
return (EAFNOSUPPORT);
|
|
|
|
p = veb_port_get(sc, ifba->ifba_ifsname);
|
|
if (p == NULL)
|
|
return (ESRCH);
|
|
|
|
error = etherbridge_add_addr(&sc->sc_eb, p, &ifba->ifba_dst, type);
|
|
|
|
veb_port_put(sc, p);
|
|
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
veb_del_addr(struct veb_softc *sc, const struct ifbareq *ifba)
|
|
{
|
|
return (etherbridge_del_addr(&sc->sc_eb, &ifba->ifba_dst));
|
|
}
|
|
|
|
static int
|
|
veb_p_ioctl(struct ifnet *ifp0, u_long cmd, caddr_t data)
|
|
{
|
|
const struct ether_brport *eb = ether_brport_get_locked(ifp0);
|
|
struct veb_port *p;
|
|
int error = 0;
|
|
|
|
KASSERTMSG(eb != NULL,
|
|
"%s: %s called without an ether_brport set",
|
|
ifp0->if_xname, __func__);
|
|
KASSERTMSG((eb->eb_input == veb_port_input) ||
|
|
(eb->eb_input == veb_span_input),
|
|
"%s called %s, but eb_input (%p) seems wrong",
|
|
ifp0->if_xname, __func__, eb->eb_input);
|
|
|
|
p = eb->eb_port;
|
|
|
|
switch (cmd) {
|
|
case SIOCSIFADDR:
|
|
error = EBUSY;
|
|
break;
|
|
|
|
default:
|
|
error = (*p->p_ioctl)(ifp0, cmd, data);
|
|
break;
|
|
}
|
|
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
veb_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_brport *eb;
|
|
|
|
/* restrict transmission to bpf only */
|
|
if ((m_tag_find(m, PACKET_TAG_DLT, NULL) == NULL)) {
|
|
m_freem(m);
|
|
return (EBUSY);
|
|
}
|
|
|
|
smr_read_enter();
|
|
eb = ether_brport_get(ifp0);
|
|
if (eb != NULL && eb->eb_input == veb_port_input) {
|
|
struct veb_port *p = eb->eb_port;
|
|
p_output = p->p_output; /* code doesn't go away */
|
|
}
|
|
smr_read_leave();
|
|
|
|
if (p_output == NULL) {
|
|
m_freem(m);
|
|
return (ENXIO);
|
|
}
|
|
|
|
return ((*p_output)(ifp0, m, dst, rt));
|
|
}
|
|
|
|
/*
|
|
* there must be an smr_barrier after ether_brport_clr() and before
|
|
* veb_port is freed in veb_p_fini()
|
|
*/
|
|
|
|
static void
|
|
veb_p_unlink(struct veb_softc *sc, struct veb_port *p)
|
|
{
|
|
struct ifnet *ifp = &sc->sc_if;
|
|
struct ifnet *ifp0 = p->p_ifp0;
|
|
|
|
ifp0->if_ioctl = p->p_ioctl;
|
|
ifp0->if_output = p->p_output;
|
|
|
|
ether_brport_clr(ifp0); /* needs an smr_barrier */
|
|
|
|
if_detachhook_del(ifp0, &p->p_dtask);
|
|
if_linkstatehook_del(ifp0, &p->p_ltask);
|
|
|
|
if (!ISSET(p->p_bif_flags, IFBIF_SPAN)) {
|
|
if (ifpromisc(ifp0, 0) != 0) {
|
|
log(LOG_WARNING, "%s %s: unable to disable promisc\n",
|
|
ifp->if_xname, ifp0->if_xname);
|
|
}
|
|
|
|
etherbridge_detach_port(&sc->sc_eb, p);
|
|
}
|
|
}
|
|
|
|
static void
|
|
veb_p_fini(struct veb_port *p)
|
|
{
|
|
struct ifnet *ifp0 = p->p_ifp0;
|
|
|
|
refcnt_finalize(&p->p_refs, "vebpdtor");
|
|
veb_rule_list_free(TAILQ_FIRST(&p->p_vrl));
|
|
|
|
if_put(ifp0);
|
|
free(p, M_DEVBUF, sizeof(*p)); /* hope you didn't forget smr_barrier */
|
|
}
|
|
|
|
static void
|
|
veb_p_dtor(struct veb_softc *sc, struct veb_port *p)
|
|
{
|
|
struct veb_ports **ports_ptr;
|
|
struct veb_ports *om, *nm;
|
|
|
|
ports_ptr = ISSET(p->p_bif_flags, IFBIF_SPAN) ?
|
|
&sc->sc_spans : &sc->sc_ports;
|
|
|
|
om = SMR_PTR_GET_LOCKED(ports_ptr);
|
|
nm = veb_ports_remove(om, p);
|
|
SMR_PTR_SET_LOCKED(ports_ptr, nm);
|
|
|
|
veb_p_unlink(sc, p);
|
|
|
|
smr_barrier();
|
|
refcnt_finalize(&om->m_refs, "vebports");
|
|
veb_ports_destroy(om);
|
|
|
|
veb_p_fini(p);
|
|
}
|
|
|
|
static void
|
|
veb_p_detach(void *arg)
|
|
{
|
|
struct veb_port *p = arg;
|
|
struct veb_softc *sc = p->p_veb;
|
|
|
|
NET_ASSERT_LOCKED();
|
|
|
|
veb_p_dtor(sc, p);
|
|
}
|
|
|
|
static int
|
|
veb_p_active(struct veb_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
|
|
veb_p_linkch(void *arg)
|
|
{
|
|
struct veb_port *p = arg;
|
|
u_char link_state = LINK_STATE_FULL_DUPLEX;
|
|
|
|
NET_ASSERT_LOCKED();
|
|
|
|
if (!veb_p_active(p))
|
|
link_state = LINK_STATE_DOWN;
|
|
|
|
p->p_link_state = link_state;
|
|
}
|
|
|
|
static int
|
|
veb_up(struct veb_softc *sc)
|
|
{
|
|
struct ifnet *ifp = &sc->sc_if;
|
|
int error;
|
|
|
|
error = etherbridge_up(&sc->sc_eb);
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
NET_ASSERT_LOCKED();
|
|
SET(ifp->if_flags, IFF_RUNNING);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
veb_iff(struct veb_softc *sc)
|
|
{
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
veb_down(struct veb_softc *sc)
|
|
{
|
|
struct ifnet *ifp = &sc->sc_if;
|
|
int error;
|
|
|
|
error = etherbridge_down(&sc->sc_eb);
|
|
if (error != 0)
|
|
return (0);
|
|
|
|
NET_ASSERT_LOCKED();
|
|
CLR(ifp->if_flags, IFF_RUNNING);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
veb_eb_port_cmp(void *arg, void *a, void *b)
|
|
{
|
|
struct veb_port *pa = a, *pb = b;
|
|
return (pa == pb);
|
|
}
|
|
|
|
static void *
|
|
veb_eb_port_take(void *arg, void *port)
|
|
{
|
|
struct veb_port *p = port;
|
|
|
|
refcnt_take(&p->p_refs);
|
|
|
|
return (p);
|
|
}
|
|
|
|
static void
|
|
veb_eb_port_rele(void *arg, void *port)
|
|
{
|
|
struct veb_port *p = port;
|
|
|
|
refcnt_rele_wake(&p->p_refs);
|
|
}
|
|
|
|
static void
|
|
veb_eb_brport_take(void *port)
|
|
{
|
|
veb_eb_port_take(NULL, port);
|
|
}
|
|
|
|
static void
|
|
veb_eb_brport_rele(void *port)
|
|
{
|
|
veb_eb_port_rele(NULL, port);
|
|
}
|
|
|
|
static size_t
|
|
veb_eb_port_ifname(void *arg, char *dst, size_t len, void *port)
|
|
{
|
|
struct veb_port *p = port;
|
|
|
|
return (strlcpy(dst, p->p_ifp0->if_xname, len));
|
|
}
|
|
|
|
static void
|
|
veb_eb_port_sa(void *arg, struct sockaddr_storage *ss, void *port)
|
|
{
|
|
ss->ss_family = AF_UNSPEC;
|
|
}
|
|
|
|
/*
|
|
* virtual ethernet bridge port
|
|
*/
|
|
|
|
static int
|
|
vport_clone_create(struct if_clone *ifc, int unit)
|
|
{
|
|
struct vport_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_ac.ac_if;
|
|
|
|
snprintf(ifp->if_xname, IFNAMSIZ, "%s%d", ifc->ifc_name, unit);
|
|
|
|
ifp->if_softc = sc;
|
|
ifp->if_type = IFT_ETHER;
|
|
ifp->if_hardmtu = ETHER_MAX_HARDMTU_LEN;
|
|
ifp->if_ioctl = vport_ioctl;
|
|
ifp->if_enqueue = vport_enqueue;
|
|
ifp->if_qstart = vport_start;
|
|
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
|
|
ifp->if_xflags = IFXF_CLONED | IFXF_MPSAFE;
|
|
ether_fakeaddr(ifp);
|
|
|
|
if_counters_alloc(ifp);
|
|
if_attach(ifp);
|
|
ether_ifattach(ifp);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
vport_clone_destroy(struct ifnet *ifp)
|
|
{
|
|
struct vport_softc *sc = ifp->if_softc;
|
|
|
|
NET_LOCK();
|
|
sc->sc_dead = 1;
|
|
|
|
if (ISSET(ifp->if_flags, IFF_RUNNING))
|
|
vport_down(sc);
|
|
NET_UNLOCK();
|
|
|
|
ether_ifdetach(ifp);
|
|
if_detach(ifp);
|
|
|
|
free(sc, M_DEVBUF, sizeof(*sc));
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
vport_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
|
|
{
|
|
struct vport_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 = vport_up(sc);
|
|
} else {
|
|
if (ISSET(ifp->if_flags, IFF_RUNNING))
|
|
error = vport_down(sc);
|
|
}
|
|
break;
|
|
|
|
case SIOCADDMULTI:
|
|
case SIOCDELMULTI:
|
|
break;
|
|
|
|
default:
|
|
error = ether_ioctl(ifp, &sc->sc_ac, cmd, data);
|
|
break;
|
|
}
|
|
|
|
if (error == ENETRESET)
|
|
error = vport_iff(sc);
|
|
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
vport_up(struct vport_softc *sc)
|
|
{
|
|
struct ifnet *ifp = &sc->sc_ac.ac_if;
|
|
|
|
NET_ASSERT_LOCKED();
|
|
SET(ifp->if_flags, IFF_RUNNING);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
vport_iff(struct vport_softc *sc)
|
|
{
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
vport_down(struct vport_softc *sc)
|
|
{
|
|
struct ifnet *ifp = &sc->sc_ac.ac_if;
|
|
|
|
NET_ASSERT_LOCKED();
|
|
CLR(ifp->if_flags, IFF_RUNNING);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
vport_if_enqueue(struct ifnet *ifp, struct mbuf *m)
|
|
{
|
|
/*
|
|
* switching an l2 packet toward a vport means pushing it
|
|
* into the network stack. this function exists to make
|
|
* if_vinput compat with veb calling if_enqueue.
|
|
*/
|
|
|
|
if_vinput(ifp, m);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
vport_enqueue(struct ifnet *ifp, struct mbuf *m)
|
|
{
|
|
struct arpcom *ac;
|
|
const struct ether_brport *eb;
|
|
int error = ENETDOWN;
|
|
#if NBPFILTER > 0
|
|
caddr_t if_bpf;
|
|
#endif
|
|
|
|
/*
|
|
* a packet sent from the l3 stack out a vport goes into
|
|
* veb for switching out another port.
|
|
*/
|
|
|
|
#if NPF > 0
|
|
/*
|
|
* there's no relationship between pf states in the l3 stack
|
|
* and the l2 bridge.
|
|
*/
|
|
pf_pkt_addr_changed(m);
|
|
#endif
|
|
|
|
ac = (struct arpcom *)ifp;
|
|
|
|
smr_read_enter();
|
|
eb = SMR_PTR_GET(&ac->ac_brport);
|
|
if (eb != NULL)
|
|
eb->eb_port_take(eb->eb_port);
|
|
smr_read_leave();
|
|
if (eb != NULL) {
|
|
struct mbuf *(*input)(struct ifnet *, struct mbuf *,
|
|
uint64_t, void *) = eb->eb_input;
|
|
struct ether_header *eh;
|
|
uint64_t dst;
|
|
|
|
counters_pkt(ifp->if_counters, ifc_opackets, ifc_obytes,
|
|
m->m_pkthdr.len);
|
|
|
|
#if NBPFILTER > 0
|
|
if_bpf = READ_ONCE(ifp->if_bpf);
|
|
if (if_bpf != NULL)
|
|
bpf_mtap_ether(if_bpf, m, BPF_DIRECTION_OUT);
|
|
#endif
|
|
|
|
eh = mtod(m, struct ether_header *);
|
|
dst = ether_addr_to_e64((struct ether_addr *)eh->ether_dhost);
|
|
|
|
if (input == veb_vport_input)
|
|
input = veb_port_input;
|
|
m = (*input)(ifp, m, dst, eb->eb_port);
|
|
|
|
error = 0;
|
|
|
|
eb->eb_port_rele(eb->eb_port);
|
|
}
|
|
|
|
m_freem(m);
|
|
|
|
return (error);
|
|
}
|
|
|
|
static void
|
|
vport_start(struct ifqueue *ifq)
|
|
{
|
|
ifq_purge(ifq);
|
|
}
|