]> git.saurik.com Git - apple/xnu.git/blame - bsd/net/if_vlan.c
xnu-344.49.tar.gz
[apple/xnu.git] / bsd / net / if_vlan.c
CommitLineData
1c79356b
A
1/*
2 * Copyright (c) 2000 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
43866e37 6 * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved.
1c79356b 7 *
43866e37
A
8 * This file contains Original Code and/or Modifications of Original Code
9 * as defined in and that are subject to the Apple Public Source License
10 * Version 2.0 (the 'License'). You may not use this file except in
11 * compliance with the License. Please obtain a copy of the License at
12 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * file.
14 *
15 * The Original Code and all software distributed under the License are
16 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
1c79356b
A
17 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
18 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
43866e37
A
19 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
20 * Please see the License for the specific language governing rights and
21 * limitations under the License.
1c79356b
A
22 *
23 * @APPLE_LICENSE_HEADER_END@
24 */
25/*
26 * Copyright 1998 Massachusetts Institute of Technology
27 *
28 * Permission to use, copy, modify, and distribute this software and
29 * its documentation for any purpose and without fee is hereby
30 * granted, provided that both the above copyright notice and this
31 * permission notice appear in all copies, that both the above
32 * copyright notice and this permission notice appear in all
33 * supporting documentation, and that the name of M.I.T. not be used
34 * in advertising or publicity pertaining to distribution of the
35 * software without specific, written prior permission. M.I.T. makes
36 * no representations about the suitability of this software for any
37 * purpose. It is provided "as is" without express or implied
38 * warranty.
39 *
40 * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS
41 * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
42 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
43 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
44 * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
45 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
46 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
47 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
48 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
49 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
50 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
51 * SUCH DAMAGE.
52 *
53 */
54
55/*
56 * if_vlan.c - pseudo-device driver for IEEE 802.1Q virtual LANs.
57 * Might be extended some day to also handle IEEE 802.1p priority
58 * tagging. This is sort of sneaky in the implementation, since
59 * we need to pretend to be enough of an Ethernet implementation
60 * to make arp work. The way we do this is by telling everyone
61 * that we are an Ethernet, and then catch the packets that
62 * ether_output() left on our output queue queue when it calls
63 * if_start(), rewrite them for use by the real outgoing interface,
64 * and ask it to send them.
65 *
66 *
67 * XXX It's incorrect to assume that we must always kludge up
68 * headers on the physical device's behalf: some devices support
69 * VLAN tag insersion and extraction in firmware. For these cases,
70 * one can change the behavior of the vlan interface by setting
71 * the LINK0 flag on it (that is setting the vlan interface's LINK0
72 * flag, _not_ the parent's LINK0 flag; we try to leave the parent
73 * alone). If the interface as the LINK0 flag set, then it will
74 * not modify the ethernet header on output because the parent
75 * can do that for itself. On input, the parent can call vlan_input_tag()
76 * directly in order to supply us with an incoming mbuf and the vlan
77 * tag value that goes with it.
78 */
79
80#include "vlan.h"
81#if NVLAN > 0
82#include "opt_inet.h"
83#include "bpfilter.h"
84
85#include <sys/param.h>
86#include <sys/kernel.h>
87#include <sys/mbuf.h>
88#include <sys/socket.h>
89#include <sys/sockio.h>
90#include <sys/sysctl.h>
91#include <sys/systm.h>
92
93#if NBPFILTER > 0
94#include <net/bpf.h>
95#endif
96#include <net/ethernet.h>
97#include <net/if.h>
98#include <net/if_arp.h>
99#include <net/if_dl.h>
100#include <net/if_types.h>
101#include <net/if_vlan_var.h>
102
103#if INET
104#include <netinet/in.h>
105#include <netinet/if_ether.h>
106#endif
107
108SYSCTL_DECL(_net_link);
109SYSCTL_NODE(_net_link, IFT_8021_VLAN, vlan, CTLFLAG_RW, 0, "IEEE 802.1Q VLAN");
110SYSCTL_NODE(_net_link_vlan, PF_LINK, link, CTLFLAG_RW, 0, "for consistency");
111
112u_int vlan_proto = ETHERTYPE_VLAN;
113SYSCTL_INT(_net_link_vlan_link, VLANCTL_PROTO, proto, CTLFLAG_RW, &vlan_proto,
114 0, "Ethernet protocol used for VLAN encapsulation");
115
116static struct ifvlan ifv_softc[NVLAN];
117
118static void vlan_start(struct ifnet *ifp);
119static void vlan_ifinit(void *foo);
120static int vlan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t addr);
121static int vlan_setmulti(struct ifnet *ifp);
122static int vlan_unconfig(struct ifnet *ifp);
123static int vlan_config(struct ifvlan *ifv, struct ifnet *p);
124
125/*
126 * Program our multicast filter. What we're actually doing is
127 * programming the multicast filter of the parent. This has the
128 * side effect of causing the parent interface to receive multicast
129 * traffic that it doesn't really want, which ends up being discarded
130 * later by the upper protocol layers. Unfortunately, there's no way
131 * to avoid this: there really is only one physical interface.
132 */
133static int vlan_setmulti(struct ifnet *ifp)
134{
135 struct ifnet *ifp_p;
136 struct ifmultiaddr *ifma, *rifma = NULL;
137 struct ifvlan *sc;
138 struct vlan_mc_entry *mc = NULL;
139 struct sockaddr_dl sdl;
140 int error;
141
142 /* Find the parent. */
143 sc = ifp->if_softc;
144 ifp_p = sc->ifv_p;
145
146 sdl.sdl_len = ETHER_ADDR_LEN;
147 sdl.sdl_family = AF_LINK;
148
149 /* First, remove any existing filter entries. */
150 while(sc->vlan_mc_listhead.slh_first != NULL) {
151 mc = sc->vlan_mc_listhead.slh_first;
152 bcopy((char *)&mc->mc_addr, LLADDR(&sdl), ETHER_ADDR_LEN);
153 error = if_delmulti(ifp_p, (struct sockaddr *)&sdl);
154 if (error)
155 return(error);
156 SLIST_REMOVE_HEAD(&sc->vlan_mc_listhead, mc_entries);
157 FREE(mc, M_DEVBUF);
158 }
159
160 /* Now program new ones. */
161 for (ifma = ifp->if_multiaddrs.lh_first;
162 ifma != NULL;ifma = ifma->ifma_link.le_next) {
163 if (ifma->ifma_addr->sa_family != AF_LINK)
164 continue;
0b4e3aa0
A
165 mc = _MALLOC(sizeof(struct vlan_mc_entry), M_DEVBUF, M_WAITOK);
166 if (mc == NULL)
167 return (ENOMEM);
1c79356b
A
168 bcopy(LLADDR((struct sockaddr_dl *)ifma->ifma_addr),
169 (char *)&mc->mc_addr, ETHER_ADDR_LEN);
170 SLIST_INSERT_HEAD(&sc->vlan_mc_listhead, mc, mc_entries);
171 error = if_addmulti(ifp_p, (struct sockaddr *)&sdl, &rifma);
172 if (error)
173 return(error);
174 }
175
176 return(0);
177}
178
179static void
180vlaninit(void *dummy)
181{
182 int i;
183
184 for (i = 0; i < NVLAN; i++) {
185 struct ifnet *ifp = &ifv_softc[i].ifv_if;
186
187 ifp->if_softc = &ifv_softc[i];
188 ifp->if_name = "vlan";
189 ifp->if_family = APPLE_IF_FAM_VLAN;
190 ifp->if_unit = i;
191 /* NB: flags are not set here */
192 ifp->if_linkmib = &ifv_softc[i].ifv_mib;
193 ifp->if_linkmiblen = sizeof ifv_softc[i].ifv_mib;
194 /* NB: mtu is not set here */
195
196 ifp->if_init = vlan_ifinit;
197 ifp->if_start = vlan_start;
198 ifp->if_ioctl = vlan_ioctl;
199 ifp->if_output = ether_output;
200 ifp->if_snd.ifq_maxlen = ifqmaxlen;
201 if_attach(ifp);
202 ether_ifattach(ifp);
203#if NBPFILTER > 0
204 bpfattach(ifp, DLT_EN10MB, sizeof(struct ether_header));
205#endif
206 /* Now undo some of the damage... */
207 ifp->if_data.ifi_type = IFT_8021_VLAN;
208 ifp->if_data.ifi_hdrlen = EVL_ENCAPLEN;
209 ifp->if_resolvemulti = 0;
210 }
211}
212PSEUDO_SET(vlaninit, if_vlan);
213
214static void
215vlan_ifinit(void *foo)
216{
217 return;
218}
219
220static void
221vlan_start(struct ifnet *ifp)
222{
223 struct ifvlan *ifv;
224 struct ifnet *p;
225 struct ether_vlan_header *evl;
226 struct mbuf *m;
227
228 ifv = ifp->if_softc;
229 p = ifv->ifv_p;
230
231 ifp->if_flags |= IFF_OACTIVE;
232 for (;;) {
233 IF_DEQUEUE(&ifp->if_snd, m);
234 if (m == 0)
235 break;
236#if NBPFILTER > 0
237 if (ifp->if_bpf)
238 bpf_mtap(ifp, m);
239#endif /* NBPFILTER > 0 */
240
241 /*
242 * If the LINK0 flag is set, it means the underlying interface
243 * can do VLAN tag insertion itself and doesn't require us to
244 * create a special header for it. In this case, we just pass
245 * the packet along. However, we need some way to tell the
246 * interface where the packet came from so that it knows how
247 * to find the VLAN tag to use, so we set the rcvif in the
248 * mbuf header to our ifnet.
249 *
250 * Note: we also set the M_PROTO1 flag in the mbuf to let
251 * the parent driver know that the rcvif pointer is really
252 * valid. We need to do this because sometimes mbufs will
253 * be allocated by other parts of the system that contain
254 * garbage in the rcvif pointer. Using the M_PROTO1 flag
255 * lets the driver perform a proper sanity check and avoid
256 * following potentially bogus rcvif pointers off into
257 * never-never land.
258 */
259 if (ifp->if_flags & IFF_LINK0) {
260 m->m_pkthdr.rcvif = ifp;
261 m->m_flags |= M_PROTO1;
262 } else {
263 M_PREPEND(m, EVL_ENCAPLEN, M_DONTWAIT);
264 if (m == 0)
265 continue;
266 /* M_PREPEND takes care of m_len, m_pkthdr.len for us */
267
268 /*
269 * Transform the Ethernet header into an Ethernet header
270 * with 802.1Q encapsulation.
271 */
272 bcopy(mtod(m, char *) + EVL_ENCAPLEN, mtod(m, char *),
273 sizeof(struct ether_header));
274 evl = mtod(m, struct ether_vlan_header *);
275 evl->evl_proto = evl->evl_encap_proto;
276 evl->evl_encap_proto = htons(vlan_proto);
277 evl->evl_tag = htons(ifv->ifv_tag);
278#ifdef DEBUG
279 printf("vlan_start: %*D\n", sizeof *evl,
280 (char *)evl, ":");
281#endif
282 }
283
284 /*
285 * Send it, precisely as ether_output() would have.
286 * We are already running at splimp.
287 */
288 if (IF_QFULL(&p->if_snd)) {
289 IF_DROP(&p->if_snd);
290 /* XXX stats */
291 ifp->if_oerrors++;
292 m_freem(m);
293 continue;
294 }
295 IF_ENQUEUE(&p->if_snd, m);
296 if ((p->if_flags & IFF_OACTIVE) == 0) {
297 p->if_start(p);
298 ifp->if_opackets++;
299 }
300 }
301 ifp->if_flags &= ~IFF_OACTIVE;
302
303 return;
304}
305
306void
307vlan_input_tag(struct ether_header *eh, struct mbuf *m, u_int16_t t)
308{
309 int i;
310 struct ifvlan *ifv;
311
312 for (i = 0; i < NVLAN; i++) {
313 ifv = &ifv_softc[i];
314 if (ifv->ifv_tag == t)
315 break;
316 }
317
318 if (i >= NVLAN || (ifv->ifv_if.if_flags & IFF_UP) == 0) {
319 m_freem(m);
320 ifv->ifv_p->if_data.ifi_noproto++;
321 return;
322 }
323
324 /*
325 * Having found a valid vlan interface corresponding to
326 * the given source interface and vlan tag, run the
327 * the real packet through ethert_input().
328 */
329 m->m_pkthdr.rcvif = &ifv->ifv_if;
330
331#if NBPFILTER > 0
332 if (ifv->ifv_if.if_bpf) {
333 /*
334 * Do the usual BPF fakery. Note that we don't support
335 * promiscuous mode here, since it would require the
336 * drivers to know about VLANs and we're not ready for
337 * that yet.
338 */
339 struct mbuf m0;
340 m0.m_next = m;
341 m0.m_len = sizeof(struct ether_header);
342 m0.m_data = (char *)eh;
343 bpf_mtap(&ifv->ifv_if, &m0);
344 }
345#endif
346 ifv->ifv_if.if_ipackets++;
347 ether_input(&ifv->ifv_if, eh, m);
348 return;
349}
350
351int
352vlan_input(struct ether_header *eh, struct mbuf *m)
353{
354 int i;
355 struct ifvlan *ifv;
356
357 for (i = 0; i < NVLAN; i++) {
358 ifv = &ifv_softc[i];
359 if (m->m_pkthdr.rcvif == ifv->ifv_p
360 && (EVL_VLANOFTAG(ntohs(*mtod(m, u_int16_t *)))
361 == ifv->ifv_tag))
362 break;
363 }
364
365 if (i >= NVLAN || (ifv->ifv_if.if_flags & IFF_UP) == 0) {
366 m_freem(m);
367 return -1; /* so ether_input can take note */
368 }
369
370 /*
371 * Having found a valid vlan interface corresponding to
372 * the given source interface and vlan tag, remove the
373 * encapsulation, and run the real packet through
374 * ether_input() a second time (it had better be
375 * reentrant!).
376 */
377 m->m_pkthdr.rcvif = &ifv->ifv_if;
378 eh->ether_type = mtod(m, u_int16_t *)[1];
379 m->m_data += EVL_ENCAPLEN;
380 m->m_len -= EVL_ENCAPLEN;
381 m->m_pkthdr.len -= EVL_ENCAPLEN;
382
383#if NBPFILTER > 0
384 if (ifv->ifv_if.if_bpf) {
385 /*
386 * Do the usual BPF fakery. Note that we don't support
387 * promiscuous mode here, since it would require the
388 * drivers to know about VLANs and we're not ready for
389 * that yet.
390 */
391 struct mbuf m0;
392 m0.m_next = m;
393 m0.m_len = sizeof(struct ether_header);
394 m0.m_data = (char *)eh;
395 bpf_mtap(&ifv->ifv_if, &m0);
396 }
397#endif
398 ifv->ifv_if.if_ipackets++;
399 ether_input(&ifv->ifv_if, eh, m);
400 return 0;
401}
402
403static int
404vlan_config(struct ifvlan *ifv, struct ifnet *p)
405{
406 struct ifaddr *ifa1, *ifa2;
407 struct sockaddr_dl *sdl1, *sdl2;
408
409 if (p->if_data.ifi_type != IFT_ETHER)
410 return EPROTONOSUPPORT;
411 if (ifv->ifv_p)
412 return EBUSY;
413 ifv->ifv_p = p;
414 if (p->if_data.ifi_hdrlen == sizeof(struct ether_vlan_header))
415 ifv->ifv_if.if_mtu = p->if_mtu;
416 else
417 ifv->ifv_if.if_mtu = p->if_data.ifi_mtu - EVL_ENCAPLEN;
418
419 /*
420 * Preserve the state of the LINK0 flag for ourselves.
421 */
422 ifv->ifv_if.if_flags = (p->if_flags & ~(IFF_LINK0));
423
424 /*
425 * Set up our ``Ethernet address'' to reflect the underlying
426 * physical interface's.
427 */
428 ifa1 = ifnet_addrs[ifv->ifv_if.if_index - 1];
429 ifa2 = ifnet_addrs[p->if_index - 1];
430 sdl1 = (struct sockaddr_dl *)ifa1->ifa_addr;
431 sdl2 = (struct sockaddr_dl *)ifa2->ifa_addr;
432 sdl1->sdl_type = IFT_ETHER;
433 sdl1->sdl_alen = ETHER_ADDR_LEN;
434 bcopy(LLADDR(sdl2), LLADDR(sdl1), ETHER_ADDR_LEN);
435 bcopy(LLADDR(sdl2), ifv->ifv_ac.ac_enaddr, ETHER_ADDR_LEN);
436 return 0;
437}
438
439static int
440vlan_unconfig(struct ifnet *ifp)
441{
442 struct ifaddr *ifa;
443 struct sockaddr_dl *sdl;
444 struct vlan_mc_entry *mc;
445 struct ifvlan *ifv;
446 struct ifnet *p;
447 int error;
448
449 ifv = ifp->if_softc;
450 p = ifv->ifv_p;
451
452 /*
453 * Since the interface is being unconfigured, we need to
454 * empty the list of multicast groups that we may have joined
455 * while we were alive and remove them from the parent's list
456 * as well.
457 */
458 while(ifv->vlan_mc_listhead.slh_first != NULL) {
459 struct sockaddr_dl sdl;
460
461 sdl.sdl_len = ETHER_ADDR_LEN;
462 sdl.sdl_family = AF_LINK;
463 mc = ifv->vlan_mc_listhead.slh_first;
464 bcopy((char *)&mc->mc_addr, LLADDR(&sdl), ETHER_ADDR_LEN);
465 error = if_delmulti(p, (struct sockaddr *)&sdl);
466 error = if_delmulti(ifp, (struct sockaddr *)&sdl);
467 if (error)
468 return(error);
469 SLIST_REMOVE_HEAD(&ifv->vlan_mc_listhead, mc_entries);
470 FREE(mc, M_DEVBUF);
471 }
472
473 /* Disconnect from parent. */
474 ifv->ifv_p = NULL;
475 ifv->ifv_if.if_mtu = ETHERMTU;
476
477 /* Clear our MAC address. */
478 ifa = ifnet_addrs[ifv->ifv_if.if_index - 1];
479 sdl = (struct sockaddr_dl *)ifa->ifa_addr;
480 sdl->sdl_type = IFT_ETHER;
481 sdl->sdl_alen = ETHER_ADDR_LEN;
482 bzero(LLADDR(sdl), ETHER_ADDR_LEN);
483 bzero(ifv->ifv_ac.ac_enaddr, ETHER_ADDR_LEN);
484
485 return 0;
486}
487
488static int
489vlan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
490{
491 struct ifaddr *ifa;
492 struct ifnet *p;
493 struct ifreq *ifr;
494 struct ifvlan *ifv;
495 struct vlanreq vlr;
496 int error = 0;
497
498 ifr = (struct ifreq *)data;
499 ifa = (struct ifaddr *)data;
500 ifv = ifp->if_softc;
501
502 switch (cmd) {
503 case SIOCSIFADDR:
504 ifp->if_flags |= IFF_UP;
505
506 switch (ifa->ifa_addr->sa_family) {
507#if INET
508 case AF_INET:
509 arp_ifinit(&ifv->ifv_ac, ifa);
510 break;
511#endif
512 default:
513 break;
514 }
515 break;
516
517 case SIOCGIFADDR:
518 {
519 struct sockaddr *sa;
520
521 sa = (struct sockaddr *) &ifr->ifr_data;
522 bcopy(((struct arpcom *)ifp->if_softc)->ac_enaddr,
523 (caddr_t) sa->sa_data, ETHER_ADDR_LEN);
524 }
525 break;
526
527 case SIOCSIFMTU:
528 /*
529 * Set the interface MTU.
530 * This is bogus. The underlying interface might support
531 * jumbo frames.
532 */
533 if (ifr->ifr_mtu > ETHERMTU) {
534 error = EINVAL;
535 } else {
536 ifp->if_mtu = ifr->ifr_mtu;
537 }
538 break;
539
540 case SIOCSETVLAN:
541 error = copyin(ifr->ifr_data, &vlr, sizeof vlr);
542 if (error)
543 break;
544 if (vlr.vlr_parent[0] == '\0') {
545 vlan_unconfig(ifp);
546 if_down(ifp);
547 ifp->if_flags = 0;
548 break;
549 }
550 p = ifunit(vlr.vlr_parent);
551 if (p == 0) {
552 error = ENOENT;
553 break;
554 }
555 error = vlan_config(ifv, p);
556 if (error)
557 break;
558 ifv->ifv_tag = vlr.vlr_tag;
559 break;
560
561 case SIOCGETVLAN:
562 bzero(&vlr, sizeof vlr);
563 if (ifv->ifv_p) {
564 snprintf(vlr.vlr_parent, sizeof(vlr.vlr_parent),
565 "%s%d", ifv->ifv_p->if_name, ifv->ifv_p->if_unit);
566 vlr.vlr_tag = ifv->ifv_tag;
567 }
568 error = copyout(&vlr, ifr->ifr_data, sizeof vlr);
569 break;
570
571 case SIOCSIFFLAGS:
572 /*
573 * We don't support promiscuous mode
574 * right now because it would require help from the
575 * underlying drivers, which hasn't been implemented.
576 */
577 if (ifr->ifr_flags & (IFF_PROMISC)) {
578 ifp->if_flags &= ~(IFF_PROMISC);
579 error = EINVAL;
580 }
581 break;
582 case SIOCADDMULTI:
583 case SIOCDELMULTI:
584 error = vlan_setmulti(ifp);
585 break;
586 default:
587 error = EINVAL;
588 }
589 return error;
590}
591
592#endif /* NVLAN > 0 */