Index | Thread | Search

From:
David Gwynne <david@gwynne.id.au>
Subject:
pvlan(4): backing up a private vlan config workaround
To:
tech@openbsd.org
Date:
Fri, 7 Jun 2024 15:09:45 +1000

Download raw body.

Thread
  • David Gwynne:

    pvlan(4): backing up a private vlan config workaround

i have some stupid switches that support private vlans, but dont
support mixing private vlans and normal vlans on a trunk to a host,
eg, openbsd firewalls. there is a way i could configure my way out
of it, but that process is extremely risky, and this equipment is
remote.

the least worst option i came up with is to tell the switch that the
openbsd firewalls are another switch that can do pvlan, so now it's
trunking both the promisc and the associated isolated pvlans to the
firewall. i just needed a way to have the packets coming in with the
tag from the isolated pvlans mapped back to the promisc pvlan so
stuff like arp will work, so i spent a couple of hours yesterday
afternoon hacking this up.

it is a sad day when it's easier to hack up the kernel than it is
to reconfigure a switch. or a great day for open source? who knows.

this adds a small pvlan(4) interface driver to vlan(4). pvlan interfaces
are configured with a vlan interface as a parent, and their vnetid is
set to the tag used by the relevant isolated or community tag that's
used on the wire. when a packet on the physical interface comes in with
one of these isolated/community tags, it gets mapped back to the
relevant vlan interface like it was there all the time.

packets sent from a host on a promisc pvlan are supposed to have the
promisc vlan tag on them, so there's nothing to do on the tx side.

config looks like this:

ifconfig pvlan901 create
ifconfig vlan900 create
ifconfig carp40900 create
ifconfig pvlan901 description pvlan-iso-dmz
ifconfig pvlan901 parent vlan900 vnetid 901
ifconfig vlan900 description pvlan-dmz
ifconfig vlan900 parent aggr0
ifconfig vlan900 inet alias 192.0.2.131/25
ifconfig vlan900 up
ifconfig pvlan901 up

pvlan901: flags=8043<UP,BROADCAST,RUNNING,MULTICAST>
	description: pvlan-iso-dmz
	index 27 priority 0 llprio 3
	encap: vnetid 901 parent vlan900
	groups: pvlan
	status: active
vlan900: flags=8943<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500
	lladdr fe:e1:ba:d0:8c:a4
	description: pvlan-dmz
	index 28 priority 0 llprio 3
	encap: vnetid 900 parent aggr0 txprio packet rxprio outer
	groups: vlan srv managed
	media: Ethernet autoselect
	status: active
	inet 192.0.2.131 netmask 0xffffff80 broadcast 192.0.2.255

the reason i set this up as a separate interface instead of adding extra
functionality to the existing vlan driver is that the changes were
limited to 1 self contained file in the kernel. i would have had to
add ioctls and plumb them through to ifconfig in userland if i wanted
to do something like `ifconfig vlan0 pvnetid 901,902`. 

i dont think this should go in the tree, it's hack to work around a
stupid switch. however, i wanted to back the diff up and maybe someone
will get something out of reading the code and understanding the
problem.

Index: if_vlan.c
===================================================================
RCS file: /cvs/src/sys/net/if_vlan.c,v
diff -u -p -r1.218 if_vlan.c
--- if_vlan.c	23 Dec 2023 10:52:54 -0000	1.218
+++ if_vlan.c	6 Jun 2024 06:42:17 -0000
@@ -83,6 +83,19 @@ struct vlan_mc_entry {
 	struct sockaddr_storage		mc_addr;
 };
 
+struct vlan_softc;
+
+struct vlan_entry {
+	struct vlan_softc	*v_sc;
+	unsigned int		 v_ifpvidx;
+	uint16_t		 v_tag;
+	uint16_t		 v_type;
+	SMR_SLIST_ENTRY(vlan_entry)
+				 v_entry;
+};
+
+SMR_SLIST_HEAD(vlan_list, vlan_entry);
+
 struct vlan_softc {
 	struct arpcom		 sc_ac;
 #define	sc_if			 sc_ac.ac_if
@@ -90,20 +103,17 @@ struct vlan_softc {
 	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 vlan_entry	 sc_entry;
+#define sc_type		sc_entry.v_type
+#define sc_tag		sc_entry.v_tag
 	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 */
 
@@ -127,7 +137,7 @@ 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);
+void	vlan_link_state(struct ifnet *, u_char, uint64_t);
 
 int	vlan_set_vnetid(struct vlan_softc *, uint16_t);
 int	vlan_set_parent(struct vlan_softc *, const char *);
@@ -153,6 +163,12 @@ struct if_clone vlan_cloner =
 struct if_clone svlan_cloner =
     IF_CLONE_INITIALIZER("svlan", vlan_clone_create, vlan_clone_destroy);
 
+static int	pvlan_clone_create(struct if_clone *, int);
+static int	pvlan_clone_destroy(struct ifnet *);
+
+static struct if_clone pvlan_cloner =
+    IF_CLONE_INITIALIZER("pvlan", pvlan_clone_create, pvlan_clone_destroy);
+
 void
 vlanattach(int count)
 {
@@ -177,6 +193,8 @@ vlanattach(int count)
 
 	if_clone_attach(&vlan_cloner);
 	if_clone_attach(&svlan_cloner);
+
+	if_clone_attach(&pvlan_cloner);
 }
 
 int
@@ -197,6 +215,8 @@ vlan_clone_create(struct if_clone *ifc, 
 	/* NB: flags are not set here */
 	/* NB: mtu is not set here */
 
+	sc->sc_entry.v_sc = sc;
+	sc->sc_entry.v_ifpvidx = 0;
 	/* Special handling for the IEEE 802.1ad QinQ variant */
 	if (strcmp("svlan", ifc->ifc_name) == 0)
 		sc->sc_type = ETHERTYPE_QINQ;
@@ -377,12 +404,14 @@ struct mbuf *
 vlan_input(struct ifnet *ifp0, struct mbuf *m, unsigned int *sdelim)
 {
 	struct vlan_softc *sc;
+	struct vlan_entry *v;
 	struct ifnet *ifp;
 	struct ether_vlan_header *evl;
 	struct vlan_list *tagh, *list;
 	uint16_t vtag, tag;
 	uint16_t etype;
 	int rxprio;
+	unsigned int ifpvidx = 0;
 
 	if (m->m_flags & M_VLANTAG) {
 		vtag = m->m_pkthdr.ether_vtag;
@@ -414,12 +443,15 @@ vlan_input(struct ifnet *ifp0, struct mb
 	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) {
+	SMR_SLIST_FOREACH(v, list, v_entry) {
+		sc = v->v_sc;
+		if (ifp0->if_index == sc->sc_ifidx0 && tag == v->v_tag &&
+		    etype == v->v_type) {
+			ifpvidx = v->v_ifpvidx;
 			refcnt_take(&sc->sc_refcnt);
 			break;
 		}
+		sc = NULL;
 	}
 	smr_read_leave();
 
@@ -471,6 +503,32 @@ vlan_input(struct ifnet *ifp0, struct mb
 		break;
 	}
 
+	/*
+	 * show the packet to the pvlan interface as a courtesy.
+	 * let's hope branch prediction does a good job otherwise.
+	 */
+	if (ifpvidx != 0) {
+		struct ifnet *ifppv = if_get(ifpvidx);
+		if (ifppv != NULL) {
+#if NBPFILTER > 0
+			caddr_t if_bpf;
+#endif
+			counters_pkt(ifppv->if_counters,
+			    ifc_ipackets, ifc_ibytes, m->m_pkthdr.len);
+#if NBPFILTER > 0
+			if_bpf = ifppv->if_bpf;
+			if (if_bpf && bpf_mtap(if_bpf, m, BPF_DIRECTION_IN)) {
+				m_freem(m);
+				m = NULL;
+			}
+#endif
+		}
+		if_put(ifppv);
+
+		if (m == NULL)
+			goto leave;
+	}
+
 	if_vinput(ifp, m);
 leave:
 	refcnt_rele_wake(&sc->sc_refcnt);
@@ -549,7 +607,7 @@ vlan_up(struct vlan_softc *sc)
 	if (error != 0)
 		goto leave;
 
-	SMR_SLIST_INSERT_HEAD_LOCKED(list, sc, sc_list);
+	SMR_SLIST_INSERT_HEAD_LOCKED(list, &sc->sc_entry, v_entry);
 	rw_exit(&vlan_tagh_lk);
 
 	/* Register callback for physical link state changes */
@@ -563,7 +621,7 @@ vlan_up(struct vlan_softc *sc)
 
 	/* we're running now */
 	SET(ifp->if_flags, IFF_RUNNING);
-	vlan_link_state(sc, ifp0->if_link_state, ifp0->if_baudrate);
+	vlan_link_state(ifp, ifp0->if_link_state, ifp0->if_baudrate);
 
 	if_put(ifp0);
 
@@ -590,13 +648,14 @@ vlan_down(struct vlan_softc *sc)
 	struct vlan_list *tagh, *list;
 	struct ifnet *ifp = &sc->sc_if;
 	struct ifnet *ifp0;
+	struct vlan_entry *v;
 
 	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);
+	vlan_link_state(ifp, LINK_STATE_DOWN, 0);
 	CLR(ifp->if_flags, IFF_RUNNING);
 
 	ifq_barrier(&ifp->if_snd);
@@ -611,8 +670,10 @@ vlan_down(struct vlan_softc *sc)
 	}
 	if_put(ifp0);
 
+	v = &sc->sc_entry;
+
 	rw_enter_write(&vlan_tagh_lk);
-	SMR_SLIST_REMOVE_LOCKED(list, sc, vlan_softc, sc_list);
+	SMR_SLIST_REMOVE_LOCKED(list, v, vlan_entry, v_entry);
 	rw_exit_write(&vlan_tagh_lk);
 
 	ifp->if_capabilities = 0;
@@ -652,19 +713,19 @@ vlan_link_hook(void *v)
 	}
 	if_put(ifp0);
 
-	vlan_link_state(sc, link, baud);
+	vlan_link_state(&sc->sc_if, link, baud);
 }
 
 void
-vlan_link_state(struct vlan_softc *sc, u_char link, uint64_t baud)
+vlan_link_state(struct ifnet *ifp, u_char link, uint64_t baud)
 {
-	if (sc->sc_if.if_link_state == link)
+	if (ifp->if_link_state == link)
 		return;
 
-	sc->sc_if.if_link_state = link;
-	sc->sc_if.if_baudrate = baud;
+	ifp->if_link_state = link;
+	ifp->if_baudrate = baud;
 
-	if_link_state_change(&sc->sc_if);
+	if_link_state_change(ifp);
 }
 
 int
@@ -877,32 +938,35 @@ vlan_set_vnetid(struct vlan_softc *sc, u
 	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);
+		vlan_link_state(ifp, LINK_STATE_DOWN, 0);
 
 	error = rw_enter(&vlan_tagh_lk, RW_WRITE);
 	if (error != 0)
-		return (error);
+		goto link;
 
 	error = vlan_inuse_locked(sc->sc_type, sc->sc_ifidx0, tag);
 	if (error != 0)
 		goto unlock;
 
+
 	if (ISSET(ifp->if_flags, IFF_RUNNING)) {
+		struct vlan_entry *v = &sc->sc_entry;
+
 		list = &tagh[TAG_HASH(sc->sc_tag)];
-		SMR_SLIST_REMOVE_LOCKED(list, sc, vlan_softc, sc_list);
+		SMR_SLIST_REMOVE_LOCKED(list, v, vlan_entry, v_entry);
 
 		sc->sc_tag = tag;
 
 		list = &tagh[TAG_HASH(sc->sc_tag)];
-		SMR_SLIST_INSERT_HEAD_LOCKED(list, sc, sc_list);
+		SMR_SLIST_INSERT_HEAD_LOCKED(list, v, v_entry);
 	} else
 		sc->sc_tag = tag;
 
 unlock:
 	rw_exit(&vlan_tagh_lk);
-
+link:
 	if (ISSET(ifp->if_flags, IFF_RUNNING) && LINK_STATE_IS_UP(link))
-		vlan_link_state(sc, link, baud);
+		vlan_link_state(ifp, link, baud);
 
 	return (error);
 }
@@ -1051,15 +1115,15 @@ int
 vlan_inuse_locked(uint16_t type, unsigned int ifidx, uint16_t tag)
 {
 	struct vlan_list *tagh, *list;
-	struct vlan_softc *sc;
+	struct vlan_entry *v;
 
 	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)
+	SMR_SLIST_FOREACH_LOCKED(v, list, v_entry) {
+		if (v->v_tag == tag &&
+		    v->v_type == type && /* wat */
+		    v->v_sc->sc_ifidx0 == ifidx) /* XXX */
 			return (EADDRINUSE);
 	}
 
@@ -1213,4 +1277,498 @@ vlan_multi_free(struct vlan_softc *sc)
 		LIST_REMOVE(mc, mc_entries);
 		free(mc, M_DEVBUF, sizeof(*mc));
 	}
+}
+
+/*
+ * pvlan(4): private vlan trunk helper
+ *
+ * pvlan(4) exists so openbsd can be connected as a host to private
+ * vlan trunk and communicate as if it were just a host on the
+ * promiscuous vlan.
+ *
+ * on a private vlan trunk, packets from hosts on isolated and
+ * community vlans will arrive from the trunk with the tags from those
+ * vlans. this driver wires itself up to those vlan tags, and then
+ * sends those packets into the stack as if they arrived on the parent
+ * vlan interface. packets sent from a host on a promiscuous private
+ * vlan are sent with the parent vlan tag as normal.
+ */
+
+struct pvlan_softc {
+	struct ifnet		 sc_if_pv;
+	unsigned int		 sc_dead;
+	unsigned int		 sc_ifidx0;	/* parent interface */
+	struct vlan_entry	 sc_entry;
+	/* sc_tag and sc_type work here too */
+	struct task		 sc_ltask;
+	struct task		 sc_dtask;
+	unsigned int		 sc_flags;
+};
+
+static void		pvlan_input(struct ifnet *, struct mbuf *);
+static int		pvlan_output(struct ifnet *, struct mbuf *,
+			    struct sockaddr *, struct rtentry *);
+static int		pvlan_enqueue(struct ifnet *, struct mbuf *);
+static void		pvlan_start(struct ifqueue *ifq);
+static int		pvlan_ioctl(struct ifnet *ifp, u_long cmd,
+			    caddr_t addr);
+static int		pvlan_up(struct pvlan_softc *);
+static int		pvlan_down(struct pvlan_softc *);
+
+static void		pvlan_link_hook(void *);
+static void		pvlan_ifdetach(void *);
+
+static int
+pvlan_clone_create(struct if_clone *ifc, int unit)
+{
+	struct pvlan_softc *sc;
+	struct ifnet *ifppv;
+
+	sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK|M_ZERO|M_CANFAIL);
+	if (sc == NULL)
+		return (ENOMEM);
+
+	sc->sc_dead = 0;
+	task_set(&sc->sc_ltask, pvlan_link_hook, sc);
+	task_set(&sc->sc_dtask, pvlan_ifdetach, sc);
+
+	sc->sc_ifidx0 = 0;
+	sc->sc_type = ETHERTYPE_VLAN;
+	sc->sc_tag = EVL_VLID_MIN;
+
+	ifppv = &sc->sc_if_pv;
+	ifppv->if_softc = sc;
+	snprintf(ifppv->if_xname, sizeof(ifppv->if_xname),
+	    "%s%d", ifc->ifc_name, unit);
+
+	ifppv->if_type = IFT_BRIDGE;
+	ifppv->if_hdrlen = ETHER_HDR_LEN;
+	ifppv->if_flags = IFF_BROADCAST | IFF_MULTICAST;
+	ifppv->if_xflags = IFXF_CLONED | IFXF_MPSAFE;
+	ifppv->if_qstart = pvlan_start;
+	ifppv->if_enqueue = pvlan_enqueue;
+	ifppv->if_input = pvlan_input;
+	ifppv->if_output = pvlan_output;
+	ifppv->if_ioctl = pvlan_ioctl;
+	ifppv->if_hardmtu = ETHER_MAX_HARDMTU_LEN;
+	ifppv->if_link_state = LINK_STATE_DOWN;
+
+	if_counters_alloc(ifppv);
+	if_attach(ifppv);
+
+	if_alloc_sadl(ifppv);
+
+#if NBPFILTER > 0
+	bpfattach(&ifppv->if_bpf, ifppv, DLT_EN10MB, ETHER_HDR_LEN);
+#endif
+
+	return (0);
+}
+
+static int
+pvlan_clone_destroy(struct ifnet *ifppv)
+{
+	struct pvlan_softc *sc = ifppv->if_softc;
+
+	NET_LOCK();
+	sc->sc_dead = 1;
+
+	if (ISSET(ifppv->if_flags, IFF_RUNNING))
+		pvlan_down(sc);
+	NET_UNLOCK();
+
+	if_detach(ifppv);
+	free(sc, M_DEVBUF, sizeof(*sc));
+
+	return (0);
+}
+
+static int
+pvlan_up(struct pvlan_softc *sc)
+{
+	struct vlan_list *tagh, *list;
+	struct ifnet *ifppv = &sc->sc_if_pv;
+	struct ifnet *ifp0;
+	struct vlan_softc *sc0;
+	int error = 0;
+
+	KASSERT(!ISSET(ifppv->if_flags, IFF_RUNNING));
+
+	tagh = vlan_tagh;
+	list = &tagh[TAG_HASH(sc->sc_tag)];
+
+	ifp0 = if_get(sc->sc_ifidx0);
+	if (ifp0 == NULL)
+		return (ENXIO);
+
+	/* check pvlan will work on top of the parent */
+	if (ifp0->if_enqueue != vlan_enqueue) {
+		error = EPROTONOSUPPORT;
+		goto put;
+	}
+	sc0 = ifp0->if_softc;
+	if (sc0->sc_type != ETHERTYPE_VLAN) {
+		error = EPROTONOSUPPORT;
+		goto put;
+	}
+
+	/* parent is fine, let's prepare the sc to handle packets */
+	if (ISSET(sc->sc_flags, IFVF_PROMISC)) {
+		error = ifpromisc(ifp0, 1);
+		if (error != 0)
+			goto put;
+	}
+
+	/* commit the sc */
+	sc->sc_entry.v_sc = sc0;
+	sc->sc_entry.v_ifpvidx = ifppv->if_index;
+
+	error = rw_enter(&vlan_tagh_lk, RW_WRITE | RW_INTR);
+	if (error != 0)
+		goto rollback;
+
+	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_entry, v_entry);
+	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);
+
+	/* we're running now */
+	SET(ifppv->if_flags, IFF_RUNNING);
+	vlan_link_state(ifppv, ifp0->if_link_state, ifp0->if_baudrate);
+
+	/* sc->sc_entry.v_sc has the ref to ifp0 now */
+
+	return (ENETRESET);
+
+leave:
+	rw_exit(&vlan_tagh_lk);
+rollback:
+	sc->sc_entry.v_sc = NULL;
+	if (ISSET(sc->sc_flags, IFVF_PROMISC))
+		(void)ifpromisc(ifp0, 0); /* XXX */
+put:
+	if_put(ifp0);
+
+	return (error);
+
+	return (0);
+}
+
+static int
+pvlan_iff(struct pvlan_softc *sc)
+{
+	struct ifnet *ifp0;
+	int promisc = 0;
+	int error = 0;
+
+	if (ISSET(sc->sc_if_pv.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_pv.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);
+}
+
+static int
+pvlan_down(struct pvlan_softc *sc)
+{
+	struct vlan_list *tagh, *list;
+	struct ifnet *ifppv = &sc->sc_if_pv;
+	struct vlan_softc *sc0;
+	struct ifnet *ifp0 = NULL;
+	struct vlan_entry *v = &sc->sc_entry;
+
+	tagh = vlan_tagh;
+	list = &tagh[TAG_HASH(sc->sc_tag)];
+
+	KASSERT(ISSET(ifppv->if_flags, IFF_RUNNING));
+
+	vlan_link_state(ifppv, LINK_STATE_DOWN, 0);
+	CLR(ifppv->if_flags, IFF_RUNNING);
+
+	rw_enter_write(&vlan_tagh_lk);
+	SMR_SLIST_REMOVE_LOCKED(list, v, vlan_entry, v_entry);
+	rw_exit_write(&vlan_tagh_lk);
+
+	sc0 = v->v_sc;
+	v->v_sc = NULL;
+
+	if (sc0 != NULL) {
+		ifp0 = &sc0->sc_if;
+
+		if (ISSET(sc->sc_flags, IFVF_PROMISC))
+			ifpromisc(ifp0, 0);
+		if_detachhook_del(ifp0, &sc->sc_dtask);
+		if_linkstatehook_del(ifp0, &sc->sc_ltask);
+
+		if_put(ifp0);
+	}
+
+	return (0);
+}
+
+static void
+pvlan_ifdetach(void *arg)
+{
+	struct pvlan_softc *sc = arg;
+	struct ifnet *ifppv = &sc->sc_if_pv;
+
+	if (ISSET(ifppv->if_flags, IFF_RUNNING)) {
+		pvlan_down(sc);
+		CLR(ifppv->if_flags, IFF_UP);
+	}
+
+	sc->sc_ifidx0 = 0;
+}
+
+void
+pvlan_link_hook(void *arg)
+{
+	struct pvlan_softc *sc = arg;
+	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->sc_if_pv, link, baud);
+}
+
+static void
+pvlan_input(struct ifnet *ifppv, struct mbuf *m)
+{
+	m_freem(m);
+}
+
+static int
+pvlan_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst,
+	struct rtentry *rt)
+{
+	m_freem(m);
+	return (ENODEV);
+}
+
+static int
+pvlan_enqueue(struct ifnet *ifp, struct mbuf *m)
+{
+	m_freem(m);
+	return (ENODEV);
+}
+
+static void
+pvlan_start(struct ifqueue *ifq)
+{
+	ifq_purge(ifq);
+}
+
+static int
+pvlan_set_parent(struct pvlan_softc *sc, const char *parent)
+{
+	struct ifnet *ifppv = &sc->sc_if_pv;
+	struct ifnet *ifp0;
+	struct vlan_softc *sc0;
+	int error = 0;
+
+	ifp0 = if_unit(parent);
+	if (ifp0 == NULL)
+		return (EINVAL);
+
+	if (ifp0->if_enqueue != vlan_enqueue) {
+		error = EPROTONOSUPPORT;
+		goto put;
+	}
+	sc0 = ifp0->if_softc;
+	if (sc0->sc_type != ETHERTYPE_VLAN) {
+		error = EPROTONOSUPPORT;
+		goto put;
+	}
+
+	if (sc->sc_ifidx0 == ifp0->if_index) {
+		/* nop */
+		goto put;
+	}
+
+	if (ISSET(ifppv->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;
+
+	/* commit */
+	sc->sc_ifidx0 = ifp0->if_index;
+
+put:
+	if_put(ifp0);
+	return (error);
+}
+
+static int
+pvlan_del_parent(struct pvlan_softc *sc)
+{
+	struct ifnet *ifppv = &sc->sc_if_pv;
+
+	if (ISSET(ifppv->if_flags, IFF_RUNNING))
+		return (EBUSY);
+
+	/* commit */
+	sc->sc_ifidx0 = 0;
+
+	return (0);
+}
+
+static int
+pvlan_set_vnetid(struct pvlan_softc *sc, uint16_t tag)
+{
+	struct ifnet *ifppv = &sc->sc_if_pv;
+	struct vlan_entry *v = &sc->sc_entry;
+	struct vlan_list *tagh, *list;
+	u_char link = ifppv->if_link_state;
+	uint64_t baud = ifppv->if_baudrate;
+	int error;
+
+	tagh = vlan_tagh;
+
+	if (ISSET(ifppv->if_flags, IFF_RUNNING) && LINK_STATE_IS_UP(link))
+		vlan_link_state(ifppv, LINK_STATE_DOWN, 0);
+
+	error = rw_enter(&vlan_tagh_lk, RW_WRITE);
+	if (error != 0)
+		goto link;
+
+	error = vlan_inuse_locked(v->v_type, sc->sc_ifidx0, tag);
+	if (error != 0)
+		goto unlock;
+
+	if (ISSET(ifppv->if_flags, IFF_RUNNING)) {
+		list = &tagh[TAG_HASH(v->v_tag)];
+		SMR_SLIST_REMOVE_LOCKED(list, v, vlan_entry, v_entry);
+
+		v->v_tag = tag;
+
+		list = &tagh[TAG_HASH(v->v_tag)];
+		SMR_SLIST_INSERT_HEAD_LOCKED(list, v, v_entry);
+	} else
+		v->v_tag = tag;
+
+unlock:
+	rw_exit(&vlan_tagh_lk);
+
+link:
+	if (ISSET(ifppv->if_flags, IFF_RUNNING) && LINK_STATE_IS_UP(link))
+		vlan_link_state(ifppv, link, baud);
+
+	return (error);
+}
+
+static int
+pvlan_ioctl(struct ifnet *ifppv, u_long cmd, caddr_t data)
+{
+	struct pvlan_softc *sc = ifppv->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:
+		error = EOPNOTSUPP;
+		break;
+
+	case SIOCSIFFLAGS:
+		if (ISSET(ifppv->if_flags, IFF_UP)) {
+			if (!ISSET(ifppv->if_flags, IFF_RUNNING))
+				error = pvlan_up(sc);
+		} else {
+			if (ISSET(ifppv->if_flags, IFF_RUNNING))
+				error = pvlan_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 = pvlan_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 = pvlan_set_vnetid(sc, 0);
+		break;
+
+	case SIOCSIFPARENT:
+		error = pvlan_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 = pvlan_del_parent(sc);
+		break;
+	default:
+		error = ENOTTY;
+		break;
+	}
+
+	if (error == ENETRESET)
+		error = pvlan_iff(sc);
+
+	return (error);
 }