On Thu, Apr 07, 2016 at 03:35:34PM +0200, Martin Pieuchot wrote:
> On 05/04/16(Tue) 09:20, David Gwynne wrote:
> > vlan config is now done via the vnet and parent ioctls. the idea
> > is config can be set up while the vlan interface is down, and then
> > "committed' when it is brought up and running.
> > 
> > vlan up does checks of the vlan state against the target parent,
> > applies any config necessary to the parent, and then configures the
> > vlan interface to handle packets.
> > 
> > vlan down just goes in reverse.
> > 
> > a side effect of this is we can mark the driver as mpsafe.
> > 
> > tests? ok?
> 
> I like it.  I still find it very hard to review because your diff mix
> functionalities change, style rewrite and typos.

\o/

ill focus on the like :)

> 
> I still strongly believe that we should agree on a general solution for
> the srp_upate() sleeping problem before adding more specific locks.

problem or constraint? the lock is already there, and it's consistently
ordered with the pool and timeout locks.

> I have some few comments, I'm not sure which part of this diff needs
> careful review.
> 
> > Index: if_vlan.c
> > ===================================================================

> > +int        vlan_up(struct ifvlan *);
> > +int        vlan_parent_up(struct ifvlan *, struct ifnet *);
> > +int        vlan_down(struct ifvlan *);
> 
> So *_up() and *_down() are the new *_init() and *_stop()?  I find these
> name really confusing.  I first though but why is vlan_down() called in
> vlan_clone_destroy(), I made sure the if layer called if_down() before.
> 
> For me up/down are related to the link state change.

i can understand that.

if_up and if_down in the if layer manipulate the admin state though
(which is the IFF_UP and IFF_DOWN stuff). vlan_up and _down are
consistent with that, and i use similar names in other code. iirc
other drivers tend to go foo_init and foo_stop.

the link state relates to the operational state of an interface,
and tends to be run through things with names like foo_link_state
and if_link_state_change.

> 
> > +int        vlan_promisc(struct ifvlan *, int);
> > +
> > +void       vlan_ifp0_detach_hook(void *);
>         ^^^^^
> Other drivers use the equivalent of vlan_ifdetach() why rename it to
> introduce a difference?

ill fix it.

> > @@ -159,9 +172,12 @@ vlan_clone_create(struct if_clone *ifc, 
> >  
> >     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 = ~0;
> 
> Other pseudo-drivers use 0xffff instead of ~0.

k

> > +   ifp->if_link_state = LINK_STATE_DOWN;
> 
> Why do we need to set the link state at all during initialization?

cos i know its operationally down? unset it's is considered unknown.

would you prefer me to make it unknown when it's unconfigured, ie,
don't do that here and clear if in vlan_down too?

> > -vlan_config(struct ifvlan *ifv, struct ifnet *ifp0, u_int16_t tag)
> > +vlan_parent_up(struct ifvlan *ifv, struct ifnet *ifp0)
> >  {
> > -   struct sockaddr_dl      *sdl1, *sdl2;
> > -   SRPL_HEAD(, ifvlan)     *tagh, *list;
> > -   u_int                    flags;
> > -
> > -   if (ifp0->if_type != IFT_ETHER)
> > -           return EPROTONOSUPPORT;
> > -   if (ifp0->if_index == ifv->ifv_p && ifv->ifv_tag == tag) /* noop */
> > -           return (0);
> > +   int error;
> >  
> > -   /* Remember existing interface flags and reset the interface */
> > -   flags = ifv->ifv_flags;
> > -   vlan_unconfig(&ifv->ifv_if, ifp0);
> > -   ifv->ifv_p = ifp0->if_index;
> > -   ifv->ifv_if.if_baudrate = ifp0->if_baudrate;
> > -
> > -   if (ifp0->if_capabilities & IFCAP_VLAN_MTU) {
> > -           ifv->ifv_if.if_mtu = ifp0->if_mtu;
> > -           ifv->ifv_if.if_hardmtu = ifp0->if_hardmtu;
> > -   } else {
> > -           ifv->ifv_if.if_mtu = ifp0->if_mtu - EVL_ENCAPLEN;
> > -           ifv->ifv_if.if_hardmtu = ifp0->if_hardmtu - EVL_ENCAPLEN;
> > +        /* Register callback for physical link state changes */
> > +   ifv->lh_cookie = hook_establish(ifp0->if_linkstatehooks, 1,
> > +       vlan_link_hook, ifv);
> > +
> > +   /* Register callback if parent wants to unregister */
> > +   ifv->dh_cookie = hook_establish(ifp0->if_detachhooks, 0,
> > +       vlan_ifp0_detach_hook, ifv);
> > +
> > +   vlan_multi_apply(ifv, ifp0, SIOCADDMULTI);
> > +
> > +   if (ISSET(ifv->ifv_flags, IFVF_PROMISC)) {
> > +           error = ifpromisc(ifp0, 1);
> > +           if (error != 0)
> > +                   goto delmulti;
> >     }
> 
> Why not start by setting the parent in promiscuous if asked to?  This
> would simplify the error path.

true.

> > +vlan_up(struct ifvlan *ifv)

> > +   /* check vlan will work on top of the parent */
> > +   if (ifp0->if_type != IFT_ETHER) {
> > +           error = EPROTONOSUPPORT;
> > +           goto put;
> >     }
> 
> This is already checked in the iocl(2) and tun(4) can no longer change
> its sex, so is it really needed?

you can attach an interface to a trunk and that will change its type.

> > -vlan_unconfig(struct ifnet *ifp, struct ifnet *newifp0)
> > +vlan_down(struct ifvlan *ifv)

> > +   vlan_link_state(ifv, LINK_STATE_DOWN, 0);
> 
> This shouldn't be necessary.

this is the operational state. it is definitely disconnected from
the medium when it is brought administratively down.

admin state changes (ie, clearing IFF_UP) do not imply a link state
change from what i can (or cant) see in if.c.

Index: if_vlan.c
===================================================================
RCS file: /cvs/src/sys/net/if_vlan.c,v
retrieving revision 1.159
diff -u -p -r1.159 if_vlan.c
--- if_vlan.c   4 Apr 2016 01:55:44 -0000       1.159
+++ if_vlan.c   8 Apr 2016 09:52:34 -0000
@@ -80,23 +80,36 @@
 SRPL_HEAD(, ifvlan) *vlan_tagh, *svlan_tagh;
 struct rwlock vlan_tagh_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 *);
 void   vlanattach(int count);
-int    vlan_set_promisc(struct ifnet *ifp);
 int    vlan_clone_create(struct if_clone *, int);
 int    vlan_clone_destroy(struct ifnet *);
+
+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_up(struct ifvlan *);
+int    vlan_parent_up(struct ifvlan *, struct ifnet *);
+int    vlan_down(struct ifvlan *);
+
+int    vlan_promisc(struct ifvlan *, int);
+
 void   vlan_ifdetach(void *);
+void   vlan_link_hook(void *);
+void   vlan_link_state(struct ifvlan *, u_char, u_int64_t);
+
+int    vlan_set_vnetid(struct ifvlan *, uint16_t);
+int    vlan_inuse(uint16_t, unsigned int, uint16_t);
+int    vlan_inuse_locked(uint16_t, unsigned int, uint16_t);
 
 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_set_compat(struct ifnet *, struct ifreq *);
+int    vlan_get_compat(struct ifnet *, struct ifreq *);
+
 struct if_clone vlan_cloner =
     IF_CLONE_INITIALIZER("vlan", vlan_clone_create, vlan_clone_destroy);
 struct if_clone svlan_cloner =
@@ -159,9 +172,12 @@ vlan_clone_create(struct if_clone *ifc, 
 
        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);
@@ -191,7 +207,9 @@ 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");
@@ -201,13 +219,6 @@ vlan_clone_destroy(struct ifnet *ifp)
        return (0);
 }
 
-void
-vlan_ifdetach(void *ptr)
-{
-       struct ifvlan   *ifv = ptr;
-       vlan_clone_destroy(&ifv->ifv_if);
-}
-
 static inline int
 vlan_mplstunnel(int ifidx)
 {
@@ -399,47 +410,76 @@ drop:
 }
 
 int
-vlan_config(struct ifvlan *ifv, struct ifnet *ifp0, u_int16_t tag)
+vlan_parent_up(struct ifvlan *ifv, struct ifnet *ifp0)
 {
-       struct sockaddr_dl      *sdl1, *sdl2;
-       SRPL_HEAD(, ifvlan)     *tagh, *list;
-       u_int                    flags;
-
-       if (ifp0->if_type != IFT_ETHER)
-               return EPROTONOSUPPORT;
-       if (ifp0->if_index == ifv->ifv_p && ifv->ifv_tag == tag) /* noop */
-               return (0);
+       int error;
 
-       /* Remember existing interface flags and reset the interface */
-       flags = ifv->ifv_flags;
-       vlan_unconfig(&ifv->ifv_if, ifp0);
-       ifv->ifv_p = ifp0->if_index;
-       ifv->ifv_if.if_baudrate = ifp0->if_baudrate;
-
-       if (ifp0->if_capabilities & IFCAP_VLAN_MTU) {
-               ifv->ifv_if.if_mtu = ifp0->if_mtu;
-               ifv->ifv_if.if_hardmtu = ifp0->if_hardmtu;
-       } else {
-               ifv->ifv_if.if_mtu = ifp0->if_mtu - EVL_ENCAPLEN;
-               ifv->ifv_if.if_hardmtu = ifp0->if_hardmtu - EVL_ENCAPLEN;
+       if (ISSET(ifv->ifv_flags, IFVF_PROMISC)) {
+               error = ifpromisc(ifp0, 1);
+               if (error != 0)
+                       return (error);
+       }
+
+        /* Register callback for physical link state changes */
+       ifv->lh_cookie = hook_establish(ifp0->if_linkstatehooks, 1,
+           vlan_link_hook, ifv);
+
+       /* Register callback if parent wants to unregister */
+       ifv->dh_cookie = hook_establish(ifp0->if_detachhooks, 0,
+           vlan_ifdetach, ifv);
+
+       vlan_multi_apply(ifv, ifp0, SIOCADDMULTI);
+
+       if_ih_insert(ifp0, vlan_input, NULL);
+
+       return (0);
+}
+
+int
+vlan_up(struct ifvlan *ifv)
+{
+       SRPL_HEAD(, ifvlan) *tagh, *list;
+       struct ifnet *ifp = &ifv->ifv_if;
+       struct ifnet *ifp0;
+       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)];
+
+       ifp0 = if_get(ifv->ifv_p);
+       if (ifp0 == NULL)
+               return (ENXIO);
+
+       /* check vlan will work on top of the parent */
+       if (ifp0->if_type != IFT_ETHER) {
+               error = EPROTONOSUPPORT;
+               goto put;
        }
 
-       ifv->ifv_if.if_flags = ifp0->if_flags &
-           (IFF_UP | IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST);
+       hardmtu = ifp0->if_hardmtu;
+       if (!ISSET(ifp0->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;
        }
 
+       /* parent is fine, let's prepare the ifv to handle packets */
+       ifp->if_hardmtu = hardmtu;
+       SET(ifp->if_flags, ifp0->if_flags & IFF_SIMPLEX);
+       if_setlladdr(ifp, LLADDR(ifp0->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;
-       } else if (ifp0->if_capabilities & IFCAP_VLAN_HWTAGGING) {
+               ifp->if_capabilities = 0;
+       } else if (ISSET(ifp0->if_capabilities, IFCAP_VLAN_HWTAGGING)) {
                /*
                 * If the parent interface can do hardware-assisted
                 * VLAN encapsulation, then propagate its hardware-
@@ -448,266 +488,424 @@ 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 = ifp0->if_capabilities &
-                   IFCAP_CSUM_MASK;
+               ifp->if_capabilities = ifp0->if_capabilities & IFCAP_CSUM_MASK;
        }
 
-       /*
-        * Set up our ``Ethernet address'' to reflect the underlying
-        * physical interface's.
-        */
-       sdl1 = ifv->ifv_if.if_sadl;
-       sdl2 = ifp0->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;
+       /* commit the ifv */
+       error = rw_enter(&vlan_tagh_lk, RW_WRITE | RW_INTR);
+       if (error != 0)
+               goto scrub;
 
-       /* Register callback for physical link state changes */
-       ifv->lh_cookie = hook_establish(ifp0->if_linkstatehooks, 1,
-           vlan_vlandev_state, ifv);
-
-       /* Register callback if parent wants to unregister */
-       ifv->dh_cookie = hook_establish(ifp0->if_detachhooks, 0,
-           vlan_ifdetach, ifv);
+       error = vlan_inuse_locked(ifv->ifv_type, ifv->ifv_p, ifv->ifv_tag);
+       if (error != 0)
+               goto leave;
 
-       vlan_multi_apply(ifv, ifp0, SIOCADDMULTI);
+       SRPL_INSERT_HEAD_LOCKED(&vlan_tagh_rc, list, ifv, ifv_list);
+       rw_exit(&vlan_tagh_lk);
 
-       vlan_vlandev_state(ifv);
+       /* configure the parent to handle packets for this vlan */
+       error = vlan_parent_up(ifv, ifp0);
+       if (error != 0)
+               goto remove;
+
+       /* we're running now */
+       SET(ifp->if_flags, IFF_RUNNING);
+       vlan_link_state(ifv, ifp0->if_link_state, ifp0->if_baudrate);
 
-       /* Change input handler of the physical interface. */
-       if_ih_insert(ifp0, vlan_input, NULL);
+       if_put(ifp0);
 
-       tagh = ifv->ifv_type == ETHERTYPE_QINQ ? svlan_tagh : vlan_tagh;
-       list = &tagh[TAG_HASH(tag)];
+       return (0);
 
-       rw_enter_write(&vlan_tagh_lk);
-       SRPL_INSERT_HEAD_LOCKED(&vlan_tagh_rc, list, ifv, ifv_list);
-       rw_exit_write(&vlan_tagh_lk);
+remove:
+       rw_enter(&vlan_tagh_lk, RW_WRITE);
+       SRPL_REMOVE_LOCKED(&vlan_tagh_rc, list, ifv, ifvlan, ifv_list);
+leave:
+       rw_exit(&vlan_tagh_lk);
+scrub:
+       ifp->if_capabilities = 0;
+       if_setlladdr(ifp, etheranyaddr);
+       CLR(ifp->if_flags, IFF_SIMPLEX);
+       ifp->if_hardmtu = 0xffff;
+put:
+       if_put(ifp0);
 
-       return (0);
+       return (error);
 }
 
 int
-vlan_unconfig(struct ifnet *ifp, struct ifnet *newifp0)
+vlan_down(struct ifvlan *ifv)
 {
-       struct sockaddr_dl      *sdl;
-       struct ifvlan           *ifv;
-       SRPL_HEAD(, ifvlan)     *tagh, *list;
-       struct ifnet            *ifp0;
-
-       ifv = ifp->if_softc;
-       ifp0 = if_get(ifv->ifv_p);
-       if (ifp0 == NULL)
-               goto disconnect;
-
-       /* Unset promisc mode on the interface and its parent */
-       if (ifv->ifv_flags & IFVF_PROMISC) {
-               ifp->if_flags &= ~IFF_PROMISC;
-               vlan_set_promisc(ifp);
-       }
+       SRPL_HEAD(, ifvlan) *tagh, *list;
+       struct ifnet *ifp = &ifv->ifv_if;
+       struct ifnet *ifp0;
 
        tagh = ifv->ifv_type == ETHERTYPE_QINQ ? svlan_tagh : vlan_tagh;
        list = &tagh[TAG_HASH(ifv->ifv_tag)];
 
+       KASSERT(ISSET(ifp->if_flags, IFF_RUNNING));
+
+       vlan_link_state(ifv, LINK_STATE_DOWN, 0);
+       CLR(ifp->if_flags, IFF_RUNNING);
+
+       ifq_barrier(&ifp->if_snd);
+
+       ifp0 = if_get(ifv->ifv_p);
+       if (ifp0 != NULL) {
+               if_ih_remove(ifp0, vlan_input, NULL);
+               if (ISSET(ifv->ifv_flags, IFVF_PROMISC))
+                       ifpromisc(ifp0, 0);
+               vlan_multi_apply(ifv, ifp0, SIOCDELMULTI);
+               hook_disestablish(ifp0->if_detachhooks, ifv->dh_cookie);
+               hook_disestablish(ifp0->if_linkstatehooks, ifv->lh_cookie);
+       }
+       if_put(ifp0);
+
        rw_enter_write(&vlan_tagh_lk);
        SRPL_REMOVE_LOCKED(&vlan_tagh_rc, list, ifv, ifvlan, ifv_list);
        rw_exit_write(&vlan_tagh_lk);
 
-       /* Restore previous input handler. */
-       if_ih_remove(ifp0, vlan_input, NULL);
-
-       hook_disestablish(ifp0->if_linkstatehooks, ifv->lh_cookie);
-       hook_disestablish(ifp0->if_detachhooks, ifv->dh_cookie);
-       /* Reset link state */
-       if (newifp0 != NULL) {
-               ifp->if_link_state = LINK_STATE_INVALID;
-               if_link_state_change(ifp);
-       }
+       ifp->if_capabilities = 0;
+       if_setlladdr(ifp, etheranyaddr);
+       CLR(ifp->if_flags, IFF_SIMPLEX);
+       ifp->if_hardmtu = 0xffff;
 
-       /*
-        * 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_multi_apply(ifv, ifp0, SIOCDELMULTI);
+       return (0);
+}
 
-disconnect:
-       /* Disconnect from parent. */
-       ifv->ifv_p = 0;
-       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);
+void
+vlan_ifdetach(void *v)
+{
+       struct ifvlan *ifv = v;
+       struct ifnet *ifp = &ifv->ifv_if;
 
-       if_put(ifp0);
+       if (ISSET(ifp->if_flags, IFF_RUNNING)) {
+               vlan_down(ifv);
+               CLR(ifp->if_flags, IFF_UP);
+       }
 
-       return (0);
+       ifv->ifv_p = 0;
 }
 
 void
-vlan_vlandev_state(void *v)
+vlan_link_hook(void *v)
 {
-       struct ifvlan   *ifv = v;
-       struct ifnet    *ifp0;
-       int              link_state = LINK_STATE_DOWN;
-       uint64_t         baudrate = 0;
+       struct ifvlan *ifv = v;
+       struct ifnet *ifp0;
+
+       u_char link = LINK_STATE_DOWN;
+       uint64_t baud = 0;
 
        ifp0 = if_get(ifv->ifv_p);
        if (ifp0 != NULL) {
-               link_state = ifp0->if_link_state;
-               baudrate = ifp0->if_baudrate;
+               link = ifp0->if_link_state;
+               baud = ifp0->if_baudrate;
        }
        if_put(ifp0);
 
-       if (ifv->ifv_if.if_link_state == link_state)
+       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_state;
-       ifv->ifv_if.if_baudrate = baudrate;
+       ifv->ifv_if.if_link_state = link;
+       ifv->ifv_if.if_baudrate = baud;
+
        if_link_state_change(&ifv->ifv_if);
 }
 
 int
-vlan_set_promisc(struct ifnet *ifp)
+vlan_promisc(struct ifvlan *ifv, int promisc)
 {
-       struct ifvlan   *ifv = ifp->if_softc;
-       struct ifnet    *ifp0;
-       int              error = 0;
+       struct ifnet *ifp0;
+       int error = 0;
 
-       ifp0 = if_get(ifv->ifv_p);
-       if (ifp0 == NULL)
-               goto leave;
+       if ((ISSET(ifv->ifv_flags, IFVF_PROMISC) ? 1 : 0) == promisc)
+               return (0);
 
-       if ((ifp->if_flags & IFF_PROMISC) != 0) {
-               if ((ifv->ifv_flags & IFVF_PROMISC) == 0)
-                       if ((error = ifpromisc(ifp0, 1)) == 0)
-                               ifv->ifv_flags |= IFVF_PROMISC;
-       } else {
-               if ((ifv->ifv_flags & IFVF_PROMISC) != 0)
-                       if ((error = ifpromisc(ifp0, 0)) == 0)
-                               ifv->ifv_flags &= ~IFVF_PROMISC;
+       ifp0 = if_get(ifv->ifv_p);
+       if (ifp0 != NULL) {
+               error = ifpromisc(ifp0, promisc);
        }
-
-leave:
        if_put(ifp0);
 
-       return (0);
+       if (error == 0) {
+               CLR(ifv->ifv_flags, IFVF_PROMISC);
+               SET(ifv->ifv_flags, promisc ? IFVF_PROMISC : 0);
+       }
+
+       return (error);
 }
 
 int
 vlan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
 {
-       struct proc     *p = curproc;   /* XXX */
-       struct ifaddr   *ifa;
-       struct ifnet    *ifp0;
-       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 *ifp0;
+       uint16_t tag;
+       int error = 0;
 
        switch (cmd) {
        case SIOCSIFADDR:
-               if (ifv->ifv_p != 0)
-                       ifp->if_flags |= IFF_UP;
-               else
-                       error = EINVAL;
-               break;
+               ifp->if_flags |= IFF_UP;
+               /* FALLTHROUGH */
 
-       case SIOCSIFMTU:
-               if (ifv->ifv_p != 0) {
-                       if (ifr->ifr_mtu < ETHERMIN ||
-                           ifr->ifr_mtu > ifv->ifv_if.if_hardmtu)
-                               error = EINVAL;
-                       else
-                               ifp->if_mtu = ifr->ifr_mtu;
-               } else
-                       error = EINVAL;
+       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 SIOCSETVLAN:
-               if ((error = suser(p, 0)) != 0)
+       case SIOCSVNETID:
+               tag = ifr->ifr_vnetid;
+               if (tag == ifv->ifv_tag)
                        break;
-               if ((error = copyin(ifr->ifr_data, &vlr, sizeof vlr)))
+
+               if (tag < EVL_VLID_MIN || tag > EVL_VLID_MAX) {
+                       error = EINVAL;
                        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);
+               }
+
+               error = vlan_set_vnetid(ifv, tag);
+               break;
+
+       case SIOCGVNETID:
+               if (ifv->ifv_tag == EVL_VLID_NULL)
+                       error = EADDRNOTAVAIL;
+               else
+                       ifr->ifr_vnetid = (uint32_t)ifv->ifv_tag;
+               break;
+
+       case SIOCDVNETID:
+               error = vlan_set_vnetid(ifv, 0);
+               break;
+
+       case SIOCSIFPARENT:
+               if (ISSET(ifp->if_flags, IFF_RUNNING)) {
+                       error = EBUSY;
                        break;
                }
-               ifp0 = ifunit(vlr.vlr_parent);
+
+               ifp0 = ifunit(parent->ifp_parent);
                if (ifp0 == NULL) {
-                       error = ENOENT;
+                       error = EINVAL;
                        break;
                }
-               /*
-                * Don't let the caller set up a VLAN tag with
-                * anything except VLID bits.
-                */
-               if (vlr.vlr_tag & ~EVL_VLID_MASK) {
-                       error = EINVAL;
+
+               if (ifv->ifv_p == ifp0->if_index) {
+                       /* nop */
                        break;
                }
-               error = vlan_config(ifv, ifp0, vlr.vlr_tag);
-               if (error)
+
+               if (ifp0->if_type != IFT_ETHER) {
+                       error = EPROTONOSUPPORT;
+                       break;
+               }
+
+               error = vlan_inuse(ifv->ifv_type, ifp0->if_index, ifv->ifv_tag);
+               if (error != 0)
                        break;
-               ifp->if_flags |= IFF_RUNNING;
 
-               /* Update promiscuous mode, if necessary. */
-               vlan_set_promisc(ifp);
+               ifv->ifv_p = ifp0->if_index;
                break;
-               
-       case SIOCGETVLAN:
-               bzero(&vlr, sizeof vlr);
+
+       case SIOCGIFPARENT:
                ifp0 = if_get(ifv->ifv_p);
-               if (ifp0) {
-                       snprintf(vlr.vlr_parent, sizeof(vlr.vlr_parent),
-                           "%s", ifp0->if_xname);
-                       vlr.vlr_tag = ifv->ifv_tag;
+               if (ifp0 == NULL)
+                       error = EADDRNOTAVAIL;
+               else {
+                       memcpy(parent->ifp_parent, ifp0->if_xname,
+                           sizeof(parent->ifp_parent));
                }
                if_put(ifp0);
-               error = copyout(&vlr, ifr->ifr_data, sizeof vlr);
                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 != 0)
-                       error = vlan_set_promisc(ifp);
+
+       case SIOCDIFPARENT:
+               if (ISSET(ifp->if_flags, IFF_RUNNING)) {
+                       error = EBUSY;
+                       break;
+               }
+
+               ifv->ifv_p = 0;
                break;
 
        case SIOCADDMULTI:
                error = vlan_multi_add(ifv, ifr);
                break;
-
        case SIOCDELMULTI:
                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;
 }
 
+int
+vlan_set_vnetid(struct ifvlan *ifv, uint16_t tag)
+{
+       struct ifnet *ifp = &ifv->ifv_if;
+       SRPL_HEAD(, ifvlan) *tagh, *list;
+       u_char link = ifp->if_link_state;
+       uint64_t baud = ifp->if_baudrate;
+       int error;
+
+       tagh = ifv->ifv_type == ETHERTYPE_QINQ ? svlan_tagh : vlan_tagh;
+
+       if (ISSET(ifp->if_flags, IFF_RUNNING) && LINK_STATE_IS_UP(link))
+               vlan_link_state(ifv, LINK_STATE_DOWN, 0);
+
+       error = rw_enter(&vlan_tagh_lk, RW_WRITE);
+       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)) {
+               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);
+       } else
+               ifv->ifv_tag = tag;
+
+unlock:
+       rw_exit(&vlan_tagh_lk);
+
+       if (ISSET(ifp->if_flags, IFF_RUNNING) && LINK_STATE_IS_UP(link))
+               vlan_link_state(ifv, link, baud);
+
+       return (error);
+}
+
+int
+vlan_set_compat(struct ifnet *ifp, struct ifreq *ifr)
+{
+       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_tagh_lk, RW_READ | RW_INTR);
+       if (error != 0)
+               return (error);
+
+       error = vlan_inuse_locked(type, ifidx, tag);
+
+       rw_exit(&vlan_tagh_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)

Reply via email to