X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/0b4e3aa066abc0728aacb4bbeb86f53f9737156e..4a2492630c73add3c3aa8a805ba4ff343d4a58ea:/bsd/net/if_vlan.c diff --git a/bsd/net/if_vlan.c b/bsd/net/if_vlan.c index 2bc498376..dee4b7782 100644 --- a/bsd/net/if_vlan.c +++ b/bsd/net/if_vlan.c @@ -1,21 +1,24 @@ /* - * Copyright (c) 2000 Apple Computer, Inc. All rights reserved. + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * - * The contents of this file constitute Original Code as defined in and - * are subject to the Apple Public Source License Version 1.1 (the - * "License"). You may not use this file except in compliance with the - * License. Please obtain a copy of the License at - * http://www.apple.com/publicsource and read it before using this file. + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. * - * This Original Code and all software distributed under the License are - * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License. + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ @@ -47,6 +50,7 @@ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * + * $FreeBSD: src/sys/net/if_vlan.c,v 1.54 2003/10/31 18:32:08 brooks Exp $ */ /* @@ -56,40 +60,24 @@ * we need to pretend to be enough of an Ethernet implementation * to make arp work. The way we do this is by telling everyone * that we are an Ethernet, and then catch the packets that - * ether_output() left on our output queue queue when it calls + * ether_output() left on our output queue when it calls * if_start(), rewrite them for use by the real outgoing interface, * and ask it to send them. - * - * - * XXX It's incorrect to assume that we must always kludge up - * headers on the physical device's behalf: some devices support - * VLAN tag insersion and extraction in firmware. For these cases, - * one can change the behavior of the vlan interface by setting - * the LINK0 flag on it (that is setting the vlan interface's LINK0 - * flag, _not_ the parent's LINK0 flag; we try to leave the parent - * alone). If the interface as the LINK0 flag set, then it will - * not modify the ethernet header on output because the parent - * can do that for itself. On input, the parent can call vlan_input_tag() - * directly in order to supply us with an incoming mbuf and the vlan - * tag value that goes with it. */ -#include "vlan.h" -#if NVLAN > 0 -#include "opt_inet.h" -#include "bpfilter.h" #include #include +#include #include +#include #include #include #include #include +#include -#if NBPFILTER > 0 #include -#endif #include #include #include @@ -97,27 +85,160 @@ #include #include -#if INET +#include + +#ifdef INET #include #include #endif +#include + +#define ETHER_VLAN_ENCAP_LEN 4 /* len of 802.1Q VLAN encapsulation */ +#define IF_MAXUNIT 0x7fff /* historical value */ + +#define IFP2AC(p) ((struct arpcom *)p) + +#define VLAN_PROTO_FAMILY 0x766c616e /* 'vlan' */ + +#define VLANNAME "vlan" + +typedef int (bpf_callback_func)(struct ifnet *, struct mbuf *); +typedef int (if_set_bpf_tap_func)(struct ifnet *ifp, int mode, bpf_callback_func * func); + +struct vlan_mc_entry { + struct ether_addr mc_addr; + SLIST_ENTRY(vlan_mc_entry) mc_entries; +}; + +struct ifvlan { + char ifv_name[IFNAMSIZ]; /* our unique id */ + struct ifnet *ifv_ifp; /* our interface */ + struct ifnet *ifv_p; /* parent interface of this vlan */ + struct ifv_linkmib { + int ifvm_parent; + int ifvm_encaplen; /* encapsulation length */ + int ifvm_mtufudge; /* MTU fudged by this much */ + int ifvm_mintu; /* min transmission unit */ + u_int16_t ifvm_proto; /* encapsulation ethertype */ + u_int16_t ifvm_tag; /* tag to apply on packets leaving if */ + } ifv_mib; + SLIST_HEAD(__vlan_mchead, vlan_mc_entry) vlan_mc_listhead; + LIST_ENTRY(ifvlan) ifv_list; + int ifv_flags; + int ifv_detaching; + u_long ifv_filter_id; + int ifv_filter_valid; + bpf_callback_func * ifv_bpf_input; + bpf_callback_func * ifv_bpf_output; +}; + +#define ifv_tag ifv_mib.ifvm_tag +#define ifv_encaplen ifv_mib.ifvm_encaplen +#define ifv_mtufudge ifv_mib.ifvm_mtufudge +#define ifv_mintu ifv_mib.ifvm_mintu + +#define IFVF_PROMISC 0x01 /* promiscuous mode enabled */ + +#if 0 SYSCTL_DECL(_net_link); -SYSCTL_NODE(_net_link, IFT_8021_VLAN, vlan, CTLFLAG_RW, 0, "IEEE 802.1Q VLAN"); +SYSCTL_NODE(_net_link, IFT_L2VLAN, vlan, CTLFLAG_RW, 0, "IEEE 802.1Q VLAN"); SYSCTL_NODE(_net_link_vlan, PF_LINK, link, CTLFLAG_RW, 0, "for consistency"); +#endif 0 -u_int vlan_proto = ETHERTYPE_VLAN; -SYSCTL_INT(_net_link_vlan_link, VLANCTL_PROTO, proto, CTLFLAG_RW, &vlan_proto, - 0, "Ethernet protocol used for VLAN encapsulation"); +#define M_VLAN M_DEVBUF -static struct ifvlan ifv_softc[NVLAN]; +MALLOC_DEFINE(M_VLAN, VLANNAME, "802.1Q Virtual LAN Interface"); -static void vlan_start(struct ifnet *ifp); +static LIST_HEAD(, ifvlan) ifv_list; + +#if 0 +/* + * Locking: one lock is used to guard both the ifv_list and modification + * to vlan data structures. We are rather conservative here; probably + * more than necessary. + */ +static struct mtx ifv_mtx; +#define VLAN_LOCK_INIT() mtx_init(&ifv_mtx, VLANNAME, NULL, MTX_DEF) +#define VLAN_LOCK_DESTROY() mtx_destroy(&ifv_mtx) +#define VLAN_LOCK_ASSERT() mtx_assert(&ifv_mtx, MA_OWNED) +#define VLAN_LOCK() mtx_lock(&ifv_mtx) +#define VLAN_UNLOCK() mtx_unlock(&ifv_mtx) +#else +#define VLAN_LOCK_INIT() +#define VLAN_LOCK_DESTROY() +#define VLAN_LOCK_ASSERT() +#define VLAN_LOCK() +#define VLAN_UNLOCK() +#endif 0 + +static int vlan_clone_create(struct if_clone *, int); +static void vlan_clone_destroy(struct ifnet *); +static int vlan_output(struct ifnet *ifp, struct mbuf *m); static void vlan_ifinit(void *foo); -static int vlan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t addr); +static int vlan_ioctl(struct ifnet *ifp, u_long cmd, void * addr); +static int vlan_set_bpf_tap(struct ifnet * ifp, int mode, + bpf_callback_func * func); +static int vlan_attach_protocol(struct ifnet *ifp); +static int vlan_detach_protocol(struct ifnet *ifp); +static int vlan_attach_filter(struct ifnet * ifp, u_long * filter_id); +static int vlan_detach_filter(u_long filter_id); static int vlan_setmulti(struct ifnet *ifp); static int vlan_unconfig(struct ifnet *ifp); -static int vlan_config(struct ifvlan *ifv, struct ifnet *p); +static int vlan_config(struct ifvlan *ifv, struct ifnet *p, int tag); +static int vlan_if_free(struct ifnet * ifp); + +static struct if_clone vlan_cloner = IF_CLONE_INITIALIZER(VLANNAME, + vlan_clone_create, vlan_clone_destroy, 0, IF_MAXUNIT); + +static if_set_bpf_tap_func nop_if_bpf; +static int nop_if_free(struct ifnet *); +static int nop_if_ioctl(struct ifnet *, u_long, void *); +static int nop_if_output(struct ifnet * ifp, struct mbuf * m); + +static void interface_link_event(struct ifnet * ifp, u_long event_code); + +static __inline__ void +vlan_bpf_output(struct ifnet * ifp, struct mbuf * m, + bpf_callback_func func) +{ + if (func != NULL) { + func(ifp, m); + } + return; +} + +static __inline__ void +vlan_bpf_input(struct ifnet * ifp, struct mbuf * m, + bpf_callback_func func, char * frame_header, + int frame_header_len, int encap_len) +{ + if (func != NULL) { + if (encap_len > 0) { + /* present the right header to bpf */ + bcopy(frame_header, frame_header + encap_len, frame_header_len); + } + m->m_data -= frame_header_len; + m->m_len += frame_header_len; + func(ifp, m); + m->m_data += frame_header_len; + m->m_len -= frame_header_len; + if (encap_len > 0) { + /* restore the header */ + bcopy(frame_header + encap_len, frame_header, frame_header_len); + } + } + return; +} + +static struct ifaddr * +ifaddr_byindex(unsigned int i) +{ + if (i > if_index || i == 0) { + return (NULL); + } + return (ifnet_addrs[i - 1]); +} /* * Program our multicast filter. What we're actually doing is @@ -127,463 +248,1310 @@ static int vlan_config(struct ifvlan *ifv, struct ifnet *p); * later by the upper protocol layers. Unfortunately, there's no way * to avoid this: there really is only one physical interface. */ -static int vlan_setmulti(struct ifnet *ifp) +static int +vlan_setmulti(struct ifnet *ifp) { - struct ifnet *ifp_p; - struct ifmultiaddr *ifma, *rifma = NULL; - struct ifvlan *sc; - struct vlan_mc_entry *mc = NULL; - struct sockaddr_dl sdl; - int error; + struct ifnet *p; + struct ifmultiaddr *ifma, *rifma = NULL; + struct ifvlan *sc; + struct vlan_mc_entry *mc = NULL; + struct sockaddr_dl sdl; + int error; - /* Find the parent. */ - sc = ifp->if_softc; - ifp_p = sc->ifv_p; + /* Find the parent. */ + sc = ifp->if_private; + p = sc->ifv_p; + if (p == NULL) { + /* no parent, so no need to program the multicast filter */ + return (0); + } - sdl.sdl_len = ETHER_ADDR_LEN; - sdl.sdl_family = AF_LINK; + bzero((char *)&sdl, sizeof sdl); + sdl.sdl_len = sizeof sdl; + sdl.sdl_family = AF_LINK; + sdl.sdl_index = p->if_index; + sdl.sdl_type = IFT_ETHER; + sdl.sdl_alen = ETHER_ADDR_LEN; - /* First, remove any existing filter entries. */ - while(sc->vlan_mc_listhead.slh_first != NULL) { - mc = sc->vlan_mc_listhead.slh_first; - bcopy((char *)&mc->mc_addr, LLADDR(&sdl), ETHER_ADDR_LEN); - error = if_delmulti(ifp_p, (struct sockaddr *)&sdl); - if (error) - return(error); - SLIST_REMOVE_HEAD(&sc->vlan_mc_listhead, mc_entries); - FREE(mc, M_DEVBUF); - } + /* First, remove any existing filter entries. */ + while (SLIST_FIRST(&sc->vlan_mc_listhead) != NULL) { + mc = SLIST_FIRST(&sc->vlan_mc_listhead); + bcopy((char *)&mc->mc_addr, LLADDR(&sdl), ETHER_ADDR_LEN); + error = if_delmulti(p, (struct sockaddr *)&sdl); + if (error) + return(error); + SLIST_REMOVE_HEAD(&sc->vlan_mc_listhead, mc_entries); + FREE(mc, M_VLAN); + } + + /* Now program new ones. */ + LIST_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { + if (ifma->ifma_addr->sa_family != AF_LINK) + continue; + mc = _MALLOC(sizeof(struct vlan_mc_entry), M_VLAN, M_WAITOK); + bcopy(LLADDR((struct sockaddr_dl *)ifma->ifma_addr), + (char *)&mc->mc_addr, ETHER_ADDR_LEN); + SLIST_INSERT_HEAD(&sc->vlan_mc_listhead, mc, mc_entries); + bcopy(LLADDR((struct sockaddr_dl *)ifma->ifma_addr), + LLADDR(&sdl), ETHER_ADDR_LEN); + error = if_addmulti(p, (struct sockaddr *)&sdl, &rifma); + if (error) + return(error); + } - /* Now program new ones. */ - for (ifma = ifp->if_multiaddrs.lh_first; - ifma != NULL;ifma = ifma->ifma_link.le_next) { - if (ifma->ifma_addr->sa_family != AF_LINK) - continue; - mc = _MALLOC(sizeof(struct vlan_mc_entry), M_DEVBUF, M_WAITOK); - if (mc == NULL) - return (ENOMEM); - bcopy(LLADDR((struct sockaddr_dl *)ifma->ifma_addr), - (char *)&mc->mc_addr, ETHER_ADDR_LEN); - SLIST_INSERT_HEAD(&sc->vlan_mc_listhead, mc, mc_entries); - error = if_addmulti(ifp_p, (struct sockaddr *)&sdl, &rifma); - if (error) - return(error); + return(0); +} + +#if 0 +/* + * VLAN support can be loaded as a module. The only place in the + * system that's intimately aware of this is ether_input. We hook + * into this code through vlan_input_p which is defined there and + * set here. Noone else in the system should be aware of this so + * we use an explicit reference here. + * + * NB: Noone should ever need to check if vlan_input_p is null or + * not. This is because interfaces have a count of the number + * of active vlans (if_nvlans) and this should never be bumped + * except by vlan_config--which is in this module so therefore + * the module must be loaded and vlan_input_p must be non-NULL. + */ +extern void (*vlan_input_p)(struct ifnet *, struct mbuf *); + +static int +vlan_modevent(module_t mod, int type, void *data) +{ + + switch (type) { + case MOD_LOAD: + LIST_INIT(&ifv_list); + VLAN_LOCK_INIT(); + vlan_input_p = vlan_input; + if_clone_attach(&vlan_cloner); + break; + case MOD_UNLOAD: + if_clone_detach(&vlan_cloner); + vlan_input_p = NULL; + while (!LIST_EMPTY(&ifv_list)) + vlan_clone_destroy(LIST_FIRST(&ifv_list)->ifv_ifp); + VLAN_LOCK_DESTROY(); + break; + } + return 0; +} + +static moduledata_t vlan_mod = { + "if_vlan", + vlan_modevent, + 0 +}; + +DECLARE_MODULE(if_vlan, vlan_mod, SI_SUB_PSEUDO, SI_ORDER_ANY); + +#endif 0 + +static struct ifvlan * +vlan_lookup_ifp_and_tag(struct ifnet * ifp, int tag) +{ + struct ifvlan * ifv; + + LIST_FOREACH(ifv, &ifv_list, ifv_list) { + if (ifp == ifv->ifv_p && tag == ifv->ifv_tag) { + return (ifv); } + } + return (NULL); +} - return(0); +static struct ifvlan * +vlan_lookup_ifp(struct ifnet * ifp) +{ + struct ifvlan * ifv; + + LIST_FOREACH(ifv, &ifv_list, ifv_list) { + if (ifp == ifv->ifv_p) { + return (ifv); + } + } + return (NULL); } static void -vlaninit(void *dummy) -{ - int i; - - for (i = 0; i < NVLAN; i++) { - struct ifnet *ifp = &ifv_softc[i].ifv_if; - - ifp->if_softc = &ifv_softc[i]; - ifp->if_name = "vlan"; - ifp->if_family = APPLE_IF_FAM_VLAN; - ifp->if_unit = i; - /* NB: flags are not set here */ - ifp->if_linkmib = &ifv_softc[i].ifv_mib; - ifp->if_linkmiblen = sizeof ifv_softc[i].ifv_mib; - /* NB: mtu is not set here */ - - ifp->if_init = vlan_ifinit; - ifp->if_start = vlan_start; - ifp->if_ioctl = vlan_ioctl; - ifp->if_output = ether_output; - ifp->if_snd.ifq_maxlen = ifqmaxlen; - if_attach(ifp); - ether_ifattach(ifp); -#if NBPFILTER > 0 - bpfattach(ifp, DLT_EN10MB, sizeof(struct ether_header)); -#endif - /* Now undo some of the damage... */ - ifp->if_data.ifi_type = IFT_8021_VLAN; - ifp->if_data.ifi_hdrlen = EVL_ENCAPLEN; - ifp->if_resolvemulti = 0; - } +vlan_clone_attach(void) +{ + if_clone_attach(&vlan_cloner); + return; +} + +static int +vlan_clone_create(struct if_clone *ifc, int unit) +{ + int error; + struct ifvlan *ifv; + struct ifnet *ifp; + + ifv = _MALLOC(sizeof(struct ifvlan), M_VLAN, M_WAITOK); + bzero(ifv, sizeof(struct ifvlan)); + SLIST_INIT(&ifv->vlan_mc_listhead); + + /* use the interface name as the unique id for ifp recycle */ + if (snprintf(ifv->ifv_name, sizeof(ifv->ifv_name), "%s%d", + ifc->ifc_name, unit) >= sizeof(ifv->ifv_name)) { + FREE(ifv, M_VLAN); + return (EINVAL); + } + error = dlil_if_acquire(APPLE_IF_FAM_VLAN, + ifv->ifv_name, + strlen(ifv->ifv_name), + &ifp); + if (error) { + FREE(ifv, M_VLAN); + return (error); + } + ifv->ifv_ifp = ifp; + ifp->if_private = ifv; + ifp->if_name = (char *)ifc->ifc_name; + ifp->if_unit = unit; + ifp->if_family = APPLE_IF_FAM_VLAN; + +#if 0 + /* NB: flags are not set here */ + ifp->if_linkmib = &ifv->ifv_mib; + ifp->if_linkmiblen = sizeof ifv->ifv_mib; + /* NB: mtu is not set here */ +#endif 0 + + ifp->if_ioctl = vlan_ioctl; + ifp->if_set_bpf_tap = vlan_set_bpf_tap; + ifp->if_free = nop_if_free; + ifp->if_output = nop_if_output; + ifp->if_hwassist = 0; + ifp->if_addrlen = ETHER_ADDR_LEN; /* XXX ethernet specific */ + ifp->if_baudrate = 0; + ifp->if_type = IFT_L2VLAN; + ifp->if_hdrlen = ETHER_VLAN_ENCAP_LEN; + error = dlil_if_attach(ifp); + if (error) { + dlil_if_release(ifp); + FREE(ifv, M_VLAN); + return (error); + } + + /* attach as ethernet */ + bpfattach(ifp, DLT_EN10MB, sizeof(struct ether_header)); + + VLAN_LOCK(); + LIST_INSERT_HEAD(&ifv_list, ifv, ifv_list); + VLAN_UNLOCK(); + + return (0); } -PSEUDO_SET(vlaninit, if_vlan); static void -vlan_ifinit(void *foo) +vlan_remove(struct ifvlan * ifv) { - return; + VLAN_LOCK_ASSERT(); + ifv->ifv_detaching = 1; + vlan_unconfig(ifv->ifv_ifp); + LIST_REMOVE(ifv, ifv_list); + return; } static void -vlan_start(struct ifnet *ifp) -{ - struct ifvlan *ifv; - struct ifnet *p; - struct ether_vlan_header *evl; - struct mbuf *m; - - ifv = ifp->if_softc; - p = ifv->ifv_p; - - ifp->if_flags |= IFF_OACTIVE; - for (;;) { - IF_DEQUEUE(&ifp->if_snd, m); - if (m == 0) - break; -#if NBPFILTER > 0 - if (ifp->if_bpf) - bpf_mtap(ifp, m); -#endif /* NBPFILTER > 0 */ - - /* - * If the LINK0 flag is set, it means the underlying interface - * can do VLAN tag insertion itself and doesn't require us to - * create a special header for it. In this case, we just pass - * the packet along. However, we need some way to tell the - * interface where the packet came from so that it knows how - * to find the VLAN tag to use, so we set the rcvif in the - * mbuf header to our ifnet. - * - * Note: we also set the M_PROTO1 flag in the mbuf to let - * the parent driver know that the rcvif pointer is really - * valid. We need to do this because sometimes mbufs will - * be allocated by other parts of the system that contain - * garbage in the rcvif pointer. Using the M_PROTO1 flag - * lets the driver perform a proper sanity check and avoid - * following potentially bogus rcvif pointers off into - * never-never land. - */ - if (ifp->if_flags & IFF_LINK0) { - m->m_pkthdr.rcvif = ifp; - m->m_flags |= M_PROTO1; - } else { - M_PREPEND(m, EVL_ENCAPLEN, M_DONTWAIT); - if (m == 0) - continue; - /* M_PREPEND takes care of m_len, m_pkthdr.len for us */ - - /* - * Transform the Ethernet header into an Ethernet header - * with 802.1Q encapsulation. - */ - bcopy(mtod(m, char *) + EVL_ENCAPLEN, mtod(m, char *), - sizeof(struct ether_header)); - evl = mtod(m, struct ether_vlan_header *); - evl->evl_proto = evl->evl_encap_proto; - evl->evl_encap_proto = htons(vlan_proto); - evl->evl_tag = htons(ifv->ifv_tag); -#ifdef DEBUG - printf("vlan_start: %*D\n", sizeof *evl, - (char *)evl, ":"); -#endif - } +vlan_if_detach(struct ifnet * ifp) +{ + ifp->if_output = nop_if_output; + ifp->if_ioctl = nop_if_ioctl; + ifp->if_set_bpf_tap = &nop_if_bpf; + if (dlil_if_detach(ifp) == DLIL_WAIT_FOR_FREE) { + ifp->if_free = vlan_if_free; + } else { + vlan_if_free(ifp); + } + return; +} - /* - * Send it, precisely as ether_output() would have. - * We are already running at splimp. - */ - if (IF_QFULL(&p->if_snd)) { - IF_DROP(&p->if_snd); - /* XXX stats */ - ifp->if_oerrors++; - m_freem(m); - continue; - } - IF_ENQUEUE(&p->if_snd, m); - if ((p->if_flags & IFF_OACTIVE) == 0) { - p->if_start(p); - ifp->if_opackets++; - } - } - ifp->if_flags &= ~IFF_OACTIVE; +static void +vlan_clone_destroy(struct ifnet *ifp) +{ + struct ifvlan *ifv = ifp->if_private; + if (ifv == NULL || ifp->if_type != IFT_L2VLAN) { + return; + } + VLAN_LOCK(); + if (ifv->ifv_detaching) { + VLAN_UNLOCK(); return; + } + vlan_remove(ifv); + VLAN_UNLOCK(); + vlan_if_detach(ifp); + return; } -void -vlan_input_tag(struct ether_header *eh, struct mbuf *m, u_int16_t t) +static int +vlan_set_bpf_tap(struct ifnet * ifp, int mode, bpf_callback_func * func) { - int i; - struct ifvlan *ifv; + struct ifvlan *ifv = ifp->if_private; - for (i = 0; i < NVLAN; i++) { - ifv = &ifv_softc[i]; - if (ifv->ifv_tag == t) - break; - } + switch (mode) { + case BPF_TAP_DISABLE: + ifv->ifv_bpf_input = ifv->ifv_bpf_output = NULL; + break; - if (i >= NVLAN || (ifv->ifv_if.if_flags & IFF_UP) == 0) { - m_freem(m); - ifv->ifv_p->if_data.ifi_noproto++; - return; - } + case BPF_TAP_INPUT: + ifv->ifv_bpf_input = func; + break; + + case BPF_TAP_OUTPUT: + ifv->ifv_bpf_output = func; + break; + + case BPF_TAP_INPUT_OUTPUT: + ifv->ifv_bpf_input = ifv->ifv_bpf_output = func; + break; + default: + break; + } + return 0; +} + +static void +vlan_ifinit(void *foo) +{ + return; +} + +static int +vlan_output(struct ifnet *ifp, struct mbuf *m) +{ + struct ifvlan *ifv; + struct ifnet *p; + struct ether_vlan_header *evl; + int soft_vlan; + ifv = ifp->if_private; + p = ifv->ifv_p; + if (p == NULL) { + return (nop_if_output(ifp, m)); + } + if (m == 0) { + printf("%s: NULL output mbuf\n", ifv->ifv_name); + return (EINVAL); + } + if ((m->m_flags & M_PKTHDR) == 0) { + printf("%s: M_PKTHDR bit not set\n", ifv->ifv_name); + m_freem(m); + return (EINVAL); + } + ifp->if_obytes += m->m_pkthdr.len; + ifp->if_opackets++; + soft_vlan = (p->if_hwassist & IF_HWASSIST_VLAN_TAGGING) == 0; + vlan_bpf_output(ifp, m, ifv->ifv_bpf_output); + + /* do not run parent's if_output() if the parent is not up */ + if ((p->if_flags & (IFF_UP | IFF_RUNNING)) != (IFF_UP | IFF_RUNNING)) { + m_freem(m); + ifp->if_collisions++; + return (0); + } + /* + * If underlying interface can do VLAN tag insertion itself, + * just pass the packet along. However, we need some way to + * tell the interface where the packet came from so that it + * knows how to find the VLAN tag to use. We use a field in + * the mbuf header to store the VLAN tag, and a bit in the + * csum_flags field to mark the field as valid. + */ + if (soft_vlan == 0) { + m->m_pkthdr.csum_flags |= CSUM_VLAN_TAG_VALID; + m->m_pkthdr.vlan_tag = ifv->ifv_tag; + } else { + M_PREPEND(m, ifv->ifv_encaplen, M_DONTWAIT); + if (m == NULL) { + printf("%s: unable to prepend VLAN header\n", + ifv->ifv_name); + ifp->if_ierrors++; + return (0); + } + /* M_PREPEND takes care of m_len, m_pkthdr.len for us */ + if (m->m_len < sizeof(*evl)) { + m = m_pullup(m, sizeof(*evl)); + if (m == NULL) { + printf("%s: cannot pullup VLAN header\n", + ifv->ifv_name); + ifp->if_ierrors++; + return (0); + } + } + /* - * Having found a valid vlan interface corresponding to - * the given source interface and vlan tag, run the - * the real packet through ethert_input(). + * Transform the Ethernet header into an Ethernet header + * with 802.1Q encapsulation. */ - m->m_pkthdr.rcvif = &ifv->ifv_if; - -#if NBPFILTER > 0 - if (ifv->ifv_if.if_bpf) { - /* - * Do the usual BPF fakery. Note that we don't support - * promiscuous mode here, since it would require the - * drivers to know about VLANs and we're not ready for - * that yet. - */ - struct mbuf m0; - m0.m_next = m; - m0.m_len = sizeof(struct ether_header); - m0.m_data = (char *)eh; - bpf_mtap(&ifv->ifv_if, &m0); - } -#endif - ifv->ifv_if.if_ipackets++; - ether_input(&ifv->ifv_if, eh, m); - return; + bcopy(mtod(m, char *) + ifv->ifv_encaplen, + mtod(m, char *), ETHER_HDR_LEN); + evl = mtod(m, struct ether_vlan_header *); + evl->evl_proto = evl->evl_encap_proto; + evl->evl_encap_proto = htons(ETHERTYPE_VLAN); + evl->evl_tag = htons(ifv->ifv_tag); + m->m_pkthdr.len += ifv->ifv_encaplen; + } + + /* + * Send it, precisely as ether_output() would have. + * We are already running at splimp. + */ + return ((*p->if_output)(p, m)); } -int -vlan_input(struct ether_header *eh, struct mbuf *m) +extern int +vlan_demux(struct ifnet * ifp, struct mbuf * m, + char * frame_header, struct if_proto * * proto) { - int i; - struct ifvlan *ifv; + register struct ether_header *eh = (struct ether_header *)frame_header; + struct ether_vlan_header *evl; + struct ifvlan *ifv = NULL; + int soft_vlan = 0; + u_int tag; - for (i = 0; i < NVLAN; i++) { - ifv = &ifv_softc[i]; - if (m->m_pkthdr.rcvif == ifv->ifv_p - && (EVL_VLANOFTAG(ntohs(*mtod(m, u_int16_t *))) - == ifv->ifv_tag)) - break; - } + if (m->m_pkthdr.csum_flags & CSUM_VLAN_TAG_VALID) { + /* + * Packet is tagged, m contains a normal + * Ethernet frame; the tag is stored out-of-band. + */ + m->m_pkthdr.csum_flags &= ~CSUM_VLAN_TAG_VALID; + tag = EVL_VLANOFTAG(m->m_pkthdr.vlan_tag); + m->m_pkthdr.vlan_tag = 0; + } else { + soft_vlan = 1; - if (i >= NVLAN || (ifv->ifv_if.if_flags & IFF_UP) == 0) { + switch (ifp->if_type) { + case IFT_ETHER: + if (m->m_len < ETHER_VLAN_ENCAP_LEN) { m_freem(m); - return -1; /* so ether_input can take note */ - } + return (EJUSTRETURN); + } + evl = (struct ether_vlan_header *)frame_header; + if (ntohs(evl->evl_proto) == ETHERTYPE_VLAN) { + /* don't allow VLAN within VLAN */ + m_freem(m); + return (EJUSTRETURN); + } + tag = EVL_VLANOFTAG(ntohs(evl->evl_tag)); + /* + * Restore the original ethertype. We'll remove + * the encapsulation after we've found the vlan + * interface corresponding to the tag. + */ + evl->evl_encap_proto = evl->evl_proto; + break; + default: + printf("vlan_demux: unsupported if type %u", + ifp->if_type); + m_freem(m); + return (EJUSTRETURN); + break; + } + } + if (tag != 0) { + if (ifp->if_nvlans == 0) { + /* don't bother looking through the VLAN list */ + m_freem(m); + ifp->if_noproto++; + return (EJUSTRETURN); + } + VLAN_LOCK(); + ifv = vlan_lookup_ifp_and_tag(ifp, tag); + if (ifv == NULL || (ifv->ifv_ifp->if_flags & IFF_UP) == 0) { + VLAN_UNLOCK(); + m_freem(m); + ifp->if_noproto++; + return (EJUSTRETURN); + } + VLAN_UNLOCK(); /* XXX extend below? */ + } + if (soft_vlan) { /* - * Having found a valid vlan interface corresponding to - * the given source interface and vlan tag, remove the - * encapsulation, and run the real packet through - * ether_input() a second time (it had better be - * reentrant!). + * Packet had an in-line encapsulation header; + * remove it. The original header has already + * been fixed up above. */ - m->m_pkthdr.rcvif = &ifv->ifv_if; - eh->ether_type = mtod(m, u_int16_t *)[1]; - m->m_data += EVL_ENCAPLEN; - m->m_len -= EVL_ENCAPLEN; - m->m_pkthdr.len -= EVL_ENCAPLEN; - -#if NBPFILTER > 0 - if (ifv->ifv_if.if_bpf) { - /* - * Do the usual BPF fakery. Note that we don't support - * promiscuous mode here, since it would require the - * drivers to know about VLANs and we're not ready for - * that yet. - */ - struct mbuf m0; - m0.m_next = m; - m0.m_len = sizeof(struct ether_header); - m0.m_data = (char *)eh; - bpf_mtap(&ifv->ifv_if, &m0); - } -#endif - ifv->ifv_if.if_ipackets++; - ether_input(&ifv->ifv_if, eh, m); - return 0; + m->m_len -= ETHER_VLAN_ENCAP_LEN; + m->m_data += ETHER_VLAN_ENCAP_LEN; + m->m_pkthdr.len -= ETHER_VLAN_ENCAP_LEN; + m->m_pkthdr.csum_flags = 0; /* can't trust hardware checksum */ + } + if (tag != 0) { + /* we found a vlan interface above, so send it up */ + m->m_pkthdr.rcvif = ifv->ifv_ifp; + ifv->ifv_ifp->if_ipackets++; + ifv->ifv_ifp->if_ibytes += m->m_pkthdr.len; + + vlan_bpf_input(ifv->ifv_ifp, m, ifv->ifv_bpf_input, frame_header, + ETHER_HDR_LEN, soft_vlan ? ETHER_VLAN_ENCAP_LEN : 0); + + /* Pass it back through the parent's demux routine. */ + return ((*ifp->if_demux)(ifv->ifv_ifp, m, frame_header, proto)); + } + /* Pass it back through calling demux routine. */ + return ((*ifp->if_demux)(ifp, m, frame_header, proto)); } static int -vlan_config(struct ifvlan *ifv, struct ifnet *p) +vlan_config(struct ifvlan *ifv, struct ifnet *p, int tag) { - struct ifaddr *ifa1, *ifa2; - struct sockaddr_dl *sdl1, *sdl2; + struct ifnet * ifp; + struct ifaddr *ifa1, *ifa2; + struct sockaddr_dl *sdl1, *sdl2; + int supports_vlan_mtu = 0; - if (p->if_data.ifi_type != IFT_ETHER) - return EPROTONOSUPPORT; - if (ifv->ifv_p) - return EBUSY; - ifv->ifv_p = p; - if (p->if_data.ifi_hdrlen == sizeof(struct ether_vlan_header)) - ifv->ifv_if.if_mtu = p->if_mtu; - else - ifv->ifv_if.if_mtu = p->if_data.ifi_mtu - EVL_ENCAPLEN; + VLAN_LOCK_ASSERT(); + if (p->if_data.ifi_type != IFT_ETHER) + return EPROTONOSUPPORT; + if (ifv->ifv_p != NULL || ifv->ifv_detaching) { + return EBUSY; + } + if (vlan_lookup_ifp_and_tag(p, tag) != NULL) { + /* already a VLAN with that tag on this interface */ + return (EADDRINUSE); + } + ifp = ifv->ifv_ifp; + ifv->ifv_encaplen = ETHER_VLAN_ENCAP_LEN; + ifv->ifv_mintu = ETHERMIN; + ifv->ifv_flags = 0; - /* - * Preserve the state of the LINK0 flag for ourselves. - */ - ifv->ifv_if.if_flags = (p->if_flags & ~(IFF_LINK0)); + /* + * If the parent supports the VLAN_MTU capability, + * i.e. can Tx/Rx larger than ETHER_MAX_LEN frames, + * enable it. + */ + if (p->if_hwassist & (IF_HWASSIST_VLAN_MTU | IF_HWASSIST_VLAN_TAGGING)) { + supports_vlan_mtu = 1; + } + if (p->if_nvlans == 0) { + u_long dltag; + u_long filter_id; + int error; + + /* attach our VLAN "interface filter" to the interface */ + error = vlan_attach_filter(p, &filter_id); + if (error) { + return (error); + } + + /* attach our VLAN "protocol" to the interface */ + error = vlan_attach_protocol(p); + if (error) { + (void)vlan_detach_filter(filter_id); + return (error); + } + ifv->ifv_filter_id = filter_id; + ifv->ifv_filter_valid = TRUE; +#if 0 + if (supports_vlan_mtu) { + /* + * Enable Tx/Rx of VLAN-sized frames. + */ + p->if_capenable |= IFCAP_VLAN_MTU; + if (p->if_flags & IFF_UP) { + struct ifreq ifr; + int error; + + ifr.ifr_flags = p->if_flags; + error = (*p->if_ioctl)(p, SIOCSIFFLAGS, + (caddr_t) &ifr); + if (error) { + if (p->if_nvlans == 0) + p->if_capenable &= ~IFCAP_VLAN_MTU; + return (error); + } + } + } +#endif 0 + } else { + struct ifvlan * other_ifv; + other_ifv = vlan_lookup_ifp(p); + if (other_ifv == NULL) { + printf("vlan: other_ifv can't be NULL\n"); + return (EINVAL); + } + ifv->ifv_filter_id = other_ifv->ifv_filter_id; + ifv->ifv_filter_valid = TRUE; + } + p->if_nvlans++; + if (supports_vlan_mtu) { + ifv->ifv_mtufudge = 0; + } else { /* - * Set up our ``Ethernet address'' to reflect the underlying - * physical interface's. + * Fudge the MTU by the encapsulation size. This + * makes us incompatible with strictly compliant + * 802.1Q implementations, but allows us to use + * the feature with other NetBSD implementations, + * which might still be useful. */ - ifa1 = ifnet_addrs[ifv->ifv_if.if_index - 1]; - ifa2 = ifnet_addrs[p->if_index - 1]; - sdl1 = (struct sockaddr_dl *)ifa1->ifa_addr; - sdl2 = (struct sockaddr_dl *)ifa2->ifa_addr; - 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); - return 0; + ifv->ifv_mtufudge = ifv->ifv_encaplen; + } + + ifv->ifv_p = p; + ifp->if_mtu = p->if_mtu - ifv->ifv_mtufudge; + /* + * Copy only a selected subset of flags from the parent. + * Other flags are none of our business. + */ + ifp->if_flags |= (p->if_flags & + (IFF_BROADCAST | IFF_MULTICAST | IFF_SIMPLEX)); + /* + * If the parent interface can do hardware-assisted + * VLAN encapsulation, then propagate its hardware- + * assisted checksumming flags. + */ + if (p->if_hwassist & IF_HWASSIST_VLAN_TAGGING) { + ifp->if_hwassist |= IF_HWASSIST_CSUM_FLAGS(p->if_hwassist); + } + /* + * Set up our ``Ethernet address'' to reflect the underlying + * physical interface's. + */ + ifa1 = ifaddr_byindex(ifp->if_index); + ifa2 = ifaddr_byindex(p->if_index); + sdl1 = (struct sockaddr_dl *)ifa1->ifa_addr; + sdl2 = (struct sockaddr_dl *)ifa2->ifa_addr; + sdl1->sdl_type = IFT_ETHER; + sdl1->sdl_alen = ETHER_ADDR_LEN; + bcopy(LLADDR(sdl2), LLADDR(sdl1), ETHER_ADDR_LEN); + bcopy(LLADDR(sdl2), IFP2AC(ifp)->ac_enaddr, ETHER_ADDR_LEN); + + /* + * Configure multicast addresses that may already be + * joined on the vlan device. + */ + (void)vlan_setmulti(ifp); + ifp->if_output = vlan_output; + ifv->ifv_tag = tag; + + return 0; +} + +static void +vlan_link_event(struct ifnet * ifp, struct ifnet * p) +{ + struct ifmediareq ifmr; + + /* generate a link event based on the state of the underlying interface */ + bzero(&ifmr, sizeof(ifmr)); + snprintf(ifmr.ifm_name, sizeof(ifmr.ifm_name), + "%s%d", p->if_name, p->if_unit); + if ((*p->if_ioctl)(p, SIOCGIFMEDIA, (caddr_t)&ifmr) == 0 + && ifmr.ifm_count > 0 && ifmr.ifm_status & IFM_AVALID) { + u_long event; + + event = (ifmr.ifm_status & IFM_ACTIVE) + ? KEV_DL_LINK_ON : KEV_DL_LINK_OFF; + interface_link_event(ifp, event); + } + return; } static int vlan_unconfig(struct ifnet *ifp) { - struct ifaddr *ifa; - struct sockaddr_dl *sdl; - struct vlan_mc_entry *mc; - struct ifvlan *ifv; - struct ifnet *p; - int error; + struct ifaddr *ifa; + struct sockaddr_dl *sdl; + struct vlan_mc_entry *mc; + struct ifvlan *ifv; + struct ifnet *p; + int error; + + VLAN_LOCK_ASSERT(); + + ifv = ifp->if_private; - ifv = ifp->if_softc; - p = ifv->ifv_p; + /* Disconnect from parent. */ + p = ifv->ifv_p; + ifv->ifv_p = NULL; + + if (p != NULL) { + struct sockaddr_dl sdl; /* - * Since the interface is being unconfigured, we need to + * 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. + * while we were alive from the parent's list. */ - while(ifv->vlan_mc_listhead.slh_first != NULL) { - struct sockaddr_dl sdl; - - sdl.sdl_len = ETHER_ADDR_LEN; - sdl.sdl_family = AF_LINK; - mc = ifv->vlan_mc_listhead.slh_first; - bcopy((char *)&mc->mc_addr, LLADDR(&sdl), ETHER_ADDR_LEN); - error = if_delmulti(p, (struct sockaddr *)&sdl); - error = if_delmulti(ifp, (struct sockaddr *)&sdl); - if (error) - return(error); - SLIST_REMOVE_HEAD(&ifv->vlan_mc_listhead, mc_entries); - FREE(mc, M_DEVBUF); + bzero((char *)&sdl, sizeof sdl); + sdl.sdl_len = sizeof sdl; + sdl.sdl_family = AF_LINK; + sdl.sdl_index = p->if_index; + sdl.sdl_type = IFT_ETHER; + sdl.sdl_alen = ETHER_ADDR_LEN; + + while (SLIST_FIRST(&ifv->vlan_mc_listhead) != NULL) { + mc = SLIST_FIRST(&ifv->vlan_mc_listhead); + bcopy((char *)&mc->mc_addr, LLADDR(&sdl), ETHER_ADDR_LEN); + error = if_delmulti(p, (struct sockaddr *)&sdl); + if (error) { + printf("vlan_unconfig: if_delmulti %s failed, %d\n", + ifv->ifv_name, error); + } + SLIST_REMOVE_HEAD(&ifv->vlan_mc_listhead, mc_entries); + FREE(mc, M_VLAN); + } + p->if_nvlans--; + if (p->if_nvlans == 0) { + /* detach our VLAN "protocol" from the interface */ + if (ifv->ifv_filter_valid) { + (void)vlan_detach_filter(ifv->ifv_filter_id); + } + (void)vlan_detach_protocol(p); +#if 0 + /* + * Disable Tx/Rx of VLAN-sized frames. + */ + p->if_capenable &= ~IFCAP_VLAN_MTU; + if (p->if_flags & IFF_UP) { + struct ifreq ifr; + + ifr.ifr_flags = p->if_flags; + (*p->if_ioctl)(p, SIOCSIFFLAGS, (caddr_t) &ifr); + } +#endif 0 } + } - /* Disconnect from parent. */ - ifv->ifv_p = NULL; - ifv->ifv_if.if_mtu = ETHERMTU; + /* return to the state we were in before SETVLAN */ + ifp->if_mtu = 0; + ifp->if_flags &= ~(IFF_BROADCAST | IFF_MULTICAST + | IFF_SIMPLEX | IFF_RUNNING); + ifv->ifv_ifp->if_hwassist = 0; + ifv->ifv_flags = 0; + ifv->ifv_ifp->if_output = nop_if_output; + ifv->ifv_mtufudge = 0; + ifv->ifv_filter_valid = FALSE; - /* Clear our MAC address. */ - ifa = ifnet_addrs[ifv->ifv_if.if_index - 1]; - sdl = (struct sockaddr_dl *)ifa->ifa_addr; - 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); + /* Clear our MAC address. */ + ifa = ifaddr_byindex(ifv->ifv_ifp->if_index); + sdl = (struct sockaddr_dl *)(ifa->ifa_addr); + sdl->sdl_type = IFT_L2VLAN; + sdl->sdl_alen = 0; + bzero(LLADDR(sdl), ETHER_ADDR_LEN); + bzero(IFP2AC(ifv->ifv_ifp)->ac_enaddr, ETHER_ADDR_LEN); - return 0; + /* send a link down event */ + if (p != NULL) { + interface_link_event(ifv->ifv_ifp, KEV_DL_LINK_OFF); + } + return 0; } static int -vlan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) -{ - struct ifaddr *ifa; - struct ifnet *p; - struct ifreq *ifr; - struct ifvlan *ifv; - struct vlanreq vlr; - int error = 0; - - ifr = (struct ifreq *)data; - ifa = (struct ifaddr *)data; - ifv = ifp->if_softc; - - switch (cmd) { - case SIOCSIFADDR: - ifp->if_flags |= IFF_UP; - - switch (ifa->ifa_addr->sa_family) { -#if INET - case AF_INET: - arp_ifinit(&ifv->ifv_ac, ifa); - break; -#endif - default: - break; - } - break; +vlan_set_promisc(struct ifnet *ifp) +{ + struct ifvlan *ifv = ifp->if_private; + int error = 0; - case SIOCGIFADDR: - { - struct sockaddr *sa; + if ((ifp->if_flags & IFF_PROMISC) != 0) { + if ((ifv->ifv_flags & IFVF_PROMISC) == 0) { + error = ifpromisc(ifv->ifv_p, 1); + if (error == 0) + ifv->ifv_flags |= IFVF_PROMISC; + } + } else { + if ((ifv->ifv_flags & IFVF_PROMISC) != 0) { + error = ifpromisc(ifv->ifv_p, 0); + if (error == 0) + ifv->ifv_flags &= ~IFVF_PROMISC; + } + } - sa = (struct sockaddr *) &ifr->ifr_data; - bcopy(((struct arpcom *)ifp->if_softc)->ac_enaddr, - (caddr_t) sa->sa_data, ETHER_ADDR_LEN); - } - break; - - case SIOCSIFMTU: - /* - * Set the interface MTU. - * This is bogus. The underlying interface might support - * jumbo frames. - */ - if (ifr->ifr_mtu > ETHERMTU) { - error = EINVAL; - } else { - ifp->if_mtu = ifr->ifr_mtu; - } - break; - - case SIOCSETVLAN: - error = copyin(ifr->ifr_data, &vlr, sizeof vlr); - if (error) - break; - if (vlr.vlr_parent[0] == '\0') { - vlan_unconfig(ifp); - if_down(ifp); - ifp->if_flags = 0; - break; - } - p = ifunit(vlr.vlr_parent); - if (p == 0) { - error = ENOENT; - break; + return (error); +} + +static int +vlan_ioctl(struct ifnet *ifp, u_long cmd, void * data) +{ + struct ifaddr *ifa; + struct ifnet *p; + struct ifreq *ifr; + struct ifvlan *ifv; + struct vlanreq vlr; + int error = 0; + + ifr = (struct ifreq *)data; + ifa = (struct ifaddr *)data; + ifv = (struct ifvlan *)ifp->if_private; + + switch (cmd) { + case SIOCSIFADDR: + ifp->if_flags |= IFF_UP; + break; + + case SIOCGIFMEDIA: + VLAN_LOCK(); + if (ifv->ifv_p != NULL) { + error = (*ifv->ifv_p->if_ioctl)(ifv->ifv_p, + SIOCGIFMEDIA, data); + VLAN_UNLOCK(); + /* Limit the result to the parent's current config. */ + if (error == 0) { + struct ifmediareq *ifmr; + + ifmr = (struct ifmediareq *) data; + if (ifmr->ifm_count >= 1 && ifmr->ifm_ulist) { + ifmr->ifm_count = 1; + error = copyout(&ifmr->ifm_current, + ifmr->ifm_ulist, + sizeof(int)); } - error = vlan_config(ifv, p); - if (error) - break; - ifv->ifv_tag = vlr.vlr_tag; - break; + } + } else { + struct ifmediareq *ifmr; + VLAN_UNLOCK(); + + ifmr = (struct ifmediareq *) data; + ifmr->ifm_current = 0; + ifmr->ifm_mask = 0; + ifmr->ifm_status = IFM_AVALID; + ifmr->ifm_active = 0; + ifmr->ifm_count = 1; + if (ifmr->ifm_ulist) { + error = copyout(&ifmr->ifm_current, + ifmr->ifm_ulist, + sizeof(int)); + } + error = 0; + } + break; + + case SIOCSIFMEDIA: + error = EINVAL; + break; + + case SIOCSIFMTU: + /* + * Set the interface MTU. + */ + VLAN_LOCK(); + if (ifv->ifv_p != NULL) { + if (ifr->ifr_mtu > (ifv->ifv_p->if_mtu - ifv->ifv_mtufudge) + || ifr->ifr_mtu < (ifv->ifv_mintu - ifv->ifv_mtufudge)) { + error = EINVAL; + } else { + ifp->if_mtu = ifr->ifr_mtu; + } + } else { + error = EINVAL; + } + VLAN_UNLOCK(); + break; + + case SIOCSETVLAN: + error = copyin(ifr->ifr_data, &vlr, sizeof(vlr)); + if (error) + break; + if (vlr.vlr_parent[0] == '\0') { + VLAN_LOCK(); + vlan_unconfig(ifp); +#if 0 + if (ifp->if_flags & IFF_UP) + if_down(ifp); + ifp->if_flags &= ~IFF_RUNNING; +#endif 0 + VLAN_UNLOCK(); + break; + } + p = ifunit(vlr.vlr_parent); + if (p == 0) { + error = ENOENT; + 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; + break; + } + VLAN_LOCK(); + error = vlan_config(ifv, p, vlr.vlr_tag); + if (error) { + VLAN_UNLOCK(); + break; + } + ifp->if_flags |= IFF_RUNNING; + VLAN_UNLOCK(); + + /* Update promiscuous mode, if necessary. */ + vlan_set_promisc(ifp); + + /* generate a link event */ + vlan_link_event(ifp, p); + break; - case SIOCGETVLAN: - bzero(&vlr, sizeof vlr); - if (ifv->ifv_p) { - snprintf(vlr.vlr_parent, sizeof(vlr.vlr_parent), - "%s%d", ifv->ifv_p->if_name, ifv->ifv_p->if_unit); - vlr.vlr_tag = ifv->ifv_tag; - } - error = copyout(&vlr, ifr->ifr_data, sizeof vlr); - break; + case SIOCGETVLAN: + bzero(&vlr, sizeof vlr); + VLAN_LOCK(); + if (ifv->ifv_p != NULL) { + snprintf(vlr.vlr_parent, sizeof(vlr.vlr_parent), + "%s%d", ifv->ifv_p->if_name, + ifv->ifv_p->if_unit); + vlr.vlr_tag = ifv->ifv_tag; + } + VLAN_UNLOCK(); + error = copyout(&vlr, ifr->ifr_data, sizeof vlr); + break; - case SIOCSIFFLAGS: - /* - * We don't support promiscuous mode - * right now because it would require help from the - * underlying drivers, which hasn't been implemented. - */ - if (ifr->ifr_flags & (IFF_PROMISC)) { - ifp->if_flags &= ~(IFF_PROMISC); - error = EINVAL; - } - break; - case SIOCADDMULTI: - case SIOCDELMULTI: - error = vlan_setmulti(ifp); - break; - default: - error = EINVAL; + 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); + break; + + case SIOCADDMULTI: + case SIOCDELMULTI: + error = vlan_setmulti(ifp); + break; + default: + error = EOPNOTSUPP; + } + return error; +} + +static int +nop_if_ioctl(struct ifnet * ifp, u_long cmd, void * data) +{ + return EOPNOTSUPP; +} + +static int +nop_if_bpf(struct ifnet *ifp, int mode, bpf_callback_func * func) +{ + return ENODEV; +} + +static int +nop_if_free(struct ifnet * ifp) +{ + return 0; +} + +static int +nop_if_output(struct ifnet * ifp, struct mbuf * m) +{ + if (m != NULL) { + m_freem_list(m); + } + return 0; +} + +static int +vlan_if_free(struct ifnet * ifp) +{ + struct ifvlan *ifv; + + if (ifp == NULL) { + return 0; + } + ifv = (struct ifvlan *)ifp->if_private; + if (ifv == NULL) { + return 0; + } + ifp->if_private = NULL; + dlil_if_release(ifp); + FREE(ifv, M_VLAN); + return 0; +} + +/* + * Function: vlan_if_filter_detach + * Purpose: + * Destroy all vlan interfaces that refer to the interface + */ +static int +vlan_if_filter_detach(caddr_t cookie) +{ + struct ifnet * ifp; + struct ifvlan * ifv; + struct ifnet * p = (struct ifnet *)cookie; + + VLAN_LOCK(); + while (TRUE) { + ifv = vlan_lookup_ifp(p); + if (ifv == NULL) { + break; + } + if (ifv->ifv_detaching) { + continue; + } + /* make sure we don't invoke vlan_detach_filter */ + ifv->ifv_filter_valid = FALSE; + vlan_remove(ifv); + ifp = ifv->ifv_ifp; + VLAN_UNLOCK(); + vlan_if_detach(ifp); + VLAN_LOCK(); + } + VLAN_UNLOCK(); + return (0); +} + +/* + * Function: vlan_attach_filter + * Purpose: + * We attach an interface filter to detect when the underlying interface + * goes away. We are forced to do that because dlil does not call our + * protocol's dl_event function for KEV_DL_IF_DETACHING. + */ + +static int +vlan_attach_filter(struct ifnet * ifp, u_long * filter_id) +{ + int error; + struct dlil_if_flt_str filt; + + bzero(&filt, sizeof(filt)); + filt.filter_detach = vlan_if_filter_detach; + filt.cookie = (caddr_t)ifp; + error = dlil_attach_interface_filter(ifp, &filt, filter_id, + DLIL_LAST_FILTER); + if (error) { + printf("vlan: dlil_attach_interface_filter(%s%d) failed, %d\n", + ifp->if_name, ifp->if_unit, error); + } + return (error); +} + +/* + * Function: vlan_detach_filter + * Purpose: + * Remove our interface filter. + */ +static int +vlan_detach_filter(u_long filter_id) +{ + int error; + + error = dlil_detach_filter(filter_id); + if (error) { + printf("vlan: dlil_detach_filter failed, %d\n", error); + } + return (error); +} + +/* + * Function: vlan_proto_input + * Purpose: + * This function is never called. We aren't allowed to leave the + * function pointer NULL, so this function simply free's the mbuf. + */ +static int +vlan_proto_input(m, frame_header, ifp, dl_tag, sync_ok) + struct mbuf *m; + char *frame_header; + struct ifnet *ifp; + u_long dl_tag; + int sync_ok; +{ + m_freem(m); + return (EJUSTRETURN); +} + +static struct ifnet * +find_if_name_unit(const char * if_name, int unit) +{ + struct ifnet * ifp; + + TAILQ_FOREACH(ifp, &ifnet, if_link) { + if (strcmp(if_name, ifp->if_name) == 0 && unit == ifp->if_unit) { + return (ifp); + } + } + return (ifp); +} + +static void +interface_link_event(struct ifnet * ifp, u_long event_code) +{ + struct { + struct kern_event_msg header; + u_long unit; + char if_name[IFNAMSIZ]; + } event; + + event.header.total_size = sizeof(event); + event.header.vendor_code = KEV_VENDOR_APPLE; + event.header.kev_class = KEV_NETWORK_CLASS; + event.header.kev_subclass = KEV_DL_SUBCLASS; + event.header.event_code = event_code; + event.header.event_data[0] = ifp->if_family; + event.unit = (u_long) ifp->if_unit; + strncpy(event.if_name, ifp->if_name, IFNAMSIZ); + dlil_event(ifp, &event.header); + return; +} + +static void +parent_link_event(struct ifnet * p, u_long event_code) +{ + struct ifvlan * ifv; + + LIST_FOREACH(ifv, &ifv_list, ifv_list) { + if (p == ifv->ifv_p) { + interface_link_event(ifv->ifv_ifp, event_code); + } + } + return; + +} + +/* + * Function: vlan_dl_event + * Purpose: + * Process DLIL events that interest us. Currently, that is + * just the interface UP and DOWN. Ideally, this would also + * include the KEV_DL_IF_DETACH{ING} messages, which would eliminate + * the need for an interface filter. + */ +static int +vlan_dl_event(struct kern_event_msg * event, u_long dl_tag) +{ + struct ifnet * p; + struct net_event_data * net_event; + + if (event->vendor_code != KEV_VENDOR_APPLE + || event->kev_class != KEV_NETWORK_CLASS + || event->kev_subclass != KEV_DL_SUBCLASS) { + goto done; + } + net_event = (struct net_event_data *)(event->event_data); + switch (event->event_code) { + case KEV_DL_LINK_OFF: + case KEV_DL_LINK_ON: + p = find_if_name_unit(net_event->if_name, net_event->if_unit); + if (p != NULL) { + parent_link_event(p, event->event_code); } - return error; + break; +#if 0 + case KEV_DL_IF_DETACHING: + case KEV_DL_IF_DETACHED: + /* we don't get these, unfortunately */ + break; +#endif 0 + default: + break; + } + + done: + return (0); +} + +/* + * Function: vlan_attach_protocol + * Purpose: + * Attach a DLIL protocol to the interface, using the ETHERTYPE_VLAN + * demux ether type. We're not a real protocol, we'll never receive + * any packets because they're intercepted by ether_demux before + * our input routine would be called. + * + * The reasons for attaching a protocol to the interface are: + * 1) add a protocol reference to the interface so that the underlying + * interface automatically gets marked up while we're attached + * 2) receive link status events which we can propagate to our + * VLAN interfaces. + */ +static int +vlan_attach_protocol(struct ifnet *ifp) +{ + struct dlil_demux_desc desc; + u_long dl_tag; + u_short en_native = ETHERTYPE_VLAN; + int error; + int i; + struct dlil_proto_reg_str reg; + + TAILQ_INIT(®.demux_desc_head); + desc.type = DLIL_DESC_RAW; + desc.variants.bitmask.proto_id_length = 0; + desc.variants.bitmask.proto_id = 0; + desc.variants.bitmask.proto_id_mask = 0; + desc.native_type = (char *) &en_native; + TAILQ_INSERT_TAIL(®.demux_desc_head, &desc, next); + reg.interface_family = ifp->if_family; + reg.unit_number = ifp->if_unit; + reg.input = vlan_proto_input; + reg.pre_output = 0; + reg.event = vlan_dl_event; + reg.offer = 0; + reg.ioctl = 0; + reg.default_proto = 0; + reg.protocol_family = VLAN_PROTO_FAMILY; + + error = dlil_attach_protocol(®, &dl_tag); + if (error) { + printf("vlan_proto_attach(%s%d) dlil_attach_protocol failed, %d\n", + ifp->if_name, ifp->if_unit, error); + } + return (error); +} + +/* + * Function: vlan_detach_protocol + * Purpose: + * Detach our DLIL protocol from an interface + */ +static int +vlan_detach_protocol(struct ifnet *ifp) +{ + u_long dl_tag; + int error; + + error = dlil_find_dltag(ifp->if_family, ifp->if_unit, + VLAN_PROTO_FAMILY, &dl_tag); + if (error) { + printf("vlan_proto_detach(%s%d) dlil_find_dltag failed, %d\n", + ifp->if_name, ifp->if_unit, error); + } else { + error = dlil_detach_protocol(dl_tag); + if (error) { + printf("vlan_proto_detach(%s%d) dlil_detach_protocol failed, %d\n", + ifp->if_name, ifp->if_unit, error); + } + } + return (error); +} + +/* + * DLIL interface family functions + * We use the ethernet dlil functions, since that's all we support. + * If we wanted to handle multiple LAN types (tokenring, etc.), we'd + * call the appropriate routines for that LAN type instead of hard-coding + * ethernet. + */ +extern int ether_add_if(struct ifnet *ifp); +extern int ether_del_if(struct ifnet *ifp); +extern int ether_init_if(struct ifnet *ifp); +extern int ether_add_proto(struct ddesc_head_str *desc_head, + struct if_proto *proto, u_long dl_tag); +extern int ether_del_proto(struct if_proto *proto, u_long dl_tag); +extern int ether_ifmod_ioctl(struct ifnet *ifp, u_long command, + caddr_t data); +extern int ether_del_proto(struct if_proto *proto, u_long dl_tag); +extern int ether_add_proto(struct ddesc_head_str *desc_head, struct if_proto *proto, u_long dl_tag); + +extern int ether_attach_inet(struct ifnet *ifp, u_long *dl_tag); +extern int ether_detach_inet(struct ifnet *ifp, u_long dl_tag); +extern int ether_attach_inet6(struct ifnet *ifp, u_long *dl_tag); +extern int ether_detach_inet6(struct ifnet *ifp, u_long dl_tag); + +static int +vlan_attach_inet(struct ifnet *ifp, u_long *dl_tag) +{ + return (ether_attach_inet(ifp, dl_tag)); +} + +static int +vlan_detach_inet(struct ifnet *ifp, u_long dl_tag) +{ + return (ether_detach_inet(ifp, dl_tag)); +} + +static int +vlan_attach_inet6(struct ifnet *ifp, u_long *dl_tag) +{ + return (ether_attach_inet6(ifp, dl_tag)); +} + +static int +vlan_detach_inet6(struct ifnet *ifp, u_long dl_tag) +{ + return (ether_detach_inet6(ifp, dl_tag)); +} + +static int +vlan_add_if(struct ifnet *ifp) +{ + return (ether_add_if(ifp)); } -#endif /* NVLAN > 0 */ +static int +vlan_del_if(struct ifnet *ifp) +{ + return (ether_del_if(ifp)); +} + +static int +vlan_init_if(struct ifnet *ifp) +{ + return (0); +} + +static int +vlan_shutdown() +{ + return 0; +} + +__private_extern__ int +vlan_family_init() +{ + int i, error=0; + struct dlil_ifmod_reg_str ifmod_reg; + struct dlil_protomod_reg_str vlan_protoreg; + +#if 0 + /* VLAN family is built-in, called from ether_family_init */ + thread_funnel_switch(KERNEL_FUNNEL, NETWORK_FUNNEL); +#endif 0 + + bzero(&ifmod_reg, sizeof(ifmod_reg)); + ifmod_reg.add_if = vlan_add_if; + ifmod_reg.del_if = vlan_del_if; + ifmod_reg.init_if = vlan_init_if; + ifmod_reg.add_proto = ether_add_proto; + ifmod_reg.del_proto = ether_del_proto; + ifmod_reg.ifmod_ioctl = ether_ifmod_ioctl; + ifmod_reg.shutdown = vlan_shutdown; + + if (dlil_reg_if_modules(APPLE_IF_FAM_VLAN, &ifmod_reg)) { + printf("WARNING: vlan_family_init -- " + "Can't register if family modules\n"); + error = EIO; + goto done; + } + + /* Register protocol registration functions */ + bzero(&vlan_protoreg, sizeof(vlan_protoreg)); + vlan_protoreg.attach_proto = vlan_attach_inet; + vlan_protoreg.detach_proto = vlan_detach_inet; + + if (error = dlil_reg_proto_module(PF_INET, APPLE_IF_FAM_VLAN, + &vlan_protoreg) != 0) { + kprintf("dlil_reg_proto_module failed for AF_INET6 error=%d\n", + error); + goto done; + } + vlan_protoreg.attach_proto = vlan_attach_inet6; + vlan_protoreg.detach_proto = vlan_detach_inet6; + + if (error = dlil_reg_proto_module(PF_INET6, APPLE_IF_FAM_VLAN, + &vlan_protoreg) != 0) { + kprintf("dlil_reg_proto_module failed for AF_INET6 error=%d\n", + error); + goto done; + } + vlan_clone_attach(); + + done: +#if 0 + thread_funnel_switch(NETWORK_FUNNEL, KERNEL_FUNNEL); +#endif 0 + return (error); +}