diff --git a/examples/drivers/windows/include/otNode.h b/examples/drivers/windows/include/otNode.h index 175c2d30e..b321c3298 100644 --- a/examples/drivers/windows/include/otNode.h +++ b/examples/drivers/windows/include/otNode.h @@ -365,6 +365,37 @@ OTNODEAPI int32_t OTCALL otNodeSendActiveSet(otNode* aNode, uint64_t aActiveTime */ OTNODEAPI int32_t OTCALL otNodeSetMaxChildren(otNode* aNode, uint8_t aMaxChildren); +/** + * The interface used to listen in on virtual nodes' MAC frames + */ + +typedef struct otListener otListener; + +/** + * Creates and starts a new listener + */ +OTNODEAPI otListener* OTCALL otListenerInit(uint32_t nodeid); + +/** + * Frees a listener + */ +OTNODEAPI int32_t OTCALL otListenerFinalize(otListener* aListener); + +/** + * Structure that represents a received MAC frame from the listener + */ +typedef struct otMacFrame +{ + uint8_t buffer[128]; + uint8_t length; + uint32_t nodeid; +} otMacFrame; + +/** + * Reads the next MAC frame from the listener + */ +OTNODEAPI int32_t OTCALL otListenerRead(otListener* aListener, otMacFrame *aFrame); + #ifdef __cplusplus } // extern "C" #endif diff --git a/examples/drivers/windows/otNodeApi/otNodeApi.cpp b/examples/drivers/windows/otNodeApi/otNodeApi.cpp index 5c1a349e6..2d3c403b5 100644 --- a/examples/drivers/windows/otNodeApi/otNodeApi.cpp +++ b/examples/drivers/windows/otNodeApi/otNodeApi.cpp @@ -39,12 +39,19 @@ typedef VOID (*fp_otvmpCloseHandle)(_In_ HANDLE handle); typedef DWORD (*fp_otvmpAddVirtualBus)(_In_ HANDLE handle, _Inout_ ULONG* pBusNumber, _Out_ ULONG* pIfIndex); typedef DWORD (*fp_otvmpRemoveVirtualBus)(_In_ HANDLE handle, ULONG BusNumber); typedef DWORD (*fp_otvmpSetAdapterTopologyGuid)(_In_ HANDLE handle, DWORD BusNumber, _In_ const GUID* pTopologyGuid); +typedef void (*fp_otvmpListenerCallback)(_In_opt_ PVOID aContext, _In_ ULONG SourceInterfaceIndex, _In_reads_bytes_(FrameLength) PUCHAR FrameBuffer, _In_ UCHAR FrameLength, _In_ UCHAR Channel); +typedef HANDLE (*fp_otvmpListenerCreate)(_In_ const GUID* pAdapterTopologyGuid); +typedef void (*fp_otvmpListenerDestroy)(_In_opt_ HANDLE pHandle); +typedef void(*fp_otvmpListenerRegister)(_In_ HANDLE pHandle, _In_opt_ fp_otvmpListenerCallback Callback, _In_opt_ PVOID Context); fp_otvmpOpenHandle otvmpOpenHandle = nullptr; fp_otvmpCloseHandle otvmpCloseHandle = nullptr; fp_otvmpAddVirtualBus otvmpAddVirtualBus = nullptr; fp_otvmpRemoveVirtualBus otvmpRemoveVirtualBus = nullptr; fp_otvmpSetAdapterTopologyGuid otvmpSetAdapterTopologyGuid = nullptr; +fp_otvmpListenerCreate otvmpListenerCreate = nullptr; +fp_otvmpListenerDestroy otvmpListenerDestroy = nullptr; +fp_otvmpListenerRegister otvmpListenerRegister = nullptr; HMODULE gVmpModule = nullptr; HANDLE gVmpHandle = nullptr; @@ -53,6 +60,8 @@ ULONG gNextBusNumber = 1; GUID gTopologyGuid = {0}; volatile LONG gNumberOfInterfaces = 0; +CRITICAL_SECTION gCS; +vector gNodes; otApiInstance *gApiInstance = nullptr; @@ -119,18 +128,27 @@ otApiInstance* GetApiInstance() otvmpAddVirtualBus = (fp_otvmpAddVirtualBus)GetProcAddress(gVmpModule, "otvmpAddVirtualBus"); otvmpRemoveVirtualBus = (fp_otvmpRemoveVirtualBus)GetProcAddress(gVmpModule, "otvmpRemoveVirtualBus"); otvmpSetAdapterTopologyGuid = (fp_otvmpSetAdapterTopologyGuid)GetProcAddress(gVmpModule, "otvmpSetAdapterTopologyGuid"); + otvmpListenerCreate = (fp_otvmpListenerCreate)GetProcAddress(gVmpModule, "otvmpListenerCreate"); + otvmpListenerDestroy = (fp_otvmpListenerDestroy)GetProcAddress(gVmpModule, "otvmpListenerDestroy"); + otvmpListenerRegister = (fp_otvmpListenerRegister)GetProcAddress(gVmpModule, "otvmpListenerRegister"); assert(otvmpOpenHandle); assert(otvmpCloseHandle); assert(otvmpAddVirtualBus); assert(otvmpRemoveVirtualBus); assert(otvmpSetAdapterTopologyGuid); + assert(otvmpListenerCreate); + assert(otvmpListenerDestroy); + assert(otvmpListenerRegister); if (otvmpOpenHandle == nullptr) printf("otvmpOpenHandle is null!\r\n"); if (otvmpCloseHandle == nullptr) printf("otvmpCloseHandle is null!\r\n"); if (otvmpAddVirtualBus == nullptr) printf("otvmpAddVirtualBus is null!\r\n"); if (otvmpRemoveVirtualBus == nullptr) printf("otvmpRemoveVirtualBus is null!\r\n"); if (otvmpSetAdapterTopologyGuid == nullptr) printf("otvmpSetAdapterTopologyGuid is null!\r\n"); + if (otvmpListenerCreate == nullptr) printf("otvmpListenerCreate is null!\r\n"); + if (otvmpListenerDestroy == nullptr) printf("otvmpListenerDestroy is null!\r\n"); + if (otvmpListenerRegister == nullptr) printf("otvmpListenerRegister is null!\r\n"); (VOID)otvmpOpenHandle(&gVmpHandle); if (gVmpHandle == nullptr) @@ -146,6 +164,8 @@ otApiInstance* GetApiInstance() return nullptr; } + InitializeCriticalSection(&gCS); + auto offset = getenv("INSTANCE"); if (offset) { @@ -187,6 +207,8 @@ void Unload() otApiFinalize(gApiInstance); gApiInstance = nullptr; + DeleteCriticalSection(&gCS); + WSACleanup(); printf("Topology destroyed\r\n"); @@ -263,6 +285,7 @@ typedef struct otNode { uint32_t mId; DWORD mBusIndex; + DWORD mInterfaceIndex; otInstance* mInstance; HANDLE mEnergyScanEvent; HANDLE mPanIdConflictEvent; @@ -762,12 +785,17 @@ OTNODEAPI otNode* OTCALL otNodeInit(uint32_t id) printf("%d: New Device " GUID_FORMAT " in compartment %d\r\n", id, GUID_ARG(DeviceGuid), Compartment); node->mId = id; + node->mInterfaceIndex = ifIndex; node->mBusIndex = newBusIndex; node->mInstance = instance; node->mEnergyScanEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); node->mPanIdConflictEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + EnterCriticalSection(&gCS); + gNodes.push_back(node); + LeaveCriticalSection(&gCS); + InitializeCriticalSection(&node->mCS); otSetStateChangedCallback(instance, otNodeStateChangedCallback, node); @@ -795,6 +823,17 @@ OTNODEAPI int32_t OTCALL otNodeFinalize(otNode* aNode) CloseHandle(aNode->mEnergyScanEvent); otSetStateChangedCallback(aNode->mInstance, nullptr, nullptr); + EnterCriticalSection(&gCS); + for (uint32_t i = 0; i < gNodes.size(); i++) + { + if (gNodes[i] == aNode) + { + gNodes.erase(gNodes.begin() + i); + break; + } + } + LeaveCriticalSection(&gCS); + // Free the instance otFreeMemory(aNode->mInstance); aNode->mInstance = nullptr; @@ -2056,3 +2095,178 @@ OTNODEAPI int32_t OTCALL otNodeSetMaxChildren(otNode* aNode, uint8_t aMaxChildre otLogFuncExit(); return result; } + +typedef struct otMacFrameEntry +{ + otMacFrame Frame; + LIST_ENTRY Link; +} otMacFrameEntry; + +typedef struct otListener +{ + HANDLE mListener; + CRITICAL_SECTION mCS; + HANDLE mStopEvent; + HANDLE mFramesUpdatedEvent; + LIST_ENTRY mFrames; // List of otMacFrameEntry +} otListener; + +void +otListenerCallback( + _In_opt_ PVOID aContext, + _In_ ULONG SourceInterfaceIndex, + _In_reads_bytes_(FrameLength) PUCHAR FrameBuffer, + _In_ UCHAR FrameLength, + _In_ UCHAR Channel +) +{ + otListener* aListener = (otListener*)aContext; + assert(aListener); + + if (FrameLength) + { + otMacFrameEntry* entry = new otMacFrameEntry; + entry->Frame.buffer[0] = Channel; + memcpy_s(entry->Frame.buffer + 1, sizeof(entry->Frame.buffer) - 1, FrameBuffer, FrameLength); + entry->Frame.length = FrameLength + 1; + entry->Frame.nodeid = (uint32_t)-1; + + // Look up the Node ID from by interface guid + EnterCriticalSection(&gCS); + for (uint32_t i = 0; i < gNodes.size(); i++) + { + if (gNodes[i]->mInterfaceIndex == SourceInterfaceIndex) + { + entry->Frame.nodeid = gNodes[i]->mId; + break; + } + } + LeaveCriticalSection(&gCS); + + // Push the frame on the list to process + EnterCriticalSection(&aListener->mCS); + InsertTailList(&aListener->mFrames, &entry->Link); + LeaveCriticalSection(&aListener->mCS); + + // Set event indicating we have a new frame to process + SetEvent(aListener->mFramesUpdatedEvent); + } +} + +OTNODEAPI otListener* OTCALL otListenerInit(uint32_t /* nodeid */) +{ + otLogFuncEntry(); + + auto ApiInstance = GetApiInstance(); + if (ApiInstance == nullptr) + { + printf("GetApiInstance failed!\r\n"); + otLogFuncExitMsg("GetApiInstance failed"); + return nullptr; + } + + InterlockedIncrement(&gNumberOfInterfaces); + + otListener *listener = new otListener(); + assert(listener); + + InitializeCriticalSection(&listener->mCS); + listener->mStopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + listener->mFramesUpdatedEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + InitializeListHead(&listener->mFrames); + + // Create the listener + listener->mListener = otvmpListenerCreate(&gTopologyGuid); + assert(listener->mListener); + + // Register for callbacks + otvmpListenerRegister(listener->mListener, otListenerCallback, listener); + + printf("S: Sniffer started\r\n"); + + otLogFuncExit(); + + return listener; +} + +OTNODEAPI int32_t OTCALL otListenerFinalize(otListener* aListener) +{ + otLogFuncEntry(); + if (aListener != nullptr) + { + // Set stop event to prevent cancel any pending otListenerRead calls + SetEvent(aListener->mStopEvent); + + // Unregisters (and waits for callbacks to complete) and cleans up the handle + otvmpListenerDestroy(aListener->mListener); + aListener->mListener = nullptr; + + // Clean up left over frames + PLIST_ENTRY Link = aListener->mFrames.Flink; + while (Link != &aListener->mFrames) + { + otMacFrameEntry *entry = CONTAINING_RECORD(Link, otMacFrameEntry, Link); + Link = Link->Flink; + delete entry; + } + + // Clean up everything else + CloseHandle(aListener->mFramesUpdatedEvent); + aListener->mFramesUpdatedEvent = nullptr; + CloseHandle(aListener->mStopEvent); + aListener->mStopEvent = nullptr; + DeleteCriticalSection(&aListener->mCS); + delete aListener; + + printf("S: Sniffer stopped\r\n"); + + if (0 == InterlockedDecrement(&gNumberOfInterfaces)) + { + // Uninitialize everything else if this is the last ref + Unload(); + } + } + otLogFuncExit(); + return 0; +} + +OTNODEAPI int32_t OTCALL otListenerRead(otListener* aListener, otMacFrame *aFrame) +{ + do + { + bool exit = false; + + EnterCriticalSection(&aListener->mCS); + + // If we have a pending frame, return it now + if (!IsListEmpty(&aListener->mFrames)) + { + PLIST_ENTRY Link = RemoveHeadList(&aListener->mFrames); + otMacFrameEntry *entry = CONTAINING_RECORD(Link, otMacFrameEntry, Link); + *aFrame = entry->Frame; + delete entry; + exit = true; + } + + LeaveCriticalSection(&aListener->mCS); + + if (exit) break; + + // Wait for the shutdown or frames updated event + auto waitResult = WaitForMultipleObjects(2, &aListener->mStopEvent, FALSE, INFINITE); + + if (waitResult == WAIT_OBJECT_0 + 1) // mFramesUpdatedEvent + { + continue; + } + else // mStopEvent + { + return 1; + } + + } while (true); + + //printf("S: Sniffer read %d bytes from node %d\r\n", aFrame->length, aFrame->nodeid); + + return 0; +} diff --git a/examples/drivers/windows/otNodeApi/precomp.h b/examples/drivers/windows/otNodeApi/precomp.h index d234355ca..160ce5aec 100644 --- a/examples/drivers/windows/otNodeApi/precomp.h +++ b/examples/drivers/windows/otNodeApi/precomp.h @@ -56,3 +56,55 @@ using namespace std; #include void Unload(); + +FORCEINLINE +VOID +InitializeListHead( + _Out_ PLIST_ENTRY ListHead + ) +{ + ListHead->Flink = ListHead->Blink = ListHead; +} + +FORCEINLINE +PLIST_ENTRY +RemoveHeadList( + _Inout_ PLIST_ENTRY ListHead + ) + +{ + + PLIST_ENTRY Entry; + PLIST_ENTRY NextEntry; + Entry = ListHead->Flink; + NextEntry = Entry->Flink; + ListHead->Flink = NextEntry; + NextEntry->Blink = ListHead; + return Entry; +} + +FORCEINLINE +VOID +InsertTailList( + _Inout_ PLIST_ENTRY ListHead, + _Out_ __drv_aliasesMem PLIST_ENTRY Entry + ) +{ + + PLIST_ENTRY PrevEntry; + PrevEntry = ListHead->Blink; + Entry->Flink = ListHead; + Entry->Blink = PrevEntry; + PrevEntry->Flink = Entry; + ListHead->Blink = Entry; +} + +_Must_inspect_result_ +BOOLEAN +CFORCEINLINE +IsListEmpty( + _In_ const LIST_ENTRY * ListHead + ) +{ + return (BOOLEAN)(ListHead->Flink == ListHead); +} diff --git a/tests/scripts/thread-cert/sniffer_transport.py b/tests/scripts/thread-cert/sniffer_transport.py index 7115ea0f7..5a6f37cd7 100644 --- a/tests/scripts/thread-cert/sniffer_transport.py +++ b/tests/scripts/thread-cert/sniffer_transport.py @@ -27,11 +27,11 @@ # POSSIBILITY OF SUCH DAMAGE. # +import ctypes import os import socket import sys - class SnifferTransport(object): """ Interface for transport that allows eavesdrop other nodes. """ @@ -148,6 +148,66 @@ class SnifferSocketTransport(SnifferTransport): return bytearray(data), nodeid +class MacFrame(ctypes.Structure): + _fields_ = [("buffer", ctypes.c_ubyte * 128), + ("length", ctypes.c_ubyte), + ("nodeid", ctypes.c_uint)] + +class SnifferVirtualTransport(SnifferTransport): + """ Virtual interface based implementation of sniffer transport. """ + + def __init__(self, nodeid): + self.Handle = None + + # Load the DLL + self.Api = ctypes.WinDLL("otnodeapi.dll") + if self.Api == None: + raise OSError("Failed to load otnodeapi.dll!") + + # Define the functions + self.Api.otListenerInit.argtypes = [ctypes.c_uint] + self.Api.otListenerInit.restype = ctypes.c_void_p + + self.Api.otListenerFinalize.argtypes = [ctypes.c_void_p] + + self.Api.otListenerRead.argtypes = [ctypes.c_void_p, ctypes.POINTER(MacFrame)] + + def __del__(self): + if not self.is_opened: + return + + self.close() + + def open(self): + if self.is_opened: + raise RuntimeError("Transport is already opened.") + + # Initialize a listener + self.Handle = self.Api.otListenerInit(0) + + if not self.is_opened: + raise RuntimeError("Transport opening failed.") + + def close(self): + if not self.is_opened: + raise RuntimeError("Transport is closed.") + + self.Api.otListenerFinalize(self.Handle); + self.Handle = None + + @property + def is_opened(self): + return bool(self.Handle is not None) + + def recv(self, bufsize): + frame = MacFrame() + pFrame = ctypes.pointer(frame); + + self.Api.otListenerRead(self.Handle, pFrame) + + return bytearray(frame.buffer)[:frame.length], frame.nodeid + + class SnifferTransportFactory(object): def create_transport(self, nodeid): @@ -155,4 +215,4 @@ class SnifferTransportFactory(object): return SnifferSocketTransport(nodeid) else: - raise NotImplementedError + return SnifferVirtualTransport(nodeid)