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