xref: /freebsd/sys/net/if_vlan.c (revision 380a989b3223d455375b4fae70fd0b9bdd43bafb)
1 /*
2  * Copyright 1998 Massachusetts Institute of Technology
3  *
4  * Permission to use, copy, modify, and distribute this software and
5  * its documentation for any purpose and without fee is hereby
6  * granted, provided that both the above copyright notice and this
7  * permission notice appear in all copies, that both the above
8  * copyright notice and this permission notice appear in all
9  * supporting documentation, and that the name of M.I.T. not be used
10  * in advertising or publicity pertaining to distribution of the
11  * software without specific, written prior permission.  M.I.T. makes
12  * no representations about the suitability of this software for any
13  * purpose.  It is provided "as is" without express or implied
14  * warranty.
15  *
16  * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''.  M.I.T. DISCLAIMS
17  * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
18  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
20  * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
26  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  *
29  *	$Id: if_vlan.c,v 1.3 1998/08/23 03:07:10 wollman Exp $
30  */
31 
32 /*
33  * if_vlan.c - pseudo-device driver for IEEE 802.1Q virtual LANs.
34  * Might be extended some day to also handle IEEE 802.1p priority
35  * tagging.  This is sort of sneaky in the implementation, since
36  * we need to pretend to be enough of an Ethernet implementation
37  * to make arp work.  The way we do this is by telling everyone
38  * that we are an Ethernet, and then catch the packets that
39  * ether_output() left on our output queue queue when it calls
40  * if_start(), rewrite them for use by the real outgoing interface,
41  * and ask it to send them.
42  */
43 
44 #include "vlan.h"
45 #if NVLAN > 0
46 #include "opt_inet.h"
47 #include "bpfilter.h"
48 
49 #include <sys/param.h>
50 #include <sys/kernel.h>
51 #include <sys/mbuf.h>
52 #include <sys/socket.h>
53 #include <sys/sockio.h>
54 #include <sys/sysctl.h>
55 #include <sys/systm.h>
56 
57 #if NBPFILTER > 0
58 #include <net/bpf.h>
59 #endif
60 #include <net/ethernet.h>
61 #include <net/if.h>
62 #include <net/if_arp.h>
63 #include <net/if_dl.h>
64 #include <net/if_types.h>
65 #include <net/if_vlan_var.h>
66 
67 #ifdef INET
68 #include <netinet/in.h>
69 #include <netinet/if_ether.h>
70 #endif
71 
72 SYSCTL_NODE(_net_link, IFT_8021_VLAN, vlan, CTLFLAG_RW, 0, "IEEE 802.1Q VLAN");
73 SYSCTL_NODE(_net_link_vlan, PF_LINK, link, CTLFLAG_RW, 0, "for consistency");
74 
75 u_int	vlan_proto = ETHERTYPE_VLAN;
76 SYSCTL_INT(_net_link_vlan_link, VLANCTL_PROTO, proto, CTLFLAG_RW, &vlan_proto,
77 	   0, "Ethernet protocol used for VLAN encapsulation");
78 
79 static	struct ifvlan ifv_softc[NVLAN];
80 
81 static	void vlan_start(struct ifnet *ifp);
82 static	void vlan_ifinit(void *foo);
83 static	int vlan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t addr);
84 
85 static void
86 vlaninit(void *dummy)
87 {
88 	int i;
89 
90 	for (i = 0; i < NVLAN; i++) {
91 		struct ifnet *ifp = &ifv_softc[i].ifv_if;
92 
93 		ifp->if_softc = &ifv_softc[i];
94 		ifp->if_name = "vlan";
95 		ifp->if_unit = i;
96 		/* NB: flags are not set here */
97 		ifp->if_linkmib = &ifv_softc[i].ifv_mib;
98 		ifp->if_linkmiblen = sizeof ifv_softc[i].ifv_mib;
99 		/* NB: mtu is not set here */
100 
101 		ifp->if_init = vlan_ifinit;
102 		ifp->if_start = vlan_start;
103 		ifp->if_ioctl = vlan_ioctl;
104 		ifp->if_output = ether_output;
105 		if_attach(ifp);
106 		ether_ifattach(ifp);
107 #if NBPFILTER > 0
108 		bpfattach(ifp, DLT_EN10MB, sizeof(struct ether_header));
109 #endif
110 		/* Now undo some of the damage... */
111 		ifp->if_data.ifi_type = IFT_8021_VLAN;
112 		ifp->if_data.ifi_hdrlen = EVL_ENCAPLEN;
113 		ifp->if_resolvemulti = 0;
114 	}
115 }
116 PSEUDO_SET(vlaninit, if_vlan);
117 
118 static void
119 vlan_ifinit(void *foo)
120 {
121 	;
122 }
123 
124 static void
125 vlan_start(struct ifnet *ifp)
126 {
127 	struct ifvlan *ifv;
128 	struct ifnet *p;
129 	struct ether_vlan_header *evl;
130 	struct mbuf *m;
131 
132 	ifv = ifp->if_softc;
133 	p = ifv->ifv_p;
134 
135 	ifp->if_flags |= IFF_OACTIVE;
136 	for (;;) {
137 		IF_DEQUEUE(&ifp->if_snd, m);
138 		if (m == 0)
139 			break;
140 #if NBPFILTER > 0
141 		if (ifp->if_bpf)
142 			bpf_mtap(ifp, m);
143 #endif /* NBPFILTER > 0 */
144 
145 		M_PREPEND(m, EVL_ENCAPLEN, M_DONTWAIT);
146 		if (m == 0)
147 			continue;
148 		/* M_PREPEND takes care of m_len, m_pkthdr.len for us */
149 
150 		/*
151 		 * Transform the Ethernet header into an Ethernet header
152 		 * with 802.1Q encapsulation.
153 		 */
154 		bcopy(mtod(m, char *) + EVL_ENCAPLEN, mtod(m, char *),
155 		      sizeof(struct ether_header));
156 		evl = mtod(m, struct ether_vlan_header *);
157 		evl->evl_proto = evl->evl_encap_proto;
158 		evl->evl_encap_proto = htons(vlan_proto);
159 		evl->evl_tag = htons(ifv->ifv_tag);
160 		printf("vlan_start: %*D\n", sizeof *evl, (char *)evl, ":");
161 
162 		/*
163 		 * Send it, precisely as ether_output() would have.
164 		 * We are already running at splimp.
165 		 */
166 		if (IF_QFULL(&p->if_snd)) {
167 			IF_DROP(&p->if_snd);
168 				/* XXX stats */
169 		}
170 		IF_ENQUEUE(&p->if_snd, m);
171 		if ((p->if_flags & IFF_OACTIVE) == 0)
172 			p->if_start(p);
173 	}
174 	ifp->if_flags &= ~IFF_OACTIVE;
175 }
176 
177 int
178 vlan_input(struct ether_header *eh, struct mbuf *m)
179 {
180 	int i;
181 	struct ifvlan *ifv;
182 
183 	for (i = 0; i < NVLAN; i++) {
184 		ifv = &ifv_softc[i];
185 		if (m->m_pkthdr.rcvif == ifv->ifv_p
186 		    && (EVL_VLANOFTAG(ntohs(*mtod(m, u_int16_t *)))
187 			== ifv->ifv_tag))
188 			break;
189 	}
190 
191 	if (i >= NVLAN || (ifv->ifv_if.if_flags & IFF_UP) == 0) {
192 		m_freem(m);
193 		return -1;	/* so ether_input can take note */
194 	}
195 
196 	/*
197 	 * Having found a valid vlan interface corresponding to
198 	 * the given source interface and vlan tag, remove the
199 	 * encapsulation, and run the real packet through
200 	 * ether_input() a second time (it had better be
201 	 * reentrant!).
202 	 */
203 	m->m_pkthdr.rcvif = &ifv->ifv_if;
204 	eh->ether_type = mtod(m, u_int16_t *)[1];
205 	m->m_data += EVL_ENCAPLEN;
206 	m->m_len -= EVL_ENCAPLEN;
207 	m->m_pkthdr.len -= EVL_ENCAPLEN;
208 
209 #if NBPFILTER > 0
210 	if (ifv->ifv_if.if_bpf) {
211 		/*
212 		 * Do the usual BPF fakery.  Note that we don't support
213 		 * promiscuous mode here, since it would require the
214 		 * drivers to know about VLANs and we're not ready for
215 		 * that yet.
216 		 */
217 		struct mbuf m0;
218 		m0.m_next = m;
219 		m0.m_len = sizeof(struct ether_header);
220 		m0.m_data = (char *)eh;
221 		bpf_mtap(&ifv->ifv_if, &m0);
222 	}
223 #endif
224 	ether_input(&ifv->ifv_if, eh, m);
225 	return 0;
226 }
227 
228 static int
229 vlan_config(struct ifvlan *ifv, struct ifnet *p)
230 {
231 	struct ifaddr *ifa1, *ifa2;
232 	struct sockaddr_dl *sdl1, *sdl2;
233 
234 	if (p->if_data.ifi_type != IFT_ETHER)
235 		return EPROTONOSUPPORT;
236 	if (ifv->ifv_p)
237 		return EBUSY;
238 	ifv->ifv_p = p;
239 	if (p->if_data.ifi_hdrlen == sizeof(struct ether_vlan_header))
240 		ifv->ifv_if.if_mtu = p->if_mtu;
241 	else
242 		ifv->ifv_if.if_mtu = p->if_data.ifi_mtu - EVL_ENCAPLEN;
243 
244 	/*
245 	 * NB: we don't support multicast at this point.
246 	 */
247 	ifv->ifv_if.if_flags = (p->if_flags & ~IFF_MULTICAST); /* XXX */
248 
249 	/*
250 	 * Set up our ``Ethernet address'' to reflect the underlying
251 	 * physical interface's.
252 	 */
253 	ifa1 = ifnet_addrs[ifv->ifv_if.if_index - 1];
254 	ifa2 = ifnet_addrs[p->if_index - 1];
255 	sdl1 = (struct sockaddr_dl *)ifa1->ifa_addr;
256 	sdl2 = (struct sockaddr_dl *)ifa2->ifa_addr;
257 	sdl1->sdl_type = IFT_ETHER;
258 	sdl1->sdl_alen = ETHER_ADDR_LEN;
259 	bcopy(LLADDR(sdl2), LLADDR(sdl1), ETHER_ADDR_LEN);
260 	bcopy(LLADDR(sdl2), ifv->ifv_ac.ac_enaddr, ETHER_ADDR_LEN);
261 	return 0;
262 }
263 
264 static int
265 vlan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
266 {
267 	struct ifaddr *ifa;
268 	struct ifnet *p;
269 	struct ifreq *ifr;
270 	struct ifvlan *ifv;
271 	struct vlanreq vlr;
272 	int error = 0;
273 
274 	ifr = (struct ifreq *)data;
275 	ifa = (struct ifaddr *)data;
276 	ifv = ifp->if_softc;
277 
278 	switch (cmd) {
279 	case SIOCSIFADDR:
280 		ifp->if_flags |= IFF_UP;
281 
282 		switch (ifa->ifa_addr->sa_family) {
283 #ifdef INET
284 		case AF_INET:
285 			arp_ifinit(&ifv->ifv_ac, ifa);
286 			break;
287 #endif
288 		default:
289 			break;
290 		}
291 		break;
292 
293 	case SIOCGIFADDR:
294 		{
295 			struct sockaddr *sa;
296 
297 			sa = (struct sockaddr *) &ifr->ifr_data;
298 			bcopy(((struct arpcom *)ifp->if_softc)->ac_enaddr,
299 			      (caddr_t) sa->sa_data, ETHER_ADDR_LEN);
300 		}
301 		break;
302 
303 	case SIOCSIFMTU:
304 		/*
305 		 * Set the interface MTU.
306 		 */
307 		if (ifr->ifr_mtu > ETHERMTU) {
308 			error = EINVAL;
309 		} else {
310 			ifp->if_mtu = ifr->ifr_mtu;
311 		}
312 		break;
313 
314 	case SIOCSETVLAN:
315 		error = copyin(ifr->ifr_data, &vlr, sizeof vlr);
316 		if (error)
317 			break;
318 		if (vlr.vlr_parent[0] == '\0') {
319 			ifv->ifv_p = 0;
320 			if_down(ifp);
321 			break;
322 		}
323 		p = ifunit(vlr.vlr_parent);
324 		if (p == 0) {
325 			error = ENOENT;
326 			break;
327 		}
328 		error = vlan_config(ifv, p);
329 		if (error)
330 			break;
331 		ifv->ifv_tag = vlr.vlr_tag;
332 		break;
333 
334 	case SIOCGETVLAN:
335 		bzero(&vlr, sizeof vlr);
336 		if (ifv->ifv_p) {
337 			snprintf(vlr.vlr_parent, sizeof(vlr.vlr_parent),
338 			    "%s%d", ifv->ifv_p->if_name, ifv->ifv_p->if_unit);
339 			vlr.vlr_tag = ifv->ifv_tag;
340 		}
341 		error = copyout(&vlr, ifr->ifr_data, sizeof vlr);
342 		break;
343 
344 	case SIOCSIFFLAGS:
345 		/*
346 		 * We don't support all-multicast or promiscuous modes
347 		 * right now because it would require help from the
348 		 * underlying drivers, which hasn't been implemented.
349 		 */
350 		if (ifr->ifr_flags & (IFF_PROMISC|IFF_ALLMULTI)) {
351 			ifp->if_flags &= ~(IFF_PROMISC|IFF_ALLMULTI);
352 			error = EINVAL;
353 		}
354 		break;
355 
356 		/* NB: this will reject multicast state changes */
357 	default:
358 		error = EINVAL;
359 	}
360 	return error;
361 }
362 
363 #endif /* NVLAN > 0 */
364