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
;