this is an unfortunately large reworking of vlan(4) to make tx mpsafe it also includes the following:
- moving away from the vlan specific SIOC[SG]ETVLAN ioctls to the SIOC[SGD]{VNETID,IFPARENT} ioctls i did this to make the config transitions more straightforward. with the new ioctls you either change one thing or the one thing failed to be changed, making it more obvious what did and didnt work. - added compat for the legacy ioctls built out of the new ioctls you can use an old ifconfig on top of a new kernel and it works fine. ive been doing this on production firewalls for months now. - added compat for the old ifconfig options. the vlan and vlandev are mapped to vnetid and parent respectively, allowing old configs to still work. - made the implicit IFF_UP handling like other drivers other drivers do IFF_UP when you configure an address on the interface. vlan(4) did it when you configured a vlan/vlandev, but not when you configured an interface. - serialise changes affecting the vlan tag hashes used on input. - restrict config changes while IFF_UP this means you cant change the vnetid or the parent interface while the interface is up. there are two reasons for this. firstly, moving a vlan interface between parents is hard to do cleanly because moving multicast group memberships is gross. secondly, moving interfaces between parents or networks without a link state change seems like incorrect behaviour to me. in my mind it is best to think of config changes like this as comparable to unplugging a cable from a switch port and moving it to another one. - rework multicast group membership handling multicast group memberships could be lost before, this tries harder to keep them. - make transmit mpsafe this relied on a lot of fixes on the config side to be safe, but the changes to vlan_start are hilariously small compared to the config changes. it is big, but pulling it apart would be error prone and a long process to get us to the same place this diff is already at. i would welcome tests and eyes. ok? Index: sbin/ifconfig/ifconfig.c =================================================================== RCS file: /cvs/src/sbin/ifconfig/ifconfig.c,v retrieving revision 1.316 diff -u -p -r1.316 ifconfig.c --- sbin/ifconfig/ifconfig.c 2 Mar 2016 00:00:16 -0000 1.316 +++ sbin/ifconfig/ifconfig.c 9 Mar 2016 05:47:13 -0000 @@ -181,6 +181,7 @@ void settunnelinst(const char *, int); void settunnelttl(const char *, int); void setvnetid(const char *, int); void delvnetid(const char *, int); +void getvnetid(void); void setifparent(const char *, int); void delifparent(const char *, int); void getifparent(void); @@ -209,12 +210,8 @@ void setmpwencap(const char *, int); void setmpwlabel(const char *, const char *); void setmpwneighbor(const char *, int); void setmpwcontrolword(const char *, int); -void setvlantag(const char *, int); -void setvlandev(const char *, int); -void unsetvlandev(const char *, int); void mpe_status(void); void mpw_status(void); -void vlan_status(void); void setinstance(const char *, int); int main(int, char *[]); int prefix(void *val, int); @@ -350,9 +347,9 @@ const struct cmd { { "scan", NEXTARG0, 0, setifscan }, { "broadcast", NEXTARG, 0, setifbroadaddr }, { "prefixlen", NEXTARG, 0, setifprefixlen}, - { "vlan", NEXTARG, 0, setvlantag }, - { "vlandev", NEXTARG, 0, setvlandev }, - { "-vlandev", 1, 0, unsetvlandev }, + { "vlan", NEXTARG, 0, setvnetid }, + { "vlandev", NEXTARG, 0, setifparent }, + { "-vlandev", 1, 0, delifparent }, { "group", NEXTARG, 0, setifgroup }, { "-group", NEXTARG, 0, unsetifgroup }, { "autoconf", 1, 0, setautoconf }, @@ -2850,8 +2847,6 @@ phys_status(int force) if (dstport) printf(":%u", ntohs(dstport)); - if (ioctl(s, SIOCGVNETID, (caddr_t)&ifr) == 0) - printf(" vnetid %d", ifr.ifr_vnetid); if (ioctl(s, SIOCGLIFPHYTTL, (caddr_t)&ifr) == 0 && ifr.ifr_ttl > 0) printf(" ttl %d", ifr.ifr_ttl); #ifndef SMALL @@ -2942,7 +2937,7 @@ status(int link, struct sockaddr_dl *sdl if_indextoname(ifrdesc.ifr_index, ifname) != NULL) printf("\tpatch: %s\n", ifname); #endif - vlan_status(); + getvnetid(); getifparent(); #ifndef SMALL carp_status(); @@ -3399,6 +3394,24 @@ delvnetid(const char *ignored, int alsoi } void +getvnetid(void) +{ + if (strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)) >= + sizeof(ifr.ifr_name)) + errx(1, "vnetid: name is too long"); + + if (ioctl(s, SIOCGVNETID, &ifr) == -1) { + if (errno != EADDRNOTAVAIL) + return; + + printf("\tvnetid: none\n"); + return; + } + + printf("\tvnetid: %u\n", ifr.ifr_vnetid); +} + +void setifparent(const char *id, int param) { struct if_parent ifp; @@ -3628,100 +3641,6 @@ setmpwcontrolword(const char *value, int imrsave.imr_flags &= ~IMR_FLAG_CONTROLWORD; } #endif /* SMALL */ - -static int __tag = 0; -static int __have_tag = 0; - -void -vlan_status(void) -{ - struct vlanreq vreq; - - bzero((char *)&vreq, sizeof(struct vlanreq)); - ifr.ifr_data = (caddr_t)&vreq; - - if (ioctl(s, SIOCGETVLAN, (caddr_t)&ifr) == -1) - return; - - if (vreq.vlr_tag || (vreq.vlr_parent[0] != '\0')) - printf("\tvlan: %d parent interface: %s\n", - vreq.vlr_tag, vreq.vlr_parent[0] == '\0' ? - "<none>" : vreq.vlr_parent); -} - -/* ARGSUSED */ -void -setvlantag(const char *val, int d) -{ - u_int16_t tag; - struct vlanreq vreq; - const char *errmsg = NULL; - - __tag = tag = strtonum(val, 0, 4095, &errmsg); - if (errmsg) - errx(1, "vlan tag %s: %s", val, errmsg); - __have_tag = 1; - - bzero((char *)&vreq, sizeof(struct vlanreq)); - ifr.ifr_data = (caddr_t)&vreq; - - if (ioctl(s, SIOCGETVLAN, (caddr_t)&ifr) == -1) - err(1, "SIOCGETVLAN"); - - vreq.vlr_tag = tag; - - if (ioctl(s, SIOCSETVLAN, (caddr_t)&ifr) == -1) - err(1, "SIOCSETVLAN"); -} - -/* ARGSUSED */ -void -setvlandev(const char *val, int d) -{ - struct vlanreq vreq; - int tag; - size_t skip; - const char *estr; - - bzero((char *)&vreq, sizeof(struct vlanreq)); - ifr.ifr_data = (caddr_t)&vreq; - - if (ioctl(s, SIOCGETVLAN, (caddr_t)&ifr) == -1) - err(1, "SIOCGETVLAN"); - - (void) strlcpy(vreq.vlr_parent, val, sizeof(vreq.vlr_parent)); - - if (!__have_tag && vreq.vlr_tag == 0) { - skip = strcspn(ifr.ifr_name, "0123456789"); - tag = strtonum(ifr.ifr_name + skip, 0, 4095, &estr); - if (estr != NULL) - errx(1, "invalid vlan tag and device specification"); - vreq.vlr_tag = tag; - } else if (__have_tag) - vreq.vlr_tag = __tag; - - if (ioctl(s, SIOCSETVLAN, (caddr_t)&ifr) == -1) - err(1, "SIOCSETVLAN"); -} - -/* ARGSUSED */ -void -unsetvlandev(const char *val, int d) -{ - struct vlanreq vreq; - - bzero((char *)&vreq, sizeof(struct vlanreq)); - ifr.ifr_data = (caddr_t)&vreq; - - if (ioctl(s, SIOCGETVLAN, (caddr_t)&ifr) == -1) - err(1, "SIOCGETVLAN"); - - bzero((char *)&vreq.vlr_parent, sizeof(vreq.vlr_parent)); - vreq.vlr_tag = 0; - - if (ioctl(s, SIOCSETVLAN, (caddr_t)&ifr) == -1) - err(1, "SIOCSETVLAN"); -} void settrunkport(const char *val, int d) Index: sys/net/if_vlan.c =================================================================== RCS file: /cvs/src/sys/net/if_vlan.c,v retrieving revision 1.152 diff -u -p -r1.152 if_vlan.c --- sys/net/if_vlan.c 3 Mar 2016 02:53:28 -0000 1.152 +++ sys/net/if_vlan.c 9 Mar 2016 05:47:17 -0000 @@ -78,23 +78,34 @@ #define TAG_HASH_MASK (TAG_HASH_SIZE - 1) #define TAG_HASH(tag) (tag & TAG_HASH_MASK) SRPL_HEAD(, ifvlan) *vlan_tagh, *svlan_tagh; -struct rwlock vlan_tagh_lk = RWLOCK_INITIALIZER("vlantag"); +struct rwlock vlan_cfg_lk = RWLOCK_INITIALIZER("vlantag"); int vlan_input(struct ifnet *, struct mbuf *, void *); void vlan_start(struct ifnet *ifp); int vlan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t addr); -int vlan_unconfig(struct ifnet *ifp, struct ifnet *newp); -int vlan_config(struct ifvlan *, struct ifnet *, u_int16_t); -void vlan_vlandev_state(void *); +int vlan_up(struct ifvlan *); +int vlan_down(struct ifvlan *); +int vlan_p_unconfig(struct ifnet *, struct ifnet *); +int vlan_p_config(struct ifvlan *, struct ifnet *); +void vlan_link_hook(void *); +void vlan_link_state(struct ifvlan *, u_char, uint64_t); void vlanattach(int count); int vlan_set_promisc(struct ifnet *ifp); -int vlan_ether_addmulti(struct ifvlan *, struct ifreq *); -int vlan_ether_delmulti(struct ifvlan *, struct ifreq *); -void vlan_ether_purgemulti(struct ifvlan *); -void vlan_ether_resetmulti(struct ifvlan *, struct ifnet *); +int vlan_set_vnetid(struct ifvlan *, uint16_t); + +int vlan_set_compat(struct ifnet *, struct ifreq *); +int vlan_get_compat(struct ifnet *, struct ifreq *); + +int vlan_multi_add(struct ifvlan *, struct ifreq *); +int vlan_multi_del(struct ifvlan *, struct ifreq *); +void vlan_multi_apply(struct ifvlan *, struct ifnet *, u_long); +void vlan_multi_free(struct ifvlan *); + +int vlan_inuse(uint16_t, unsigned int, uint16_t); +int vlan_inuse_locked(uint16_t, unsigned int, uint16_t); + int vlan_clone_create(struct if_clone *, int); int vlan_clone_destroy(struct ifnet *); -void vlan_ifdetach(void *); struct if_clone vlan_cloner = IF_CLONE_INITIALIZER("vlan", vlan_clone_create, vlan_clone_destroy); @@ -145,10 +156,9 @@ vlan_clone_create(struct if_clone *ifc, LIST_INIT(&ifv->vlan_mc_listhead); ifp = &ifv->ifv_if; ifp->if_softc = ifv; - snprintf(ifp->if_xname, sizeof ifp->if_xname, "%s%d", ifc->ifc_name, - unit); + snprintf(ifp->if_xname, sizeof(ifp->if_xname), "%s%d", + ifc->ifc_name, unit); /* NB: flags are not set here */ - /* NB: mtu is not set here */ /* Special handling for the IEEE 802.1ad QinQ variant */ if (strcmp("svlan", ifc->ifc_name) == 0) @@ -157,10 +167,12 @@ vlan_clone_create(struct if_clone *ifc, ifv->ifv_type = ETHERTYPE_VLAN; refcnt_init(&ifv->ifv_refcnt); - + ifp->if_flags = IFF_BROADCAST | IFF_MULTICAST; + ifp->if_xflags = IFXF_MPSAFE; ifp->if_start = vlan_start; ifp->if_ioctl = vlan_ioctl; - IFQ_SET_MAXLEN(&ifp->if_snd, 1); + ifp->if_hardmtu = 0xffff; + ifp->if_link_state = LINK_STATE_DOWN; IFQ_SET_READY(&ifp->if_snd); if_attach(ifp); ether_ifattach(ifp); @@ -190,22 +202,18 @@ vlan_clone_destroy(struct ifnet *ifp) { struct ifvlan *ifv = ifp->if_softc; - vlan_unconfig(ifp, NULL); + if (ISSET(ifp->if_flags, IFF_RUNNING)) + vlan_down(ifv); + ether_ifdetach(ifp); if_detach(ifp); refcnt_finalize(&ifv->ifv_refcnt, "vlanrefs"); + vlan_multi_free(ifv); free(ifv, M_DEVBUF, sizeof(*ifv)); return (0); } -void -vlan_ifdetach(void *ptr) -{ - struct ifvlan *ifv = ptr; - vlan_clone_destroy(&ifv->ifv_if); -} - static inline int vlan_mplstunnel(int ifidx) { @@ -227,31 +235,23 @@ vlan_mplstunnel(int ifidx) void vlan_start(struct ifnet *ifp) { - struct ifvlan *ifv; + struct ifvlan *ifv = ifp->if_softc; struct ifnet *p; struct mbuf *m; uint8_t prio; - ifv = ifp->if_softc; - p = ifv->ifv_p; - - for (;;) { - IFQ_DEQUEUE(&ifp->if_snd, m); - if (m == NULL) - break; + p = if_get(ifv->ifv_p); + if (p == NULL) { + IFQ_PURGE(&ifp->if_snd); + return; + } + while ((m = ifq_dequeue(&ifp->if_snd)) != NULL) { #if NBPFILTER > 0 if (ifp->if_bpf) bpf_mtap_ether(ifp->if_bpf, m, BPF_DIRECTION_OUT); #endif /* NBPFILTER > 0 */ - if ((p->if_flags & (IFF_UP|IFF_RUNNING)) != - (IFF_UP|IFF_RUNNING)) { - ifp->if_oerrors++; - m_freem(m); - continue; - } - /* IEEE 802.1p has prio 0 and 1 swapped */ prio = m->m_pkthdr.pf.prio; if (prio <= 1) @@ -314,7 +314,7 @@ vlan_inject(struct mbuf *m, uint16_t typ * vlan_input() returns 1 if it has consumed the packet, 0 otherwise. */ int -vlan_input(struct ifnet *ifp, struct mbuf *m, void *cookie) +vlan_input(struct ifnet *p, struct mbuf *m, void *cookie) { struct ifvlan *ifv; struct ether_vlan_header *evl; @@ -332,9 +332,9 @@ vlan_input(struct ifnet *ifp, struct mbu etype = ETHERTYPE_VLAN; tagh = vlan_tagh; } else if ((etype == ETHERTYPE_VLAN) || (etype == ETHERTYPE_QINQ)) { - if (m->m_len < EVL_ENCAPLEN && - (m = m_pullup(m, EVL_ENCAPLEN)) == NULL) { - ifp->if_ierrors++; + if (m->m_len < sizeof(*evl) && + (m = m_pullup(m, sizeof(*evl))) == NULL) { + p->if_ierrors++; return (1); } @@ -356,13 +356,14 @@ vlan_input(struct ifnet *ifp, struct mbu list = &tagh[TAG_HASH(tag)]; SRPL_FOREACH(ifv, list, &i, ifv_list) { - if (ifp == ifv->ifv_p && tag == ifv->ifv_tag && + if (p->if_index == ifv->ifv_p && + tag == ifv->ifv_tag && etype == ifv->ifv_type) break; } if (ifv == NULL) { - ifp->if_noproto++; + p->if_noproto++; goto drop; } @@ -371,18 +372,6 @@ vlan_input(struct ifnet *ifp, struct mbu goto drop; /* - * Drop promiscuously received packets if we are not in - * promiscuous mode. - */ - if (!ETHER_IS_MULTICAST(eh->ether_dhost) && - (ifp->if_flags & IFF_PROMISC) && - (ifv->ifv_if.if_flags & IFF_PROMISC) == 0) { - if (bcmp(&ifv->ifv_ac.ac_enaddr, eh->ether_dhost, - ETHER_ADDR_LEN)) - goto drop; - } - - /* * Having found a valid vlan interface corresponding to * the given source interface and vlan tag, remove the * encapsulation. @@ -406,47 +395,150 @@ drop: return (1); } +void +vlan_p_detach(void *v) +{ + struct ifvlan *ifv = v; + struct ifnet *ifp = &ifv->ifv_if; + + if (ISSET(ifp->if_flags, IFF_RUNNING)) { + (void)vlan_down(ifv); + CLR(ifp->if_flags, IFF_UP); + } + + ifv->ifv_p = 0; +} + +void +vlan_link_hook(void *v) +{ + struct ifvlan *ifv = v; + struct ifnet *p; + + u_char link = LINK_STATE_DOWN; + uint64_t baud = 0; + + p = if_get(ifv->ifv_p); + if (p != NULL) { + link = p->if_link_state; + baud = p->if_baudrate; + } + if_put(p); + + vlan_link_state(ifv, link, baud); +} + +void +vlan_link_state(struct ifvlan *ifv, u_char link, uint64_t baud) +{ + if (ifv->ifv_if.if_link_state == link) + return; + + ifv->ifv_if.if_link_state = link; + ifv->ifv_if.if_baudrate = baud; + + if_link_state_change(&ifv->ifv_if); +} + int -vlan_config(struct ifvlan *ifv, struct ifnet *p, u_int16_t tag) +vlan_promisc(struct ifvlan *ifv, int promisc) { - struct sockaddr_dl *sdl1, *sdl2; - SRPL_HEAD(, ifvlan) *tagh, *list; - u_int flags; - - if (p->if_type != IFT_ETHER) - return EPROTONOSUPPORT; - if (ifv->ifv_p == p && ifv->ifv_tag == tag) /* noop */ + struct ifnet *p; + int error = 0; + + if (ifv->ifv_promisc == promisc) return (0); - /* Remember existing interface flags and reset the interface */ - flags = ifv->ifv_flags; - vlan_unconfig(&ifv->ifv_if, p); - ifv->ifv_p = p; - ifv->ifv_if.if_baudrate = p->if_baudrate; - - if (p->if_capabilities & IFCAP_VLAN_MTU) { - ifv->ifv_if.if_mtu = p->if_mtu; - ifv->ifv_if.if_hardmtu = p->if_hardmtu; - } else { - ifv->ifv_if.if_mtu = p->if_mtu - EVL_ENCAPLEN; - ifv->ifv_if.if_hardmtu = p->if_hardmtu - EVL_ENCAPLEN; + p = if_get(ifv->ifv_p); + if (p != NULL) { + error = ifpromisc(p, promisc); + } + if_put(p); + + if (error == 0) + ifv->ifv_promisc = promisc; + + return (error); +} + +int +vlan_parent_config(struct ifvlan *ifv, struct ifnet *p) +{ + int error; + + vlan_multi_apply(ifv, p, SIOCADDMULTI); + + if (ifv->ifv_promisc) { + error = ifpromisc(p, 1); + if (error != 0) + goto delmulti; + } + + return (0); + +delmulti: + vlan_multi_apply(ifv, p, SIOCDELMULTI); + return (error); +} + +int +vlan_up(struct ifvlan *ifv) +{ + struct ifnet *ifp = &ifv->ifv_if; + struct ifnet *p; + SRPL_HEAD(, ifvlan) *tagh, *list; + int error = 0; + u_int hardmtu; + + KASSERT(!ISSET(ifp->if_flags, IFF_RUNNING)); + + tagh = ifv->ifv_type == ETHERTYPE_QINQ ? svlan_tagh : vlan_tagh; + list = &tagh[TAG_HASH(ifv->ifv_tag)]; + + if (ifv->ifv_tag == EVL_VLID_NONE) + return (EADDRNOTAVAIL); + + p = if_get(ifv->ifv_p); + if (p == NULL) + return (ENXIO); + + if (p->if_type != IFT_ETHER) { + error = EPROTONOSUPPORT; + goto put; } - ifv->ifv_if.if_flags = p->if_flags & - (IFF_UP | IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST); + hardmtu = p->if_hardmtu; + if (!ISSET(p->if_capabilities, IFCAP_VLAN_MTU)) + hardmtu -= EVL_ENCAPLEN; - /* Reset promisc mode on the interface and its parent */ - if (flags & IFVF_PROMISC) { - ifv->ifv_if.if_flags |= IFF_PROMISC; - vlan_set_promisc(&ifv->ifv_if); + if (ifp->if_mtu > hardmtu) { + error = ENOBUFS; + goto put; } + error = rw_enter(&vlan_cfg_lk, RW_WRITE | RW_INTR); + if (error != 0) + goto put; + + error = vlan_inuse_locked(ifv->ifv_type, ifv->ifv_p, ifv->ifv_tag); + if (error != 0) + goto leave; + + error = vlan_parent_config(ifv, p); + if (error != 0) + goto leave; + + /* commit */ + ifp->if_hardmtu = hardmtu; + ifp->if_flags |= p->if_flags & IFF_SIMPLEX; + + if_setlladdr(ifp, LLADDR(p->if_sadl)); if (ifv->ifv_type != ETHERTYPE_VLAN) { /* - * Hardware offload only works with the default VLAN + * Hardware offloads only works with the default VLAN * ethernet type (0x8100). */ - ifv->ifv_if.if_capabilities = 0; + ifp->if_capabilities = 0; } else if (p->if_capabilities & IFCAP_VLAN_HWTAGGING) { /* * If the parent interface can do hardware-assisted @@ -456,251 +548,359 @@ vlan_config(struct ifvlan *ifv, struct i * If the card cannot handle hardware tagging, it cannot * possibly compute the correct checksums for tagged packets. */ - ifv->ifv_if.if_capabilities = p->if_capabilities & - IFCAP_CSUM_MASK; + ifp->if_capabilities = p->if_capabilities & IFCAP_CSUM_MASK; } - /* - * Set up our ``Ethernet address'' to reflect the underlying - * physical interface's. - */ - sdl1 = ifv->ifv_if.if_sadl; - sdl2 = p->if_sadl; - sdl1->sdl_type = IFT_ETHER; - sdl1->sdl_alen = ETHER_ADDR_LEN; - bcopy(LLADDR(sdl2), LLADDR(sdl1), ETHER_ADDR_LEN); - bcopy(LLADDR(sdl2), ifv->ifv_ac.ac_enaddr, ETHER_ADDR_LEN); - - ifv->ifv_tag = tag; - /* Register callback for physical link state changes */ ifv->lh_cookie = hook_establish(p->if_linkstatehooks, 1, - vlan_vlandev_state, ifv); + vlan_link_hook, ifv); /* Register callback if parent wants to unregister */ ifv->dh_cookie = hook_establish(p->if_detachhooks, 0, - vlan_ifdetach, ifv); + vlan_p_detach, ifv); - vlan_vlandev_state(ifv); - - /* Change input handler of the physical interface. */ + SRPL_INSERT_HEAD_LOCKED(&vlan_tagh_rc, list, ifv, ifv_list); if_ih_insert(p, vlan_input, NULL); - tagh = ifv->ifv_type == ETHERTYPE_QINQ ? svlan_tagh : vlan_tagh; - list = &tagh[TAG_HASH(tag)]; + vlan_link_state(ifv, p->if_link_state, p->if_baudrate); - rw_enter_write(&vlan_tagh_lk); - SRPL_INSERT_HEAD_LOCKED(&vlan_tagh_rc, list, ifv, ifv_list); - rw_exit_write(&vlan_tagh_lk); + SET(ifp->if_flags, IFF_RUNNING); - return (0); +leave: + rw_exit(&vlan_cfg_lk); + +put: + if_put(p); + return (error); } int -vlan_unconfig(struct ifnet *ifp, struct ifnet *newp) +vlan_down(struct ifvlan *ifv) { - struct sockaddr_dl *sdl; - struct ifvlan *ifv; - SRPL_HEAD(, ifvlan) *tagh, *list; - struct ifnet *p; + SRPL_HEAD(, ifvlan) *tagh, *list; + struct ifnet *ifp = &ifv->ifv_if; + struct ifnet *p; + int error; - ifv = ifp->if_softc; - if ((p = ifv->ifv_p) == NULL) - return 0; - - /* Unset promisc mode on the interface and its parent */ - if (ifv->ifv_flags & IFVF_PROMISC) { - ifp->if_flags &= ~IFF_PROMISC; - vlan_set_promisc(ifp); - } + KASSERT(ISSET(ifp->if_flags, IFF_RUNNING)); tagh = ifv->ifv_type == ETHERTYPE_QINQ ? svlan_tagh : vlan_tagh; list = &tagh[TAG_HASH(ifv->ifv_tag)]; - rw_enter_write(&vlan_tagh_lk); - SRPL_REMOVE_LOCKED(&vlan_tagh_rc, list, ifv, ifvlan, ifv_list); - rw_exit_write(&vlan_tagh_lk); + error = rw_enter(&vlan_cfg_lk, RW_WRITE | RW_INTR); + if (error != 0) + return (error); - /* Restore previous input handler. */ - if_ih_remove(p, vlan_input, NULL); + CLR(ifp->if_flags, IFF_RUNNING); - hook_disestablish(p->if_linkstatehooks, ifv->lh_cookie); - hook_disestablish(p->if_detachhooks, ifv->dh_cookie); - /* Reset link state */ - if (newp != NULL) { - ifp->if_link_state = LINK_STATE_INVALID; - if_link_state_change(ifp); - } + ifq_barrier(&ifp->if_snd); - /* - * 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 - * as well. - */ - vlan_ether_resetmulti(ifv, newp); + vlan_link_state(ifv, LINK_STATE_DOWN, 0); - /* Disconnect from parent. */ - ifv->ifv_p = NULL; - ifv->ifv_if.if_mtu = ETHERMTU; - ifv->ifv_if.if_hardmtu = ETHERMTU; - ifv->ifv_flags = 0; - - /* Clear our MAC address. */ - sdl = ifv->ifv_if.if_sadl; - sdl->sdl_type = IFT_ETHER; - sdl->sdl_alen = ETHER_ADDR_LEN; - bzero(LLADDR(sdl), ETHER_ADDR_LEN); - bzero(ifv->ifv_ac.ac_enaddr, ETHER_ADDR_LEN); + p = if_get(ifv->ifv_p); + if (p != NULL) + if_ih_remove(p, vlan_input, NULL); + if_put(p); - return (0); -} + SRPL_REMOVE_LOCKED(&vlan_tagh_rc, list, ifv, ifvlan, ifv_list); -void -vlan_vlandev_state(void *v) -{ - struct ifvlan *ifv = v; + hook_disestablish(p->if_detachhooks, ifv->dh_cookie); + hook_disestablish(p->if_linkstatehooks, ifv->lh_cookie); - if (ifv->ifv_if.if_link_state == ifv->ifv_p->if_link_state) - return; + ifp->if_capabilities = 0; - ifv->ifv_if.if_link_state = ifv->ifv_p->if_link_state; - ifv->ifv_if.if_baudrate = ifv->ifv_p->if_baudrate; - if_link_state_change(&ifv->ifv_if); -} + if_setlladdr(ifp, etheranyaddr); -int -vlan_set_promisc(struct ifnet *ifp) -{ - struct ifvlan *ifv = ifp->if_softc; - int error = 0; + CLR(ifp->if_flags, IFF_SIMPLEX); + + ifp->if_hardmtu = 0xffff; + + rw_exit(&vlan_cfg_lk); - if ((ifp->if_flags & IFF_PROMISC) != 0) { - if ((ifv->ifv_flags & IFVF_PROMISC) == 0) - if ((error = ifpromisc(ifv->ifv_p, 1)) == 0) - ifv->ifv_flags |= IFVF_PROMISC; - } else { - if ((ifv->ifv_flags & IFVF_PROMISC) != 0) - if ((error = ifpromisc(ifv->ifv_p, 0)) == 0) - ifv->ifv_flags &= ~IFVF_PROMISC; - } return (0); } int vlan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { - struct proc *p = curproc; /* XXX */ - struct ifaddr *ifa; - struct ifnet *pr; - struct ifreq *ifr; - struct ifvlan *ifv; - struct vlanreq vlr; - int error = 0, s; - - ifr = (struct ifreq *)data; - ifa = (struct ifaddr *)data; - ifv = ifp->if_softc; + struct ifvlan *ifv = ifp->if_softc; + struct ifreq *ifr = (struct ifreq *)data; + struct if_parent *parent = (struct if_parent *)data; + struct ifnet *p; + uint16_t tag; + int error = 0; switch (cmd) { case SIOCSIFADDR: - if (ifv->ifv_p != NULL) - ifp->if_flags |= IFF_UP; - else - error = EINVAL; + ifp->if_flags |= IFF_UP; + /* FALLTHROUGH */ + + case SIOCSIFFLAGS: + error = vlan_promisc(ifv, + ISSET(ifp->if_flags, IFF_PROMISC) ? 1 : 0); + if (error != 0) + break; + + if (ISSET(ifp->if_flags, IFF_UP)) { + if (!ISSET(ifp->if_flags, IFF_RUNNING)) + error = vlan_up(ifv); + } else { + if (ISSET(ifp->if_flags, IFF_RUNNING)) + error = vlan_down(ifv); + } break; - case SIOCSIFMTU: - if (ifv->ifv_p != NULL) { - if (ifr->ifr_mtu < ETHERMIN || - ifr->ifr_mtu > ifv->ifv_if.if_hardmtu) - error = EINVAL; - else - ifp->if_mtu = ifr->ifr_mtu; - } else + case SIOCSVNETID: + tag = ifr->ifr_vnetid; + if (tag == ifv->ifv_tag) + break; + + if (tag < EVL_VLID_MIN || tag > EVL_VLID_MAX) { error = EINVAL; + break; + } + error = vlan_set_vnetid(ifv, tag); break; - case SIOCSETVLAN: - if ((error = suser(p, 0)) != 0) - break; - if ((error = copyin(ifr->ifr_data, &vlr, sizeof vlr))) + case SIOCGVNETID: + if (ifv->ifv_tag == EVL_VLID_NONE) + error = EADDRNOTAVAIL; + else + ifr->ifr_vnetid = (uint32_t)ifv->ifv_tag; + break; + + case SIOCDVNETID: + if (ISSET(ifp->if_flags, IFF_RUNNING)) { + error = EBUSY; break; - if (vlr.vlr_parent[0] == '\0') { - s = splnet(); - vlan_unconfig(ifp, NULL); - if (ifp->if_flags & IFF_UP) - if_down(ifp); - ifp->if_flags &= ~IFF_RUNNING; - splx(s); - break; - } - pr = ifunit(vlr.vlr_parent); - if (pr == NULL) { - error = ENOENT; + } + + ifv->ifv_tag = 0; + break; + + case SIOCSIFPARENT: + if (ISSET(ifp->if_flags, IFF_RUNNING)) { + error = EBUSY; break; } - /* - * Don't let the caller set up a VLAN tag with - * anything except VLID bits. - */ - if (vlr.vlr_tag & ~EVL_VLID_MASK) { + + p = ifunit(parent->ifp_parent); + if (p == NULL) { error = EINVAL; break; } - error = vlan_config(ifv, pr, vlr.vlr_tag); - if (error) + + if (ifv->ifv_p == p->if_index) { + /* nop */ break; - ifp->if_flags |= IFF_RUNNING; + } - /* Update promiscuous mode, if necessary. */ - vlan_set_promisc(ifp); - break; - - case SIOCGETVLAN: - bzero(&vlr, sizeof vlr); - if (ifv->ifv_p) { - snprintf(vlr.vlr_parent, sizeof(vlr.vlr_parent), - "%s", ifv->ifv_p->if_xname); - vlr.vlr_tag = ifv->ifv_tag; + if (p->if_type != IFT_ETHER) { + error = EPROTONOSUPPORT; + break; } - error = copyout(&vlr, ifr->ifr_data, sizeof vlr); + + error = vlan_inuse(ifv->ifv_type, p->if_index, ifv->ifv_tag); + if (error != 0) + break; + + ifv->ifv_p = p->if_index; break; - case SIOCSIFFLAGS: - /* - * For promiscuous mode, we enable promiscuous mode on - * the parent if we need promiscuous on the VLAN interface. - */ - if (ifv->ifv_p != NULL) - error = vlan_set_promisc(ifp); + + case SIOCGIFPARENT: + p = if_get(ifv->ifv_p); + if (p == NULL) + error = EADDRNOTAVAIL; + else { + memcpy(parent->ifp_parent, p->if_xname, + sizeof(parent->ifp_parent)); + } + if_put(p); break; + case SIOCDIFPARENT: + if (ISSET(ifp->if_flags, IFF_RUNNING)) { + error = EBUSY; + break; + } + + ifv->ifv_p = 0; + break; + case SIOCADDMULTI: - error = (ifv->ifv_p != NULL) ? - vlan_ether_addmulti(ifv, ifr) : EINVAL; + error = vlan_multi_add(ifv, ifr); break; - case SIOCDELMULTI: - error = (ifv->ifv_p != NULL) ? - vlan_ether_delmulti(ifv, ifr) : EINVAL; + error = vlan_multi_del(ifv, ifr); + break; + + case SIOCSETVLAN: + error = vlan_set_compat(ifp, ifr); + break; + case SIOCGETVLAN: + error = vlan_get_compat(ifp, ifr); break; + default: - error = ENOTTY; + error = ether_ioctl(ifp, &ifv->ifv_ac, cmd, data); + break; } - return error; + + return (error); } +int +vlan_set_vnetid(struct ifvlan *ifv, uint16_t tag) +{ + struct ifnet *ifp = &ifv->ifv_if; + SRPL_HEAD(, ifvlan) *tagh, *list; + int error; + + tagh = ifv->ifv_type == ETHERTYPE_QINQ ? svlan_tagh : vlan_tagh; + + error = rw_enter(&vlan_cfg_lk, RW_WRITE | RW_INTR); + if (error != 0) + return (error); + + error = vlan_inuse_locked(ifv->ifv_type, ifv->ifv_p, tag); + if (error != 0) + goto unlock; + + if (ISSET(ifp->if_flags, IFF_RUNNING)) { + u_char link = ifp->if_link_state; + uint64_t baud = ifp->if_baudrate; + + if (LINK_STATE_IS_UP(link)) + vlan_link_state(ifv, LINK_STATE_DOWN, 0); + + list = &tagh[TAG_HASH(ifv->ifv_tag)]; + SRPL_REMOVE_LOCKED(&vlan_tagh_rc, list, ifv, ifvlan, ifv_list); + + ifv->ifv_tag = tag; + list = &tagh[TAG_HASH(ifv->ifv_tag)]; + SRPL_INSERT_HEAD_LOCKED(&vlan_tagh_rc, list, ifv, ifv_list); + + if (LINK_STATE_IS_UP(link)) + vlan_link_state(ifv, link, baud); + } else + ifv->ifv_tag = tag; + +unlock: + rw_exit(&vlan_cfg_lk); + + return (error); +} int -vlan_ether_addmulti(struct ifvlan *ifv, struct ifreq *ifr) +vlan_set_compat(struct ifnet *ifp, struct ifreq *ifr) { - struct ifnet *ifp = ifv->ifv_p; + struct vlanreq vlr; + struct ifreq req; + struct if_parent parent; + + int error; + + error = suser(curproc, 0); + if (error != 0) + return (error); + + error = copyin(ifr->ifr_data, &vlr, sizeof(vlr)); + if (error != 0) + return (error); + + if (vlr.vlr_parent[0] == '\0') + return (vlan_ioctl(ifp, SIOCDIFPARENT, (caddr_t)ifr)); + + memset(&req, 0, sizeof(req)); + memcpy(req.ifr_name, ifp->if_xname, sizeof(req.ifr_name)); + req.ifr_vnetid = vlr.vlr_tag; + + error = vlan_ioctl(ifp, SIOCSVNETID, (caddr_t)&req); + if (error != 0) + return (error); + + memset(&parent, 0, sizeof(parent)); + memcpy(parent.ifp_name, ifp->if_xname, sizeof(parent.ifp_name)); + memcpy(parent.ifp_parent, vlr.vlr_parent, sizeof(parent.ifp_parent)); + error = vlan_ioctl(ifp, SIOCSIFPARENT, (caddr_t)&parent); + if (error != 0) + return (error); + + memset(&req, 0, sizeof(req)); + memcpy(req.ifr_name, ifp->if_xname, sizeof(req.ifr_name)); + SET(ifp->if_flags, IFF_UP); + return (vlan_ioctl(ifp, SIOCSIFFLAGS, (caddr_t)&req)); +} + +int +vlan_get_compat(struct ifnet *ifp, struct ifreq *ifr) +{ + struct ifvlan *ifv = ifp->if_softc; + struct vlanreq vlr; + struct ifnet *p; + + memset(&vlr, 0, sizeof(vlr)); + p = if_get(ifv->ifv_p); + if (p != NULL) + memcpy(vlr.vlr_parent, p->if_xname, sizeof(vlr.vlr_parent)); + if_put(p); + + vlr.vlr_tag = ifv->ifv_tag; + + return (copyout(&vlr, ifr->ifr_data, sizeof(vlr))); +} + +/* + * do a quick check of up and running vlans for existing configurations. + * + * NOTE: this does allow the same config on down vlans, but vlan_up() + * will catch them. + */ +int +vlan_inuse(uint16_t type, unsigned int ifidx, uint16_t tag) +{ + int error = 0; + + error = rw_enter(&vlan_cfg_lk, RW_READ | RW_INTR); + if (error != 0) + return (error); + + error = vlan_inuse_locked(type, ifidx, tag); + + rw_exit(&vlan_cfg_lk); + + return (error); +} + +int +vlan_inuse_locked(uint16_t type, unsigned int ifidx, uint16_t tag) +{ + SRPL_HEAD(, ifvlan) *tagh, *list; + struct ifvlan *ifv; + + tagh = type == ETHERTYPE_QINQ ? svlan_tagh : vlan_tagh; + list = &tagh[TAG_HASH(tag)]; + + SRPL_FOREACH_LOCKED(ifv, list, ifv_list) { + if (ifv->ifv_tag == tag && + ifv->ifv_type == type && /* wat */ + ifv->ifv_p == ifidx) + return (EADDRINUSE); + } + + return (0); +} + +int +vlan_multi_add(struct ifvlan *ifv, struct ifreq *ifr) +{ + struct ifnet *p; struct vlan_mc_entry *mc; u_int8_t addrlo[ETHER_ADDR_LEN], addrhi[ETHER_ADDR_LEN]; int error; - error = ether_addmulti(ifr, (struct arpcom *)&ifv->ifv_ac); + error = ether_addmulti(ifr, &ifv->ifv_ac); if (error != ENETRESET) return (error); @@ -723,24 +923,28 @@ vlan_ether_addmulti(struct ifvlan *ifv, memcpy(&mc->mc_addr, &ifr->ifr_addr, ifr->ifr_addr.sa_len); LIST_INSERT_HEAD(&ifv->vlan_mc_listhead, mc, mc_entries); - if ((error = (*ifp->if_ioctl)(ifp, SIOCADDMULTI, (caddr_t)ifr)) != 0) + p = if_get(ifv->ifv_p); + error = p == NULL ? 0 : (*p->if_ioctl)(p, SIOCADDMULTI, (caddr_t)ifr); + if_put(p); + + if (error != 0) goto ioctl_failed; return (error); ioctl_failed: LIST_REMOVE(mc, mc_entries); - free(mc, M_DEVBUF, sizeof *mc); + free(mc, M_DEVBUF, sizeof(*mc)); alloc_failed: - (void)ether_delmulti(ifr, (struct arpcom *)&ifv->ifv_ac); + (void)ether_delmulti(ifr, &ifv->ifv_ac); return (error); } int -vlan_ether_delmulti(struct ifvlan *ifv, struct ifreq *ifr) +vlan_multi_del(struct ifvlan *ifv, struct ifreq *ifr) { - struct ifnet *ifp = ifv->ifv_p; + struct ifnet *p; struct ether_multi *enm; struct vlan_mc_entry *mc; u_int8_t addrlo[ETHER_ADDR_LEN], addrhi[ETHER_ADDR_LEN]; @@ -756,35 +960,42 @@ vlan_ether_delmulti(struct ifvlan *ifv, if (enm == NULL) return (EINVAL); - LIST_FOREACH(mc, &ifv->vlan_mc_listhead, mc_entries) + LIST_FOREACH(mc, &ifv->vlan_mc_listhead, mc_entries) { if (mc->mc_enm == enm) break; + } /* We won't delete entries we didn't add */ if (mc == NULL) return (EINVAL); - if ((error = ether_delmulti(ifr, (struct arpcom *)&ifv->ifv_ac)) != 0) + error = ether_delmulti(ifr, &ifv->ifv_ac); + if (error != ENETRESET) + return (error); + + if (!ISSET(ifv->ifv_if.if_flags, IFF_RUNNING)) + goto forget; + + p = if_get(ifv->ifv_p); + error = p == NULL ? 0 : (*p->if_ioctl)(p, SIOCDELMULTI, (caddr_t)ifr); + if_put(p); + + if (error != 0) { + (void)ether_addmulti(ifr, &ifv->ifv_ac); return (error); + } - /* We no longer use this multicast address. Tell parent so. */ - if ((error = (*ifp->if_ioctl)(ifp, SIOCDELMULTI, (caddr_t)ifr)) != 0) { - /* And forget about this address. */ - LIST_REMOVE(mc, mc_entries); - free(mc, M_DEVBUF, sizeof *mc); - } else - (void)ether_addmulti(ifr, (struct arpcom *)&ifv->ifv_ac); - return (error); +forget: + /* forget about this address */ + LIST_REMOVE(mc, mc_entries); + free(mc, M_DEVBUF, sizeof(*mc)); + + return (0); } -/* - * Delete any multicast address we have asked to add from parent - * interface. Called when the vlan is being unconfigured. - */ void -vlan_ether_purgemulti(struct ifvlan *ifv) +vlan_multi_apply(struct ifvlan *ifv, struct ifnet *p, u_long cmd) { - struct ifnet *ifp = ifv->ifv_p; struct vlan_mc_entry *mc; union { struct ifreq ifreq; @@ -795,44 +1006,21 @@ vlan_ether_purgemulti(struct ifvlan *ifv } ifreq; struct ifreq *ifr = &ifreq.ifreq; - memcpy(ifr->ifr_name, ifp->if_xname, IFNAMSIZ); - while ((mc = LIST_FIRST(&ifv->vlan_mc_listhead)) != NULL) { + memcpy(ifr->ifr_name, p->if_xname, IFNAMSIZ); + LIST_FOREACH(mc, &ifv->vlan_mc_listhead, mc_entries) { memcpy(&ifr->ifr_addr, &mc->mc_addr, mc->mc_addr.ss_len); - (void)(*ifp->if_ioctl)(ifp, SIOCDELMULTI, (caddr_t)ifr); - LIST_REMOVE(mc, mc_entries); - free(mc, M_DEVBUF, sizeof *mc); + + (void)(*p->if_ioctl)(p, cmd, (caddr_t)ifr); } } void -vlan_ether_resetmulti(struct ifvlan *ifv, struct ifnet *p) +vlan_multi_free(struct ifvlan *ifv) { - struct ifnet *ifp = ifv->ifv_p; struct vlan_mc_entry *mc; - union { - struct ifreq ifreq; - struct { - char ifr_name[IFNAMSIZ]; - struct sockaddr_storage ifr_ss; - } ifreq_storage; - } ifreq; - struct ifreq *ifr = &ifreq.ifreq; - if (p == NULL) { - vlan_ether_purgemulti(ifv); - return; - } else if (ifp == p) - return; - - LIST_FOREACH(mc, &ifv->vlan_mc_listhead, mc_entries) { - memcpy(&ifr->ifr_addr, &mc->mc_addr, mc->mc_addr.ss_len); - - /* Remove from the old parent */ - memcpy(ifr->ifr_name, ifp->if_xname, IFNAMSIZ); - (void)(*ifp->if_ioctl)(ifp, SIOCDELMULTI, (caddr_t)ifr); - - /* Try to add to the new parent */ - memcpy(ifr->ifr_name, p->if_xname, IFNAMSIZ); - (void)(*p->if_ioctl)(p, SIOCADDMULTI, (caddr_t)ifr); + while ((mc = LIST_FIRST(&ifv->vlan_mc_listhead)) != NULL) { + LIST_REMOVE(mc, mc_entries); + free(mc, M_DEVBUF, sizeof(*mc)); } } Index: sys/net/if_vlan_var.h =================================================================== RCS file: /cvs/src/sys/net/if_vlan_var.h,v retrieving revision 1.32 diff -u -p -r1.32 if_vlan_var.h --- sys/net/if_vlan_var.h 3 Mar 2016 09:27:51 -0000 1.32 +++ sys/net/if_vlan_var.h 9 Mar 2016 05:47:17 -0000 @@ -42,7 +42,11 @@ struct ether_vlan_header { u_int16_t evl_proto; }; -#define EVL_VLID_MASK 0x0FFF +#define EVL_VLID_MASK 0xFFF +#define EVL_VLID_NONE 0x000 +/* 0x000 and 0xFFF are reserved */ +#define EVL_VLID_MIN 0x001 +#define EVL_VLID_MAX 0xFFE #define EVL_VLANOFTAG(tag) ((tag) & EVL_VLID_MASK) #define EVL_PRIOFTAG(tag) (((tag) >> EVL_PRIO_BITS) & 7) #define EVL_ENCAPLEN 4 /* length in octets of encapsulation */ @@ -76,28 +80,24 @@ struct vlan_mc_entry { struct ifvlan { struct arpcom ifv_ac; /* make this an interface */ - struct ifnet *ifv_p; /* parent interface of this vlan */ + unsigned int ifv_p; /* parent interface of this vlan */ struct ifv_linkmib { - int ifvm_parent; - u_int16_t ifvm_proto; /* encapsulation ethertype */ u_int16_t ifvm_tag; /* tag to apply on packets leaving if */ u_int16_t ifvm_prio; /* prio to apply on packet leaving if */ u_int16_t ifvm_type; /* non-standard ethertype or 0x8100 */ } ifv_mib; LIST_HEAD(__vlan_mchead, vlan_mc_entry) vlan_mc_listhead; SRPL_ENTRY(ifvlan) ifv_list; - int ifv_flags; + int ifv_promisc; struct refcnt ifv_refcnt; void *lh_cookie; void *dh_cookie; - struct ifih *ifv_ifih; }; #define ifv_if ifv_ac.ac_if #define ifv_tag ifv_mib.ifvm_tag #define ifv_prio ifv_mib.ifvm_prio #define ifv_type ifv_mib.ifvm_type -#define IFVF_PROMISC 0x01 struct mbuf *vlan_inject(struct mbuf *, uint16_t, uint16_t); #endif /* _KERNEL */