xref: /illumos-gate/usr/src/uts/common/io/mac/plugins/mac_wifi.c (revision 7ab4e62e3b5c454f248a38bec0d489e8f5543324)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*
27  * WiFi MAC Type plugin for the Nemo mac module
28  *
29  * This is a bit of mutant since we pretend to be mostly DL_ETHER.
30  */
31 
32 #include <sys/types.h>
33 #include <sys/modctl.h>
34 #include <sys/dlpi.h>
35 #include <sys/dld_impl.h>
36 #include <sys/mac_wifi.h>
37 #include <sys/ethernet.h>
38 #include <sys/byteorder.h>
39 #include <sys/strsun.h>
40 #include <inet/common.h>
41 
42 uint8_t wifi_bcastaddr[]	= { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
43 static uint8_t wifi_ietfmagic[]	= { 0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00 };
44 static uint8_t wifi_ieeemagic[]	= { 0xaa, 0xaa, 0x03, 0x00, 0x00, 0xf8 };
45 
46 static mac_stat_info_t wifi_stats[] = {
47 	/* statistics described in ieee802.11(5) */
48 { WIFI_STAT_TX_FRAGS, 		"tx_frags",		KSTAT_DATA_UINT32, 0 },
49 { WIFI_STAT_MCAST_TX,		"mcast_tx",		KSTAT_DATA_UINT32, 0 },
50 { WIFI_STAT_TX_FAILED,		"tx_failed",		KSTAT_DATA_UINT32, 0 },
51 { WIFI_STAT_TX_RETRANS,		"tx_retrans",		KSTAT_DATA_UINT32, 0 },
52 { WIFI_STAT_TX_RERETRANS,	"tx_reretrans",		KSTAT_DATA_UINT32, 0 },
53 { WIFI_STAT_RTS_SUCCESS,	"rts_success",		KSTAT_DATA_UINT32, 0 },
54 { WIFI_STAT_RTS_FAILURE,	"rts_failure",		KSTAT_DATA_UINT32, 0 },
55 { WIFI_STAT_ACK_FAILURE,	"ack_failure",		KSTAT_DATA_UINT32, 0 },
56 { WIFI_STAT_RX_FRAGS, 		"rx_frags",		KSTAT_DATA_UINT32, 0 },
57 { WIFI_STAT_MCAST_RX,		"mcast_rx", 		KSTAT_DATA_UINT32, 0 },
58 { WIFI_STAT_FCS_ERRORS,		"fcs_errors", 		KSTAT_DATA_UINT32, 0 },
59 { WIFI_STAT_WEP_ERRORS,		"wep_errors",		KSTAT_DATA_UINT32, 0 },
60 { WIFI_STAT_RX_DUPS,		"rx_dups",		KSTAT_DATA_UINT32, 0 }
61 };
62 
63 static struct modlmisc mac_wifi_modlmisc = {
64 	&mod_miscops,
65 	"WiFi MAC plugin 1.4"
66 };
67 
68 static struct modlinkage mac_wifi_modlinkage = {
69 	MODREV_1,
70 	&mac_wifi_modlmisc,
71 	NULL
72 };
73 
74 static mactype_ops_t mac_wifi_type_ops;
75 
76 int
77 _init(void)
78 {
79 	mactype_register_t *mtrp = mactype_alloc(MACTYPE_VERSION);
80 	int err;
81 
82 	/*
83 	 * If `mtrp' is NULL, then this plugin is not compatible with
84 	 * the system's MAC Type plugin framework.
85 	 */
86 	if (mtrp == NULL)
87 		return (ENOTSUP);
88 
89 	mtrp->mtr_ops		= &mac_wifi_type_ops;
90 	mtrp->mtr_ident		= MAC_PLUGIN_IDENT_WIFI;
91 	mtrp->mtr_mactype	= DL_ETHER;
92 	mtrp->mtr_nativetype	= DL_WIFI;
93 	mtrp->mtr_stats		= wifi_stats;
94 	mtrp->mtr_statcount	= A_CNT(wifi_stats);
95 	mtrp->mtr_addrlen	= IEEE80211_ADDR_LEN;
96 	mtrp->mtr_brdcst_addr	= wifi_bcastaddr;
97 
98 	if ((err = mactype_register(mtrp)) == 0) {
99 		if ((err = mod_install(&mac_wifi_modlinkage)) != 0)
100 			(void) mactype_unregister(MAC_PLUGIN_IDENT_WIFI);
101 	}
102 	mactype_free(mtrp);
103 	return (err);
104 }
105 
106 int
107 _fini(void)
108 {
109 	int	err;
110 
111 	if ((err = mactype_unregister(MAC_PLUGIN_IDENT_WIFI)) != 0)
112 		return (err);
113 	return (mod_remove(&mac_wifi_modlinkage));
114 }
115 
116 int
117 _info(struct modinfo *modinfop)
118 {
119 	return (mod_info(&mac_wifi_modlinkage, modinfop));
120 }
121 
122 /*
123  * MAC Type plugin operations
124  */
125 
126 static boolean_t
127 mac_wifi_pdata_verify(void *pdata, size_t pdata_size)
128 {
129 	wifi_data_t *wdp = pdata;
130 
131 	return (pdata_size == sizeof (wifi_data_t) && wdp->wd_opts == 0);
132 }
133 
134 /* ARGSUSED */
135 static int
136 mac_wifi_unicst_verify(const void *addr, void *pdata)
137 {
138 	/* If it's not a group address, then it's a valid unicast address. */
139 	return (IEEE80211_IS_MULTICAST(addr) ? EINVAL : 0);
140 }
141 
142 /* ARGSUSED */
143 static int
144 mac_wifi_multicst_verify(const void *addr, void *pdata)
145 {
146 	/* The address must be a group address. */
147 	if (!IEEE80211_IS_MULTICAST(addr))
148 		return (EINVAL);
149 	/* The address must not be the media broadcast address. */
150 	if (bcmp(addr, wifi_bcastaddr, sizeof (wifi_bcastaddr)) == 0)
151 		return (EINVAL);
152 	return (0);
153 }
154 
155 /*
156  * Verify that `sap' is valid, and return the actual SAP to bind to in
157  * `*bind_sap'.  The WiFI SAP space is identical to Ethernet.
158  */
159 /* ARGSUSED */
160 static boolean_t
161 mac_wifi_sap_verify(uint32_t sap, uint32_t *bind_sap, void *pdata)
162 {
163 	if (sap >= ETHERTYPE_802_MIN && sap <= ETHERTYPE_MAX) {
164 		if (bind_sap != NULL)
165 			*bind_sap = sap;
166 		return (B_TRUE);
167 	}
168 
169 	if (sap <= ETHERMTU) {
170 		if (bind_sap != NULL)
171 			*bind_sap = DLS_SAP_LLC;
172 		return (B_TRUE);
173 	}
174 	return (B_FALSE);
175 }
176 
177 /*
178  * Create a template WiFi datalink header for `sap' packets between `saddr'
179  * and `daddr'.  Any enabled modes and features relevant to building the
180  * header are passed via `pdata'.  Return NULL on failure.
181  */
182 /* ARGSUSED */
183 static mblk_t *
184 mac_wifi_header(const void *saddr, const void *daddr, uint32_t sap,
185     void *pdata, mblk_t *payload, size_t extra_len)
186 {
187 	struct ieee80211_frame	*wh;
188 	struct ieee80211_llc	*llc;
189 	mblk_t			*mp;
190 	wifi_data_t		*wdp = pdata;
191 
192 	if (!mac_wifi_sap_verify(sap, NULL, NULL))
193 		return (NULL);
194 
195 	if ((mp = allocb(WIFI_HDRSIZE + extra_len, BPRI_HI)) == NULL)
196 		return (NULL);
197 	bzero(mp->b_rptr, WIFI_HDRSIZE + extra_len);
198 
199 	/*
200 	 * Fill in the fixed parts of the ieee80211_frame.
201 	 */
202 	wh = (struct ieee80211_frame *)mp->b_rptr;
203 	mp->b_wptr += sizeof (struct ieee80211_frame) + wdp->wd_qospad;
204 	wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_DATA;
205 
206 	switch (wdp->wd_opmode) {
207 	case IEEE80211_M_STA:
208 		wh->i_fc[1] = IEEE80211_FC1_DIR_TODS;
209 		IEEE80211_ADDR_COPY(wh->i_addr1, wdp->wd_bssid);
210 		IEEE80211_ADDR_COPY(wh->i_addr2, saddr);
211 		IEEE80211_ADDR_COPY(wh->i_addr3, daddr);
212 		break;
213 
214 	case IEEE80211_M_IBSS:
215 	case IEEE80211_M_AHDEMO:
216 		wh->i_fc[1] = IEEE80211_FC1_DIR_NODS;
217 		IEEE80211_ADDR_COPY(wh->i_addr1, daddr);
218 		IEEE80211_ADDR_COPY(wh->i_addr2, saddr);
219 		IEEE80211_ADDR_COPY(wh->i_addr3, wdp->wd_bssid);
220 		break;
221 
222 	case IEEE80211_M_HOSTAP:
223 		wh->i_fc[1] = IEEE80211_FC1_DIR_FROMDS;
224 		IEEE80211_ADDR_COPY(wh->i_addr1, daddr);
225 		IEEE80211_ADDR_COPY(wh->i_addr2, wdp->wd_bssid);
226 		IEEE80211_ADDR_COPY(wh->i_addr3, saddr);
227 		break;
228 	}
229 
230 	if (wdp->wd_qospad) {
231 		struct ieee80211_qosframe *qwh =
232 		    (struct ieee80211_qosframe *)wh;
233 		qwh->i_qos[1] = 0;
234 		qwh->i_fc[0] |= IEEE80211_FC0_SUBTYPE_QOS;
235 	}
236 
237 	switch (wdp->wd_secalloc) {
238 	case WIFI_SEC_WEP:
239 		/*
240 		 * Fill in the fixed parts of the WEP-portion of the frame.
241 		 */
242 		wh->i_fc[1] |= IEEE80211_FC1_WEP;
243 		/*
244 		 * The actual contents of the WEP-portion of the packet
245 		 * are computed when the packet is sent -- for now, we
246 		 * just need to account for the size.
247 		 */
248 		mp->b_wptr += IEEE80211_WEP_IVLEN + IEEE80211_WEP_KIDLEN;
249 		break;
250 
251 	case WIFI_SEC_WPA:
252 		wh->i_fc[1] |= IEEE80211_FC1_WEP;
253 		mp->b_wptr += IEEE80211_WEP_IVLEN +
254 		    IEEE80211_WEP_KIDLEN + IEEE80211_WEP_EXTIVLEN;
255 		break;
256 
257 	default:
258 		break;
259 	}
260 
261 	/*
262 	 * Fill in the fixed parts of the ieee80211_llc header.
263 	 */
264 	llc = (struct ieee80211_llc *)mp->b_wptr;
265 	mp->b_wptr += sizeof (struct ieee80211_llc);
266 	bcopy(wifi_ietfmagic, llc, sizeof (wifi_ietfmagic));
267 	llc->illc_ether_type = htons(sap);
268 
269 	return (mp);
270 }
271 
272 /*
273  * Use the provided `mp' (which is expected to point to a WiFi header), and
274  * fill in the provided `mhp'.  Return an errno on failure.
275  */
276 /* ARGSUSED */
277 static int
278 mac_wifi_header_info(mblk_t *mp, void *pdata, mac_header_info_t *mhp)
279 {
280 	struct ieee80211_frame	*wh;
281 	struct ieee80211_llc	*llc;
282 	uchar_t			*llcp;
283 	wifi_data_t		*wdp = pdata;
284 
285 	if (MBLKL(mp) < sizeof (struct ieee80211_frame))
286 		return (EINVAL);
287 
288 	wh = (struct ieee80211_frame *)mp->b_rptr;
289 	llcp = mp->b_rptr + sizeof (struct ieee80211_frame);
290 
291 	/*
292 	 * Generally, QoS data field takes 2 bytes, but some special hardware,
293 	 * such as Atheros, will need the 802.11 header padded to a 32-bit
294 	 * boundary for 4-address and QoS frames, at this time, it's 4 bytes.
295 	 */
296 	if (wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_QOS)
297 		llcp += wdp->wd_qospad;
298 
299 	/*
300 	 * When we receive frames from other hosts, the hardware will have
301 	 * already performed WEP decryption, and thus there will not be a WEP
302 	 * portion.  However, when we receive a loopback copy of our own
303 	 * packets, it will still have a WEP portion.  Skip past it to get to
304 	 * the LLC header.
305 	 */
306 	if (wh->i_fc[1] & IEEE80211_FC1_WEP) {
307 		llcp += IEEE80211_WEP_IVLEN + IEEE80211_WEP_KIDLEN;
308 		if (wdp->wd_secalloc == WIFI_SEC_WPA)
309 			llcp += IEEE80211_WEP_EXTIVLEN;
310 	}
311 
312 	if ((uintptr_t)mp->b_wptr - (uintptr_t)llcp <
313 	    sizeof (struct ieee80211_llc))
314 		return (EINVAL);
315 
316 	llc = (struct ieee80211_llc *)llcp;
317 	mhp->mhi_origsap = ntohs(llc->illc_ether_type);
318 	mhp->mhi_bindsap = mhp->mhi_origsap;
319 	mhp->mhi_pktsize = 0;
320 	mhp->mhi_hdrsize = (uintptr_t)llcp + sizeof (*llc) -
321 	    (uintptr_t)mp->b_rptr;
322 
323 	/*
324 	 * Verify the LLC header is one of the known formats.  As per MSFT's
325 	 * convention, if the header is using IEEE 802.1H encapsulation, then
326 	 * treat the LLC header as data.  As per DL_ETHER custom when treating
327 	 * the LLC header as data, set the mhi_bindsap to be DLS_SAP_LLC, and
328 	 * assume mhi_origsap contains the data length.
329 	 */
330 	if (bcmp(llc, wifi_ieeemagic, sizeof (wifi_ieeemagic)) == 0) {
331 		mhp->mhi_bindsap = DLS_SAP_LLC;
332 		mhp->mhi_hdrsize -= sizeof (*llc);
333 		mhp->mhi_pktsize = mhp->mhi_hdrsize + mhp->mhi_origsap;
334 	} else if (bcmp(llc, wifi_ietfmagic, sizeof (wifi_ietfmagic)) != 0) {
335 		return (EINVAL);
336 	}
337 
338 	switch (wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) {
339 	case IEEE80211_FC1_DIR_NODS:
340 		mhp->mhi_daddr = wh->i_addr1;
341 		mhp->mhi_saddr = wh->i_addr2;
342 		break;
343 
344 	case IEEE80211_FC1_DIR_TODS:
345 		mhp->mhi_daddr = wh->i_addr3;
346 		mhp->mhi_saddr = wh->i_addr2;
347 		break;
348 
349 	case IEEE80211_FC1_DIR_FROMDS:
350 		mhp->mhi_daddr = wh->i_addr1;
351 		mhp->mhi_saddr = wh->i_addr3;
352 		break;
353 
354 	case IEEE80211_FC1_DIR_DSTODS:
355 		/* We don't support AP-to-AP mode yet */
356 		return (ENOTSUP);
357 	}
358 
359 	if (mac_wifi_unicst_verify(mhp->mhi_daddr, NULL) == 0)
360 		mhp->mhi_dsttype = MAC_ADDRTYPE_UNICAST;
361 	else if (mac_wifi_multicst_verify(mhp->mhi_daddr, NULL) == 0)
362 		mhp->mhi_dsttype = MAC_ADDRTYPE_MULTICAST;
363 	else
364 		mhp->mhi_dsttype = MAC_ADDRTYPE_BROADCAST;
365 
366 	return (0);
367 }
368 
369 /*
370  * Take the provided `mp' (which is expected to have an Ethernet header), and
371  * return a pointer to an mblk_t with a WiFi header.  Note that the returned
372  * header will not be complete until the driver finishes filling it in prior
373  * to transmit.  If the conversion cannot be performed, return NULL.
374  */
375 static mblk_t *
376 mac_wifi_header_cook(mblk_t *mp, void *pdata)
377 {
378 	struct ether_header	*ehp;
379 	mblk_t			*llmp;
380 
381 	if (MBLKL(mp) < sizeof (struct ether_header))
382 		return (NULL);
383 
384 	ehp = (void *)mp->b_rptr;
385 	llmp = mac_wifi_header(&ehp->ether_shost, &ehp->ether_dhost,
386 	    ntohs(ehp->ether_type), pdata, NULL, 0);
387 	if (llmp == NULL)
388 		return (NULL);
389 
390 	/*
391 	 * The plugin framework guarantees that we have the only reference
392 	 * to the mblk_t, so we can safely modify it.
393 	 */
394 	ASSERT(DB_REF(mp) == 1);
395 	mp->b_rptr += sizeof (struct ether_header);
396 	llmp->b_cont = mp;
397 	return (llmp);
398 }
399 
400 /*
401  * Take the provided `mp' (which is expected to have a WiFi header), and
402  * return a pointer to an mblk_t with an Ethernet header.  If the conversion
403  * cannot be performed, return NULL.
404  */
405 static mblk_t *
406 mac_wifi_header_uncook(mblk_t *mp, void *pdata)
407 {
408 	mac_header_info_t	mhi;
409 	struct ether_header	eh;
410 
411 	if (mac_wifi_header_info(mp, pdata, &mhi) != 0) {
412 		/*
413 		 * The plugin framework guarantees the header is properly
414 		 * formed, so this should never happen.
415 		 */
416 		return (NULL);
417 	}
418 
419 	/*
420 	 * The plugin framework guarantees that we have the only reference to
421 	 * the mblk_t and the underlying dblk_t, so we can safely modify it.
422 	 */
423 	ASSERT(DB_REF(mp) == 1);
424 
425 	IEEE80211_ADDR_COPY(&eh.ether_dhost, mhi.mhi_daddr);
426 	IEEE80211_ADDR_COPY(&eh.ether_shost, mhi.mhi_saddr);
427 	eh.ether_type = htons(mhi.mhi_origsap);
428 
429 	ASSERT(mhi.mhi_hdrsize >= sizeof (struct ether_header));
430 	mp->b_rptr += mhi.mhi_hdrsize - sizeof (struct ether_header);
431 	bcopy(&eh, mp->b_rptr, sizeof (struct ether_header));
432 	return (mp);
433 }
434 
435 static mactype_ops_t mac_wifi_type_ops = {
436 	MTOPS_PDATA_VERIFY | MTOPS_HEADER_COOK | MTOPS_HEADER_UNCOOK,
437 	mac_wifi_unicst_verify,
438 	mac_wifi_multicst_verify,
439 	mac_wifi_sap_verify,
440 	mac_wifi_header,
441 	mac_wifi_header_info,
442 	mac_wifi_pdata_verify,
443 	mac_wifi_header_cook,
444 	mac_wifi_header_uncook
445 };
446