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); }