2 * ndis_events - Receive NdisMIndicateStatus() events using WMI
3 * Copyright (c) 2004-2006, Jouni Malinen <j@w1.fi>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2 as
7 * published by the Free Software Foundation.
9 * Alternatively, this software may be distributed under the terms of BSD
12 * See README and COPYING for more details.
15 #define _WIN32_WINNT 0x0400
21 #endif /* COBJMACROS */
27 static int wmi_refcnt = 0;
28 static int wmi_first = 1;
30 struct ndis_events_data {
32 IWbemObjectSinkVtbl sink_vtbl;
37 HANDLE read_pipe, write_pipe, event_avail;
40 char *ifname; /* {GUID..} */
44 enum event_types { EVENT_CONNECT, EVENT_DISCONNECT, EVENT_MEDIA_SPECIFIC,
45 EVENT_ADAPTER_ARRIVAL, EVENT_ADAPTER_REMOVAL };
47 static int ndis_events_get_adapter(struct ndis_events_data *events,
48 const char *ifname, const char *desc);
51 static int ndis_events_constructor(struct ndis_events_data *events)
55 if (!CreatePipe(&events->read_pipe, &events->write_pipe, NULL, 512)) {
56 wpa_printf(MSG_ERROR, "CreatePipe() failed: %d",
57 (int) GetLastError());
60 events->event_avail = CreateEvent(NULL, TRUE, FALSE, NULL);
61 if (events->event_avail == NULL) {
62 wpa_printf(MSG_ERROR, "CreateEvent() failed: %d",
63 (int) GetLastError());
64 CloseHandle(events->read_pipe);
65 CloseHandle(events->write_pipe);
73 static void ndis_events_destructor(struct ndis_events_data *events)
75 CloseHandle(events->read_pipe);
76 CloseHandle(events->write_pipe);
77 CloseHandle(events->event_avail);
78 IWbemServices_Release(events->pSvc);
79 IWbemLocator_Release(events->pLoc);
80 if (--wmi_refcnt == 0)
85 static HRESULT STDMETHODCALLTYPE
86 ndis_events_query_interface(IWbemObjectSink *this, REFIID riid, void **obj)
90 if (IsEqualIID(riid, &IID_IUnknown) ||
91 IsEqualIID(riid, &IID_IWbemObjectSink)) {
93 IWbemObjectSink_AddRef(this);
101 static ULONG STDMETHODCALLTYPE ndis_events_add_ref(IWbemObjectSink *this)
103 struct ndis_events_data *events = (struct ndis_events_data *) this;
104 return ++events->ref;
108 static ULONG STDMETHODCALLTYPE ndis_events_release(IWbemObjectSink *this)
110 struct ndis_events_data *events = (struct ndis_events_data *) this;
112 if (--events->ref != 0)
115 ndis_events_destructor(events);
116 wpa_printf(MSG_DEBUG, "ndis_events: terminated");
117 os_free(events->adapter_desc);
118 os_free(events->ifname);
124 static int ndis_events_send_event(struct ndis_events_data *events,
125 enum event_types type,
126 char *data, size_t data_len)
128 char buf[512], *pos, *end;
132 end = buf + sizeof(buf);
134 os_memcpy(buf, &_type, sizeof(_type));
135 pos = buf + sizeof(_type);
138 if (2 + data_len > (size_t) (end - pos)) {
139 wpa_printf(MSG_DEBUG, "Not enough room for send_event "
140 "data (%d)", data_len);
143 *pos++ = data_len >> 8;
144 *pos++ = data_len & 0xff;
145 os_memcpy(pos, data, data_len);
149 if (WriteFile(events->write_pipe, buf, pos - buf, &written, NULL)) {
150 SetEvent(events->event_avail);
153 wpa_printf(MSG_INFO, "WriteFile() failed: %d", (int) GetLastError());
158 static void ndis_events_media_connect(struct ndis_events_data *events)
160 wpa_printf(MSG_DEBUG, "MSNdis_StatusMediaConnect");
161 ndis_events_send_event(events, EVENT_CONNECT, NULL, 0);
165 static void ndis_events_media_disconnect(struct ndis_events_data *events)
167 wpa_printf(MSG_DEBUG, "MSNdis_StatusMediaDisconnect");
168 ndis_events_send_event(events, EVENT_DISCONNECT, NULL, 0);
172 static void ndis_events_media_specific(struct ndis_events_data *events,
173 IWbemClassObject *pObj)
177 LONG lower, upper, k;
182 wpa_printf(MSG_DEBUG, "MSNdis_StatusMediaSpecificIndication");
184 /* This is the StatusBuffer from NdisMIndicateStatus() call */
185 hr = IWbemClassObject_Get(pObj, L"NdisStatusMediaSpecificIndication",
188 wpa_printf(MSG_DEBUG, "Could not get "
189 "NdisStatusMediaSpecificIndication from "
194 SafeArrayGetLBound(V_ARRAY(&vt), 1, &lower);
195 SafeArrayGetUBound(V_ARRAY(&vt), 1, &upper);
196 data_len = upper - lower + 1;
197 data = os_malloc(data_len);
199 wpa_printf(MSG_DEBUG, "Failed to allocate buffer for event "
206 for (k = lower; k <= upper; k++) {
207 SafeArrayGetElement(V_ARRAY(&vt), &k, &ch);
210 wpa_hexdump(MSG_DEBUG, "MediaSpecificEvent", data, data_len);
214 ndis_events_send_event(events, EVENT_MEDIA_SPECIFIC, data, data_len);
220 static void ndis_events_adapter_arrival(struct ndis_events_data *events)
222 wpa_printf(MSG_DEBUG, "MSNdis_NotifyAdapterArrival");
223 ndis_events_send_event(events, EVENT_ADAPTER_ARRIVAL, NULL, 0);
227 static void ndis_events_adapter_removal(struct ndis_events_data *events)
229 wpa_printf(MSG_DEBUG, "MSNdis_NotifyAdapterRemoval");
230 ndis_events_send_event(events, EVENT_ADAPTER_REMOVAL, NULL, 0);
234 static HRESULT STDMETHODCALLTYPE
235 ndis_events_indicate(IWbemObjectSink *this, long lObjectCount,
236 IWbemClassObject __RPC_FAR *__RPC_FAR *ppObjArray)
238 struct ndis_events_data *events = (struct ndis_events_data *) this;
241 if (events->terminating) {
242 wpa_printf(MSG_DEBUG, "ndis_events_indicate: Ignore "
243 "indication - terminating");
244 return WBEM_NO_ERROR;
246 /* wpa_printf(MSG_DEBUG, "Notification received - %d object(s)",
249 for (i = 0; i < lObjectCount; i++) {
250 IWbemClassObject *pObj = ppObjArray[i];
254 hr = IWbemClassObject_Get(pObj, L"__CLASS", 0, &vtClass, NULL,
257 wpa_printf(MSG_DEBUG, "Failed to get __CLASS from "
261 /* wpa_printf(MSG_DEBUG, "CLASS: '%S'", vtClass.bstrVal); */
263 hr = IWbemClassObject_Get(pObj, L"InstanceName", 0, &vt, NULL,
266 wpa_printf(MSG_DEBUG, "Failed to get InstanceName "
268 VariantClear(&vtClass);
272 if (wcscmp(vtClass.bstrVal,
273 L"MSNdis_NotifyAdapterArrival") == 0) {
274 wpa_printf(MSG_DEBUG, "ndis_events_indicate: Try to "
275 "update adapter description since it may "
276 "have changed with new adapter instance");
277 ndis_events_get_adapter(events, events->ifname, NULL);
280 if (wcscmp(events->adapter_desc, vt.bstrVal) != 0) {
281 wpa_printf(MSG_DEBUG, "ndis_events_indicate: Ignore "
282 "indication for foreign adapter: "
283 "InstanceName: '%S' __CLASS: '%S'",
284 vt.bstrVal, vtClass.bstrVal);
285 VariantClear(&vtClass);
291 if (wcscmp(vtClass.bstrVal,
292 L"MSNdis_StatusMediaSpecificIndication") == 0) {
293 ndis_events_media_specific(events, pObj);
294 } else if (wcscmp(vtClass.bstrVal,
295 L"MSNdis_StatusMediaConnect") == 0) {
296 ndis_events_media_connect(events);
297 } else if (wcscmp(vtClass.bstrVal,
298 L"MSNdis_StatusMediaDisconnect") == 0) {
299 ndis_events_media_disconnect(events);
300 } else if (wcscmp(vtClass.bstrVal,
301 L"MSNdis_NotifyAdapterArrival") == 0) {
302 ndis_events_adapter_arrival(events);
303 } else if (wcscmp(vtClass.bstrVal,
304 L"MSNdis_NotifyAdapterRemoval") == 0) {
305 ndis_events_adapter_removal(events);
307 wpa_printf(MSG_DEBUG, "Unepected event - __CLASS: "
308 "'%S'", vtClass.bstrVal);
311 VariantClear(&vtClass);
314 return WBEM_NO_ERROR;
318 static HRESULT STDMETHODCALLTYPE
319 ndis_events_set_status(IWbemObjectSink *this, long lFlags, HRESULT hResult,
320 BSTR strParam, IWbemClassObject __RPC_FAR *pObjParam)
322 return WBEM_NO_ERROR;
326 static int notification_query(IWbemObjectSink *pDestSink,
327 IWbemServices *pSvc, const char *class_name)
332 _snwprintf(query, 256,
333 L"SELECT * FROM %S", class_name);
334 wpa_printf(MSG_DEBUG, "ndis_events: WMI: %S", query);
335 hr = IWbemServices_ExecNotificationQueryAsync(pSvc, L"WQL", query, 0,
338 wpa_printf(MSG_DEBUG, "ExecNotificationQueryAsync for %s "
339 "failed with hresult of 0x%x",
340 class_name, (int) hr);
348 static int register_async_notification(IWbemObjectSink *pDestSink,
352 const char *class_list[] = {
353 "MSNdis_StatusMediaConnect",
354 "MSNdis_StatusMediaDisconnect",
355 "MSNdis_StatusMediaSpecificIndication",
356 "MSNdis_NotifyAdapterArrival",
357 "MSNdis_NotifyAdapterRemoval",
361 for (i = 0; class_list[i]; i++) {
362 if (notification_query(pDestSink, pSvc, class_list[i]) < 0)
370 void ndis_events_deinit(struct ndis_events_data *events)
372 events->terminating = 1;
373 IWbemServices_CancelAsyncCall(events->pSvc, &events->sink);
374 IWbemObjectSink_Release(&events->sink);
376 * Rest of deinitialization is done in ndis_events_destructor() once
377 * all reference count drops to zero.
382 static int ndis_events_use_desc(struct ndis_events_data *events,
389 if (events->adapter_desc == NULL)
391 /* Continue using old description */
395 tmp = os_strdup(desc);
399 pos = os_strstr(tmp, " (Microsoft's Packet Scheduler)");
403 len = os_strlen(tmp);
404 events->adapter_desc = os_malloc((len + 1) * sizeof(WCHAR));
405 if (events->adapter_desc == NULL) {
409 _snwprintf(events->adapter_desc, len + 1, L"%S", tmp);
415 static int ndis_events_get_adapter(struct ndis_events_data *events,
416 const char *ifname, const char *desc)
420 #define MAX_QUERY_LEN 256
421 WCHAR query[MAX_QUERY_LEN];
422 IEnumWbemClassObject *pEnumerator;
423 IWbemClassObject *pObj;
429 * Try to get adapter descriptor through WMI CIMv2 Win32_NetworkAdapter
430 * to have better probability of matching with InstanceName from
431 * MSNdis events. If this fails, use the provided description.
434 os_free(events->adapter_desc);
435 events->adapter_desc = NULL;
437 hr = IWbemLocator_ConnectServer(events->pLoc, L"ROOT\\CIMV2", NULL,
438 NULL, 0, 0, 0, 0, &pSvc);
440 wpa_printf(MSG_ERROR, "ndis_events: Could not connect to WMI "
441 "server (ROOT\\CIMV2) - error 0x%x", (int) hr);
442 return ndis_events_use_desc(events, desc);
444 wpa_printf(MSG_DEBUG, "ndis_events: Connected to ROOT\\CIMV2.");
446 _snwprintf(query, MAX_QUERY_LEN,
447 L"SELECT Index FROM Win32_NetworkAdapterConfiguration "
448 L"WHERE SettingID='%S'", ifname);
449 wpa_printf(MSG_DEBUG, "ndis_events: WMI: %S", query);
451 hr = IWbemServices_ExecQuery(pSvc, L"WQL", query,
452 WBEM_FLAG_FORWARD_ONLY |
453 WBEM_FLAG_RETURN_IMMEDIATELY,
455 if (!SUCCEEDED(hr)) {
456 wpa_printf(MSG_DEBUG, "ndis_events: Failed to query interface "
457 "GUID from Win32_NetworkAdapterConfiguration: "
459 IWbemServices_Release(pSvc);
460 return ndis_events_use_desc(events, desc);
464 hr = IEnumWbemClassObject_Next(pEnumerator, WBEM_INFINITE, 1,
466 if (!SUCCEEDED(hr) || uReturned == 0) {
467 wpa_printf(MSG_DEBUG, "ndis_events: Failed to find interface "
468 "GUID from Win32_NetworkAdapterConfiguration: "
470 IEnumWbemClassObject_Release(pEnumerator);
471 IWbemServices_Release(pSvc);
472 return ndis_events_use_desc(events, desc);
474 IEnumWbemClassObject_Release(pEnumerator);
477 hr = IWbemClassObject_Get(pObj, L"Index", 0, &vt, NULL, NULL);
478 if (!SUCCEEDED(hr)) {
479 wpa_printf(MSG_DEBUG, "ndis_events: Failed to get Index from "
480 "Win32_NetworkAdapterConfiguration: 0x%x",
482 IWbemServices_Release(pSvc);
483 return ndis_events_use_desc(events, desc);
486 _snwprintf(query, MAX_QUERY_LEN,
487 L"SELECT Name,PNPDeviceID FROM Win32_NetworkAdapter WHERE "
490 wpa_printf(MSG_DEBUG, "ndis_events: WMI: %S", query);
492 IWbemClassObject_Release(pObj);
494 hr = IWbemServices_ExecQuery(pSvc, L"WQL", query,
495 WBEM_FLAG_FORWARD_ONLY |
496 WBEM_FLAG_RETURN_IMMEDIATELY,
498 if (!SUCCEEDED(hr)) {
499 wpa_printf(MSG_DEBUG, "ndis_events: Failed to query interface "
500 "from Win32_NetworkAdapter: 0x%x", (int) hr);
501 IWbemServices_Release(pSvc);
502 return ndis_events_use_desc(events, desc);
506 hr = IEnumWbemClassObject_Next(pEnumerator, WBEM_INFINITE, 1,
508 if (!SUCCEEDED(hr) || uReturned == 0) {
509 wpa_printf(MSG_DEBUG, "ndis_events: Failed to find interface "
510 "from Win32_NetworkAdapter: 0x%x", (int) hr);
511 IEnumWbemClassObject_Release(pEnumerator);
512 IWbemServices_Release(pSvc);
513 return ndis_events_use_desc(events, desc);
515 IEnumWbemClassObject_Release(pEnumerator);
517 hr = IWbemClassObject_Get(pObj, L"Name", 0, &vt, NULL, NULL);
518 if (!SUCCEEDED(hr)) {
519 wpa_printf(MSG_DEBUG, "ndis_events: Failed to get Name from "
520 "Win32_NetworkAdapter: 0x%x", (int) hr);
521 IWbemClassObject_Release(pObj);
522 IWbemServices_Release(pSvc);
523 return ndis_events_use_desc(events, desc);
526 wpa_printf(MSG_DEBUG, "ndis_events: Win32_NetworkAdapter::Name='%S'",
528 events->adapter_desc = _wcsdup(vt.bstrVal);
532 * Try to get even better candidate for matching with InstanceName
533 * from Win32_PnPEntity. This is needed at least for some USB cards
534 * that can change the InstanceName whenever being unplugged and
538 hr = IWbemClassObject_Get(pObj, L"PNPDeviceID", 0, &vt, NULL, NULL);
539 if (!SUCCEEDED(hr)) {
540 wpa_printf(MSG_DEBUG, "ndis_events: Failed to get PNPDeviceID "
541 "from Win32_NetworkAdapter: 0x%x", (int) hr);
542 IWbemClassObject_Release(pObj);
543 IWbemServices_Release(pSvc);
544 if (events->adapter_desc == NULL)
545 return ndis_events_use_desc(events, desc);
546 return 0; /* use Win32_NetworkAdapter::Name */
549 wpa_printf(MSG_DEBUG, "ndis_events: Win32_NetworkAdapter::PNPDeviceID="
552 len = _snwprintf(query, MAX_QUERY_LEN,
553 L"SELECT Name FROM Win32_PnPEntity WHERE DeviceID='");
554 if (len < 0 || len >= MAX_QUERY_LEN - 1) {
556 IWbemClassObject_Release(pObj);
557 IWbemServices_Release(pSvc);
558 if (events->adapter_desc == NULL)
559 return ndis_events_use_desc(events, desc);
560 return 0; /* use Win32_NetworkAdapter::Name */
564 for (pos = 0; vt.bstrVal[pos] && len < MAX_QUERY_LEN - 2; pos++) {
565 if (vt.bstrVal[pos] == '\\') {
566 if (len >= MAX_QUERY_LEN - 3)
570 query[len++] = vt.bstrVal[pos];
572 query[len++] = L'\'';
575 IWbemClassObject_Release(pObj);
576 wpa_printf(MSG_DEBUG, "ndis_events: WMI: %S", query);
578 hr = IWbemServices_ExecQuery(pSvc, L"WQL", query,
579 WBEM_FLAG_FORWARD_ONLY |
580 WBEM_FLAG_RETURN_IMMEDIATELY,
582 if (!SUCCEEDED(hr)) {
583 wpa_printf(MSG_DEBUG, "ndis_events: Failed to query interface "
584 "Name from Win32_PnPEntity: 0x%x", (int) hr);
585 IWbemServices_Release(pSvc);
586 if (events->adapter_desc == NULL)
587 return ndis_events_use_desc(events, desc);
588 return 0; /* use Win32_NetworkAdapter::Name */
592 hr = IEnumWbemClassObject_Next(pEnumerator, WBEM_INFINITE, 1,
594 if (!SUCCEEDED(hr) || uReturned == 0) {
595 wpa_printf(MSG_DEBUG, "ndis_events: Failed to find interface "
596 "from Win32_PnPEntity: 0x%x", (int) hr);
597 IEnumWbemClassObject_Release(pEnumerator);
598 IWbemServices_Release(pSvc);
599 if (events->adapter_desc == NULL)
600 return ndis_events_use_desc(events, desc);
601 return 0; /* use Win32_NetworkAdapter::Name */
603 IEnumWbemClassObject_Release(pEnumerator);
605 hr = IWbemClassObject_Get(pObj, L"Name", 0, &vt, NULL, NULL);
606 if (!SUCCEEDED(hr)) {
607 wpa_printf(MSG_DEBUG, "ndis_events: Failed to get Name from "
608 "Win32_PnPEntity: 0x%x", (int) hr);
609 IWbemClassObject_Release(pObj);
610 IWbemServices_Release(pSvc);
611 if (events->adapter_desc == NULL)
612 return ndis_events_use_desc(events, desc);
613 return 0; /* use Win32_NetworkAdapter::Name */
616 wpa_printf(MSG_DEBUG, "ndis_events: Win32_PnPEntity::Name='%S'",
618 os_free(events->adapter_desc);
619 events->adapter_desc = _wcsdup(vt.bstrVal);
622 IWbemClassObject_Release(pObj);
624 IWbemServices_Release(pSvc);
626 if (events->adapter_desc == NULL)
627 return ndis_events_use_desc(events, desc);
633 struct ndis_events_data *
634 ndis_events_init(HANDLE *read_pipe, HANDLE *event_avail,
635 const char *ifname, const char *desc)
638 IWbemObjectSink *pSink;
639 struct ndis_events_data *events;
641 events = os_zalloc(sizeof(*events));
642 if (events == NULL) {
643 wpa_printf(MSG_ERROR, "Could not allocate sink for events.");
646 events->ifname = os_strdup(ifname);
647 if (events->ifname == NULL) {
652 if (wmi_refcnt++ == 0) {
653 hr = CoInitializeEx(0, COINIT_MULTITHREADED);
655 wpa_printf(MSG_ERROR, "CoInitializeEx() failed - "
656 "returned 0x%x", (int) hr);
663 /* CoInitializeSecurity() must be called once and only once
664 * per process, so let's use wmi_first flag to protect against
668 hr = CoInitializeSecurity(NULL, -1, NULL, NULL,
669 RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
670 RPC_C_IMP_LEVEL_IMPERSONATE,
671 NULL, EOAC_SECURE_REFS, NULL);
673 wpa_printf(MSG_ERROR, "CoInitializeSecurity() failed "
674 "- returned 0x%x", (int) hr);
680 hr = CoCreateInstance(&CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER,
681 &IID_IWbemLocator, (LPVOID *) &events->pLoc);
683 wpa_printf(MSG_ERROR, "CoCreateInstance() failed - returned "
690 if (ndis_events_get_adapter(events, ifname, desc) < 0) {
695 wpa_printf(MSG_DEBUG, "ndis_events: use adapter descriptor '%S'",
696 events->adapter_desc);
698 hr = IWbemLocator_ConnectServer(events->pLoc, L"ROOT\\WMI", NULL, NULL,
699 0, 0, 0, 0, &events->pSvc);
701 wpa_printf(MSG_ERROR, "Could not connect to server - error "
704 os_free(events->adapter_desc);
708 wpa_printf(MSG_DEBUG, "Connected to ROOT\\WMI.");
710 ndis_events_constructor(events);
711 pSink = &events->sink;
712 pSink->lpVtbl = &events->sink_vtbl;
713 events->sink_vtbl.QueryInterface = ndis_events_query_interface;
714 events->sink_vtbl.AddRef = ndis_events_add_ref;
715 events->sink_vtbl.Release = ndis_events_release;
716 events->sink_vtbl.Indicate = ndis_events_indicate;
717 events->sink_vtbl.SetStatus = ndis_events_set_status;
719 if (register_async_notification(pSink, events->pSvc) < 0) {
720 wpa_printf(MSG_DEBUG, "Failed to register async "
722 ndis_events_destructor(events);
723 os_free(events->adapter_desc);
728 *read_pipe = events->read_pipe;
729 *event_avail = events->event_avail;