From 9e03b6e475bb42f496369a0cf8db99218f15b231 Mon Sep 17 00:00:00 2001 From: Dave Thaler Date: Thu, 6 Oct 2016 12:20:44 -0700 Subject: [PATCH] Add network monitor support for Windows Also fix a memory leak in the Linux code Change-Id: I8d243604a49cf4f2947849ac13624b2c044259d5 Signed-off-by: Dave Thaler Reviewed-on: https://gerrit.iotivity.org/gerrit/12953 Tested-by: jenkins-iotivity Reviewed-by: Soemin Tjong Reviewed-by: Dan Mihai Reviewed-by: David Antler Reviewed-by: Ashok Babu Channa --- resource/csdk/connectivity/api/cacommon.h | 3 +- resource/csdk/connectivity/inc/caipinterface.h | 6 +- resource/csdk/connectivity/inc/caipnwmonitor.h | 5 + .../csdk/connectivity/src/ip_adapter/caipserver.c | 88 ++- .../src/ip_adapter/linux/caipnwmonitor.c | 4 +- .../src/ip_adapter/windows/caipnwmonitor.c | 693 ++++++++++++++++----- 6 files changed, 635 insertions(+), 164 deletions(-) diff --git a/resource/csdk/connectivity/api/cacommon.h b/resource/csdk/connectivity/api/cacommon.h index 9fd62d1..8f455e6 100644 --- a/resource/csdk/connectivity/api/cacommon.h +++ b/resource/csdk/connectivity/api/cacommon.h @@ -553,10 +553,11 @@ typedef struct CASocket_t m6s; /**< multicast IPv6 secure */ CASocket_t m4; /**< multicast IPv4 */ CASocket_t m4s; /**< multicast IPv4 secure */ - CASocketFd_t netlinkFd; /**< netlink */ #if defined(_WIN32) + WSAEVENT addressChangeEvent;/**< Event used to signal address changes */ WSAEVENT shutdownEvent; /**< Event used to signal threads to stop */ #else + int netlinkFd; /**< netlink */ int shutdownFds[2]; /**< fds used to signal threads to stop */ #endif int selectTimeout; /**< in seconds */ diff --git a/resource/csdk/connectivity/inc/caipinterface.h b/resource/csdk/connectivity/inc/caipinterface.h index 5cf683a..e08d7f0 100644 --- a/resource/csdk/connectivity/inc/caipinterface.h +++ b/resource/csdk/connectivity/inc/caipinterface.h @@ -120,6 +120,7 @@ CAResult_t CAIPStopListenServer(); */ void CAIPSetPacketReceiveCallback(CAIPPacketReceivedCallback callback); +#ifdef WITH_ARDUINO /** * Set socket description for sending unicast UDP data. * Once the Unicast server is started, @@ -128,6 +129,7 @@ void CAIPSetPacketReceiveCallback(CAIPPacketReceivedCallback callback); * @param[in] socketFD Socket descriptor used for sending UDP data. */ void CAIPSetUnicastSocket(int socketFD); +#endif /** * Set the port number for sending unicast UDP data. @@ -164,8 +166,8 @@ void CAIPPullData(); #define CA_SECURE_COAP 5684 /** - * Let the network monitor update the polling interval. - * @param [in] current polling interval + * Let the network monitor update the polling interval, in seconds. + * @param[in] interval current polling interval * * @return desired polling interval */ diff --git a/resource/csdk/connectivity/inc/caipnwmonitor.h b/resource/csdk/connectivity/inc/caipnwmonitor.h index 8946135..d76c6fe 100644 --- a/resource/csdk/connectivity/inc/caipnwmonitor.h +++ b/resource/csdk/connectivity/inc/caipnwmonitor.h @@ -47,6 +47,11 @@ extern "C" typedef void (*CAIPAdapterStateChangeCallback)(CATransportAdapter_t adapter, CANetworkStatus_t status); +/* + * Structure for IP address information, to be used to construct a CAEndpoint_t. The + * structure name is a misnomer, as there is one entry per address not one per interface. + * An interface with 4 addresses should result in 4 instances of CAInterface_t. + */ typedef struct { char name[INTERFACE_NAME_MAX]; diff --git a/resource/csdk/connectivity/src/ip_adapter/caipserver.c b/resource/csdk/connectivity/src/ip_adapter/caipserver.c index 1e2a1f9..10f988b 100644 --- a/resource/csdk/connectivity/src/ip_adapter/caipserver.c +++ b/resource/csdk/connectivity/src/ip_adapter/caipserver.c @@ -368,7 +368,11 @@ static void CAFindReadyMessage() PUSH_HANDLE(caglobals.ip.shutdownEvent, eventArray, arraySize); } - /** @todo Support netlink events */ + if (WSA_INVALID_EVENT != caglobals.ip.addressChangeEvent) + { + INSERT_SOCKET(OC_INVALID_SOCKET, socketArray, arraySize); + PUSH_HANDLE(caglobals.ip.addressChangeEvent, eventArray, arraySize); + } // Should not have overflowed buffer assert(arraySize <= (_countof(socketArray))); @@ -400,7 +404,20 @@ static void CAFindReadyMessage() OIC_LOG_V(ERROR, TAG, "WSAResetEvent failed 0x%08x", WSAGetLastError()); } - // Break out if shutdownEvent is triggered + // Handle address changes if addressChangeEvent is triggered. + if ((caglobals.ip.addressChangeEvent != WSA_INVALID_EVENT) && + (caglobals.ip.addressChangeEvent == eventArray[eventIndex])) + { + CAInterface_t *newAddress; + while ((newAddress = CAFindInterfaceChange()) != NULL) + { + CAProcessNewInterface(newAddress); + OICFree(newAddress); + } + break; + } + + // Break out if shutdownEvent is triggered. if ((caglobals.ip.shutdownEvent != WSA_INVALID_EVENT) && (caglobals.ip.shutdownEvent == eventArray[eventIndex])) { @@ -460,6 +477,23 @@ static void CAEventReturned(CASocketFd_t socket) #endif +void CAUnregisterForAddressChanges() +{ +#ifdef _WIN32 + if (caglobals.ip.addressChangeEvent != WSA_INVALID_EVENT) + { + WSACloseEvent(caglobals.ip.addressChangeEvent); + caglobals.ip.addressChangeEvent = WSA_INVALID_EVENT; + } +#else + if (caglobals.ip.netlinkFd != OC_INVALID_SOCKET) + { + close(caglobals.ip.netlinkFd); + caglobals.ip.netlinkFd = OC_INVALID_SOCKET; + } +#endif +} + void CADeInitializeIPGlobals() { CLOSE_SOCKET(u6); @@ -471,15 +505,7 @@ void CADeInitializeIPGlobals() CLOSE_SOCKET(m4); CLOSE_SOCKET(m4s); - if (caglobals.ip.netlinkFd != OC_INVALID_SOCKET) - { -#ifdef _WIN32 - closesocket(caglobals.ip.netlinkFd); -#else - close(caglobals.ip.netlinkFd); -#endif - caglobals.ip.netlinkFd = OC_INVALID_SOCKET; - } + CAUnregisterForAddressChanges(); } static CAResult_t CAReceiveMessage(CASocketFd_t fd, CATransportFlags_t flags) @@ -769,8 +795,15 @@ static CASocketFd_t CACreateSocket(int family, uint16_t *port, bool isMulticast) } \ CHECKFD(caglobals.ip.NAME.fd) -static void CAInitializeNetlink() +static void CARegisterForAddressChanges() { +#ifdef _WIN32 + caglobals.ip.addressChangeEvent = WSACreateEvent(); + if (WSA_INVALID_EVENT != caglobals.ip.addressChangeEvent) + { + OIC_LOG(ERROR, TAG, "WSACreateEvent failed"); + } +#else caglobals.ip.netlinkFd = OC_INVALID_SOCKET; #ifdef __linux__ // create NETLINK fd for interface change notifications @@ -796,6 +829,7 @@ static void CAInitializeNetlink() } } #endif +#endif } static void CAInitializeFastShutdownMechanism() @@ -925,8 +959,8 @@ CAResult_t CAIPStartServer(const ca_thread_pool_t threadPool) // set up appropriate FD mechanism for fast shutdown CAInitializeFastShutdownMechanism(); - // create source of network interface change notifications - CAInitializeNetlink(); + // create source of network address change notifications + CARegisterForAddressChanges(); caglobals.ip.selectTimeout = CAGetPollingInterval(caglobals.ip.selectTimeout); @@ -1031,7 +1065,18 @@ static void applyMulticastToInterface4(uint32_t ifindex) #if !defined(WSAEINVAL) if (EADDRINUSE != errno) #else - if (WSAEINVAL != WSAGetLastError()) // Joining multicast group more than once (IPv4 Flavor) + if (WSAEINVAL == WSAGetLastError()) + { + // We're trying to join the multicast group again. + // If the interface has gone down and come back up, the socket might be in + // an inconsistent state where it still thinks we're joined when the interface + // doesn't think we're joined. So try to leave and rejoin the group just in case. + setsockopt(caglobals.ip.m4s.fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, OPTVAL_T(&mreq), + sizeof(mreq)); + ret = setsockopt(caglobals.ip.m4s.fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, OPTVAL_T(&mreq), + sizeof(mreq)); + } + if (OC_SOCKET_ERROR == ret) #endif { OIC_LOG_V(ERROR, TAG, "SECURE IPv4 IP_ADD_MEMBERSHIP failed: %s", CAIPS_GET_ERROR); @@ -1051,9 +1096,18 @@ static void applyMulticast6(int fd, struct in6_addr *addr, uint32_t ifindex) if (OC_SOCKET_ERROR == ret) { #if !defined(_WIN32) - if (EADDRINUSE != errno) + if (EADDRINUSE != errno) #else - if (WSAEINVAL != WSAGetLastError()) // Joining multicast group more than once (IPv6 Flavor) + if (WSAEINVAL == WSAGetLastError()) + { + // We're trying to join the multicast group again. + // If the interface has gone down and come back up, the socket might be in + // an inconsistent state where it still thinks we're joined when the interface + // doesn't think we're joined. So try to leave and rejoin the group just in case. + setsockopt(fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, OPTVAL_T(&mreq), sizeof(mreq)); + ret = setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, OPTVAL_T(&mreq), sizeof(mreq)); + } + if (OC_SOCKET_ERROR == ret) #endif { OIC_LOG_V(ERROR, TAG, "IPv6 IPV6_JOIN_GROUP failed: %s", CAIPS_GET_ERROR); diff --git a/resource/csdk/connectivity/src/ip_adapter/linux/caipnwmonitor.c b/resource/csdk/connectivity/src/ip_adapter/linux/caipnwmonitor.c index d7cb718..c1471b2 100644 --- a/resource/csdk/connectivity/src/ip_adapter/linux/caipnwmonitor.c +++ b/resource/csdk/connectivity/src/ip_adapter/linux/caipnwmonitor.c @@ -85,7 +85,7 @@ static bool CACmpNetworkList(uint32_t ifiindex); static CAResult_t CAAddNetworkMonitorList(CAInterface_t *ifitem); /** - * Remove network interace from list. + * Remove network interface from list. */ static void CARemoveNetworkMonitorList(int ifiindex); @@ -337,7 +337,6 @@ CAInterface_t *CAFindInterfaceChange() struct ifinfomsg *ifi = (struct ifinfomsg *)NLMSG_DATA(nh); int ifiIndex = ifi->ifi_index; - u_arraylist_t *iflist = CAIPGetInterfaceInformation(ifiIndex); if ((!ifi || (ifi->ifi_flags & IFF_LOOPBACK) || !(ifi->ifi_flags & IFF_RUNNING))) { @@ -350,6 +349,7 @@ CAInterface_t *CAFindInterfaceChange() continue; } + u_arraylist_t *iflist = CAIPGetInterfaceInformation(ifiIndex); if (!iflist) { OIC_LOG_V(ERROR, TAG, "get interface info failed: %s", strerror(errno)); diff --git a/resource/csdk/connectivity/src/ip_adapter/windows/caipnwmonitor.c b/resource/csdk/connectivity/src/ip_adapter/windows/caipnwmonitor.c index 53b058c..5d3f37b 100644 --- a/resource/csdk/connectivity/src/ip_adapter/windows/caipnwmonitor.c +++ b/resource/csdk/connectivity/src/ip_adapter/windows/caipnwmonitor.c @@ -1,7 +1,6 @@ /* ***************************************************************** * -* Copyright 2016 Intel Corporation -* +* Copyright 2016 Microsoft * * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,76 +17,431 @@ * ******************************************************************/ +#include "iotivity_config.h" #include "caipinterface.h" +#include #include #include -#include #include +#include #include -#include "platform_features.h" +#include #include #include +#include "octhread.h" #include "caadapterutils.h" #include "logger.h" #include "oic_malloc.h" #include "oic_string.h" #include "caipnwmonitor.h" +#include #define TAG "IP_MONITOR" /** - * @todo Implement network interface monitoring in case the IP changes. - * Not critical for win32 bring-up. + * Mutex for synchronizing access to cached address information. + */ +static oc_mutex g_CAIPNetworkMonitorMutex = NULL; + +static bool g_CAIPNetworkMonitorSomeAddressWentAway = false; + +typedef struct CANewAddress_t { + struct CANewAddress_t *next; + struct CANewAddress_t *prev; + CAInterface_t *ipAddressInfo; +} CANewAddress_t; + +/** + * List of network addresses that we've already reported. + */ +static u_arraylist_t *g_CAIPNetworkMonitorAddressList = NULL; + +/** + * Queue of new addresses that haven't yet been returned in CAFindInterfaceChange(). + */ +static CANewAddress_t *g_CAIPNetworkMonitorNewAddressQueue = NULL; + +/** + * Transport adapter change callback list. + */ +static struct CAIPCBData_t *g_CAIPNetworkMonitorAdapterCallbackList = NULL; + +static CAInterface_t *AllocateCAInterface(int index, const char *name, int family, + const char *addr, int flags); + +static u_arraylist_t *GetInterfaceInformation(int desiredIndex); + +static void CAIPDestroyNetworkMonitorList(); + +static CAResult_t CAIPInitializeNetworkMonitorList() +{ + assert(!g_CAIPNetworkMonitorMutex); + assert(!g_CAIPNetworkMonitorAddressList); + + g_CAIPNetworkMonitorMutex = oc_mutex_new(); + if (!g_CAIPNetworkMonitorMutex) + { + OIC_LOG(ERROR, TAG, "oc_mutex_new has failed"); + return CA_STATUS_FAILED; + } + + g_CAIPNetworkMonitorAddressList = u_arraylist_create(); + if (!g_CAIPNetworkMonitorAddressList) + { + OIC_LOG(ERROR, TAG, "u_arraylist_create has failed"); + CAIPDestroyNetworkMonitorList(); + return CA_STATUS_FAILED; + } + + return CA_STATUS_OK; +} + +/** + * Destroy the network monitoring list. + */ +static void CAIPDestroyNetworkMonitorList() +{ + // Free any new addresses waiting to be indicated up. + while (g_CAIPNetworkMonitorNewAddressQueue) + { + CANewAddress_t *change = g_CAIPNetworkMonitorNewAddressQueue; + DL_DELETE(g_CAIPNetworkMonitorNewAddressQueue, change); + OICFree(change->ipAddressInfo); + OICFree(change); + } + + // Free our cache of operational addresses. + if (g_CAIPNetworkMonitorAddressList) + { + u_arraylist_destroy(g_CAIPNetworkMonitorAddressList); + g_CAIPNetworkMonitorAddressList = NULL; + } + + if (g_CAIPNetworkMonitorMutex) + { + oc_mutex_free(g_CAIPNetworkMonitorMutex); + g_CAIPNetworkMonitorMutex = NULL; + } +} + +/** + * See if a CAInterface_t with a given index and address already exists in a list. + * + * @param[in] ifIndex Interface index to look for. + * @param[in] family Family of address to look for. + * @param[in] addr Address to look for. + * @return true if already in the list, false if not. + */ +static bool CACmpNetworkList(uint32_t ifIndex, int family, const char *addr, u_arraylist_t *iflist) +{ + uint32_t list_length = u_arraylist_length(iflist); + for (uint32_t list_index = 0; list_index < list_length; list_index++) + { + CAInterface_t *currItem = (CAInterface_t *) u_arraylist_get(iflist, list_index); + if ((currItem->index == ifIndex) && (currItem->family == family) && + (strcmp(currItem->addr, addr) == 0)) + { + return true; + } + } + return false; +} + +static HANDLE g_CAIPNetworkMonitorChangeNotificationHandle = NULL; + +/** + * Handle a notification that the IP address info changed. + * + * @param[in] context Context passed to NotifyUnicastIpChange. + * @param[in] row Interface that changed, or NULL on the initial callback. + * @param[in] notificationType Type of change that occurred. + */ +static void IpAddressChangeCallback(void *context, + MIB_UNICASTIPADDRESS_ROW *row, + MIB_NOTIFICATION_TYPE notificationType) +{ + oc_mutex_lock(g_CAIPNetworkMonitorMutex); + + // Fetch new network address info. + u_arraylist_t *newList = GetInterfaceInformation(0); + uint32_t newLen = u_arraylist_length(newList); + + u_arraylist_t *oldList = g_CAIPNetworkMonitorAddressList; + uint32_t oldLen = u_arraylist_length(oldList); + + if (caglobals.ip.addressChangeEvent) + { + // Check whether any addresses went away. + for (uint32_t i = 0; i < oldLen; i++) + { + CAInterface_t *ifitem = (CAInterface_t *)u_arraylist_get(oldList, i); + if (!CACmpNetworkList(ifitem->index, ifitem->family, ifitem->addr, newList)) + { + g_CAIPNetworkMonitorSomeAddressWentAway = true; + break; + } + } + + // Check whether any new addresses are available. + for (uint32_t i = 0; i < newLen; i++) + { + CAInterface_t *ifitem = (CAInterface_t *)u_arraylist_get(newList, i); + if (!CACmpNetworkList(ifitem->index, ifitem->family, ifitem->addr, oldList)) + { + // Create a new CAInterface_t to add to the queue to indicate to the higher + // layer. We cannot simply invoke the callback here currently, since the + // higher layer is not thread-safe. + CAInterface_t *dup = AllocateCAInterface(ifitem->index, ifitem->name, + ifitem->family, ifitem->addr, + ifitem->flags); + CANewAddress_t *change = (CANewAddress_t *)OICCalloc(1, sizeof(*change)); + if (change) + { + change->ipAddressInfo = dup; + DL_APPEND(g_CAIPNetworkMonitorNewAddressQueue, change); + } + else + { + OIC_LOG(WARNING, TAG, "Couldn't allocate memory for CANewAddress_t"); + } + } + } + + // If the new address queue is not empty, signal the transport server that it needs + // to call CAFindInterfaceChange(). We don't need to set the event if an address + // went away, since the higher layer just uses the event to ask for new addresses + // in order to join the multicast group on the associated interface and address family. + if (g_CAIPNetworkMonitorNewAddressQueue) + { + int ret = WSASetEvent(caglobals.ip.addressChangeEvent); + + // Setting the event should always succeed, since the handle should always be + // valid when this code is reached. + assert(ret); + } + } + + // Replace old cached info. + g_CAIPNetworkMonitorAddressList = newList; + u_arraylist_destroy(oldList); + + oc_mutex_unlock(g_CAIPNetworkMonitorMutex); +} + +/** + * Start network monitor. + * + * @param[in] callback Callback to be notified when IP/TCP adapter connection state changes. + * @param[in] adapter Transport adapter. + * @return ::CA_STATUS_OK or an appropriate error code. */ CAResult_t CAIPStartNetworkMonitor(CAIPAdapterStateChangeCallback callback, CATransportAdapter_t adapter) { + CAResult_t res = CAIPInitializeNetworkMonitorList(); + if (res != CA_STATUS_OK) + { + return res; + } + + res = CAIPSetNetworkMonitorCallback(callback, adapter); + if (res != CA_STATUS_OK) + { + return res; + } + + if (g_CAIPNetworkMonitorChangeNotificationHandle == NULL) + { + int err = NotifyUnicastIpAddressChange(AF_UNSPEC, IpAddressChangeCallback, NULL, + true, + &g_CAIPNetworkMonitorChangeNotificationHandle); + if (err != NO_ERROR) + { + return CA_STATUS_FAILED; + } + } return CA_STATUS_OK; } /** - * @todo Implement network interface monitoring in case the IP changes. - * Not critical for win32 bring-up. + * Stops network monitor. + * + * @param[in] adapter Transport adapter. + * @return ::CA_STATUS_OK or an appropriate error code. */ CAResult_t CAIPStopNetworkMonitor(CATransportAdapter_t adapter) - { - return CA_STATUS_OK; + if (g_CAIPNetworkMonitorChangeNotificationHandle != NULL) + { + int err = CancelMibChangeNotify2(g_CAIPNetworkMonitorChangeNotificationHandle); + assert(err == NO_ERROR); + g_CAIPNetworkMonitorChangeNotificationHandle = NULL; + } + + CAIPDestroyNetworkMonitorList(); + return CAIPUnSetNetworkMonitorCallback(adapter); } /** - * @todo Implement network interface monitoring. - * Not used in win32, but caipserver currently requires this function - * be defined. not critical. + * Let the network monitor update the polling interval. + * @param[in] interval Current polling interval, in seconds + * + * @return desired polling interval */ int CAGetPollingInterval(int interval) { + // Don't change the polling interval. return interval; } /** - * @todo Implement network interface monitoring. - * Not used in win32, but caipserver currently requires this function - * be defined. not critical. + * Pass the changed network status through the stored callback. + * Note that the current API doesn't allow us to specify which address changed, + * the caller has to look at the return from CAFindInterfaceChange() to learn about + * each new address, and look through CAIPGetInterfaceInformation() to see what's + * missing to detect any removed addresses. + * + * @param[in] status Network status to pass to the callback. */ -CAInterface_t *CAFindInterfaceChange() +static void CAIPPassNetworkChangesToTransportAdapter(CANetworkStatus_t status) { - CAInterface_t *foundNewInterface = NULL; - return foundNewInterface; + CAIPCBData_t *cbitem = NULL; + LL_FOREACH(g_CAIPNetworkMonitorAdapterCallbackList, cbitem) + { + if (cbitem && cbitem->adapter) + { + cbitem->callback(cbitem->adapter, status); + } + } } /** - * @todo Implement network interface monitoring. - * Not critical for win32 bring-up. + * Set callback for receiving local IP/TCP adapter connection status. + * + * @param[in] callback Callback to be notified when IP/TCP adapter connection state changes. + * @param[in] adapter Transport adapter. + * @return ::CA_STATUS_OK or an appropriate error code. */ CAResult_t CAIPSetNetworkMonitorCallback(CAIPAdapterStateChangeCallback callback, CATransportAdapter_t adapter) { - return CA_NOT_SUPPORTED; + if (!callback) + { + OIC_LOG(ERROR, TAG, "callback is null"); + return CA_STATUS_INVALID_PARAM; + } + + CAIPCBData_t *cbitem = NULL; + LL_FOREACH(g_CAIPNetworkMonitorAdapterCallbackList, cbitem) + { + if ((adapter == cbitem->adapter) && (callback == cbitem->callback)) + { + OIC_LOG(DEBUG, TAG, "this callback is already added"); + return CA_STATUS_OK; + } + } + + cbitem = (CAIPCBData_t *)OICCalloc(1, sizeof(*cbitem)); + if (!cbitem) + { + OIC_LOG(ERROR, TAG, "Malloc failed"); + return CA_STATUS_FAILED; + } + + cbitem->adapter = adapter; + cbitem->callback = callback; + LL_APPEND(g_CAIPNetworkMonitorAdapterCallbackList, cbitem); + + return CA_STATUS_OK; +} + +/** + * Unset callback for receiving local IP/TCP adapter connection status. + * + * @param[in] adapter Transport adapter. + * @return CA_STATUS_OK. + */ +CAResult_t CAIPUnSetNetworkMonitorCallback(CATransportAdapter_t adapter) +{ + CAIPCBData_t *cbitem = NULL; + CAIPCBData_t *tmpCbitem = NULL; + LL_FOREACH_SAFE(g_CAIPNetworkMonitorAdapterCallbackList, cbitem, tmpCbitem) + { + if (cbitem && adapter == cbitem->adapter) + { + OIC_LOG(DEBUG, TAG, "remove specific callback"); + LL_DELETE(g_CAIPNetworkMonitorAdapterCallbackList, cbitem); + OICFree(cbitem); + return CA_STATUS_OK; + } + } + return CA_STATUS_OK; +} + +/** + * Allocate a new CAInterface_t entry for a given IP address. + */ +static CAInterface_t *AllocateCAInterface(int index, const char *name, int family, + const char *addr, int flags) +{ + CAInterface_t *ifitem = (CAInterface_t *)OICCalloc(1, sizeof(*ifitem)); + if (!ifitem) + { + OIC_LOG(ERROR, TAG, "Allocating memory for a CAInterface_t failed"); + return NULL; + } + + OICStrcpy(ifitem->name, sizeof(ifitem->name), name); + ifitem->index = index; + ifitem->family = family; + OICStrcpy(ifitem->addr, sizeof(ifitem->addr), addr); + ifitem->flags = flags; + + return ifitem; +} + +/** + * Find a new IP address. Note that this can only return one, so the caller must + * call multiple times to get the list, which is pretty inefficient. The caller is + * responsible for freeing the pointer returned via OICFree(). + * @todo Change the API to allow returning a list or, even better, allow calling + * CAIPPassNetworkChangesToTransportAdapter() at any time from IpAddressChangeCallback. + * + * @return Dynamically allocated IP address entry, or NULL if no change. + */ +CAInterface_t *CAFindInterfaceChange() +{ + oc_mutex_lock(g_CAIPNetworkMonitorMutex); + + bool someAddressWentAway = g_CAIPNetworkMonitorSomeAddressWentAway; + g_CAIPNetworkMonitorSomeAddressWentAway = false; + + CAInterface_t *newAddress = NULL; + if (g_CAIPNetworkMonitorNewAddressQueue) + { + // Pop the first new address to return. + CANewAddress_t *change = g_CAIPNetworkMonitorNewAddressQueue; + DL_DELETE(g_CAIPNetworkMonitorNewAddressQueue, change); + newAddress = change->ipAddressInfo; + OICFree(change); + } + + oc_mutex_unlock(g_CAIPNetworkMonitorMutex); + + if (someAddressWentAway) + { + CAIPPassNetworkChangesToTransportAdapter(CA_INTERFACE_DOWN); + } + if (newAddress) + { + CAIPPassNetworkChangesToTransportAdapter(CA_INTERFACE_UP); + } + + return newAddress; } -bool IsValidAdapter(PIP_ADAPTER_ADDRESSES pAdapterAddr, int desiredIndex, uint16_t family) +static bool IsValidNetworkAdapter(PIP_ADAPTER_ADDRESSES pAdapterAddr, int desiredIndex) { bool valid = true; @@ -105,189 +459,244 @@ bool IsValidAdapter(PIP_ADAPTER_ADDRESSES pAdapterAddr, int desiredIndex, uint16 valid = false; } - // If the adapter must support the requested family - if ((family == AF_INET6) && ((pAdapterAddr->Flags & IP_ADAPTER_IPV6_ENABLED) == 0)) + if ((pAdapterAddr->OperStatus & IfOperStatusUp) == 0) { - OIC_LOG_V(DEBUG, TAG, "\t\tInterface %i does not support IPv6", pAdapterAddr->IfIndex); + OIC_LOG_V(DEBUG, TAG, "\t\tInterface %i is not operational.", pAdapterAddr->IfIndex); valid = false; } - else if ((family == AF_INET) && ((pAdapterAddr->Flags & IP_ADAPTER_IPV4_ENABLED) == 0)) + return valid; +} + +/* + * Allocate a new CAInterface_t structure and add it to a given list. A new entry is added + * for each address. + * + * @param[in/out] iflist List to add to. + * @param[in] name Interface name. + * @param[in] index Interface index. + * @param[in] family Address family. + * @param[in] addr Address. + * @return Pointer to entry added, or NULL on failure. + */ +CAInterface_t *AddCAInterface(u_arraylist_t *iflist, const char *name, uint32_t index, + uint16_t family, const char *addr) +{ + CAInterface_t *ifitem = AllocateCAInterface(index, name, family, addr, IFF_UP); + if (ifitem == NULL) { - OIC_LOG_V(DEBUG, TAG, "\t\tInterface %i does not support IPv4", pAdapterAddr->IfIndex); - valid = false; + return NULL; } - if ((pAdapterAddr->OperStatus & IfOperStatusUp) == 0) + if (!u_arraylist_add(iflist, ifitem)) { - OIC_LOG_V(DEBUG, TAG, "\t\tInterface %i is not operational.", pAdapterAddr->IfIndex); - valid = false; + OIC_LOG(ERROR, TAG, "u_arraylist_add failed"); + OICFree(ifitem); + return NULL; } - return valid; + return ifitem; } - -bool AddCAInterface(u_arraylist_t *iflist, const char * name, uint32_t index, uint16_t family) +bool IsValidAddress(PIP_ADAPTER_UNICAST_ADDRESS pAddress) { - bool bSucceeded = false; - CAInterface_t *ifitem = (CAInterface_t *)OICCalloc(1, sizeof(*ifitem)); - if (ifitem) + if (pAddress->Address.lpSockaddr->sa_family != AF_INET6) { - OICStrcpy(ifitem->name, INTERFACE_NAME_MAX, name); - ifitem->index = index; - ifitem->family = family; - ifitem->flags |= IFF_UP;// IsValidAddress() will have filtered out non-operational addresses already. - - if (u_arraylist_add(iflist, ifitem)) - { - bSucceeded = true; - } - else - { - OIC_LOG(ERROR, TAG, "u_arraylist_add failed"); - OICFree(ifitem); - } + // All IPv4 addresses are valid. + return true; } - else + + PSOCKADDR_IN6 sockAddr = (PSOCKADDR_IN6)pAddress->Address.lpSockaddr; + if (Ipv6UnicastAddressScope(sockAddr->sin6_addr.s6_addr) == ScopeLevelLink) { - OIC_LOG(ERROR, TAG, "Allocating memory for a CAInterface_t failed"); + // IPv6 link local addresses are valid. + return true; } - return bSucceeded; + + // Other IPv6 addresses are valid if they are DNS eligible. + // That is, ignore temporary addresses. + return ((pAddress->Flags & IP_ADAPTER_ADDRESS_DNS_ELIGIBLE) != 0); } -bool AddInterfaces(PIP_ADAPTER_ADDRESSES pAdapterAddr, u_arraylist_t *iflist, int desiredIndex) +bool AddAddresses(PIP_ADAPTER_ADDRESSES pAdapterAddr, u_arraylist_t *iflist, int desiredIndex) { - bool bSucceeded = false; + bool bSucceeded = true; for (PIP_ADAPTER_ADDRESSES pCurAdapterAddr = pAdapterAddr; pCurAdapterAddr != NULL; pCurAdapterAddr = pCurAdapterAddr->Next) { OIC_LOG_V(DEBUG, TAG, "\tInterface Index: %u", pCurAdapterAddr->IfIndex); OIC_LOG_V(DEBUG, TAG, "\tInterface name: %s", pCurAdapterAddr->AdapterName); - // Prefer IPv6 over IPv4. - if (pCurAdapterAddr->Flags & IP_ADAPTER_IPV6_ENABLED) + if (!IsValidNetworkAdapter(pCurAdapterAddr, desiredIndex)) { - // Do not add loopback, duplicate, or non-operational adapters - if (IsValidAdapter(pCurAdapterAddr, desiredIndex, AF_INET6)) - { - if (AddCAInterface(iflist, pCurAdapterAddr->AdapterName, pCurAdapterAddr->IfIndex, AF_INET6)) - { - OIC_LOG_V(DEBUG, TAG, "\t\tAdded IPv6 interface %i", pCurAdapterAddr->IfIndex); - bSucceeded = true; - } - else - { - OIC_LOG_V(ERROR, TAG, "\tAdding IPv6 interface %i failed", pCurAdapterAddr->IfIndex); - break; - } - } - else - { - OIC_LOG_V(DEBUG, TAG, "\t\tIPv6 interface %i not valid, skipping...", pCurAdapterAddr->IfIndex); - } + continue; } - if (pCurAdapterAddr->Flags & IP_ADAPTER_IPV4_ENABLED) + for (PIP_ADAPTER_UNICAST_ADDRESS pAddress = pCurAdapterAddr->FirstUnicastAddress; + pAddress != NULL; + pAddress = pAddress->Next) { - // Do not add loopback, duplicate, or non-operational adapters - if (IsValidAdapter(pCurAdapterAddr, desiredIndex, AF_INET)) + if (!IsValidAddress(pAddress)) { - if (AddCAInterface(iflist, pCurAdapterAddr->AdapterName, pCurAdapterAddr->IfIndex, AF_INET)) - { - OIC_LOG_V(DEBUG, TAG, "\t\tAdded IPv4 interface %i", pCurAdapterAddr->IfIndex); - bSucceeded = true; - } - else - { - OIC_LOG_V(ERROR, TAG, "\tAdding IPv4 interface %i failed", pCurAdapterAddr->IfIndex); - break; - } + continue; + } + + char addr[INET6_ADDRSTRLEN]; + if (!inet_ntop(pAddress->Address.lpSockaddr->sa_family, + INETADDR_ADDRESS(pAddress->Address.lpSockaddr), + addr, + sizeof(addr))) + { + continue; } - else + + CAInterface_t *ipAddressInfo = AddCAInterface(iflist, pCurAdapterAddr->AdapterName, + pCurAdapterAddr->IfIndex, + pAddress->Address.lpSockaddr->sa_family, + addr); + if (!ipAddressInfo) { - OIC_LOG_V(DEBUG, TAG, "\t\tIPv6 interface %i not valid, skipping...", pCurAdapterAddr->IfIndex); + OIC_LOG_V(ERROR, TAG, "\tAdding address on interface %i failed", + pCurAdapterAddr->IfIndex); + bSucceeded = false; + break; } + OIC_LOG_V(DEBUG, TAG, "\t\tAdded address %s", ipAddressInfo->addr); } } return bSucceeded; } +/* + * Get the set of IP_ADAPTER_ADDRESSES structures. The caller is responsible for + * freeng the set using OICFree on the pointer returned. + * + * @return List of network adapters. + */ PIP_ADAPTER_ADDRESSES GetAdapters() { - ULONG ulOutBufLen = sizeof(IP_ADAPTER_ADDRESSES); - PIP_ADAPTER_ADDRESSES pAdapterAddr = (IP_ADAPTER_ADDRESSES *) OICMalloc(ulOutBufLen); - if (pAdapterAddr != NULL) + ULONG ulOutBufLen = 0; + PIP_ADAPTER_ADDRESSES pAdapterAddr = NULL; + + // We don't need most of the default information, so optimize this call by not + // asking for them. + ULONG flags = GAA_FLAG_SKIP_ANYCAST | + GAA_FLAG_SKIP_MULTICAST | + GAA_FLAG_SKIP_DNS_SERVER | + GAA_FLAG_SKIP_FRIENDLY_NAME; + + // Call up to 3 times: once to get the size, once to get the data, and once more + // just in case there was an increase in length in between the first two. If the + // length is still increasing due to more addresses being added, even this may fail + // and we'll have to wait for the next IP address change notification. + for (int i = 0; i < 3; i++) { - ULONG flags = 0; ULONG ret = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, pAdapterAddr, &ulOutBufLen); if (ERROR_BUFFER_OVERFLOW == ret) { - // Redo with updated length - OICFree(pAdapterAddr); - pAdapterAddr = (PIP_ADAPTER_ADDRESSES) OICMalloc(ulOutBufLen); - if (pAdapterAddr != NULL) { - ret = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, pAdapterAddr, &ulOutBufLen); - if (NO_ERROR != ret) - { - OIC_LOG(ERROR, TAG, "GetAdaptersAddresses() failed"); - OICFree(pAdapterAddr); - pAdapterAddr = NULL; - } - else - { - // Succeeded getting adapters - } - } - else + // Redo with updated length. + if (pAdapterAddr != NULL) { - OIC_LOG(ERROR, TAG, "Second time allocating memory for GetAdaptersAddresses() failed"); + OICFree(pAdapterAddr); + } + pAdapterAddr = (PIP_ADAPTER_ADDRESSES) OICMalloc(ulOutBufLen); + if (pAdapterAddr == NULL) { + OIC_LOG(ERROR, TAG, "Allocating memory for GetAdaptersAddresses() failed"); + break; } + continue; } - else + if (NO_ERROR != ret) { - OIC_LOG(ERROR, TAG, "Expected GetAdaptersAddresses() to fail on first try, but it didn't."); + OIC_LOG(ERROR, TAG, "GetAdaptersAddresses() failed"); + break; } + + // Succeeded getting adapters + return pAdapterAddr; } - else + + if (pAdapterAddr != NULL) { - OIC_LOG(ERROR, TAG, "First time allocating memory for GetAdaptersAddresses() failed"); + OICFree(pAdapterAddr); } - return pAdapterAddr; + return NULL; +} + +/** + * Get the list of CAInterface_t items. Currently only 0 is passed as the desiredIndex by any + * caller. + * + * @param[in] desiredIndex Network interface index, or 0 for all. + * @return List of CAInterface_t items. + */ +static u_arraylist_t *GetInterfaceInformation(int desiredIndex) +{ + if (desiredIndex < 0) + { + OIC_LOG_V(ERROR, TAG, "invalid index : %d", desiredIndex); + return NULL; + } + + u_arraylist_t *iflist = u_arraylist_create(); + if (!iflist) + { + OIC_LOG(ERROR, TAG, "Failed to create iflist"); + return NULL; + } + + PIP_ADAPTER_ADDRESSES pAdapterAddr = GetAdapters(); + if (!pAdapterAddr) + { + OIC_LOG(ERROR, TAG, "Enumerating Adapters failed"); + u_arraylist_destroy(iflist); + return NULL; + } + + // Cycle through network adapters. + // Add valid network addresses to the address list. + bool ret = AddAddresses(pAdapterAddr, iflist, desiredIndex); + if (false == ret) + { + OIC_LOG(ERROR, TAG, "AddAddresses() failed"); + u_arraylist_destroy(iflist); + iflist = NULL; + } + + // Finished with network adapter list. + OICFree(pAdapterAddr); + + return iflist; } u_arraylist_t *CAIPGetInterfaceInformation(int desiredIndex) { u_arraylist_t *iflist = u_arraylist_create(); - if (iflist) + if (!iflist) { - PIP_ADAPTER_ADDRESSES pAdapterAddr = NULL; - pAdapterAddr = GetAdapters(); - if (pAdapterAddr) - { - // Cycle through adapters - // Add valid adapters to the interface list. - bool ret = AddInterfaces(pAdapterAddr, iflist, desiredIndex); - if (false == ret) - { - OIC_LOG(ERROR, TAG, "AddInterfaces() failed"); - u_arraylist_destroy(iflist); - iflist = NULL; - } + OIC_LOG(ERROR, TAG, "Failed to create iflist"); + return NULL; + } - // Finished with Adapter List - OICFree(pAdapterAddr); - } - else + // Avoid extra kernel calls by just duplicating what's in our cache. + oc_mutex_lock(g_CAIPNetworkMonitorMutex); + + uint32_t list_length = u_arraylist_length(g_CAIPNetworkMonitorAddressList); + for (uint32_t list_index = 0; list_index < list_length; list_index++) + { + CAInterface_t *currItem = (CAInterface_t *)u_arraylist_get(g_CAIPNetworkMonitorAddressList, + list_index); + if (!AddCAInterface(iflist, currItem->name, currItem->index, currItem->family, + currItem->addr)) { - OIC_LOG(ERROR, TAG, "Enumerating Adapters failed"); + OIC_LOG(ERROR, TAG, "AddCAInterface() failed"); u_arraylist_destroy(iflist); iflist = NULL; + break; } } - else - { - OIC_LOG(ERROR, TAG, "Failed to create iflist"); - } + + oc_mutex_unlock(g_CAIPNetworkMonitorMutex); + return iflist; } -- 2.7.4