From: David Gwynne Subject: pvlan(4): backing up a private vlan config workaround To: tech@openbsd.org Date: Fri, 7 Jun 2024 15:09:45 +1000 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 description: pvlan-iso-dmz index 27 priority 0 llprio 3 encap: vnetid 901 parent vlan900 groups: pvlan status: active vlan900: flags=8943 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); }