blob: 98abaa13bde6d3f6b198976f18c87d55a7d68aa7 [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.
*/
#include "precomp.h"
#include "address.tmh"
_IRQL_requires_max_(PASSIVE_LEVEL)
BOOLEAN
otLwfOnAddressAdded(
_In_ PMS_FILTER pFilter,
_In_ const otNetifAddress* Addr,
_In_ BOOLEAN UpdateWindows
)
{
if (pFilter->otCachedAddrCount >= OT_MAX_ADDRESSES)
{
LogError(DRIVER_DEFAULT, "Failing to add new address as we have reached our max!");
return FALSE;
}
LogInfo(DRIVER_DEFAULT, "Interface %!GUID! adding address: %!IPV6ADDR! (%u-bit prefix)",
&pFilter->InterfaceGuid,
(PIN6_ADDR)&Addr->mAddress,
Addr->mPrefixLength
);
// Update local cache
memcpy(pFilter->otCachedAddr + pFilter->otCachedAddrCount, Addr, sizeof(IN6_ADDR));
pFilter->otCachedAddrCount++;
// If this is link local, cache it as our link local address
if (IN6_IS_ADDR_LINKLOCAL((PIN6_ADDR)&Addr->mAddress))
{
memcpy(&pFilter->otLinkLocalAddr, Addr, sizeof(IN6_ADDR));
}
// Update Windows if necessary
if (UpdateWindows)
{
NTSTATUS status;
MIB_UNICASTIPADDRESS_ROW newRow;
MIB_IPFORWARD_ROW2 newRouteRow;
COMPARTMENT_ID OriginalCompartmentID;
InitializeUnicastIpAddressEntry(&newRow);
InitializeIpForwardEntry(&newRouteRow);
newRow.InterfaceIndex = pFilter->InterfaceIndex;
newRow.InterfaceLuid = pFilter->InterfaceLuid;
newRow.Address.si_family = AF_INET6;
newRow.Address.Ipv6.sin6_family = AF_INET6;
static_assert(sizeof(IN6_ADDR) == sizeof(otIp6Address), "Windows and OpenThread IPv6 Addr Structs must be same size");
memcpy(&newRow.Address.Ipv6.sin6_addr, &Addr->mAddress, sizeof(IN6_ADDR));
newRow.OnLinkPrefixLength = Addr->mPrefixLength;
newRow.PreferredLifetime = Addr->mPreferred ? 0xffffffff : 0;
newRow.ValidLifetime = Addr->mValid ? 0xffffffff : 0;
newRow.PrefixOrigin = IpPrefixOriginOther; // Derived from network XPANID
newRow.SkipAsSource = FALSE; // Allow automatic binding to this address (default)
if (IN6_IS_ADDR_LINKLOCAL(&newRow.Address.Ipv6.sin6_addr))
{
newRow.SuffixOrigin = IpSuffixOriginLinkLayerAddress; // Derived from Extended MAC address
}
else
{
newRow.SuffixOrigin = IpSuffixOriginRandom; // Was created randomly
}
// Make sure we are in the right compartment
(VOID)otLwfSetCompartment(pFilter, &OriginalCompartmentID);
status = CreateUnicastIpAddressEntry(&newRow);
//NT_ASSERT(NT_SUCCESS(status));
if (!NT_SUCCESS(status))
{
LogError(DRIVER_DEFAULT, "CreateUnicastIpAddressEntry failed %!STATUS!", status);
}
newRouteRow.InterfaceIndex = pFilter->InterfaceIndex;
newRouteRow.InterfaceLuid = pFilter->InterfaceLuid;
newRouteRow.DestinationPrefix.Prefix.si_family = AF_INET6;
newRouteRow.DestinationPrefix.PrefixLength = 0;
status = CreateIpForwardEntry2(&newRouteRow);
if (!NT_SUCCESS(status) && status != STATUS_DUPLICATE_OBJECTID)
{
LogVerbose(DRIVER_DEFAULT, "CreateIpForwardEntry2 failed %!STATUS!", status);
}
// Revert back to original compartment
otLwfRevertCompartment(OriginalCompartmentID);
}
return TRUE;
}
_IRQL_requires_max_(PASSIVE_LEVEL)
VOID
otLwfOnAddressRemoved(
_In_ PMS_FILTER pFilter,
_In_ ULONG CachedIndex,
_In_ BOOLEAN UpdateWindows
)
{
// Cache address before we delete local cache
IN6_ADDR Addr = pFilter->otCachedAddr[CachedIndex];
LogInfo(DRIVER_DEFAULT, "Interface %!GUID! removing address: %!IPV6ADDR!", &pFilter->InterfaceGuid, &Addr);
NT_ASSERT(pFilter->otCachedAddrCount != 0);
NT_ASSERT(CachedIndex < pFilter->otCachedAddrCount);
// Remove the cached entry
if (CachedIndex + 1 != pFilter->otCachedAddrCount)
memmove(pFilter->otCachedAddr + CachedIndex,
pFilter->otCachedAddr + CachedIndex + 1,
(pFilter->otCachedAddrCount - CachedIndex - 1) * sizeof(IN6_ADDR)
);
pFilter->otCachedAddrCount--;
// Update Windows if necessary
if (UpdateWindows)
{
MIB_UNICASTIPADDRESS_ROW deleteRow;
COMPARTMENT_ID OriginalCompartmentID;
InitializeUnicastIpAddressEntry(&deleteRow);
deleteRow.InterfaceIndex = pFilter->InterfaceIndex;
deleteRow.InterfaceLuid = pFilter->InterfaceLuid;
deleteRow.Address.si_family = AF_INET6;
deleteRow.Address.Ipv6.sin6_addr = Addr;
// Make sure we are in the right compartment
(VOID)otLwfSetCompartment(pFilter, &OriginalCompartmentID);
// Best effort remove address from TCPIP
(VOID)DeleteUnicastIpAddressEntry(&deleteRow);
// Revert back to original compartment
otLwfRevertCompartment(OriginalCompartmentID);
}
}
int
otLwfFindCachedAddrIndex(
_In_ PMS_FILTER pFilter,
_In_ PIN6_ADDR addr
)
{
for (ULONG i = 0; i < pFilter->otCachedAddrCount; i++)
if (memcmp(pFilter->otCachedAddr + i, addr, sizeof(IN6_ADDR)) == 0)
return (int)i;
return -1;
}
_IRQL_requires_max_(PASSIVE_LEVEL)
NTSTATUS
otLwfInitializeAddresses(
_In_ PMS_FILTER pFilter
)
{
NTSTATUS status = STATUS_SUCCESS;
PMIB_UNICASTIPADDRESS_TABLE pMibUnicastAddressTable = NULL;
COMPARTMENT_ID OriginalCompartmentID;
LogFuncEntry(DRIVER_DEFAULT);
pFilter->otCachedAddrCount = 0;
// Make sure we are in the right compartment
(VOID)otLwfSetCompartment(pFilter, &OriginalCompartmentID);
// Query the table for the current compartment
status = GetUnicastIpAddressTable(AF_INET6, &pMibUnicastAddressTable);
// Revert the compartment, now that we have the table
otLwfRevertCompartment(OriginalCompartmentID);
if (!NT_SUCCESS(status))
{
LogError(DRIVER_DEFAULT, "GetUnicastIpAddressTable failed, %!STATUS!", status);
goto error;
}
// Iterate through the addresses and delete (best effort) the ones for our interface
for (ULONG Index = 0; Index < pMibUnicastAddressTable->NumEntries; Index++)
{
MIB_UNICASTIPADDRESS_ROW* row = &pMibUnicastAddressTable->Table[Index];
if ((0 == memcmp(&row->InterfaceLuid, &pFilter->InterfaceLuid, sizeof(NET_LUID))))
{
LogInfo(DRIVER_DEFAULT, "Caching initial address: %!IPV6ADDR!", &row->Address.Ipv6.sin6_addr);
memcpy(pFilter->otCachedAddr + pFilter->otCachedAddrCount, &row->Address.Ipv6.sin6_addr, sizeof(IN6_ADDR));
pFilter->otCachedAddrCount++;
}
}
error:
if (NULL != pMibUnicastAddressTable)
{
FreeMibTable(pMibUnicastAddressTable);
}
LogFuncExitNT(DRIVER_DEFAULT, status);
return status;
}
// Callback from Windows TCPIP stack when an address change occurs
_IRQL_requires_max_(PASSIVE_LEVEL)
VOID
NETIOAPI_API_
otLwfAddressChangeCallback(
_In_ PVOID CallerContext,
_In_opt_ PMIB_UNICASTIPADDRESS_ROW Row,
_In_ MIB_NOTIFICATION_TYPE NotificationType
)
{
PMS_FILTER pFilter = (PMS_FILTER)CallerContext;
if (Row == NULL || pFilter == NULL) return;
// Ignore notifications that aren't for our interface
if (Row->InterfaceIndex != pFilter->InterfaceIndex) return;
LogFuncEntryMsg(DRIVER_DEFAULT, "%p (%u) %!IPV6ADDR!", pFilter, NotificationType, &Row->Address.Ipv6.sin6_addr);
// Since we don't pass in the initial flag, we shouldn't get this type
NT_ASSERT(NotificationType != MibInitialNotification);
// Make sure we can reference the interface
if (ExAcquireRundownProtection(&pFilter->ExternalRefs))
{
if (pFilter->DeviceStatus == OTLWF_DEVICE_STATUS_RADIO_MODE)
{
// Queue up the event for processing
otLwfEventProcessingIndicateAddressChange(
pFilter,
NotificationType,
&Row->Address.Ipv6.sin6_addr
);
}
else
{
NT_ASSERT(FALSE); // Need to add support for this in tunnel mode
}
// Release reference on the interface
ExReleaseRundownProtection(&pFilter->ExternalRefs);
}
LogFuncExit(DRIVER_DEFAULT);
}
// Callback on the OpenThread thread for processing an address change
_IRQL_requires_max_(PASSIVE_LEVEL)
VOID
otLwfEventProcessingAddressChanged(
_In_ PMS_FILTER pFilter,
_In_ MIB_NOTIFICATION_TYPE NotificationType,
_In_ PIN6_ADDR pAddr
)
{
LogFuncEntryMsg(DRIVER_DEFAULT, "%p (%u)", pFilter, NotificationType);
if (NotificationType == MibAddInstance ||
NotificationType == MibParameterNotification)
{
MIB_UNICASTIPADDRESS_ROW Row;
InitializeUnicastIpAddressEntry(&Row);
Row.Address.si_family = AF_INET6;
Row.Address.Ipv6.sin6_addr = *pAddr;
Row.InterfaceIndex = pFilter->InterfaceIndex;
Row.InterfaceLuid = pFilter->InterfaceLuid;
NTSTATUS status = GetUnicastIpAddressEntry(&Row);
if (!NT_SUCCESS(status))
{
LogError(DRIVER_DEFAULT, "GetUnicastIpAddressEntry failed, %!STATUS!", status);
}
else
{
otNetifAddress otAddr = {0};
memcpy(&otAddr.mAddress, pAddr, sizeof(IN6_ADDR));
otAddr.mPreferred = Row.PreferredLifetime != 0;
otAddr.mPrefixLength = Row.OnLinkPrefixLength;
otAddr.mValid = Row.ValidLifetime != 0;
BOOLEAN ShouldDelete = FALSE;
BOOLEAN AddedToCache = FALSE;
BOOLEAN IsCached = otLwfFindCachedAddrIndex(pFilter, pAddr) != -1;
// Ignore link local addresses
if (IN6_IS_ADDR_LINKLOCAL(pAddr) && !IsCached)
{
ShouldDelete = TRUE;
goto add_complete;
}
// Add to the cache if this is a new address
if (NotificationType == MibAddInstance && !IsCached)
{
AddedToCache = otLwfOnAddressAdded(pFilter, &otAddr, FALSE);
if (AddedToCache == FALSE)
{
ShouldDelete = TRUE;
goto add_complete;
}
}
// Update OpenThread if we don't have this cached or it is being updated
if (!IsCached/* || NotificationType == MibParameterNotification*/)
{
LogInfo(DRIVER_DEFAULT, "Filter %p trying to add/update address: %!IPV6ADDR!", pFilter, pAddr);
// Add (or update) the address to OpenThread
otError otError = otIp6AddUnicastAddress(pFilter->otCtx, &otAddr);
if (otError != OT_ERROR_NONE)
{
LogError(DRIVER_DEFAULT, "otIp6AddUnicastAddress failed, %!otError!", otError);
ShouldDelete = otError == OT_ERROR_NO_BUFS ? TRUE : FALSE;
}
}
add_complete:
// Remove it from TCPIP if necessary
if (ShouldDelete)
{
LogInfo(DRIVER_DEFAULT, "Filter %p deleting recently added address: %!IPV6ADDR!", pFilter, pAddr);
// Best effort remove address from TCPIP
(VOID)DeleteUnicastIpAddressEntry(&Row);
}
}
}
else if (NotificationType == MibDeleteInstance)
{
// Look for the address in our cache
int index = otLwfFindCachedAddrIndex(pFilter, pAddr);
// If it's not already deleted from our cache, then Windows
// is deleting the adddress and we need to update OpenThread.
if (index != -1)
{
// Update our cache
otLwfOnAddressRemoved(pFilter, (ULONG)index, FALSE);
LogInfo(DRIVER_DEFAULT, "Filter %p trying to remove address: %!IPV6ADDR!", pFilter, pAddr);
// Find the correct address from OpenThread to remove (best effort)
(void)otIp6RemoveUnicastAddress(pFilter->otCtx, (otIp6Address*)pAddr);
}
}
LogFuncExit(DRIVER_DEFAULT);
}
_IRQL_requires_max_(PASSIVE_LEVEL)
VOID
otLwfRadioAddressesUpdated(
_In_ PMS_FILTER pFilter
)
{
LogFuncEntry(DRIVER_DEFAULT);
ULONG FoundInOpenThread = 0; // Bit field
ULONG OriginalCacheLength = pFilter->otCachedAddrCount;
NT_ASSERT(pFilter->DeviceStatus == OTLWF_DEVICE_STATUS_RADIO_MODE);
const otNetifAddress* addr = otIp6GetUnicastAddresses(pFilter->otCtx);
// Process the addresses
while (addr)
{
int index = otLwfFindCachedAddrIndex(pFilter, (PIN6_ADDR)&addr->mAddress);
if (index == -1)
{
otLwfOnAddressAdded(pFilter, addr, TRUE);
}
else
{
NT_ASSERT(index < 8 * sizeof(FoundInOpenThread));
FoundInOpenThread |= 1 << index;
}
addr = addr->mNext;
}
// Look for missing addresses and mark them as removed
for (int i = OriginalCacheLength - 1; i >= 0; i--)
{
if ((FoundInOpenThread & (1 << i)) == 0)
{
otLwfOnAddressRemoved(pFilter, (ULONG)i, TRUE);
}
}
LogFuncExit(DRIVER_DEFAULT);
}
_IRQL_requires_max_(PASSIVE_LEVEL)
VOID
otLwfTunAddressesUpdated(
_In_ PMS_FILTER pFilter,
_In_reads_bytes_(value_data_len) const uint8_t* value_data_ptr,
_In_ spinel_size_t value_data_len,
_Out_ uint32_t *aNotifFlags
)
{
ULONG FoundInOpenThread = 0; // Bit field
ULONG OriginalCacheLength = pFilter->otCachedAddrCount;
LogFuncEntry(DRIVER_DEFAULT);
NT_ASSERT (pFilter->DeviceStatus == OTLWF_DEVICE_STATUS_THREAD_MODE);
*aNotifFlags = 0;
while (value_data_len > 0)
{
const uint8_t *entry_ptr = NULL;
spinel_size_t entry_len = 0;
spinel_ssize_t len = spinel_datatype_unpack(value_data_ptr, value_data_len, "d", &entry_ptr, &entry_len);
if (len < 1) break;
{
PIN6_ADDR pAddr = NULL;
otNetifAddress addr = { { 0 }, 0, 1, 1, 0, 0, 0, NULL };
uint32_t preferredLifetime = 0xFFFFFFFF;
uint32_t validLifetime = 0xFFFFFFFF;
spinel_datatype_unpack(
entry_ptr,
entry_len,
"6CLL",
&pAddr,
&addr.mPrefixLength,
&validLifetime,
&preferredLifetime);
addr.mPreferred = preferredLifetime != 0;
addr.mValid = validLifetime != 0;
if (pAddr)
{
int index = otLwfFindCachedAddrIndex(pFilter, pAddr);
if (index == -1)
{
memcpy_s(&addr.mAddress, sizeof(addr.mAddress), pAddr, sizeof(IN6_ADDR));
otLwfOnAddressAdded(pFilter, &addr, TRUE);
*aNotifFlags |= OT_CHANGED_IP6_ADDRESS_ADDED;
}
else
{
NT_ASSERT(index < 8 * sizeof(FoundInOpenThread));
FoundInOpenThread |= 1 << index;
}
}
}
value_data_len -= len;
value_data_ptr += len;
}
// Look for missing addresses and mark them as removed
for (int i = OriginalCacheLength - 1; i >= 0; i--)
{
if ((FoundInOpenThread & (1 << i)) == 0)
{
otLwfOnAddressRemoved(pFilter, (ULONG)i, TRUE);
*aNotifFlags |= OT_CHANGED_IP6_ADDRESS_REMOVED;
}
}
LogFuncExit(DRIVER_DEFAULT);
}