summaryrefslogtreecommitdiffstats
path: root/sys/net
diff options
context:
space:
mode:
authoryar <yar@FreeBSD.org>2006-06-21 07:29:44 +0000
committeryar <yar@FreeBSD.org>2006-06-21 07:29:44 +0000
commitda67d554639deb86ced07d4da75e13a1c5a8642e (patch)
tree69b9020d0ad584df5fc05e2eb1285202ac7ec080 /sys/net
parentd1e0f2db3d463b9d826c181053dde9c0a5be4c5b (diff)
downloadFreeBSD-src-da67d554639deb86ced07d4da75e13a1c5a8642e.zip
FreeBSD-src-da67d554639deb86ced07d4da75e13a1c5a8642e.tar.gz
Track interface department events and detach vlans from
departing trunk so that we don't get into trouble later by dereferencing a stale pointer to dead trunk's things. Prodded by: oleg Sponsored by: RiNet (Cronyx Plus LLC) MFC after: 1 week
Diffstat (limited to 'sys/net')
-rw-r--r--sys/net/if_vlan.c84
1 files changed, 76 insertions, 8 deletions
diff --git a/sys/net/if_vlan.c b/sys/net/if_vlan.c
index 8f0a569..49090b0 100644
--- a/sys/net/if_vlan.c
+++ b/sys/net/if_vlan.c
@@ -81,7 +81,8 @@ struct ifvlantrunk {
struct ifnet *parent; /* parent interface of this trunk */
struct rwlock rw;
#ifdef VLAN_ARRAY
- struct ifvlan *vlans[EVL_VLID_MASK+1]; /* static table */
+#define VLAN_ARRAY_SIZE (EVL_VLID_MASK + 1)
+ struct ifvlan *vlans[VLAN_ARRAY_SIZE]; /* static table */
#else
struct ifvlanhead *hash; /* dynamic hash-list table */
uint16_t hmask;
@@ -135,6 +136,8 @@ SYSCTL_NODE(_net_link_vlan, PF_LINK, link, CTLFLAG_RW, 0, "for consistency");
static MALLOC_DEFINE(M_VLAN, VLANNAME, "802.1Q Virtual LAN Interface");
+static eventhandler_tag ifdetach_tag;
+
/*
* We have a global mutex, that is used to serialize configuration
* changes and isn't used in normal packet delivery.
@@ -182,6 +185,7 @@ static int vlan_setflag(struct ifnet *ifp, int flag, int status,
static int vlan_setflags(struct ifnet *ifp, int status);
static int vlan_setmulti(struct ifnet *ifp);
static int vlan_unconfig(struct ifnet *ifp);
+static int vlan_unconfig_locked(struct ifnet *ifp);
static int vlan_config(struct ifvlan *ifv, struct ifnet *p, uint16_t tag);
static void vlan_link_state(struct ifnet *ifp, int link);
static void vlan_capabilities(struct ifvlan *ifv);
@@ -193,6 +197,8 @@ static int vlan_clone_match(struct if_clone *, const char *);
static int vlan_clone_create(struct if_clone *, char *, size_t);
static int vlan_clone_destroy(struct if_clone *, struct ifnet *);
+static void vlan_ifdetach(void *arg, struct ifnet *ifp);
+
static struct if_clone vlan_cloner = IFC_CLONE_INITIALIZER(VLANNAME, NULL,
IF_MAXUNIT, NULL, vlan_clone_match, vlan_clone_create, vlan_clone_destroy);
@@ -452,6 +458,54 @@ vlan_setmulti(struct ifnet *ifp)
}
/*
+ * A handler for network interface departure events.
+ * Track departure of trunks here so that we don't access invalid
+ * pointers or whatever if a trunk is ripped from under us, e.g.,
+ * by ejecting its hot-plug card.
+ */
+static void
+vlan_ifdetach(void *arg __unused, struct ifnet *ifp)
+{
+ struct ifvlan *ifv;
+ int i;
+
+ /*
+ * Check if it's a trunk interface first of all
+ * to avoid needless locking.
+ */
+ if (ifp->if_vlantrunk == NULL)
+ return;
+
+ VLAN_LOCK();
+ /*
+ * OK, it's a trunk. Loop over and detach all vlan's on it.
+ * Check trunk pointer after each vlan_unconfig() as it will
+ * free it and set to NULL after the last vlan was detached.
+ */
+#ifdef VLAN_ARRAY
+ for (i = 0; i < VLAN_ARRAY_SIZE; i++)
+ if ((ifv = ifp->if_vlantrunk->vlans[i])) {
+ vlan_unconfig_locked(ifv->ifv_ifp);
+ if (ifp->if_vlantrunk == NULL)
+ break;
+ }
+#else /* VLAN_ARRAY */
+restart:
+ for (i = 0; i < (1 << ifp->if_vlantrunk->hwidth); i++)
+ if ((ifv = LIST_FIRST(&ifp->if_vlantrunk->hash[i]))) {
+ vlan_unconfig_locked(ifv->ifv_ifp);
+ if (ifp->if_vlantrunk)
+ goto restart; /* trunk->hwidth can change */
+ else
+ break;
+ }
+#endif /* VLAN_ARRAY */
+ /* Trunk should have been destroyed in vlan_unconfig(). */
+ KASSERT(ifp->if_vlantrunk == NULL, ("%s: purge failed", __func__));
+ VLAN_UNLOCK();
+}
+
+/*
* VLAN support can be loaded as a module. The only place in the
* system that's intimately aware of this is ether_input. We hook
* into this code through vlan_input_p which is defined there and
@@ -469,6 +523,10 @@ vlan_modevent(module_t mod, int type, void *data)
switch (type) {
case MOD_LOAD:
+ ifdetach_tag = EVENTHANDLER_REGISTER(ifnet_departure_event,
+ vlan_ifdetach, NULL, EVENTHANDLER_PRI_ANY);
+ if (ifdetach_tag == NULL)
+ return (ENOMEM);
LIST_INIT(&trunk_list);
VLAN_LOCK_INIT();
vlan_input_p = vlan_input;
@@ -481,6 +539,7 @@ vlan_modevent(module_t mod, int type, void *data)
struct ifvlantrunk *trunk, *trunk1;
if_clone_detach(&vlan_cloner);
+ EVENTHANDLER_DEREGISTER(ifnet_departure_event, ifdetach_tag);
vlan_input_p = NULL;
vlan_link_state_p = NULL;
vlan_trunk_cap_p = NULL;
@@ -1017,12 +1076,23 @@ done:
static int
vlan_unconfig(struct ifnet *ifp)
{
+ int ret;
+
+ VLAN_LOCK();
+ ret = vlan_unconfig_locked(ifp);
+ VLAN_UNLOCK();
+ return (ret);
+}
+
+static int
+vlan_unconfig_locked(struct ifnet *ifp)
+{
struct ifvlantrunk *trunk;
struct vlan_mc_entry *mc;
struct ifvlan *ifv;
int error;
- VLAN_LOCK();
+ VLAN_LOCK_ASSERT();
ifv = ifp->if_softc;
trunk = ifv->ifv_trunk;
@@ -1091,13 +1161,12 @@ vlan_unconfig(struct ifnet *ifp)
/* Disconnect from parent. */
if (ifv->ifv_pflags)
if_printf(ifp, "%s: ifv_pflags unclean\n", __func__);
- ifv->ifv_ifp->if_mtu = ETHERMTU; /* XXX why not 0? */
- ifv->ifv_ifp->if_link_state = LINK_STATE_UNKNOWN;
+ ifp->if_mtu = ETHERMTU;
+ ifp->if_link_state = LINK_STATE_UNKNOWN;
+ ifp->if_drv_flags &= ~IFF_DRV_RUNNING;
/* Clear our MAC address. */
- bzero(IF_LLADDR(ifv->ifv_ifp), ETHER_ADDR_LEN);
-
- VLAN_UNLOCK();
+ bzero(IF_LLADDR(ifp), ETHER_ADDR_LEN);
return (0);
}
@@ -1316,7 +1385,6 @@ vlan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
break;
if (vlr.vlr_parent[0] == '\0') {
vlan_unconfig(ifp);
- ifp->if_drv_flags &= ~IFF_DRV_RUNNING;
break;
}
p = ifunit(vlr.vlr_parent);
OpenPOWER on IntegriCloud