blob: 770f635ae5b31255c046b353f39ad6563d250b22 [file] [log] [blame]
/*
* Copyright (c) 2016, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file
* @brief
* This file implements the Tunnel mode (Thread Miniport) functions required for the OpenThread library.
*/
#include "precomp.h"
#include "tunnel.tmh"
KSTART_ROUTINE otLwfTunWorkerThread;
SPINEL_CMD_HANDLER otLwfIrpCommandHandler;
typedef struct _SPINEL_IRP_CMD_CONTEXT
{
PMS_FILTER pFilter;
PIRP Irp;
SPINEL_IRP_CMD_HANDLER *Handler;
spinel_tid_t tid;
} SPINEL_IRP_CMD_CONTEXT;
_IRQL_requires_max_(PASSIVE_LEVEL)
NDIS_STATUS
otLwfTunInitialize(
_In_ PMS_FILTER pFilter
)
{
NDIS_STATUS Status = NDIS_STATUS_SUCCESS;
HANDLE threadHandle = NULL;
LogFuncEntry(DRIVER_DEFAULT);
NT_ASSERT(pFilter->DeviceCapabilities & OTLWF_DEVICE_CAP_THREAD_1_0);
KeInitializeEvent(
&pFilter->TunWorkerThreadStopEvent,
SynchronizationEvent, // auto-clearing event
FALSE // event initially non-signalled
);
KeInitializeEvent(
&pFilter->TunWorkerThreadAddressChangedEvent,
SynchronizationEvent, // auto-clearing event
FALSE // event initially non-signalled
);
// Start the worker thread
Status = PsCreateSystemThread(
&threadHandle, // ThreadHandle
THREAD_ALL_ACCESS, // DesiredAccess
NULL, // ObjectAttributes
NULL, // ProcessHandle
NULL, // ClientId
otLwfTunWorkerThread, // StartRoutine
pFilter // StartContext
);
if (!NT_SUCCESS(Status))
{
LogError(DRIVER_DEFAULT, "PsCreateSystemThread failed, %!STATUS!", Status);
goto error;
}
// Grab the object reference to the worker thread
Status = ObReferenceObjectByHandle(
threadHandle,
THREAD_ALL_ACCESS,
*PsThreadType,
KernelMode,
&pFilter->TunWorkerThread,
NULL
);
if (!NT_VERIFYMSG("ObReferenceObjectByHandle can't fail with a valid kernel handle", NT_SUCCESS(Status)))
{
LogError(DRIVER_DEFAULT, "ObReferenceObjectByHandle failed, %!STATUS!", Status);
KeSetEvent(&pFilter->TunWorkerThreadStopEvent, IO_NO_INCREMENT, FALSE);
}
// Make sure to enable RLOC passthrough
Status =
otLwfCmdSetProp(
pFilter,
SPINEL_PROP_THREAD_RLOC16_DEBUG_PASSTHRU,
SPINEL_DATATYPE_BOOL_S,
TRUE
);
if (!NT_SUCCESS(Status))
{
LogError(DRIVER_DEFAULT, "Enabling RLOC pass through failed, %!STATUS!", Status);
goto error;
}
// TODO - Query other values and capabilities
error:
if (!NT_SUCCESS(Status))
{
otLwfTunUninitialize(pFilter);
}
LogFuncExitNDIS(DRIVER_DEFAULT, Status);
return Status;
}
_IRQL_requires_max_(PASSIVE_LEVEL)
void
otLwfTunUninitialize(
_In_ PMS_FILTER pFilter
)
{
LogFuncEntry(DRIVER_DEFAULT);
// Clean up worker thread
if (pFilter->TunWorkerThread)
{
LogInfo(DRIVER_DEFAULT, "Stopping tunnel worker thread and waiting for it to complete.");
// Send event to shutdown worker thread
KeSetEvent(&pFilter->TunWorkerThreadStopEvent, 0, FALSE);
// Wait for worker thread to finish
KeWaitForSingleObject(
pFilter->TunWorkerThread,
Executive,
KernelMode,
FALSE,
NULL
);
// Free worker thread
ObDereferenceObject(pFilter->TunWorkerThread);
pFilter->TunWorkerThread = NULL;
LogInfo(DRIVER_DEFAULT, "Tunnel worker thread cleaned up.");
}
LogFuncExit(DRIVER_DEFAULT);
}
// Worker thread for processing all tunnel events
_Use_decl_annotations_
VOID
otLwfTunWorkerThread(
PVOID Context
)
{
PMS_FILTER pFilter = (PMS_FILTER)Context;
NT_ASSERT(pFilter);
LogFuncEntry(DRIVER_DEFAULT);
PKEVENT WaitEvents[] =
{
&pFilter->TunWorkerThreadStopEvent,
&pFilter->TunWorkerThreadAddressChangedEvent
};
LogFuncExit(DRIVER_DEFAULT);
while (true)
{
// Wait for event to stop or process event to fire
NTSTATUS status =
KeWaitForMultipleObjects(
ARRAYSIZE(WaitEvents),
(PVOID*)WaitEvents,
WaitAny,
Executive,
KernelMode,
FALSE,
NULL,
NULL);
// If it is the first event, then we are shutting down. Exit loop and terminate thread
if (status == STATUS_WAIT_0)
{
LogInfo(DRIVER_DEFAULT, "Received tunnel worker thread shutdown event.");
break;
}
else if (status == STATUS_WAIT_0 + 1) // TunWorkerThreadAddressChangedEvent fired
{
PVOID DataBuffer = NULL;
const uint8_t* value_data_ptr = NULL;
spinel_size_t value_data_len = 0;
// Query the current addresses
status =
otLwfCmdGetProp(
pFilter,
&DataBuffer,
SPINEL_PROP_IPV6_ADDRESS_TABLE,
SPINEL_DATATYPE_DATA_S,
&value_data_ptr,
&value_data_len);
if (NT_SUCCESS(status))
{
uint32_t aNotifFlags = 0;
otLwfTunAddressesUpdated(pFilter, value_data_ptr, value_data_len, &aNotifFlags);
// Send notification
if (aNotifFlags != 0)
{
PFILTER_NOTIFICATION_ENTRY NotifEntry = FILTER_ALLOC_NOTIF(pFilter);
if (NotifEntry)
{
RtlZeroMemory(NotifEntry, sizeof(FILTER_NOTIFICATION_ENTRY));
NotifEntry->Notif.InterfaceGuid = pFilter->InterfaceGuid;
NotifEntry->Notif.NotifType = OTLWF_NOTIF_STATE_CHANGE;
NotifEntry->Notif.StateChangePayload.Flags = aNotifFlags;
otLwfIndicateNotification(NotifEntry);
}
}
}
else
{
LogWarning(DRIVER_DEFAULT, "Failed to query addresses, %!STATUS!", status);
}
if (DataBuffer) FILTER_FREE_MEM(DataBuffer);
}
else
{
LogWarning(DRIVER_DEFAULT, "Unexpected wait result, %!STATUS!", status);
}
}
PsTerminateSystemThread(STATUS_SUCCESS);
}
_IRQL_requires_max_(DISPATCH_LEVEL)
VOID
otLwfIrpCommandHandler(
_In_ PMS_FILTER pFilter,
_In_ PVOID Context,
_In_ UINT Command,
_In_ spinel_prop_key_t Key,
_In_reads_bytes_(DataLength) const uint8_t* Data,
_In_ spinel_size_t DataLength
)
{
SPINEL_IRP_CMD_CONTEXT* CmdContext = (SPINEL_IRP_CMD_CONTEXT*)Context;
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(CmdContext->Irp);
ULONG IoControlCode = IrpSp->Parameters.DeviceIoControl.IoControlCode;
PVOID OutBuffer = CmdContext->Irp->AssociatedIrp.SystemBuffer;
ULONG OutBufferLength = IrpSp->Parameters.DeviceIoControl.OutputBufferLength;
ULONG OrigOutBufferLength = OutBufferLength;
NTSTATUS status;
UNREFERENCED_PARAMETER(pFilter);
// Clear the cancel routine
IoSetCancelRoutine(CmdContext->Irp, NULL);
if (Data == NULL)
{
status = STATUS_CANCELLED;
OutBufferLength = 0;
}
else if (Command == SPINEL_CMD_PROP_VALUE_IS && Key == SPINEL_PROP_LAST_STATUS)
{
spinel_status_t spinel_status = SPINEL_STATUS_OK;
spinel_ssize_t packed_len = spinel_datatype_unpack(Data, DataLength, "i", &spinel_status);
if (packed_len < 0 || (ULONG)packed_len > DataLength)
{
status = STATUS_INSUFFICIENT_RESOURCES;
}
else
{
status = ThreadErrorToNtstatus(SpinelStatusToThreadError(spinel_status));
}
}
else if (CmdContext->Handler)
{
status = CmdContext->Handler(Key, Data, DataLength, OutBuffer, &OutBufferLength);
}
else // No handler, so no output
{
status = STATUS_SUCCESS;
OutBufferLength = 0;
}
// Clear any leftover output buffer
if (OutBufferLength < OrigOutBufferLength)
{
RtlZeroMemory((PUCHAR)OutBuffer + OutBufferLength, OrigOutBufferLength - OutBufferLength);
}
LogVerbose(DRIVER_IOCTL, "Completing Irp=%p, with %!STATUS! for %s (Out:%u)",
CmdContext->Irp, status, IoCtlString(IoControlCode), OutBufferLength);
// Complete the IRP
CmdContext->Irp->IoStatus.Information = OutBufferLength;
CmdContext->Irp->IoStatus.Status = status;
IoCompleteRequest(CmdContext->Irp, IO_NO_INCREMENT);
FILTER_FREE_MEM(Context);
}
_Function_class_(DRIVER_CANCEL)
_Requires_lock_held_(_Global_cancel_spin_lock_)
_Releases_lock_(_Global_cancel_spin_lock_)
_IRQL_requires_min_(DISPATCH_LEVEL)
_IRQL_requires_(DISPATCH_LEVEL)
VOID
otLwfTunCancelIrp(
_Inout_ struct _DEVICE_OBJECT *DeviceObject,
_Inout_ _IRQL_uses_cancel_ struct _IRP *Irp
)
{
PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);
SPINEL_IRP_CMD_CONTEXT* CmdContext = (SPINEL_IRP_CMD_CONTEXT*)IrpStack->Context;
UNREFERENCED_PARAMETER(DeviceObject);
LogFuncEntryMsg(DRIVER_IOCTL, "Irp=%p", Irp);
IoReleaseCancelSpinLock(Irp->CancelIrql);
// Try to cancel pending command
otLwfCmdCancel(
CmdContext->pFilter,
(Irp->CancelIrql == DISPATCH_LEVEL) ? TRUE : FALSE,
CmdContext->tid);
LogFuncExit(DRIVER_IOCTL);
}
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
otLwfTunSendCommandForIrp(
_In_ PMS_FILTER pFilter,
_In_ PIRP Irp,
_In_opt_ SPINEL_IRP_CMD_HANDLER *Handler,
_In_ UINT Command,
_In_ spinel_prop_key_t Key,
_In_ ULONG MaxDataLength,
_In_opt_ const char *pack_format,
...
)
{
NTSTATUS status = STATUS_SUCCESS;
SPINEL_IRP_CMD_CONTEXT *pContext = NULL;
PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);
// Create the context structure
pContext = FILTER_ALLOC_MEM(pFilter->FilterHandle, sizeof(SPINEL_IRP_CMD_CONTEXT));
if (pContext == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
LogWarning(DRIVER_DEFAULT, "Failed to allocate irp cmd context");
goto exit;
}
pContext->pFilter = pFilter;
pContext->Irp = Irp;
pContext->Handler = Handler;
NT_ASSERT(IrpStack->Context == NULL);
IrpStack->Context = pContext;
// Set the cancel routine
IoSetCancelRoutine(Irp, otLwfTunCancelIrp);
va_list args;
va_start(args, pack_format);
status =
otLwfCmdSendAsyncV(
pFilter,
otLwfIrpCommandHandler,
pContext,
&pContext->tid,
Command,
Key,
MaxDataLength,
pack_format,
args);
va_end(args);
// Remove the handler entry from the list
if (!NT_SUCCESS(status))
{
// Clear the cancel routine
IoSetCancelRoutine(Irp, NULL);
FILTER_FREE_MEM(pContext);
}
exit:
return status;
}
_IRQL_requires_max_(DISPATCH_LEVEL)
void
otLwfTunValueIs(
_In_ PMS_FILTER pFilter,
_In_ BOOLEAN DispatchLevel,
_In_ spinel_prop_key_t key,
_In_reads_bytes_(value_data_len) const uint8_t* value_data_ptr,
_In_ spinel_size_t value_data_len
)
{
uint32_t aNotifFlags = 0;
LogFuncEntryMsg(DRIVER_DEFAULT, "[%p] received Value for %s", pFilter, spinel_prop_key_to_cstr(key));
if (key == SPINEL_PROP_NET_ROLE)
{
uint8_t value;
spinel_datatype_unpack(value_data_ptr, value_data_len, SPINEL_DATATYPE_UINT8_S, &value);
LogInfo(DRIVER_DEFAULT, "Interface %!GUID! new spinel role: %u", &pFilter->InterfaceGuid, value);
// Make sure we are in the correct media connect state
otLwfIndicateLinkState(
pFilter,
value > SPINEL_NET_ROLE_DETACHED ?
MediaConnectStateConnected :
MediaConnectStateDisconnected);
// Set flag to indicate we should send a notification
aNotifFlags = OT_CHANGED_THREAD_ROLE;
}
else if (key == SPINEL_PROP_IPV6_LL_ADDR)
{
// Set flag to indicate we should send a notification
aNotifFlags = OT_CHANGED_THREAD_LL_ADDR;
}
else if (key == SPINEL_PROP_IPV6_ML_ADDR)
{
// Set flag to indicate we should send a notification
aNotifFlags = OT_CHANGED_THREAD_ML_ADDR;
}
else if (key == SPINEL_PROP_NET_PARTITION_ID)
{
// Set flag to indicate we should send a notification
aNotifFlags = OT_CHANGED_THREAD_PARTITION_ID;
}
else if (key == SPINEL_PROP_NET_KEY_SEQUENCE_COUNTER)
{
// Set flag to indicate we should send a notification
aNotifFlags = OT_CHANGED_THREAD_KEY_SEQUENCE_COUNTER;
}
else if (key == SPINEL_PROP_IPV6_ADDRESS_TABLE)
{
KeSetEvent(&pFilter->TunWorkerThreadAddressChangedEvent, IO_NO_INCREMENT, FALSE);
}
else if (key == SPINEL_PROP_THREAD_CHILD_TABLE)
{
// TODO - Update cached children
// TODO - Send notification
}
else if (key == SPINEL_PROP_THREAD_ON_MESH_NETS)
{
// TODO - Slaac
// Set flag to indicate we should send a notification
aNotifFlags = OT_CHANGED_THREAD_NETDATA;
}
else if ((key == SPINEL_PROP_STREAM_NET) || (key == SPINEL_PROP_STREAM_NET_INSECURE))
{
const uint8_t* frame_ptr = NULL;
UINT frame_len = 0;
spinel_ssize_t ret;
ret = spinel_datatype_unpack(
value_data_ptr,
value_data_len,
SPINEL_DATATYPE_DATA_WLEN_S SPINEL_DATATYPE_DATA_S,
&frame_ptr,
&frame_len,
NULL,
NULL);
NT_ASSERT(ret > 0);
if (ret > 0)
{
otLwfTunReceiveIp6Packet(
pFilter,
DispatchLevel,
(SPINEL_PROP_STREAM_NET_INSECURE == key) ? FALSE : TRUE,
frame_ptr,
frame_len);
}
}
else if (key == SPINEL_PROP_MAC_SCAN_STATE)
{
// TODO - If pending scan, send notification of completion
}
else if (key == SPINEL_PROP_STREAM_RAW)
{
// May be used in the future
}
else if (key == SPINEL_PROP_STREAM_DEBUG)
{
const uint8_t* output = NULL;
UINT output_len = 0;
spinel_ssize_t ret;
ret = spinel_datatype_unpack(
value_data_ptr,
value_data_len,
SPINEL_DATATYPE_DATA_S,
&output,
&output_len);
NT_ASSERT(ret > 0);
if (ret > 0 && output && output_len <= (UINT)ret)
{
if (strnlen((char*)output, output_len) != output_len)
{
LogInfo(DRIVER_DEFAULT, "DEVICE: %s", (char*)output);
}
else if (output_len < 128)
{
char strOutput[128] = { 0 };
memcpy(strOutput, output, output_len);
LogInfo(DRIVER_DEFAULT, "DEVICE: %s", strOutput);
}
}
}
// Send notification
if (aNotifFlags != 0)
{
PFILTER_NOTIFICATION_ENTRY NotifEntry = FILTER_ALLOC_NOTIF(pFilter);
if (NotifEntry)
{
RtlZeroMemory(NotifEntry, sizeof(FILTER_NOTIFICATION_ENTRY));
NotifEntry->Notif.InterfaceGuid = pFilter->InterfaceGuid;
NotifEntry->Notif.NotifType = OTLWF_NOTIF_STATE_CHANGE;
NotifEntry->Notif.StateChangePayload.Flags = aNotifFlags;
otLwfIndicateNotification(NotifEntry);
}
}
LogFuncExit(DRIVER_DEFAULT);
}
_IRQL_requires_max_(DISPATCH_LEVEL)
void
otLwfTunValueInserted(
_In_ PMS_FILTER pFilter,
_In_ BOOLEAN DispatchLevel,
_In_ spinel_prop_key_t key,
_In_reads_bytes_(value_data_len) const uint8_t* value_data_ptr,
_In_ spinel_size_t value_data_len
)
{
LogFuncEntryMsg(DRIVER_DEFAULT, "[%p] received Value Inserted for %s", pFilter, spinel_prop_key_to_cstr(key));
UNREFERENCED_PARAMETER(pFilter);
UNREFERENCED_PARAMETER(DispatchLevel);
UNREFERENCED_PARAMETER(value_data_ptr);
UNREFERENCED_PARAMETER(value_data_len);
if (key == SPINEL_PROP_MAC_SCAN_BEACON)
{
PFILTER_NOTIFICATION_ENTRY NotifEntry = FILTER_ALLOC_NOTIF(pFilter);
if (NotifEntry)
{
RtlZeroMemory(NotifEntry, sizeof(FILTER_NOTIFICATION_ENTRY));
NotifEntry->Notif.InterfaceGuid = pFilter->InterfaceGuid;
NotifEntry->Notif.NotifType = OTLWF_NOTIF_ACTIVE_SCAN;
NotifEntry->Notif.ActiveScanPayload.Valid = TRUE;
const uint8_t *aExtAddr = NULL;
const uint8_t *aExtPanId = NULL;
const char *aNetworkName = NULL;
unsigned int xpanid_len = 0;
if (try_spinel_datatype_unpack(
value_data_ptr,
value_data_len,
SPINEL_DATATYPE_MAC_SCAN_RESULT_S(
SPINEL_802_15_4_DATATYPE_MAC_SCAN_RESULT_V1_S,
SPINEL_NET_DATATYPE_MAC_SCAN_RESULT_V1_S
),
&NotifEntry->Notif.ActiveScanPayload.Results.mChannel,
&NotifEntry->Notif.ActiveScanPayload.Results.mRssi,
&aExtAddr,
NULL, // saddr (don't care)
&NotifEntry->Notif.ActiveScanPayload.Results.mPanId,
&NotifEntry->Notif.ActiveScanPayload.Results.mLqi,
NULL, // proto (don't care)
NULL, // flags (don't care)
&aNetworkName,
&aExtPanId,
&xpanid_len
) &&
aExtAddr != NULL && aExtPanId != NULL && aNetworkName != NULL &&
xpanid_len == OT_EXT_PAN_ID_SIZE)
{
memcpy_s(NotifEntry->Notif.ActiveScanPayload.Results.mExtAddress.m8,
sizeof(NotifEntry->Notif.ActiveScanPayload.Results.mExtAddress.m8),
aExtAddr, sizeof(otExtAddress));
memcpy_s(NotifEntry->Notif.ActiveScanPayload.Results.mExtendedPanId.m8,
sizeof(NotifEntry->Notif.ActiveScanPayload.Results.mExtendedPanId.m8),
aExtPanId, sizeof(otExtendedPanId));
strcpy_s(NotifEntry->Notif.ActiveScanPayload.Results.mNetworkName.m8,
sizeof(NotifEntry->Notif.ActiveScanPayload.Results.mNetworkName.m8),
aNetworkName);
otLwfIndicateNotification(NotifEntry);
}
else
{
FILTER_FREE_MEM(NotifEntry);
}
}
}
else if (key == SPINEL_PROP_MAC_ENERGY_SCAN_RESULT)
{
PFILTER_NOTIFICATION_ENTRY NotifEntry = FILTER_ALLOC_NOTIF(pFilter);
if (NotifEntry)
{
RtlZeroMemory(NotifEntry, sizeof(FILTER_NOTIFICATION_ENTRY));
NotifEntry->Notif.InterfaceGuid = pFilter->InterfaceGuid;
NotifEntry->Notif.NotifType = OTLWF_NOTIF_ENERGY_SCAN;
NotifEntry->Notif.EnergyScanPayload.Valid = TRUE;
if (try_spinel_datatype_unpack(
value_data_ptr,
value_data_len,
"Cc",
&NotifEntry->Notif.EnergyScanPayload.Results.mChannel,
&NotifEntry->Notif.EnergyScanPayload.Results.mMaxRssi
))
{
otLwfIndicateNotification(NotifEntry);
}
else
{
FILTER_FREE_MEM(NotifEntry);
}
}
}
LogFuncExit(DRIVER_DEFAULT);
}