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