Module Name: src
Committed By: snj
Date: Wed Jun 21 17:39:24 UTC 2017
Modified Files:
src/sys/net [netbsd-8]: if_vlan.c if_vlanvar.h
Log Message:
Pull up following revision(s) (requested by knakahara in ticket #41):
sys/net/if_vlan.c: revision 1.98
sys/net/if_vlanvar.h: revision 1.10
vlan(4) MP-ify. contributed by s-yamaguchi@IIJ, thanks.
To generate a diff of this commit:
cvs rdiff -u -r1.97 -r1.97.2.1 src/sys/net/if_vlan.c
cvs rdiff -u -r1.9 -r1.9.80.1 src/sys/net/if_vlanvar.h
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
Modified files:
Index: src/sys/net/if_vlan.c
diff -u src/sys/net/if_vlan.c:1.97 src/sys/net/if_vlan.c:1.97.2.1
--- src/sys/net/if_vlan.c:1.97 Mon May 29 02:55:49 2017
+++ src/sys/net/if_vlan.c Wed Jun 21 17:39:24 2017
@@ -1,4 +1,4 @@
-/* $NetBSD: if_vlan.c,v 1.97 2017/05/29 02:55:49 ozaki-r Exp $ */
+/* $NetBSD: if_vlan.c,v 1.97.2.1 2017/06/21 17:39:24 snj Exp $ */
/*-
* Copyright (c) 2000, 2001 The NetBSD Foundation, Inc.
@@ -78,7 +78,7 @@
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: if_vlan.c,v 1.97 2017/05/29 02:55:49 ozaki-r Exp $");
+__KERNEL_RCSID(0, "$NetBSD: if_vlan.c,v 1.97.2.1 2017/06/21 17:39:24 snj Exp $");
#ifdef _KERNEL_OPT
#include "opt_inet.h"
@@ -86,6 +86,7 @@ __KERNEL_RCSID(0, "$NetBSD: if_vlan.c,v
#endif
#include <sys/param.h>
+#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/mbuf.h>
#include <sys/queue.h>
@@ -95,6 +96,12 @@ __KERNEL_RCSID(0, "$NetBSD: if_vlan.c,v
#include <sys/proc.h>
#include <sys/kauth.h>
#include <sys/mutex.h>
+#include <sys/kmem.h>
+#include <sys/cpu.h>
+#include <sys/pserialize.h>
+#include <sys/psref.h>
+#include <sys/pslist.h>
+#include <sys/atomic.h>
#include <sys/device.h>
#include <sys/module.h>
@@ -116,6 +123,10 @@ __KERNEL_RCSID(0, "$NetBSD: if_vlan.c,v
#include "ioconf.h"
+#ifdef NET_MPSAFE
+#define VLAN_MPSAFE 1
+#endif
+
struct vlan_mc_entry {
LIST_ENTRY(vlan_mc_entry) mc_entries;
/*
@@ -131,21 +142,33 @@ struct vlan_mc_entry {
#define mc_enm mc_u.mcu_enm
+
+struct ifvlan_linkmib {
+ struct ifvlan *ifvm_ifvlan;
+ const struct vlan_multisw *ifvm_msw;
+ int ifvm_encaplen; /* encapsulation length */
+ int ifvm_mtufudge; /* MTU fudged by this much */
+ int ifvm_mintu; /* min transmission unit */
+ uint16_t ifvm_proto; /* encapsulation ethertype */
+ uint16_t ifvm_tag; /* tag to apply on packets */
+ struct ifnet *ifvm_p; /* parent interface of this vlan */
+
+ struct psref_target ifvm_psref;
+};
+
struct ifvlan {
union {
struct ethercom ifvu_ec;
} ifv_u;
- struct ifnet *ifv_p; /* parent interface of this vlan */
- struct ifv_linkmib {
- const struct vlan_multisw *ifvm_msw;
- int ifvm_encaplen; /* encapsulation length */
- int ifvm_mtufudge; /* MTU fudged by this much */
- int ifvm_mintu; /* min transmission unit */
- uint16_t ifvm_proto; /* encapsulation ethertype */
- uint16_t ifvm_tag; /* tag to apply on packets */
- } ifv_mib;
+ struct ifvlan_linkmib *ifv_mib; /*
+ * reader must use vlan_getref_linkmib()
+ * instead of direct dereference
+ */
+ kmutex_t ifv_lock; /* writer lock for ifv_mib */
+
LIST_HEAD(__vlan_mchead, vlan_mc_entry) ifv_mc_listhead;
LIST_ENTRY(ifvlan) ifv_list;
+ struct pslist_entry ifv_hash;
int ifv_flags;
};
@@ -179,15 +202,47 @@ const struct vlan_multisw vlan_ether_mul
static int vlan_clone_create(struct if_clone *, int);
static int vlan_clone_destroy(struct ifnet *);
-static int vlan_config(struct ifvlan *, struct ifnet *);
+static int vlan_config(struct ifvlan *, struct ifnet *,
+ uint16_t);
static int vlan_ioctl(struct ifnet *, u_long, void *);
static void vlan_start(struct ifnet *);
+static int vlan_transmit(struct ifnet *, struct mbuf *);
static void vlan_unconfig(struct ifnet *);
+static int vlan_unconfig_locked(struct ifvlan *,
+ struct ifvlan_linkmib *);
+static void vlan_hash_init(void);
+static int vlan_hash_fini(void);
+static struct ifvlan_linkmib* vlan_getref_linkmib(struct ifvlan *,
+ struct psref *);
+static void vlan_putref_linkmib(struct ifvlan_linkmib *,
+ struct psref *);
+static void vlan_linkmib_update(struct ifvlan *,
+ struct ifvlan_linkmib *);
+static struct ifvlan_linkmib* vlan_lookup_tag_psref(struct ifnet *,
+ uint16_t, struct psref *);
+static int tag_hash_func(uint16_t, u_long);
+
+LIST_HEAD(vlan_ifvlist, ifvlan);
+static struct {
+ kmutex_t lock;
+ struct vlan_ifvlist list;
+} ifv_list __cacheline_aligned;
-/* XXX This should be a hash table with the tag as the basis of the key. */
-static LIST_HEAD(, ifvlan) ifv_list;
-static kmutex_t ifv_mtx __cacheline_aligned;
+#if !defined(VLAN_TAG_HASH_SIZE)
+#define VLAN_TAG_HASH_SIZE 32
+#endif
+static struct {
+ kmutex_t lock;
+ struct pslist_head *lists;
+ u_long mask;
+} ifv_hash __cacheline_aligned = {
+ .lists = NULL,
+ .mask = 0,
+};
+
+pserialize_t vlan_psz __read_mostly;
+static struct psref_class *ifvm_psref_class __read_mostly;
struct if_clone vlan_cloner =
IF_CLONE_INITIALIZER("vlan", vlan_clone_create, vlan_clone_destroy);
@@ -208,10 +263,15 @@ vlanattach(int n)
static void
vlaninit(void)
{
+ mutex_init(&ifv_list.lock, MUTEX_DEFAULT, IPL_NONE);
+ LIST_INIT(&ifv_list.list);
- LIST_INIT(&ifv_list);
- mutex_init(&ifv_mtx, MUTEX_DEFAULT, IPL_NONE);
+ mutex_init(&ifv_hash.lock, MUTEX_DEFAULT, IPL_NONE);
+ vlan_psz = pserialize_create();
+ ifvm_psref_class = psref_class_create("vlanlinkmib", IPL_SOFTNET);
if_clone_attach(&vlan_cloner);
+
+ vlan_hash_init();
}
static int
@@ -219,15 +279,24 @@ vlandetach(void)
{
int error = 0;
- if (!LIST_EMPTY(&ifv_list))
- error = EBUSY;
-
- if (error == 0) {
- if_clone_detach(&vlan_cloner);
- mutex_destroy(&ifv_mtx);
+ mutex_enter(&ifv_list.lock);
+ if (!LIST_EMPTY(&ifv_list.list)) {
+ mutex_exit(&ifv_list.lock);
+ return EBUSY;
}
+ mutex_exit(&ifv_list.lock);
- return error;
+ error = vlan_hash_fini();
+ if (error != 0)
+ return error;
+
+ if_clone_detach(&vlan_cloner);
+ psref_class_destroy(ifvm_psref_class);
+ pserialize_destroy(vlan_psz);
+ mutex_destroy(&ifv_hash.lock);
+ mutex_destroy(&ifv_list.lock);
+
+ return 0;
}
static void
@@ -252,20 +321,32 @@ vlan_clone_create(struct if_clone *ifc,
{
struct ifvlan *ifv;
struct ifnet *ifp;
- int s;
+ struct ifvlan_linkmib *mib;
ifv = malloc(sizeof(struct ifvlan), M_DEVBUF, M_WAITOK|M_ZERO);
+ mib = kmem_zalloc(sizeof(struct ifvlan_linkmib), KM_SLEEP);
ifp = &ifv->ifv_if;
LIST_INIT(&ifv->ifv_mc_listhead);
- s = splnet();
- LIST_INSERT_HEAD(&ifv_list, ifv, ifv_list);
- splx(s);
+ mib->ifvm_ifvlan = ifv;
+ mib->ifvm_p = NULL;
+ psref_target_init(&mib->ifvm_psref, ifvm_psref_class);
+
+ mutex_init(&ifv->ifv_lock, MUTEX_DEFAULT, IPL_NONE);
+ ifv->ifv_mib = mib;
+
+ mutex_enter(&ifv_list.lock);
+ LIST_INSERT_HEAD(&ifv_list.list, ifv, ifv_list);
+ mutex_exit(&ifv_list.lock);
if_initname(ifp, ifc->ifc_name, unit);
ifp->if_softc = ifv;
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
+#ifdef VLAN_MPSAFE
+ ifp->if_extflags = IFEF_START_MPSAFE;
+#endif
ifp->if_start = vlan_start;
+ ifp->if_transmit = vlan_transmit;
ifp->if_ioctl = vlan_ioctl;
IFQ_SET_READY(&ifp->if_snd);
@@ -280,47 +361,66 @@ static int
vlan_clone_destroy(struct ifnet *ifp)
{
struct ifvlan *ifv = ifp->if_softc;
- int s;
- s = splnet();
+ mutex_enter(&ifv_list.lock);
LIST_REMOVE(ifv, ifv_list);
+ mutex_exit(&ifv_list.lock);
+
vlan_unconfig(ifp);
if_detach(ifp);
- splx(s);
+ psref_target_destroy(&ifv->ifv_mib->ifvm_psref, ifvm_psref_class);
+ kmem_free(ifv->ifv_mib, sizeof(struct ifvlan_linkmib));
+ mutex_destroy(&ifv->ifv_lock);
free(ifv, M_DEVBUF);
return (0);
}
/*
- * Configure a VLAN interface. Must be called at splnet().
+ * Configure a VLAN interface.
*/
static int
-vlan_config(struct ifvlan *ifv, struct ifnet *p)
+vlan_config(struct ifvlan *ifv, struct ifnet *p, uint16_t tag)
{
struct ifnet *ifp = &ifv->ifv_if;
- int error;
+ struct ifvlan_linkmib *nmib = NULL;
+ struct ifvlan_linkmib *omib = NULL;
+ struct psref_target *nmib_psref = NULL;
+ int error = 0;
+ int idx;
+ bool omib_cleanup = false;
+
+ nmib = kmem_alloc(sizeof(*nmib), KM_SLEEP);
+
+ mutex_enter(&ifv->ifv_lock);
+ omib = ifv->ifv_mib;
+
+ if (omib->ifvm_p != NULL) {
+ error = EBUSY;
+ goto done;
+ }
- if (ifv->ifv_p != NULL)
- return (EBUSY);
+ *nmib = *omib;
+ nmib_psref = &nmib->ifvm_psref;
+
+ psref_target_init(nmib_psref, ifvm_psref_class);
switch (p->if_type) {
case IFT_ETHER:
{
struct ethercom *ec = (void *) p;
-
- ifv->ifv_msw = &vlan_ether_multisw;
- ifv->ifv_encaplen = ETHER_VLAN_ENCAP_LEN;
- ifv->ifv_mintu = ETHERMIN;
+ nmib->ifvm_msw = &vlan_ether_multisw;
+ nmib->ifvm_encaplen = ETHER_VLAN_ENCAP_LEN;
+ nmib->ifvm_mintu = ETHERMIN;
if (ec->ec_nvlans++ == 0) {
if ((error = ether_enable_vlan_mtu(p)) >= 0) {
if (error) {
ec->ec_nvlans--;
- return error;
+ goto done;
}
- ifv->ifv_mtufudge = 0;
+ nmib->ifvm_mtufudge = 0;
} else {
/*
* Fudge the MTU by the encapsulation size. This
@@ -329,8 +429,9 @@ vlan_config(struct ifvlan *ifv, struct i
* the feature with other NetBSD
* implementations, which might still be useful.
*/
- ifv->ifv_mtufudge = ifv->ifv_encaplen;
+ nmib->ifvm_mtufudge = nmib->ifvm_encaplen;
}
+ error = 0;
}
/*
@@ -358,11 +459,13 @@ vlan_config(struct ifvlan *ifv, struct i
}
default:
- return (EPROTONOSUPPORT);
+ error = EPROTONOSUPPORT;
+ goto done;
}
- ifv->ifv_p = p;
- ifv->ifv_if.if_mtu = p->if_mtu - ifv->ifv_mtufudge;
+ nmib->ifvm_p = p;
+ nmib->ifvm_tag = tag;
+ ifv->ifv_if.if_mtu = p->if_mtu - nmib->ifvm_mtufudge;
ifv->ifv_if.if_flags = p->if_flags &
(IFF_UP | IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST);
@@ -372,32 +475,80 @@ vlan_config(struct ifvlan *ifv, struct i
*/
ifv->ifv_if.if_type = p->if_type;
- return (0);
+ idx = tag_hash_func(tag, ifv_hash.mask);
+
+ mutex_enter(&ifv_hash.lock);
+ PSLIST_WRITER_INSERT_HEAD(&ifv_hash.lists[idx], ifv, ifv_hash);
+ mutex_exit(&ifv_hash.lock);
+
+ vlan_linkmib_update(ifv, nmib);
+ nmib = NULL;
+ nmib_psref = NULL;
+ omib_cleanup = true;
+
+done:
+ mutex_exit(&ifv->ifv_lock);
+
+ if (nmib_psref)
+ psref_target_destroy(nmib_psref, ifvm_psref_class);
+
+ if (nmib)
+ kmem_free(nmib, sizeof(*nmib));
+
+ if (omib_cleanup)
+ kmem_free(omib, sizeof(*omib));
+
+ return error;
}
/*
- * Unconfigure a VLAN interface. Must be called at splnet().
+ * Unconfigure a VLAN interface.
*/
static void
vlan_unconfig(struct ifnet *ifp)
{
struct ifvlan *ifv = ifp->if_softc;
+ struct ifvlan_linkmib *nmib = NULL;
+ int error;
+
+ nmib = kmem_alloc(sizeof(*nmib), KM_SLEEP);
+
+ mutex_enter(&ifv->ifv_lock);
+ error = vlan_unconfig_locked(ifv, nmib);
+ mutex_exit(&ifv->ifv_lock);
+
+ if (error)
+ kmem_free(nmib, sizeof(*nmib));
+}
+static int
+vlan_unconfig_locked(struct ifvlan *ifv, struct ifvlan_linkmib *nmib)
+{
struct ifnet *p;
+ struct ifnet *ifp = &ifv->ifv_if;
+ struct psref_target *nmib_psref = NULL;
+ struct ifvlan_linkmib *omib;
+ int error = 0;
+
+ KASSERT(mutex_owned(&ifv->ifv_lock));
- mutex_enter(&ifv_mtx);
- p = ifv->ifv_p;
+ omib = ifv->ifv_mib;
+ p = omib->ifvm_p;
if (p == NULL) {
- mutex_exit(&ifv_mtx);
- return;
+ error = -1;
+ goto done;
}
+ *nmib = *omib;
+ nmib_psref = &nmib->ifvm_psref;
+ psref_target_init(nmib_psref, ifvm_psref_class);
+
/*
* Since the interface is being unconfigured, we need to empty the
* list of multicast groups that we may have joined while we were
* alive and remove them from the parent's list also.
*/
- (*ifv->ifv_msw->vmsw_purgemulti)(ifv);
+ (*nmib->ifvm_msw->vmsw_purgemulti)(ifv);
/* Disconnect from parent. */
switch (p->if_type) {
@@ -420,22 +571,156 @@ vlan_unconfig(struct ifnet *ifp)
#endif
}
- ifv->ifv_p = NULL;
+ nmib->ifvm_p = NULL;
ifv->ifv_if.if_mtu = 0;
ifv->ifv_flags = 0;
+ mutex_enter(&ifv_hash.lock);
+ PSLIST_WRITER_REMOVE(ifv, ifv_hash);
+ pserialize_perform(vlan_psz);
+ mutex_exit(&ifv_hash.lock);
+
+ vlan_linkmib_update(ifv, nmib);
+
+ mutex_exit(&ifv->ifv_lock);
+
+ nmib_psref = NULL;
+ kmem_free(omib, sizeof(*omib));
+
#ifdef INET6
/* To delete v6 link local addresses */
if (in6_present)
in6_ifdetach(ifp);
#endif
+
if ((ifp->if_flags & IFF_PROMISC) != 0)
ifpromisc(ifp, 0);
if_down(ifp);
ifp->if_flags &= ~(IFF_UP|IFF_RUNNING);
ifp->if_capabilities = 0;
+ mutex_enter(&ifv->ifv_lock);
+done:
+
+ if (nmib_psref)
+ psref_target_destroy(nmib_psref, ifvm_psref_class);
- mutex_exit(&ifv_mtx);
+ return error;
+}
+
+static void
+vlan_hash_init(void)
+{
+
+ ifv_hash.lists = hashinit(VLAN_TAG_HASH_SIZE, HASH_PSLIST, true,
+ &ifv_hash.mask);
+}
+
+static int
+vlan_hash_fini(void)
+{
+ int i;
+
+ mutex_enter(&ifv_hash.lock);
+
+ for (i = 0; i < ifv_hash.mask + 1; i++) {
+ if (PSLIST_WRITER_FIRST(&ifv_hash.lists[i], struct ifvlan,
+ ifv_hash) != NULL) {
+ mutex_exit(&ifv_hash.lock);
+ return EBUSY;
+ }
+ }
+
+ for (i = 0; i < ifv_hash.mask + 1; i++)
+ PSLIST_DESTROY(&ifv_hash.lists[i]);
+
+ mutex_exit(&ifv_hash.lock);
+
+ hashdone(ifv_hash.lists, HASH_PSLIST, ifv_hash.mask);
+
+ ifv_hash.lists = NULL;
+ ifv_hash.mask = 0;
+
+ return 0;
+}
+
+static int
+tag_hash_func(uint16_t tag, u_long mask)
+{
+ uint32_t hash;
+
+ hash = (tag >> 8) ^ tag;
+ hash = (hash >> 2) ^ hash;
+
+ return hash & mask;
+}
+
+static struct ifvlan_linkmib *
+vlan_getref_linkmib(struct ifvlan *sc, struct psref *psref)
+{
+ struct ifvlan_linkmib *mib;
+ int s;
+
+ s = pserialize_read_enter();
+ mib = sc->ifv_mib;
+ if (mib == NULL) {
+ pserialize_read_exit(s);
+ return NULL;
+ }
+ membar_datadep_consumer();
+ psref_acquire(psref, &mib->ifvm_psref, ifvm_psref_class);
+ pserialize_read_exit(s);
+
+ return mib;
+}
+
+static void
+vlan_putref_linkmib(struct ifvlan_linkmib *mib, struct psref *psref)
+{
+ if (mib == NULL)
+ return;
+ psref_release(psref, &mib->ifvm_psref, ifvm_psref_class);
+}
+
+static struct ifvlan_linkmib *
+vlan_lookup_tag_psref(struct ifnet *ifp, uint16_t tag, struct psref *psref)
+{
+ int idx;
+ int s;
+ struct ifvlan *sc;
+
+ idx = tag_hash_func(tag, ifv_hash.mask);
+
+ s = pserialize_read_enter();
+ PSLIST_READER_FOREACH(sc, &ifv_hash.lists[idx], struct ifvlan,
+ ifv_hash) {
+ struct ifvlan_linkmib *mib = sc->ifv_mib;
+ if (mib == NULL)
+ continue;
+ if (mib->ifvm_tag != tag)
+ continue;
+ if (mib->ifvm_p != ifp)
+ continue;
+
+ psref_acquire(psref, &mib->ifvm_psref, ifvm_psref_class);
+ pserialize_read_exit(s);
+ return mib;
+ }
+ pserialize_read_exit(s);
+ return NULL;
+}
+
+static void
+vlan_linkmib_update(struct ifvlan *ifv, struct ifvlan_linkmib *nmib)
+{
+ struct ifvlan_linkmib *omib = ifv->ifv_mib;
+
+ KASSERT(mutex_owned(&ifv->ifv_lock));
+
+ membar_producer();
+ ifv->ifv_mib = nmib;
+
+ pserialize_perform(vlan_psz);
+ psref_target_destroy(&omib->ifvm_psref, ifvm_psref_class);
}
/*
@@ -446,38 +731,98 @@ void
vlan_ifdetach(struct ifnet *p)
{
struct ifvlan *ifv;
- int s;
+ struct ifvlan_linkmib *mib, **nmibs;
+ struct psref psref;
+ int error;
+ int bound;
+ int i, cnt = 0;
- s = splnet();
+ bound = curlwp_bind();
+ mutex_enter(&ifv_list.lock);
+ LIST_FOREACH(ifv, &ifv_list.list, ifv_list) {
+ mib = vlan_getref_linkmib(ifv, &psref);
+ if (mib == NULL)
+ continue;
- for (ifv = LIST_FIRST(&ifv_list); ifv != NULL;
- ifv = LIST_NEXT(ifv, ifv_list)) {
- if (ifv->ifv_p == p)
- vlan_unconfig(&ifv->ifv_if);
+ if (mib->ifvm_p == p)
+ cnt++;
+
+ vlan_putref_linkmib(mib, &psref);
}
+ mutex_exit(&ifv_list.lock);
- splx(s);
+ /*
+ * The value of "cnt" does not increase while ifv_list.lock
+ * and ifv->ifv_lock are released here, because the parent
+ * interface is detaching.
+ */
+ nmibs = kmem_alloc(sizeof(*nmibs) * cnt, KM_SLEEP);
+ for (i=0; i < cnt; i++) {
+ nmibs[i] = kmem_alloc(sizeof(*nmibs[i]), KM_SLEEP);
+ }
+
+ mutex_enter(&ifv_list.lock);
+
+ i = 0;
+ LIST_FOREACH(ifv, &ifv_list.list, ifv_list) {
+ mutex_enter(&ifv->ifv_lock);
+ if (ifv->ifv_mib->ifvm_p == p) {
+ KASSERTMSG(i < cnt, "no memory for unconfig, parent=%s",
+ p->if_xname);
+ error = vlan_unconfig_locked(ifv, nmibs[i]);
+ if (!error) {
+ nmibs[i] = NULL;
+ i++;
+ }
+
+ }
+ mutex_exit(&ifv->ifv_lock);
+ }
+
+ mutex_exit(&ifv_list.lock);
+ curlwp_bindx(bound);
+
+ for (i=0; i < cnt; i++) {
+ if (nmibs[i])
+ kmem_free(nmibs[i], sizeof(*nmibs[i]));
+ }
+
+ kmem_free(nmibs, sizeof(*nmibs) * cnt);
+
+ return;
}
static int
vlan_set_promisc(struct ifnet *ifp)
{
struct ifvlan *ifv = ifp->if_softc;
+ struct ifvlan_linkmib *mib;
+ struct psref psref;
int error = 0;
+ int bound;
+
+ bound = curlwp_bind();
+ mib = vlan_getref_linkmib(ifv, &psref);
+ if (mib == NULL) {
+ curlwp_bindx(bound);
+ return EBUSY;
+ }
if ((ifp->if_flags & IFF_PROMISC) != 0) {
if ((ifv->ifv_flags & IFVF_PROMISC) == 0) {
- error = ifpromisc(ifv->ifv_p, 1);
+ error = ifpromisc(mib->ifvm_p, 1);
if (error == 0)
ifv->ifv_flags |= IFVF_PROMISC;
}
} else {
if ((ifv->ifv_flags & IFVF_PROMISC) != 0) {
- error = ifpromisc(ifv->ifv_p, 0);
+ error = ifpromisc(mib->ifvm_p, 0);
if (error == 0)
ifv->ifv_flags &= ~IFVF_PROMISC;
}
}
+ vlan_putref_linkmib(mib, &psref);
+ curlwp_bindx(bound);
return (error);
}
@@ -492,20 +837,40 @@ vlan_ioctl(struct ifnet *ifp, u_long cmd
struct ifnet *pr;
struct ifcapreq *ifcr;
struct vlanreq vlr;
- int s, error = 0;
-
- s = splnet();
+ struct ifvlan_linkmib *mib;
+ struct psref psref;
+ int error = 0;
+ int bound;
switch (cmd) {
case SIOCSIFMTU:
- if (ifv->ifv_p == NULL)
+ bound = curlwp_bind();
+ mib = vlan_getref_linkmib(ifv, &psref);
+ if (mib == NULL) {
+ curlwp_bindx(bound);
+ error = EBUSY;
+ break;
+ }
+
+ if (mib->ifvm_p == NULL) {
+ vlan_putref_linkmib(mib, &psref);
+ curlwp_bindx(bound);
error = EINVAL;
- else if (
- ifr->ifr_mtu > (ifv->ifv_p->if_mtu - ifv->ifv_mtufudge) ||
- ifr->ifr_mtu < (ifv->ifv_mintu - ifv->ifv_mtufudge))
+ } else if (
+ ifr->ifr_mtu > (mib->ifvm_p->if_mtu - mib->ifvm_mtufudge) ||
+ ifr->ifr_mtu < (mib->ifvm_mintu - mib->ifvm_mtufudge)) {
+ vlan_putref_linkmib(mib, &psref);
+ curlwp_bindx(bound);
error = EINVAL;
- else if ((error = ifioctl_common(ifp, cmd, data)) == ENETRESET)
- error = 0;
+ } else {
+ vlan_putref_linkmib(mib, &psref);
+ curlwp_bindx(bound);
+
+ error = ifioctl_common(ifp, cmd, data);
+ if (error == ENETRESET)
+ error = 0;
+ }
+
break;
case SIOCSETVLAN:
@@ -516,10 +881,23 @@ vlan_ioctl(struct ifnet *ifp, u_long cmd
break;
if ((error = copyin(ifr->ifr_data, &vlr, sizeof(vlr))) != 0)
break;
+
if (vlr.vlr_parent[0] == '\0') {
- if (ifv->ifv_p != NULL &&
+ bound = curlwp_bind();
+ mib = vlan_getref_linkmib(ifv, &psref);
+ if (mib == NULL) {
+ curlwp_bindx(bound);
+ error = EBUSY;
+ break;
+ }
+
+ if (mib->ifvm_p != NULL &&
(ifp->if_flags & IFF_PROMISC) != 0)
- error = ifpromisc(ifv->ifv_p, 0);
+ error = ifpromisc(mib->ifvm_p, 0);
+
+ vlan_putref_linkmib(mib, &psref);
+ curlwp_bindx(bound);
+
vlan_unconfig(ifp);
break;
}
@@ -531,9 +909,10 @@ vlan_ioctl(struct ifnet *ifp, u_long cmd
error = ENOENT;
break;
}
- if ((error = vlan_config(ifv, pr)) != 0)
+ error = vlan_config(ifv, pr, vlr.vlr_tag);
+ if (error != 0) {
break;
- ifv->ifv_tag = vlr.vlr_tag;
+ }
ifp->if_flags |= IFF_RUNNING;
/* Update promiscuous mode, if necessary. */
@@ -542,11 +921,20 @@ vlan_ioctl(struct ifnet *ifp, u_long cmd
case SIOCGETVLAN:
memset(&vlr, 0, sizeof(vlr));
- if (ifv->ifv_p != NULL) {
+ bound = curlwp_bind();
+ mib = vlan_getref_linkmib(ifv, &psref);
+ if (mib == NULL) {
+ curlwp_bindx(bound);
+ error = EBUSY;
+ break;
+ }
+ if (mib->ifvm_p != NULL) {
snprintf(vlr.vlr_parent, sizeof(vlr.vlr_parent), "%s",
- ifv->ifv_p->if_xname);
- vlr.vlr_tag = ifv->ifv_tag;
+ mib->ifvm_p->if_xname);
+ vlr.vlr_tag = mib->ifvm_tag;
}
+ vlan_putref_linkmib(mib, &psref);
+ curlwp_bindx(bound);
error = copyout(&vlr, ifr->ifr_data, sizeof(vlr));
break;
@@ -557,40 +945,97 @@ vlan_ioctl(struct ifnet *ifp, u_long cmd
* For promiscuous mode, we enable promiscuous mode on
* the parent if we need promiscuous on the VLAN interface.
*/
- if (ifv->ifv_p != NULL)
+ bound = curlwp_bind();
+ mib = vlan_getref_linkmib(ifv, &psref);
+ if (mib == NULL) {
+ curlwp_bindx(bound);
+ error = EBUSY;
+ break;
+ }
+
+ if (mib->ifvm_p != NULL)
error = vlan_set_promisc(ifp);
+ vlan_putref_linkmib(mib, &psref);
+ curlwp_bindx(bound);
break;
case SIOCADDMULTI:
- error = (ifv->ifv_p != NULL) ?
- (*ifv->ifv_msw->vmsw_addmulti)(ifv, ifr) : EINVAL;
+ mutex_enter(&ifv->ifv_lock);
+ mib = ifv->ifv_mib;
+ if (mib == NULL) {
+ error = EBUSY;
+ mutex_exit(&ifv->ifv_lock);
+ break;
+ }
+
+ error = (mib->ifvm_p != NULL) ?
+ (*mib->ifvm_msw->vmsw_addmulti)(ifv, ifr) : EINVAL;
+ mib = NULL;
+ mutex_exit(&ifv->ifv_lock);
break;
case SIOCDELMULTI:
- error = (ifv->ifv_p != NULL) ?
- (*ifv->ifv_msw->vmsw_delmulti)(ifv, ifr) : EINVAL;
+ mutex_enter(&ifv->ifv_lock);
+ mib = ifv->ifv_mib;
+ if (mib == NULL) {
+ error = EBUSY;
+ mutex_exit(&ifv->ifv_lock);
+ break;
+ }
+ error = (mib->ifvm_p != NULL) ?
+ (*mib->ifvm_msw->vmsw_delmulti)(ifv, ifr) : EINVAL;
+ mib = NULL;
+ mutex_exit(&ifv->ifv_lock);
break;
case SIOCSIFCAP:
ifcr = data;
/* make sure caps are enabled on parent */
- if (ifv->ifv_p == NULL) {
+ bound = curlwp_bind();
+ mib = vlan_getref_linkmib(ifv, &psref);
+ if (mib == NULL) {
+ curlwp_bindx(bound);
+ error = EBUSY;
+ break;
+ }
+
+ if (mib->ifvm_p == NULL) {
+ vlan_putref_linkmib(mib, &psref);
+ curlwp_bindx(bound);
error = EINVAL;
break;
}
- if ((ifv->ifv_p->if_capenable & ifcr->ifcr_capenable) !=
+ if ((mib->ifvm_p->if_capenable & ifcr->ifcr_capenable) !=
ifcr->ifcr_capenable) {
+ vlan_putref_linkmib(mib, &psref);
+ curlwp_bindx(bound);
error = EINVAL;
break;
}
+
+ vlan_putref_linkmib(mib, &psref);
+ curlwp_bindx(bound);
+
if ((error = ifioctl_common(ifp, cmd, data)) == ENETRESET)
error = 0;
break;
case SIOCINITIFADDR:
- if (ifv->ifv_p == NULL) {
+ bound = curlwp_bind();
+ mib = vlan_getref_linkmib(ifv, &psref);
+ if (mib == NULL) {
+ curlwp_bindx(bound);
+ error = EBUSY;
+ break;
+ }
+
+ if (mib->ifvm_p == NULL) {
error = EINVAL;
+ vlan_putref_linkmib(mib, &psref);
+ curlwp_bindx(bound);
break;
}
+ vlan_putref_linkmib(mib, &psref);
+ curlwp_bindx(bound);
ifp->if_flags |= IFF_UP;
#ifdef INET
@@ -603,8 +1048,6 @@ vlan_ioctl(struct ifnet *ifp, u_long cmd
error = ether_ioctl(ifp, cmd, data);
}
- splx(s);
-
return (error);
}
@@ -614,8 +1057,11 @@ vlan_ether_addmulti(struct ifvlan *ifv,
const struct sockaddr *sa = ifreq_getaddr(SIOCADDMULTI, ifr);
struct vlan_mc_entry *mc;
uint8_t addrlo[ETHER_ADDR_LEN], addrhi[ETHER_ADDR_LEN];
+ struct ifvlan_linkmib *mib;
int error;
+ KASSERT(mutex_owned(&ifv->ifv_lock));
+
if (sa->sa_len > sizeof(struct sockaddr_storage))
return (EINVAL);
@@ -643,7 +1089,9 @@ vlan_ether_addmulti(struct ifvlan *ifv,
memcpy(&mc->mc_addr, sa, sa->sa_len);
LIST_INSERT_HEAD(&ifv->ifv_mc_listhead, mc, mc_entries);
- error = if_mcast_op(ifv->ifv_p, SIOCADDMULTI, sa);
+ mib = ifv->ifv_mib;
+ error = if_mcast_op(mib->ifvm_p, SIOCADDMULTI, sa);
+
if (error != 0)
goto ioctl_failed;
return (error);
@@ -662,9 +1110,12 @@ vlan_ether_delmulti(struct ifvlan *ifv,
const struct sockaddr *sa = ifreq_getaddr(SIOCDELMULTI, ifr);
struct ether_multi *enm;
struct vlan_mc_entry *mc;
+ struct ifvlan_linkmib *mib;
uint8_t addrlo[ETHER_ADDR_LEN], addrhi[ETHER_ADDR_LEN];
int error;
+ KASSERT(mutex_owned(&ifv->ifv_lock));
+
/*
* Find a key to lookup vlan_mc_entry. We have to do this
* before calling ether_delmulti for obvious reason.
@@ -678,7 +1129,9 @@ vlan_ether_delmulti(struct ifvlan *ifv,
return (error);
/* We no longer use this multicast address. Tell parent so. */
- error = if_mcast_op(ifv->ifv_p, SIOCDELMULTI, sa);
+ mib = ifv->ifv_mib;
+ error = if_mcast_op(mib->ifvm_p, SIOCDELMULTI, sa);
+
if (error == 0) {
/* And forget about this address. */
for (mc = LIST_FIRST(&ifv->ifv_mc_listhead); mc != NULL;
@@ -702,11 +1155,17 @@ vlan_ether_delmulti(struct ifvlan *ifv,
static void
vlan_ether_purgemulti(struct ifvlan *ifv)
{
- struct ifnet *ifp = ifv->ifv_p; /* Parent. */
struct vlan_mc_entry *mc;
+ struct ifvlan_linkmib *mib;
+
+ KASSERT(mutex_owned(&ifv->ifv_lock));
+ mib = ifv->ifv_mib;
+ if (mib == NULL) {
+ return;
+ }
while ((mc = LIST_FIRST(&ifv->ifv_mc_listhead)) != NULL) {
- (void)if_mcast_op(ifp, SIOCDELMULTI,
+ (void)if_mcast_op(mib->ifvm_p, SIOCDELMULTI,
(const struct sockaddr *)&mc->mc_addr);
LIST_REMOVE(mc, mc_entries);
free(mc, M_DEVBUF);
@@ -717,15 +1176,23 @@ static void
vlan_start(struct ifnet *ifp)
{
struct ifvlan *ifv = ifp->if_softc;
- struct ifnet *p = ifv->ifv_p;
- struct ethercom *ec = (void *) ifv->ifv_p;
+ struct ifnet *p;
+ struct ethercom *ec;
struct mbuf *m;
+ struct ifvlan_linkmib *mib;
+ struct psref psref;
int error;
#ifndef NET_MPSAFE
KASSERT(KERNEL_LOCKED_P());
#endif
+ mib = vlan_getref_linkmib(ifv, &psref);
+ if (mib == NULL)
+ return;
+ p = mib->ifvm_p;
+ ec = (void *)mib->ifvm_p;
+
ifp->if_flags |= IFF_OACTIVE;
for (;;) {
@@ -773,16 +1240,16 @@ vlan_start(struct ifnet *ifp)
m_freem(m);
continue;
}
- *(u_int *)(mtag + 1) = ifv->ifv_tag;
+ *(u_int *)(mtag + 1) = mib->ifvm_tag;
m_tag_prepend(m, mtag);
} else {
/*
* insert the tag ourselves
*/
- M_PREPEND(m, ifv->ifv_encaplen, M_DONTWAIT);
+ M_PREPEND(m, mib->ifvm_encaplen, M_DONTWAIT);
if (m == NULL) {
printf("%s: unable to prepend encap header",
- ifv->ifv_p->if_xname);
+ p->if_xname);
ifp->if_oerrors++;
continue;
}
@@ -797,7 +1264,7 @@ vlan_start(struct ifnet *ifp)
sizeof(struct ether_vlan_header));
if (m == NULL) {
printf("%s: unable to pullup encap "
- "header", ifv->ifv_p->if_xname);
+ "header", p->if_xname);
ifp->if_oerrors++;
continue;
}
@@ -807,12 +1274,12 @@ vlan_start(struct ifnet *ifp)
* Ethernet header with 802.1Q encapsulation.
*/
memmove(mtod(m, void *),
- mtod(m, char *) + ifv->ifv_encaplen,
+ mtod(m, char *) + mib->ifvm_encaplen,
sizeof(struct ether_header));
evl = mtod(m, struct ether_vlan_header *);
evl->evl_proto = evl->evl_encap_proto;
evl->evl_encap_proto = htons(ETHERTYPE_VLAN);
- evl->evl_tag = htons(ifv->ifv_tag);
+ evl->evl_tag = htons(mib->ifvm_tag);
/*
* To cater for VLAN-aware layer 2 ethernet
@@ -858,6 +1325,145 @@ vlan_start(struct ifnet *ifp)
}
ifp->if_flags &= ~IFF_OACTIVE;
+
+ /* Remove reference to mib before release */
+ p = NULL;
+ ec = NULL;
+
+ vlan_putref_linkmib(mib, &psref);
+}
+
+static int
+vlan_transmit(struct ifnet *ifp, struct mbuf *m)
+{
+ struct ifvlan *ifv = ifp->if_softc;
+ struct ifnet *p;
+ struct ethercom *ec;
+ struct ifvlan_linkmib *mib;
+ struct psref psref;
+ int error;
+
+ mib = vlan_getref_linkmib(ifv, &psref);
+ if (mib == NULL) {
+ m_freem(m);
+ return ENETDOWN;
+ }
+
+ p = mib->ifvm_p;
+ ec = (void *)mib->ifvm_p;
+
+ bpf_mtap(ifp, m);
+ /*
+ * If the parent can insert the tag itself, just mark
+ * the tag in the mbuf header.
+ */
+ if (ec->ec_capabilities & ETHERCAP_VLAN_HWTAGGING) {
+ struct m_tag *mtag;
+
+ mtag = m_tag_get(PACKET_TAG_VLAN, sizeof(u_int),
+ M_NOWAIT);
+ if (mtag == NULL) {
+ ifp->if_oerrors++;
+ m_freem(m);
+ error = ENOBUFS;
+ goto out;
+ }
+ *(u_int *)(mtag + 1) = mib->ifvm_tag;
+ m_tag_prepend(m, mtag);
+ } else {
+ /*
+ * insert the tag ourselves
+ */
+ M_PREPEND(m, mib->ifvm_encaplen, M_DONTWAIT);
+ if (m == NULL) {
+ printf("%s: unable to prepend encap header",
+ p->if_xname);
+ ifp->if_oerrors++;
+ error = ENOBUFS;
+ goto out;
+ }
+
+ switch (p->if_type) {
+ case IFT_ETHER:
+ {
+ struct ether_vlan_header *evl;
+
+ if (m->m_len < sizeof(struct ether_vlan_header))
+ m = m_pullup(m,
+ sizeof(struct ether_vlan_header));
+ if (m == NULL) {
+ printf("%s: unable to pullup encap "
+ "header", p->if_xname);
+ ifp->if_oerrors++;
+ error = ENOBUFS;
+ goto out;
+ }
+
+ /*
+ * Transform the Ethernet header into an
+ * Ethernet header with 802.1Q encapsulation.
+ */
+ memmove(mtod(m, void *),
+ mtod(m, char *) + mib->ifvm_encaplen,
+ sizeof(struct ether_header));
+ evl = mtod(m, struct ether_vlan_header *);
+ evl->evl_proto = evl->evl_encap_proto;
+ evl->evl_encap_proto = htons(ETHERTYPE_VLAN);
+ evl->evl_tag = htons(mib->ifvm_tag);
+
+ /*
+ * To cater for VLAN-aware layer 2 ethernet
+ * switches which may need to strip the tag
+ * before forwarding the packet, make sure
+ * the packet+tag is at least 68 bytes long.
+ * This is necessary because our parent will
+ * only pad to 64 bytes (ETHER_MIN_LEN) and
+ * some switches will not pad by themselves
+ * after deleting a tag.
+ */
+ if (m->m_pkthdr.len <
+ (ETHER_MIN_LEN - ETHER_CRC_LEN +
+ ETHER_VLAN_ENCAP_LEN)) {
+ m_copyback(m, m->m_pkthdr.len,
+ (ETHER_MIN_LEN - ETHER_CRC_LEN +
+ ETHER_VLAN_ENCAP_LEN) -
+ m->m_pkthdr.len,
+ vlan_zero_pad_buff);
+ }
+ break;
+ }
+
+#ifdef DIAGNOSTIC
+ default:
+ panic("vlan_transmit: impossible");
+#endif
+ }
+ }
+
+ if ((p->if_flags & IFF_RUNNING) == 0) {
+ m_freem(m);
+ error = ENETDOWN;
+ goto out;
+ }
+
+ error = if_transmit_lock(p, m);
+ if (error) {
+ /* mbuf is already freed */
+ ifp->if_oerrors++;
+ } else {
+ ifp->if_opackets++;
+ /*
+ * obytes is incremented at ether_output() or bridge_enqueue().
+ */
+ }
+
+out:
+ /* Remove reference to mib before release */
+ p = NULL;
+ ec = NULL;
+
+ vlan_putref_linkmib(mib, &psref);
+ return error;
}
/*
@@ -871,6 +1477,8 @@ vlan_input(struct ifnet *ifp, struct mbu
struct ifvlan *ifv;
u_int tag;
struct m_tag *mtag;
+ struct ifvlan_linkmib *mib;
+ struct psref psref;
mtag = m_tag_find(m, PACKET_TAG_VLAN, NULL);
if (mtag != NULL) {
@@ -912,27 +1520,29 @@ vlan_input(struct ifnet *ifp, struct mbu
}
}
- for (ifv = LIST_FIRST(&ifv_list); ifv != NULL;
- ifv = LIST_NEXT(ifv, ifv_list))
- if (ifp == ifv->ifv_p && tag == ifv->ifv_tag)
- break;
-
- if (ifv == NULL ||
- (ifv->ifv_if.if_flags & (IFF_UP|IFF_RUNNING)) !=
- (IFF_UP|IFF_RUNNING)) {
+ mib = vlan_lookup_tag_psref(ifp, tag, &psref);
+ if (mib == NULL) {
m_freem(m);
ifp->if_noproto++;
return;
}
+ ifv = mib->ifvm_ifvlan;
+ if ((ifv->ifv_if.if_flags & (IFF_UP|IFF_RUNNING)) !=
+ (IFF_UP|IFF_RUNNING)) {
+ m_freem(m);
+ ifp->if_noproto++;
+ goto out;
+ }
+
/*
* Now, remove the encapsulation header. The original
* header has already been fixed up above.
*/
if (mtag == NULL) {
- memmove(mtod(m, char *) + ifv->ifv_encaplen,
+ memmove(mtod(m, char *) + mib->ifvm_encaplen,
mtod(m, void *), sizeof(struct ether_header));
- m_adj(m, ifv->ifv_encaplen);
+ m_adj(m, mib->ifvm_encaplen);
}
m_set_rcvif(m, &ifv->ifv_if);
@@ -940,6 +1550,8 @@ vlan_input(struct ifnet *ifp, struct mbu
m->m_flags &= ~M_PROMISC;
if_input(&ifv->ifv_if, m);
+out:
+ vlan_putref_linkmib(mib, &psref);
}
/*
Index: src/sys/net/if_vlanvar.h
diff -u src/sys/net/if_vlanvar.h:1.9 src/sys/net/if_vlanvar.h:1.9.80.1
--- src/sys/net/if_vlanvar.h:1.9 Mon Apr 28 20:24:09 2008
+++ src/sys/net/if_vlanvar.h Wed Jun 21 17:39:24 2017
@@ -1,4 +1,4 @@
-/* $NetBSD: if_vlanvar.h,v 1.9 2008/04/28 20:24:09 martin Exp $ */
+/* $NetBSD: if_vlanvar.h,v 1.9.80.1 2017/06/21 17:39:24 snj Exp $ */
/*-
* Copyright (c) 2000 The NetBSD Foundation, Inc.
@@ -86,6 +86,37 @@ struct vlanreq {
#ifdef _KERNEL
void vlan_input(struct ifnet *, struct mbuf *);
void vlan_ifdetach(struct ifnet *);
+
+/*
+ * Locking notes:
+ * + ifv_list.list is protected by ifv_list.lock (an adaptive mutex)
+ * ifv_list.list is list of all ifvlans, and it is used to avoid
+ * unload while busy.
+ * + ifv_hash.lists is protected by
+ * - ifv_hash.lock (an adaptive mutex) for writer
+ * - pserialize for reader
+ * ifv_hash.lists is hashed list of all configured
+ * vlan interface, and it is used to avoid unload while busy.
+ * + ifvlan->ifv_linkmib is protected by
+ * - ifvlan->ifv_lock (an adaptive mutex) for writer
+ * - ifv_linkmib->ifvm_psref for reader
+ * ifvlan->ifv_linkmib is used for variant values while tagging
+ * and untagging
+ *
+ * Locking order:
+ * - ifv_list.lock => struct ifvlan->ifv_lock
+ * - struct ifvlan->ifv_lock => ifv_hash.lock
+ * Other mutexes must not hold simultaneously
+ *
+ * NOTICE
+ * - ifvlan must not have a variant value while tagging and
+ * untagging. Such variant values must be in ifvlan->ifv_mib
+ * - ifvlan->ifv_mib is modified like read-copy-update.
+ * So, once we dereference ifvlan->ifv_mib,
+ * we must keep the pointer during the same context. If we
+ * re-dereference ifvlan->ifv_mib, the ifv_mib may be other
+ * one because of concurrent writer processing.
+ */
#endif /* _KERNEL */
#endif /* !_NET_IF_VLANVAR_H_ */