1216 lines
27 KiB
C
1216 lines
27 KiB
C
/* $OpenBSD: if_vlan.c,v 1.218 2023/12/23 10:52:54 bluhm Exp $ */
|
|
|
|
/*
|
|
* Copyright 1998 Massachusetts Institute of Technology
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software and
|
|
* its documentation for any purpose and without fee is hereby
|
|
* granted, provided that both the above copyright notice and this
|
|
* permission notice appear in all copies, that both the above
|
|
* copyright notice and this permission notice appear in all
|
|
* supporting documentation, and that the name of M.I.T. not be used
|
|
* in advertising or publicity pertaining to distribution of the
|
|
* software without specific, written prior permission. M.I.T. makes
|
|
* no representations about the suitability of this software for any
|
|
* purpose. It is provided "as is" without express or implied
|
|
* warranty.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS
|
|
* ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
|
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
|
|
* SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
|
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
|
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
* $FreeBSD: src/sys/net/if_vlan.c,v 1.16 2000/03/26 15:21:40 charnier Exp $
|
|
*/
|
|
|
|
/*
|
|
* if_vlan.c - pseudo-device driver for IEEE 802.1Q virtual LANs.
|
|
* This is sort of sneaky in the implementation, since
|
|
* we need to pretend to be enough of an Ethernet implementation
|
|
* to make arp work. The way we do this is by telling everyone
|
|
* that we are an Ethernet, and then catch the packets that
|
|
* ether_output() left on our output queue when it calls
|
|
* if_start(), rewrite them for use by the real outgoing interface,
|
|
* and ask it to send them.
|
|
*
|
|
* Some devices support 802.1Q tag insertion in firmware. The
|
|
* vlan interface behavior changes when the IFCAP_VLAN_HWTAGGING
|
|
* capability is set on the parent. In this case, vlan_start()
|
|
* will not modify the ethernet header.
|
|
*/
|
|
|
|
#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/rwlock.h>
|
|
#include <sys/percpu.h>
|
|
#include <sys/refcnt.h>
|
|
#include <sys/smr.h>
|
|
|
|
#include <net/if.h>
|
|
#include <net/if_dl.h>
|
|
#include <net/if_types.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <netinet/if_ether.h>
|
|
|
|
#include <net/if_vlan_var.h>
|
|
|
|
#include "bpfilter.h"
|
|
#if NBPFILTER > 0
|
|
#include <net/bpf.h>
|
|
#endif
|
|
|
|
struct vlan_mc_entry {
|
|
LIST_ENTRY(vlan_mc_entry) mc_entries;
|
|
union {
|
|
struct ether_multi *mcu_enm;
|
|
} mc_u;
|
|
#define mc_enm mc_u.mcu_enm
|
|
struct sockaddr_storage mc_addr;
|
|
};
|
|
|
|
struct vlan_softc {
|
|
struct arpcom sc_ac;
|
|
#define sc_if sc_ac.ac_if
|
|
unsigned int sc_dead;
|
|
unsigned int sc_ifidx0; /* parent interface */
|
|
int sc_txprio;
|
|
int sc_rxprio;
|
|
uint16_t sc_proto; /* encapsulation ethertype */
|
|
uint16_t sc_tag;
|
|
uint16_t sc_type; /* non-standard ethertype or 0x8100 */
|
|
LIST_HEAD(__vlan_mchead, vlan_mc_entry)
|
|
sc_mc_listhead;
|
|
SMR_SLIST_ENTRY(vlan_softc) sc_list;
|
|
int sc_flags;
|
|
struct refcnt sc_refcnt;
|
|
struct task sc_ltask;
|
|
struct task sc_dtask;
|
|
};
|
|
|
|
SMR_SLIST_HEAD(vlan_list, vlan_softc);
|
|
|
|
#define IFVF_PROMISC 0x01 /* the parent should be made promisc */
|
|
#define IFVF_LLADDR 0x02 /* don't inherit the parents mac */
|
|
|
|
#define TAG_HASH_BITS 5
|
|
#define TAG_HASH_SIZE (1 << TAG_HASH_BITS)
|
|
#define TAG_HASH_MASK (TAG_HASH_SIZE - 1)
|
|
#define TAG_HASH(tag) (tag & TAG_HASH_MASK)
|
|
struct vlan_list *vlan_tagh, *svlan_tagh;
|
|
struct rwlock vlan_tagh_lk = RWLOCK_INITIALIZER("vlantag");
|
|
|
|
void vlanattach(int count);
|
|
int vlan_clone_create(struct if_clone *, int);
|
|
int vlan_clone_destroy(struct ifnet *);
|
|
|
|
int vlan_enqueue(struct ifnet *, struct mbuf *);
|
|
void vlan_start(struct ifqueue *ifq);
|
|
int vlan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t addr);
|
|
|
|
int vlan_up(struct vlan_softc *);
|
|
int vlan_down(struct vlan_softc *);
|
|
|
|
void vlan_ifdetach(void *);
|
|
void vlan_link_hook(void *);
|
|
void vlan_link_state(struct vlan_softc *, u_char, uint64_t);
|
|
|
|
int vlan_set_vnetid(struct vlan_softc *, uint16_t);
|
|
int vlan_set_parent(struct vlan_softc *, const char *);
|
|
int vlan_del_parent(struct vlan_softc *);
|
|
int vlan_inuse(uint16_t, unsigned int, uint16_t);
|
|
int vlan_inuse_locked(uint16_t, unsigned int, uint16_t);
|
|
|
|
int vlan_multi_add(struct vlan_softc *, struct ifreq *);
|
|
int vlan_multi_del(struct vlan_softc *, struct ifreq *);
|
|
void vlan_multi_apply(struct vlan_softc *, struct ifnet *, u_long);
|
|
void vlan_multi_free(struct vlan_softc *);
|
|
|
|
int vlan_media_get(struct vlan_softc *, struct ifreq *);
|
|
|
|
int vlan_iff(struct vlan_softc *);
|
|
int vlan_setlladdr(struct vlan_softc *, struct ifreq *);
|
|
|
|
int vlan_set_compat(struct ifnet *, struct ifreq *);
|
|
int vlan_get_compat(struct ifnet *, struct ifreq *);
|
|
|
|
struct if_clone vlan_cloner =
|
|
IF_CLONE_INITIALIZER("vlan", vlan_clone_create, vlan_clone_destroy);
|
|
struct if_clone svlan_cloner =
|
|
IF_CLONE_INITIALIZER("svlan", vlan_clone_create, vlan_clone_destroy);
|
|
|
|
void
|
|
vlanattach(int count)
|
|
{
|
|
unsigned int i;
|
|
|
|
/* Normal VLAN */
|
|
vlan_tagh = mallocarray(TAG_HASH_SIZE, sizeof(*vlan_tagh),
|
|
M_DEVBUF, M_NOWAIT);
|
|
if (vlan_tagh == NULL)
|
|
panic("vlanattach: hashinit");
|
|
|
|
/* Service-VLAN for QinQ/802.1ad provider bridges */
|
|
svlan_tagh = mallocarray(TAG_HASH_SIZE, sizeof(*svlan_tagh),
|
|
M_DEVBUF, M_NOWAIT);
|
|
if (svlan_tagh == NULL)
|
|
panic("vlanattach: hashinit");
|
|
|
|
for (i = 0; i < TAG_HASH_SIZE; i++) {
|
|
SMR_SLIST_INIT(&vlan_tagh[i]);
|
|
SMR_SLIST_INIT(&svlan_tagh[i]);
|
|
}
|
|
|
|
if_clone_attach(&vlan_cloner);
|
|
if_clone_attach(&svlan_cloner);
|
|
}
|
|
|
|
int
|
|
vlan_clone_create(struct if_clone *ifc, int unit)
|
|
{
|
|
struct vlan_softc *sc;
|
|
struct ifnet *ifp;
|
|
|
|
sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK|M_ZERO);
|
|
sc->sc_dead = 0;
|
|
LIST_INIT(&sc->sc_mc_listhead);
|
|
task_set(&sc->sc_ltask, vlan_link_hook, sc);
|
|
task_set(&sc->sc_dtask, vlan_ifdetach, sc);
|
|
ifp = &sc->sc_if;
|
|
ifp->if_softc = sc;
|
|
snprintf(ifp->if_xname, sizeof ifp->if_xname, "%s%d", ifc->ifc_name,
|
|
unit);
|
|
/* NB: flags are not set here */
|
|
/* NB: mtu is not set here */
|
|
|
|
/* Special handling for the IEEE 802.1ad QinQ variant */
|
|
if (strcmp("svlan", ifc->ifc_name) == 0)
|
|
sc->sc_type = ETHERTYPE_QINQ;
|
|
else
|
|
sc->sc_type = ETHERTYPE_VLAN;
|
|
|
|
refcnt_init(&sc->sc_refcnt);
|
|
sc->sc_txprio = IF_HDRPRIO_PACKET;
|
|
sc->sc_rxprio = IF_HDRPRIO_OUTER;
|
|
|
|
ifp->if_flags = IFF_BROADCAST | IFF_MULTICAST;
|
|
ifp->if_xflags = IFXF_CLONED|IFXF_MPSAFE;
|
|
ifp->if_qstart = vlan_start;
|
|
ifp->if_enqueue = vlan_enqueue;
|
|
ifp->if_ioctl = vlan_ioctl;
|
|
ifp->if_hardmtu = 0xffff;
|
|
ifp->if_link_state = LINK_STATE_DOWN;
|
|
|
|
if_counters_alloc(ifp);
|
|
if_attach(ifp);
|
|
ether_ifattach(ifp);
|
|
ifp->if_hdrlen = EVL_ENCAPLEN;
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
vlan_clone_destroy(struct ifnet *ifp)
|
|
{
|
|
struct vlan_softc *sc = ifp->if_softc;
|
|
|
|
NET_LOCK();
|
|
sc->sc_dead = 1;
|
|
|
|
if (ISSET(ifp->if_flags, IFF_RUNNING))
|
|
vlan_down(sc);
|
|
NET_UNLOCK();
|
|
|
|
ether_ifdetach(ifp);
|
|
if_detach(ifp);
|
|
smr_barrier();
|
|
refcnt_finalize(&sc->sc_refcnt, "vlanrefs");
|
|
vlan_multi_free(sc);
|
|
free(sc, M_DEVBUF, sizeof(*sc));
|
|
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
vlan_transmit(struct vlan_softc *sc, struct ifnet *ifp0, struct mbuf *m)
|
|
{
|
|
struct ifnet *ifp = &sc->sc_if;
|
|
int txprio = sc->sc_txprio;
|
|
uint8_t prio;
|
|
|
|
#if NBPFILTER > 0
|
|
if (ifp->if_bpf)
|
|
bpf_mtap_ether(ifp->if_bpf, m, BPF_DIRECTION_OUT);
|
|
#endif /* NBPFILTER > 0 */
|
|
|
|
prio = (txprio == IF_HDRPRIO_PACKET) ?
|
|
m->m_pkthdr.pf.prio : txprio;
|
|
|
|
/* IEEE 802.1p has prio 0 and 1 swapped */
|
|
if (prio <= 1)
|
|
prio = !prio;
|
|
|
|
/*
|
|
* If the underlying interface cannot do VLAN tag insertion
|
|
* itself, create an encapsulation header.
|
|
*/
|
|
if ((ifp0->if_capabilities & IFCAP_VLAN_HWTAGGING) &&
|
|
(sc->sc_type == ETHERTYPE_VLAN)) {
|
|
m->m_pkthdr.ether_vtag = sc->sc_tag |
|
|
(prio << EVL_PRIO_BITS);
|
|
m->m_flags |= M_VLANTAG;
|
|
} else {
|
|
m = vlan_inject(m, sc->sc_type, sc->sc_tag |
|
|
(prio << EVL_PRIO_BITS));
|
|
if (m == NULL) {
|
|
counters_inc(ifp->if_counters, ifc_oerrors);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (if_enqueue(ifp0, m))
|
|
counters_inc(ifp->if_counters, ifc_oerrors);
|
|
}
|
|
|
|
int
|
|
vlan_enqueue(struct ifnet *ifp, struct mbuf *m)
|
|
{
|
|
struct ifnet *ifp0;
|
|
struct vlan_softc *sc;
|
|
int error = 0;
|
|
|
|
if (!ifq_is_priq(&ifp->if_snd))
|
|
return (if_enqueue_ifq(ifp, m));
|
|
|
|
sc = ifp->if_softc;
|
|
ifp0 = if_get(sc->sc_ifidx0);
|
|
|
|
if (ifp0 == NULL || !ISSET(ifp0->if_flags, IFF_RUNNING)) {
|
|
m_freem(m);
|
|
error = ENETDOWN;
|
|
} else {
|
|
counters_pkt(ifp->if_counters,
|
|
ifc_opackets, ifc_obytes, m->m_pkthdr.len);
|
|
vlan_transmit(sc, ifp0, m);
|
|
}
|
|
|
|
if_put(ifp0);
|
|
|
|
return (error);
|
|
}
|
|
|
|
void
|
|
vlan_start(struct ifqueue *ifq)
|
|
{
|
|
struct ifnet *ifp = ifq->ifq_if;
|
|
struct vlan_softc *sc = ifp->if_softc;
|
|
struct ifnet *ifp0;
|
|
struct mbuf *m;
|
|
|
|
ifp0 = if_get(sc->sc_ifidx0);
|
|
if (ifp0 == NULL || !ISSET(ifp0->if_flags, IFF_RUNNING)) {
|
|
ifq_purge(ifq);
|
|
goto leave;
|
|
}
|
|
|
|
while ((m = ifq_dequeue(ifq)) != NULL)
|
|
vlan_transmit(sc, ifp0, m);
|
|
|
|
leave:
|
|
if_put(ifp0);
|
|
}
|
|
|
|
struct mbuf *
|
|
vlan_strip(struct mbuf *m)
|
|
{
|
|
if (ISSET(m->m_flags, M_VLANTAG)) {
|
|
CLR(m->m_flags, M_VLANTAG);
|
|
} else {
|
|
struct ether_vlan_header *evl;
|
|
|
|
evl = mtod(m, struct ether_vlan_header *);
|
|
memmove((caddr_t)evl + EVL_ENCAPLEN, evl,
|
|
offsetof(struct ether_vlan_header, evl_encap_proto));
|
|
m_adj(m, EVL_ENCAPLEN);
|
|
}
|
|
|
|
return (m);
|
|
}
|
|
|
|
struct mbuf *
|
|
vlan_inject(struct mbuf *m, uint16_t type, uint16_t tag)
|
|
{
|
|
struct ether_vlan_header evh;
|
|
|
|
m_copydata(m, 0, ETHER_HDR_LEN, &evh);
|
|
evh.evl_proto = evh.evl_encap_proto;
|
|
evh.evl_encap_proto = htons(type);
|
|
evh.evl_tag = htons(tag);
|
|
m_adj(m, ETHER_HDR_LEN);
|
|
M_PREPEND(m, sizeof(evh) + ETHER_ALIGN, M_DONTWAIT);
|
|
if (m == NULL)
|
|
return (NULL);
|
|
|
|
m_adj(m, ETHER_ALIGN);
|
|
|
|
m_copyback(m, 0, sizeof(evh), &evh, M_NOWAIT);
|
|
CLR(m->m_flags, M_VLANTAG);
|
|
|
|
return (m);
|
|
}
|
|
|
|
struct mbuf *
|
|
vlan_input(struct ifnet *ifp0, struct mbuf *m, unsigned int *sdelim)
|
|
{
|
|
struct vlan_softc *sc;
|
|
struct ifnet *ifp;
|
|
struct ether_vlan_header *evl;
|
|
struct vlan_list *tagh, *list;
|
|
uint16_t vtag, tag;
|
|
uint16_t etype;
|
|
int rxprio;
|
|
|
|
if (m->m_flags & M_VLANTAG) {
|
|
vtag = m->m_pkthdr.ether_vtag;
|
|
etype = ETHERTYPE_VLAN;
|
|
tagh = vlan_tagh;
|
|
} else {
|
|
if (m->m_len < sizeof(*evl)) {
|
|
m = m_pullup(m, sizeof(*evl));
|
|
if (m == NULL)
|
|
return (NULL);
|
|
}
|
|
|
|
evl = mtod(m, struct ether_vlan_header *);
|
|
vtag = bemtoh16(&evl->evl_tag);
|
|
etype = bemtoh16(&evl->evl_encap_proto);
|
|
switch (etype) {
|
|
case ETHERTYPE_VLAN:
|
|
tagh = vlan_tagh;
|
|
break;
|
|
case ETHERTYPE_QINQ:
|
|
tagh = svlan_tagh;
|
|
break;
|
|
default:
|
|
panic("%s: unexpected etype 0x%04x", __func__, etype);
|
|
/* NOTREACHED */
|
|
}
|
|
}
|
|
|
|
tag = EVL_VLANOFTAG(vtag);
|
|
list = &tagh[TAG_HASH(tag)];
|
|
smr_read_enter();
|
|
SMR_SLIST_FOREACH(sc, list, sc_list) {
|
|
if (ifp0->if_index == sc->sc_ifidx0 && tag == sc->sc_tag &&
|
|
etype == sc->sc_type) {
|
|
refcnt_take(&sc->sc_refcnt);
|
|
break;
|
|
}
|
|
}
|
|
smr_read_leave();
|
|
|
|
if (sc == NULL) {
|
|
/* VLAN 0 Priority Tagging */
|
|
if (tag == 0 && etype == ETHERTYPE_VLAN) {
|
|
struct ether_header *eh;
|
|
|
|
/* XXX we should actually use the prio value? */
|
|
m = vlan_strip(m);
|
|
|
|
eh = mtod(m, struct ether_header *);
|
|
if (eh->ether_type == htons(ETHERTYPE_VLAN) ||
|
|
eh->ether_type == htons(ETHERTYPE_QINQ)) {
|
|
m_freem(m);
|
|
return (NULL);
|
|
}
|
|
} else
|
|
*sdelim = 1;
|
|
|
|
return (m); /* decline */
|
|
}
|
|
|
|
ifp = &sc->sc_if;
|
|
if (!ISSET(ifp->if_flags, IFF_RUNNING)) {
|
|
m_freem(m);
|
|
goto leave;
|
|
}
|
|
|
|
/*
|
|
* Having found a valid vlan interface corresponding to
|
|
* the given source interface and vlan tag, remove the
|
|
* encapsulation.
|
|
*/
|
|
m = vlan_strip(m);
|
|
|
|
rxprio = sc->sc_rxprio;
|
|
switch (rxprio) {
|
|
case IF_HDRPRIO_PACKET:
|
|
break;
|
|
case IF_HDRPRIO_OUTER:
|
|
m->m_pkthdr.pf.prio = EVL_PRIOFTAG(m->m_pkthdr.ether_vtag);
|
|
/* IEEE 802.1p has prio 0 and 1 swapped */
|
|
if (m->m_pkthdr.pf.prio <= 1)
|
|
m->m_pkthdr.pf.prio = !m->m_pkthdr.pf.prio;
|
|
break;
|
|
default:
|
|
m->m_pkthdr.pf.prio = rxprio;
|
|
break;
|
|
}
|
|
|
|
if_vinput(ifp, m);
|
|
leave:
|
|
refcnt_rele_wake(&sc->sc_refcnt);
|
|
return (NULL);
|
|
}
|
|
|
|
int
|
|
vlan_up(struct vlan_softc *sc)
|
|
{
|
|
struct vlan_list *tagh, *list;
|
|
struct ifnet *ifp = &sc->sc_if;
|
|
struct ifnet *ifp0;
|
|
int error = 0;
|
|
unsigned int hardmtu;
|
|
|
|
KASSERT(!ISSET(ifp->if_flags, IFF_RUNNING));
|
|
|
|
tagh = sc->sc_type == ETHERTYPE_QINQ ? svlan_tagh : vlan_tagh;
|
|
list = &tagh[TAG_HASH(sc->sc_tag)];
|
|
|
|
ifp0 = if_get(sc->sc_ifidx0);
|
|
if (ifp0 == NULL)
|
|
return (ENXIO);
|
|
|
|
/* check vlan will work on top of the parent */
|
|
if (ifp0->if_type != IFT_ETHER) {
|
|
error = EPROTONOSUPPORT;
|
|
goto put;
|
|
}
|
|
|
|
hardmtu = ifp0->if_hardmtu;
|
|
if (!ISSET(ifp0->if_capabilities, IFCAP_VLAN_MTU))
|
|
hardmtu -= EVL_ENCAPLEN;
|
|
|
|
if (ifp->if_mtu > hardmtu) {
|
|
error = ENOBUFS;
|
|
goto put;
|
|
}
|
|
|
|
/* parent is fine, let's prepare the sc to handle packets */
|
|
ifp->if_hardmtu = hardmtu;
|
|
SET(ifp->if_flags, ifp0->if_flags & IFF_SIMPLEX);
|
|
|
|
if (ISSET(sc->sc_flags, IFVF_PROMISC)) {
|
|
error = ifpromisc(ifp0, 1);
|
|
if (error != 0)
|
|
goto scrub;
|
|
}
|
|
|
|
/*
|
|
* Note: In cases like vio(4) and em(4) where the offsets of the
|
|
* csum can be freely defined, we could actually do csum offload
|
|
* for VLAN and QINQ packets.
|
|
*/
|
|
if (sc->sc_type != ETHERTYPE_VLAN) {
|
|
/*
|
|
* Hardware offload only works with the default VLAN
|
|
* ethernet type (0x8100).
|
|
*/
|
|
ifp->if_capabilities = 0;
|
|
} else if (ISSET(ifp0->if_capabilities, IFCAP_VLAN_HWTAGGING)) {
|
|
/*
|
|
* Chips that can do hardware-assisted VLAN encapsulation, can
|
|
* calculate the correct checksum for VLAN tagged packets.
|
|
*/
|
|
ifp->if_capabilities = ifp0->if_capabilities &
|
|
(IFCAP_CSUM_MASK | IFCAP_TSOv4 | IFCAP_TSOv6);
|
|
}
|
|
|
|
/* commit the sc */
|
|
error = rw_enter(&vlan_tagh_lk, RW_WRITE | RW_INTR);
|
|
if (error != 0)
|
|
goto unpromisc;
|
|
|
|
error = vlan_inuse_locked(sc->sc_type, sc->sc_ifidx0, sc->sc_tag);
|
|
if (error != 0)
|
|
goto leave;
|
|
|
|
SMR_SLIST_INSERT_HEAD_LOCKED(list, sc, sc_list);
|
|
rw_exit(&vlan_tagh_lk);
|
|
|
|
/* 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);
|
|
|
|
/* configure the parent to handle packets for this vlan */
|
|
vlan_multi_apply(sc, ifp0, SIOCADDMULTI);
|
|
|
|
/* we're running now */
|
|
SET(ifp->if_flags, IFF_RUNNING);
|
|
vlan_link_state(sc, ifp0->if_link_state, ifp0->if_baudrate);
|
|
|
|
if_put(ifp0);
|
|
|
|
return (ENETRESET);
|
|
|
|
leave:
|
|
rw_exit(&vlan_tagh_lk);
|
|
unpromisc:
|
|
if (ISSET(sc->sc_flags, IFVF_PROMISC))
|
|
(void)ifpromisc(ifp0, 0); /* XXX */
|
|
scrub:
|
|
ifp->if_capabilities = 0;
|
|
CLR(ifp->if_flags, IFF_SIMPLEX);
|
|
ifp->if_hardmtu = 0xffff;
|
|
put:
|
|
if_put(ifp0);
|
|
|
|
return (error);
|
|
}
|
|
|
|
int
|
|
vlan_down(struct vlan_softc *sc)
|
|
{
|
|
struct vlan_list *tagh, *list;
|
|
struct ifnet *ifp = &sc->sc_if;
|
|
struct ifnet *ifp0;
|
|
|
|
tagh = sc->sc_type == ETHERTYPE_QINQ ? svlan_tagh : vlan_tagh;
|
|
list = &tagh[TAG_HASH(sc->sc_tag)];
|
|
|
|
KASSERT(ISSET(ifp->if_flags, IFF_RUNNING));
|
|
|
|
vlan_link_state(sc, LINK_STATE_DOWN, 0);
|
|
CLR(ifp->if_flags, IFF_RUNNING);
|
|
|
|
ifq_barrier(&ifp->if_snd);
|
|
|
|
ifp0 = if_get(sc->sc_ifidx0);
|
|
if (ifp0 != NULL) {
|
|
if (ISSET(sc->sc_flags, IFVF_PROMISC))
|
|
ifpromisc(ifp0, 0);
|
|
vlan_multi_apply(sc, ifp0, SIOCDELMULTI);
|
|
if_detachhook_del(ifp0, &sc->sc_dtask);
|
|
if_linkstatehook_del(ifp0, &sc->sc_ltask);
|
|
}
|
|
if_put(ifp0);
|
|
|
|
rw_enter_write(&vlan_tagh_lk);
|
|
SMR_SLIST_REMOVE_LOCKED(list, sc, vlan_softc, sc_list);
|
|
rw_exit_write(&vlan_tagh_lk);
|
|
|
|
ifp->if_capabilities = 0;
|
|
CLR(ifp->if_flags, IFF_SIMPLEX);
|
|
ifp->if_hardmtu = 0xffff;
|
|
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
vlan_ifdetach(void *v)
|
|
{
|
|
struct vlan_softc *sc = v;
|
|
struct ifnet *ifp = &sc->sc_if;
|
|
|
|
if (ISSET(ifp->if_flags, IFF_RUNNING)) {
|
|
vlan_down(sc);
|
|
CLR(ifp->if_flags, IFF_UP);
|
|
}
|
|
|
|
sc->sc_ifidx0 = 0;
|
|
}
|
|
|
|
void
|
|
vlan_link_hook(void *v)
|
|
{
|
|
struct vlan_softc *sc = v;
|
|
struct ifnet *ifp0;
|
|
|
|
u_char link = LINK_STATE_DOWN;
|
|
uint64_t baud = 0;
|
|
|
|
ifp0 = if_get(sc->sc_ifidx0);
|
|
if (ifp0 != NULL) {
|
|
link = ifp0->if_link_state;
|
|
baud = ifp0->if_baudrate;
|
|
}
|
|
if_put(ifp0);
|
|
|
|
vlan_link_state(sc, link, baud);
|
|
}
|
|
|
|
void
|
|
vlan_link_state(struct vlan_softc *sc, u_char link, uint64_t baud)
|
|
{
|
|
if (sc->sc_if.if_link_state == link)
|
|
return;
|
|
|
|
sc->sc_if.if_link_state = link;
|
|
sc->sc_if.if_baudrate = baud;
|
|
|
|
if_link_state_change(&sc->sc_if);
|
|
}
|
|
|
|
int
|
|
vlan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
|
|
{
|
|
struct vlan_softc *sc = ifp->if_softc;
|
|
struct ifreq *ifr = (struct ifreq *)data;
|
|
struct if_parent *parent = (struct if_parent *)data;
|
|
struct ifnet *ifp0;
|
|
uint16_t tag;
|
|
int error = 0;
|
|
|
|
if (sc->sc_dead)
|
|
return (ENXIO);
|
|
|
|
switch (cmd) {
|
|
case SIOCSIFADDR:
|
|
ifp->if_flags |= IFF_UP;
|
|
/* FALLTHROUGH */
|
|
|
|
case SIOCSIFFLAGS:
|
|
if (ISSET(ifp->if_flags, IFF_UP)) {
|
|
if (!ISSET(ifp->if_flags, IFF_RUNNING))
|
|
error = vlan_up(sc);
|
|
else
|
|
error = ENETRESET;
|
|
} else {
|
|
if (ISSET(ifp->if_flags, IFF_RUNNING))
|
|
error = vlan_down(sc);
|
|
}
|
|
break;
|
|
|
|
case SIOCSVNETID:
|
|
if (ifr->ifr_vnetid < EVL_VLID_MIN ||
|
|
ifr->ifr_vnetid > EVL_VLID_MAX) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
|
|
tag = ifr->ifr_vnetid;
|
|
if (tag == sc->sc_tag)
|
|
break;
|
|
|
|
error = vlan_set_vnetid(sc, tag);
|
|
break;
|
|
|
|
case SIOCGVNETID:
|
|
if (sc->sc_tag == EVL_VLID_NULL)
|
|
error = EADDRNOTAVAIL;
|
|
else
|
|
ifr->ifr_vnetid = (int64_t)sc->sc_tag;
|
|
break;
|
|
|
|
case SIOCDVNETID:
|
|
error = vlan_set_vnetid(sc, 0);
|
|
break;
|
|
|
|
case SIOCSIFPARENT:
|
|
error = vlan_set_parent(sc, parent->ifp_parent);
|
|
break;
|
|
|
|
case SIOCGIFPARENT:
|
|
ifp0 = if_get(sc->sc_ifidx0);
|
|
if (ifp0 == NULL)
|
|
error = EADDRNOTAVAIL;
|
|
else {
|
|
memcpy(parent->ifp_parent, ifp0->if_xname,
|
|
sizeof(parent->ifp_parent));
|
|
}
|
|
if_put(ifp0);
|
|
break;
|
|
|
|
case SIOCDIFPARENT:
|
|
error = vlan_del_parent(sc);
|
|
break;
|
|
|
|
case SIOCADDMULTI:
|
|
error = vlan_multi_add(sc, ifr);
|
|
break;
|
|
|
|
case SIOCDELMULTI:
|
|
error = vlan_multi_del(sc, ifr);
|
|
break;
|
|
|
|
case SIOCGIFMEDIA:
|
|
error = vlan_media_get(sc, ifr);
|
|
break;
|
|
|
|
case SIOCSIFMEDIA:
|
|
error = ENOTTY;
|
|
break;
|
|
|
|
case SIOCSIFLLADDR:
|
|
error = vlan_setlladdr(sc, ifr);
|
|
break;
|
|
|
|
case SIOCSETVLAN:
|
|
error = vlan_set_compat(ifp, ifr);
|
|
break;
|
|
case SIOCGETVLAN:
|
|
error = vlan_get_compat(ifp, ifr);
|
|
break;
|
|
|
|
case SIOCSTXHPRIO:
|
|
error = if_txhprio_l2_check(ifr->ifr_hdrprio);
|
|
if (error != 0)
|
|
break;
|
|
|
|
sc->sc_txprio = ifr->ifr_hdrprio;
|
|
break;
|
|
case SIOCGTXHPRIO:
|
|
ifr->ifr_hdrprio = sc->sc_txprio;
|
|
break;
|
|
|
|
case SIOCSRXHPRIO:
|
|
error = if_rxhprio_l2_check(ifr->ifr_hdrprio);
|
|
if (error != 0)
|
|
break;
|
|
|
|
sc->sc_rxprio = ifr->ifr_hdrprio;
|
|
break;
|
|
case SIOCGRXHPRIO:
|
|
ifr->ifr_hdrprio = sc->sc_rxprio;
|
|
break;
|
|
|
|
default:
|
|
error = ether_ioctl(ifp, &sc->sc_ac, cmd, data);
|
|
break;
|
|
}
|
|
|
|
if (error == ENETRESET)
|
|
error = vlan_iff(sc);
|
|
|
|
return error;
|
|
}
|
|
|
|
int
|
|
vlan_iff(struct vlan_softc *sc)
|
|
{
|
|
struct ifnet *ifp0;
|
|
int promisc = 0;
|
|
int error = 0;
|
|
|
|
if (ISSET(sc->sc_if.if_flags, IFF_PROMISC) ||
|
|
ISSET(sc->sc_flags, IFVF_LLADDR))
|
|
promisc = IFVF_PROMISC;
|
|
|
|
if (ISSET(sc->sc_flags, IFVF_PROMISC) == promisc)
|
|
return (0);
|
|
|
|
if (ISSET(sc->sc_if.if_flags, IFF_RUNNING)) {
|
|
ifp0 = if_get(sc->sc_ifidx0);
|
|
if (ifp0 != NULL)
|
|
error = ifpromisc(ifp0, promisc);
|
|
if_put(ifp0);
|
|
}
|
|
|
|
if (error == 0) {
|
|
CLR(sc->sc_flags, IFVF_PROMISC);
|
|
SET(sc->sc_flags, promisc);
|
|
}
|
|
|
|
return (error);
|
|
}
|
|
|
|
int
|
|
vlan_setlladdr(struct vlan_softc *sc, struct ifreq *ifr)
|
|
{
|
|
struct ifnet *ifp = &sc->sc_if;
|
|
struct ifnet *ifp0;
|
|
uint8_t lladdr[ETHER_ADDR_LEN];
|
|
int flag;
|
|
|
|
memcpy(lladdr, ifr->ifr_addr.sa_data, sizeof(lladdr));
|
|
|
|
/* setting the mac addr to 00:00:00:00:00:00 means reset lladdr */
|
|
if (memcmp(lladdr, etheranyaddr, sizeof(lladdr)) == 0) {
|
|
ifp0 = if_get(sc->sc_ifidx0);
|
|
if (ifp0 != NULL)
|
|
memcpy(lladdr, LLADDR(ifp0->if_sadl), sizeof(lladdr));
|
|
if_put(ifp0);
|
|
|
|
flag = 0;
|
|
} else
|
|
flag = IFVF_LLADDR;
|
|
|
|
if (memcmp(lladdr, LLADDR(ifp->if_sadl), sizeof(lladdr)) == 0 &&
|
|
ISSET(sc->sc_flags, IFVF_LLADDR) == flag) {
|
|
/* nop */
|
|
return (0);
|
|
}
|
|
|
|
/* commit */
|
|
if_setlladdr(ifp, lladdr);
|
|
CLR(sc->sc_flags, IFVF_LLADDR);
|
|
SET(sc->sc_flags, flag);
|
|
|
|
return (ENETRESET);
|
|
}
|
|
|
|
int
|
|
vlan_set_vnetid(struct vlan_softc *sc, uint16_t tag)
|
|
{
|
|
struct ifnet *ifp = &sc->sc_if;
|
|
struct vlan_list *tagh, *list;
|
|
u_char link = ifp->if_link_state;
|
|
uint64_t baud = ifp->if_baudrate;
|
|
int error;
|
|
|
|
tagh = sc->sc_type == ETHERTYPE_QINQ ? svlan_tagh : vlan_tagh;
|
|
|
|
if (ISSET(ifp->if_flags, IFF_RUNNING) && LINK_STATE_IS_UP(link))
|
|
vlan_link_state(sc, LINK_STATE_DOWN, 0);
|
|
|
|
error = rw_enter(&vlan_tagh_lk, RW_WRITE);
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
error = vlan_inuse_locked(sc->sc_type, sc->sc_ifidx0, tag);
|
|
if (error != 0)
|
|
goto unlock;
|
|
|
|
if (ISSET(ifp->if_flags, IFF_RUNNING)) {
|
|
list = &tagh[TAG_HASH(sc->sc_tag)];
|
|
SMR_SLIST_REMOVE_LOCKED(list, sc, vlan_softc, sc_list);
|
|
|
|
sc->sc_tag = tag;
|
|
|
|
list = &tagh[TAG_HASH(sc->sc_tag)];
|
|
SMR_SLIST_INSERT_HEAD_LOCKED(list, sc, sc_list);
|
|
} else
|
|
sc->sc_tag = tag;
|
|
|
|
unlock:
|
|
rw_exit(&vlan_tagh_lk);
|
|
|
|
if (ISSET(ifp->if_flags, IFF_RUNNING) && LINK_STATE_IS_UP(link))
|
|
vlan_link_state(sc, link, baud);
|
|
|
|
return (error);
|
|
}
|
|
|
|
int
|
|
vlan_set_parent(struct vlan_softc *sc, const char *parent)
|
|
{
|
|
struct ifnet *ifp = &sc->sc_if;
|
|
struct ifnet *ifp0;
|
|
int error = 0;
|
|
|
|
ifp0 = if_unit(parent);
|
|
if (ifp0 == NULL)
|
|
return (EINVAL);
|
|
|
|
if (ifp0->if_type != IFT_ETHER) {
|
|
error = EPROTONOSUPPORT;
|
|
goto put;
|
|
}
|
|
|
|
if (sc->sc_ifidx0 == ifp0->if_index) {
|
|
/* nop */
|
|
goto put;
|
|
}
|
|
|
|
if (ISSET(ifp->if_flags, IFF_RUNNING)) {
|
|
error = EBUSY;
|
|
goto put;
|
|
}
|
|
|
|
error = vlan_inuse(sc->sc_type, ifp0->if_index, sc->sc_tag);
|
|
if (error != 0)
|
|
goto put;
|
|
|
|
if (ether_brport_isset(ifp))
|
|
ifsetlro(ifp0, 0);
|
|
|
|
/* commit */
|
|
sc->sc_ifidx0 = ifp0->if_index;
|
|
if (!ISSET(sc->sc_flags, IFVF_LLADDR))
|
|
if_setlladdr(ifp, LLADDR(ifp0->if_sadl));
|
|
|
|
put:
|
|
if_put(ifp0);
|
|
return (error);
|
|
}
|
|
|
|
int
|
|
vlan_del_parent(struct vlan_softc *sc)
|
|
{
|
|
struct ifnet *ifp = &sc->sc_if;
|
|
|
|
if (ISSET(ifp->if_flags, IFF_RUNNING))
|
|
return (EBUSY);
|
|
|
|
/* commit */
|
|
sc->sc_ifidx0 = 0;
|
|
if (!ISSET(sc->sc_flags, IFVF_LLADDR))
|
|
if_setlladdr(ifp, etheranyaddr);
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
vlan_set_compat(struct ifnet *ifp, struct ifreq *ifr)
|
|
{
|
|
struct vlanreq vlr;
|
|
struct ifreq req;
|
|
struct if_parent parent;
|
|
|
|
int error;
|
|
|
|
error = suser(curproc);
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
error = copyin(ifr->ifr_data, &vlr, sizeof(vlr));
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
if (vlr.vlr_parent[0] == '\0')
|
|
return (vlan_ioctl(ifp, SIOCDIFPARENT, (caddr_t)ifr));
|
|
|
|
memset(&req, 0, sizeof(req));
|
|
memcpy(req.ifr_name, ifp->if_xname, sizeof(req.ifr_name));
|
|
req.ifr_vnetid = vlr.vlr_tag;
|
|
|
|
error = vlan_ioctl(ifp, SIOCSVNETID, (caddr_t)&req);
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
memset(&parent, 0, sizeof(parent));
|
|
memcpy(parent.ifp_name, ifp->if_xname, sizeof(parent.ifp_name));
|
|
memcpy(parent.ifp_parent, vlr.vlr_parent, sizeof(parent.ifp_parent));
|
|
error = vlan_ioctl(ifp, SIOCSIFPARENT, (caddr_t)&parent);
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
memset(&req, 0, sizeof(req));
|
|
memcpy(req.ifr_name, ifp->if_xname, sizeof(req.ifr_name));
|
|
SET(ifp->if_flags, IFF_UP);
|
|
return (vlan_ioctl(ifp, SIOCSIFFLAGS, (caddr_t)&req));
|
|
}
|
|
|
|
int
|
|
vlan_get_compat(struct ifnet *ifp, struct ifreq *ifr)
|
|
{
|
|
struct vlan_softc *sc = ifp->if_softc;
|
|
struct vlanreq vlr;
|
|
struct ifnet *p;
|
|
|
|
memset(&vlr, 0, sizeof(vlr));
|
|
p = if_get(sc->sc_ifidx0);
|
|
if (p != NULL)
|
|
memcpy(vlr.vlr_parent, p->if_xname, sizeof(vlr.vlr_parent));
|
|
if_put(p);
|
|
|
|
vlr.vlr_tag = sc->sc_tag;
|
|
|
|
return (copyout(&vlr, ifr->ifr_data, sizeof(vlr)));
|
|
}
|
|
|
|
/*
|
|
* do a quick check of up and running vlans for existing configurations.
|
|
*
|
|
* NOTE: this does allow the same config on down vlans, but vlan_up()
|
|
* will catch them.
|
|
*/
|
|
int
|
|
vlan_inuse(uint16_t type, unsigned int ifidx, uint16_t tag)
|
|
{
|
|
int error = 0;
|
|
|
|
error = rw_enter(&vlan_tagh_lk, RW_READ | RW_INTR);
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
error = vlan_inuse_locked(type, ifidx, tag);
|
|
|
|
rw_exit(&vlan_tagh_lk);
|
|
|
|
return (error);
|
|
}
|
|
|
|
int
|
|
vlan_inuse_locked(uint16_t type, unsigned int ifidx, uint16_t tag)
|
|
{
|
|
struct vlan_list *tagh, *list;
|
|
struct vlan_softc *sc;
|
|
|
|
tagh = type == ETHERTYPE_QINQ ? svlan_tagh : vlan_tagh;
|
|
list = &tagh[TAG_HASH(tag)];
|
|
|
|
SMR_SLIST_FOREACH_LOCKED(sc, list, sc_list) {
|
|
if (sc->sc_tag == tag &&
|
|
sc->sc_type == type && /* wat */
|
|
sc->sc_ifidx0 == ifidx)
|
|
return (EADDRINUSE);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
vlan_multi_add(struct vlan_softc *sc, struct ifreq *ifr)
|
|
{
|
|
struct ifnet *ifp0;
|
|
struct vlan_mc_entry *mc;
|
|
uint8_t addrlo[ETHER_ADDR_LEN], addrhi[ETHER_ADDR_LEN];
|
|
int error;
|
|
|
|
error = ether_addmulti(ifr, &sc->sc_ac);
|
|
if (error != ENETRESET)
|
|
return (error);
|
|
|
|
/*
|
|
* This is new multicast address. We have to tell parent
|
|
* about it. Also, remember this multicast address so that
|
|
* we can delete them on unconfigure.
|
|
*/
|
|
if ((mc = malloc(sizeof(*mc), M_DEVBUF, M_NOWAIT)) == NULL) {
|
|
error = ENOMEM;
|
|
goto alloc_failed;
|
|
}
|
|
|
|
/*
|
|
* As ether_addmulti() returns ENETRESET, following two
|
|
* statement shouldn't fail.
|
|
*/
|
|
(void)ether_multiaddr(&ifr->ifr_addr, addrlo, addrhi);
|
|
ETHER_LOOKUP_MULTI(addrlo, addrhi, &sc->sc_ac, mc->mc_enm);
|
|
memcpy(&mc->mc_addr, &ifr->ifr_addr, ifr->ifr_addr.sa_len);
|
|
LIST_INSERT_HEAD(&sc->sc_mc_listhead, mc, mc_entries);
|
|
|
|
ifp0 = if_get(sc->sc_ifidx0);
|
|
error = (ifp0 == NULL) ? 0 :
|
|
(*ifp0->if_ioctl)(ifp0, SIOCADDMULTI, (caddr_t)ifr);
|
|
if_put(ifp0);
|
|
|
|
if (error != 0)
|
|
goto ioctl_failed;
|
|
|
|
return (error);
|
|
|
|
ioctl_failed:
|
|
LIST_REMOVE(mc, mc_entries);
|
|
free(mc, M_DEVBUF, sizeof(*mc));
|
|
alloc_failed:
|
|
(void)ether_delmulti(ifr, &sc->sc_ac);
|
|
|
|
return (error);
|
|
}
|
|
|
|
int
|
|
vlan_multi_del(struct vlan_softc *sc, struct ifreq *ifr)
|
|
{
|
|
struct ifnet *ifp0;
|
|
struct ether_multi *enm;
|
|
struct vlan_mc_entry *mc;
|
|
uint8_t addrlo[ETHER_ADDR_LEN], addrhi[ETHER_ADDR_LEN];
|
|
int error;
|
|
|
|
/*
|
|
* Find a key to lookup vlan_mc_entry. We have to do this
|
|
* before calling ether_delmulti for obvious reason.
|
|
*/
|
|
if ((error = ether_multiaddr(&ifr->ifr_addr, addrlo, addrhi)) != 0)
|
|
return (error);
|
|
ETHER_LOOKUP_MULTI(addrlo, addrhi, &sc->sc_ac, enm);
|
|
if (enm == NULL)
|
|
return (EINVAL);
|
|
|
|
LIST_FOREACH(mc, &sc->sc_mc_listhead, mc_entries) {
|
|
if (mc->mc_enm == enm)
|
|
break;
|
|
}
|
|
|
|
/* We won't delete entries we didn't add */
|
|
if (mc == NULL)
|
|
return (EINVAL);
|
|
|
|
error = ether_delmulti(ifr, &sc->sc_ac);
|
|
if (error != ENETRESET)
|
|
return (error);
|
|
|
|
if (!ISSET(sc->sc_if.if_flags, IFF_RUNNING))
|
|
goto forget;
|
|
|
|
ifp0 = if_get(sc->sc_ifidx0);
|
|
error = (ifp0 == NULL) ? 0 :
|
|
(*ifp0->if_ioctl)(ifp0, SIOCDELMULTI, (caddr_t)ifr);
|
|
if_put(ifp0);
|
|
|
|
if (error != 0) {
|
|
(void)ether_addmulti(ifr, &sc->sc_ac);
|
|
return (error);
|
|
}
|
|
|
|
forget:
|
|
/* forget about this address */
|
|
LIST_REMOVE(mc, mc_entries);
|
|
free(mc, M_DEVBUF, sizeof(*mc));
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
vlan_media_get(struct vlan_softc *sc, struct ifreq *ifr)
|
|
{
|
|
struct ifnet *ifp0;
|
|
int error;
|
|
|
|
ifp0 = if_get(sc->sc_ifidx0);
|
|
error = (ifp0 == NULL) ? ENOTTY :
|
|
(*ifp0->if_ioctl)(ifp0, SIOCGIFMEDIA, (caddr_t)ifr);
|
|
if_put(ifp0);
|
|
|
|
return (error);
|
|
}
|
|
|
|
void
|
|
vlan_multi_apply(struct vlan_softc *sc, struct ifnet *ifp0, u_long cmd)
|
|
{
|
|
struct vlan_mc_entry *mc;
|
|
union {
|
|
struct ifreq ifreq;
|
|
struct {
|
|
char ifr_name[IFNAMSIZ];
|
|
struct sockaddr_storage ifr_ss;
|
|
} ifreq_storage;
|
|
} ifreq;
|
|
struct ifreq *ifr = &ifreq.ifreq;
|
|
|
|
memcpy(ifr->ifr_name, ifp0->if_xname, IFNAMSIZ);
|
|
LIST_FOREACH(mc, &sc->sc_mc_listhead, mc_entries) {
|
|
memcpy(&ifr->ifr_addr, &mc->mc_addr, mc->mc_addr.ss_len);
|
|
|
|
(void)(*ifp0->if_ioctl)(ifp0, cmd, (caddr_t)ifr);
|
|
}
|
|
}
|
|
|
|
void
|
|
vlan_multi_free(struct vlan_softc *sc)
|
|
{
|
|
struct vlan_mc_entry *mc;
|
|
|
|
while ((mc = LIST_FIRST(&sc->sc_mc_listhead)) != NULL) {
|
|
LIST_REMOVE(mc, mc_entries);
|
|
free(mc, M_DEVBUF, sizeof(*mc));
|
|
}
|
|
}
|