blob: bb8742a5be4df8c035fc4042ffe0155ca6dfb3e4 [file] [log] [blame]
/*
*
* Copyright (c) 2020 Project CHIP Authors
* Copyright (c) 2017 Nest Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file
* This file implements AsyncDNSResolverSockets, the object that implements
* Asynchronous Domain Name System (DNS) resolution in InetLayer.
*
*/
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <support/CodeUtils.h>
#include <support/logging/CHIPLogging.h>
#include <InetLayer.h>
#if CHIP_SYSTEM_CONFIG_USE_SOCKETS
#if INET_CONFIG_ENABLE_DNS_RESOLVER && INET_CONFIG_ENABLE_ASYNC_DNS_SOCKETS
#include "AsyncDNSResolverSockets.h"
namespace chip {
namespace Inet {
/**
* The explicit initializer for the AsynchronousDNSResolverSockets class.
* This initializes the mutex and semaphore variables and creates the
* threads for handling the asynchronous DNS resolution.
*
* @param[in] aInet A pointer to the InetLayer object.
*
* @retval #INET_NO_ERROR if initialization is
* successful.
* @retval other appropriate POSIX network or OS error.
*/
INET_ERROR AsyncDNSResolverSockets::Init(InetLayer * aInet)
{
INET_ERROR err = INET_NO_ERROR;
int pthreadErr;
mInet = aInet;
mAsyncDNSQueueHead = NULL;
mAsyncDNSQueueTail = NULL;
pthreadErr = pthread_cond_init(&mAsyncDNSCondVar, NULL);
VerifyOrDie(pthreadErr == 0);
pthreadErr = pthread_mutex_init(&mAsyncDNSMutex, NULL);
VerifyOrDie(pthreadErr == 0);
// Create the thread pool for asynchronous DNS resolution.
for (int i = 0; i < INET_CONFIG_DNS_ASYNC_MAX_THREAD_COUNT; i++)
{
pthreadErr = pthread_create(&mAsyncDNSThreadHandle[i], NULL, &AsyncDNSThreadRun, this);
VerifyOrDie(pthreadErr == 0);
}
return err;
}
/**
* This is the explicit deinitializer of the AsyncDNSResolverSockets class
* and it takes care of shutting the threads down and destroying the mutex
* and semaphore variables.
*
* @retval #INET_NO_ERROR if shutdown is successful.
* @retval other appropriate POSIX network or OS error.
*/
INET_ERROR AsyncDNSResolverSockets::Shutdown(void)
{
INET_ERROR err = INET_NO_ERROR;
int pthreadErr;
AsyncMutexLock();
mInet->State = InetLayer::kState_ShutdownInProgress;
pthreadErr = pthread_cond_broadcast(&mAsyncDNSCondVar);
VerifyOrDie(pthreadErr == 0);
AsyncMutexUnlock();
// Have the CHIP thread join the thread pool for asynchronous DNS resolution.
for (int i = 0; i < INET_CONFIG_DNS_ASYNC_MAX_THREAD_COUNT; i++)
{
pthreadErr = pthread_join(mAsyncDNSThreadHandle[i], NULL);
VerifyOrDie(pthreadErr == 0);
}
pthreadErr = pthread_mutex_destroy(&mAsyncDNSMutex);
VerifyOrDie(pthreadErr == 0);
pthreadErr = pthread_cond_destroy(&mAsyncDNSCondVar);
VerifyOrDie(pthreadErr == 0);
return err;
}
/**
* This method prepares a DNSResolver object prior to asynchronous resolution.
*
* @param[in] resolver A reference to an allocated DNSResolver object.
*
* @param[in] hostName A pointer to a C string representing the host name
* to be queried.
* @param[in] hostNameLen The string length of host name.
* @param[in] options An integer value controlling how host name address
* resolution is performed. Values are from the #DNSOptions
* enumeration.
* @param[in] maxAddrs The maximum number of addresses to store in the DNS
* table.
* @param[in] addrArray A pointer to the DNS table.
* @param[in] onComplete A pointer to the callback function when a DNS
* request is complete.
* @param[in] appState A pointer to the application state to be passed to
* onComplete when a DNS request is complete.
*
* @retval INET_NO_ERROR if a DNS request is handled
* successfully.
*
*/
INET_ERROR AsyncDNSResolverSockets::PrepareDNSResolver(DNSResolver & resolver, const char * hostName, uint16_t hostNameLen,
uint8_t options, uint8_t maxAddrs, IPAddress * addrArray,
DNSResolver::OnResolveCompleteFunct onComplete, void * appState)
{
INET_ERROR err = INET_NO_ERROR;
memcpy(resolver.asyncHostNameBuf, hostName, hostNameLen);
resolver.asyncHostNameBuf[hostNameLen] = 0;
resolver.MaxAddrs = maxAddrs;
resolver.NumAddrs = 0;
resolver.DNSOptions = options;
resolver.AddrArray = addrArray;
resolver.AppState = appState;
resolver.OnComplete = onComplete;
resolver.asyncDNSResolveResult = INET_NO_ERROR;
resolver.mState = DNSResolver::kState_Active;
resolver.pNextAsyncDNSResolver = NULL;
return err;
}
/**
* Enqueue a DNSResolver object for asynchronous IP address resolution of a specified hostname.
*
* @param[in] resolver A reference to the DNSResolver object.
*
* @retval #INET_NO_ERROR if a DNS request is queued
* successfully.
* @retval #INET_ERROR_NO_MEMORY if the Inet layer resolver pool
* is full.
* @retval other appropriate POSIX network or OS error.
*
*/
INET_ERROR AsyncDNSResolverSockets::EnqueueRequest(DNSResolver & resolver)
{
INET_ERROR err = INET_NO_ERROR;
int pthreadErr;
AsyncMutexLock();
// Add the DNSResolver object to the queue.
if (mAsyncDNSQueueHead == NULL)
{
mAsyncDNSQueueHead = &resolver;
}
if (mAsyncDNSQueueTail != NULL)
{
mAsyncDNSQueueTail->pNextAsyncDNSResolver = &resolver;
}
mAsyncDNSQueueTail = &resolver;
pthreadErr = pthread_cond_signal(&mAsyncDNSCondVar);
VerifyOrDie(pthreadErr == 0);
AsyncMutexUnlock();
return err;
}
/**
* Dequeue a DNSResolver object from the queue if there is one.
* Make the worker thread block if there is no item in the queue.
*
*/
INET_ERROR AsyncDNSResolverSockets::DequeueRequest(DNSResolver ** outResolver)
{
INET_ERROR err = INET_NO_ERROR;
int pthreadErr;
AsyncMutexLock();
// block until there is work to do or we detect a shutdown
while ((mAsyncDNSQueueHead == NULL) && (mInet->State == InetLayer::kState_Initialized))
{
pthreadErr = pthread_cond_wait(&mAsyncDNSCondVar, &mAsyncDNSMutex);
VerifyOrDie(pthreadErr == 0);
}
ChipLogDetail(Inet, "Async DNS worker thread woke up.");
// on shutdown, return NULL. Otherwise, pop the head of the DNS request queue
if (mInet->State != InetLayer::kState_Initialized)
{
*outResolver = NULL;
}
else
{
*outResolver = const_cast<DNSResolver *>(mAsyncDNSQueueHead);
mAsyncDNSQueueHead = mAsyncDNSQueueHead->pNextAsyncDNSResolver;
if (mAsyncDNSQueueHead == NULL)
{
// Queue is empty
mAsyncDNSQueueTail = NULL;
}
}
AsyncMutexUnlock();
return err;
}
/**
* Cancel an outstanding DNS query that may still be active.
*
* @param[in] resolver A reference to the DNSResolver object.
*/
INET_ERROR AsyncDNSResolverSockets::Cancel(DNSResolver & resolver)
{
INET_ERROR err = INET_NO_ERROR;
AsyncMutexLock();
resolver.mState = DNSResolver::kState_Canceled;
AsyncMutexUnlock();
return err;
}
void AsyncDNSResolverSockets::UpdateDNSResult(DNSResolver & resolver, struct addrinfo * inLookupRes)
{
resolver.NumAddrs = 0;
for (struct addrinfo * addr = inLookupRes; addr != NULL && resolver.NumAddrs < resolver.MaxAddrs;
addr = addr->ai_next, resolver.NumAddrs++)
{
resolver.AddrArray[resolver.NumAddrs] = IPAddress::FromSockAddr(*addr->ai_addr);
}
}
void AsyncDNSResolverSockets::Resolve(DNSResolver & resolver)
{
struct addrinfo gaiHints;
struct addrinfo * gaiResults = NULL;
int gaiReturnCode;
// Configure the hints argument for getaddrinfo()
resolver.InitAddrInfoHints(gaiHints);
// Call getaddrinfo() to perform the name resolution.
gaiReturnCode = getaddrinfo(resolver.asyncHostNameBuf, NULL, &gaiHints, &gaiResults);
// Mutex protects the read and write operation on resolver->mState
AsyncMutexLock();
// Process the return code and results list returned by getaddrinfo(). If the call
// was successful this will copy the resultant addresses into the caller's array.
resolver.asyncDNSResolveResult = resolver.ProcessGetAddrInfoResult(gaiReturnCode, gaiResults);
// Set the DNS resolver state.
resolver.mState = DNSResolver::kState_Complete;
// Release lock.
AsyncMutexUnlock();
return;
}
/* Event handler function for asynchronous DNS notification */
void AsyncDNSResolverSockets::DNSResultEventHandler(chip::System::Layer * aLayer, void * aAppState, chip::System::Error aError)
{
DNSResolver * resolver = static_cast<DNSResolver *>(aAppState);
if (resolver)
{
resolver->HandleAsyncResolveComplete();
}
}
void AsyncDNSResolverSockets::NotifyChipThread(DNSResolver * resolver)
{
// Post work item via Timer Event for the CHIP thread
chip::System::Layer & lSystemLayer = resolver->SystemLayer();
ChipLogDetail(Inet, "Posting DNS completion event to CHIP thread.");
lSystemLayer.ScheduleWork(AsyncDNSResolverSockets::DNSResultEventHandler, resolver);
}
void * AsyncDNSResolverSockets::AsyncDNSThreadRun(void * args)
{
INET_ERROR err = INET_NO_ERROR;
AsyncDNSResolverSockets * asyncResolver = static_cast<AsyncDNSResolverSockets *>(args);
while (true)
{
DNSResolver * request = NULL;
// Dequeue a DNSResolver for resolution. This function would block until there
// is an item in the queue or shutdown has been called.
err = asyncResolver->DequeueRequest(&request);
// If shutdown has been called, DeQueue would return with an empty request.
// In that case, break out of the loop and exit thread.
VerifyOrExit(err == INET_NO_ERROR && request != NULL, );
if (request->mState != DNSResolver::kState_Canceled)
{
asyncResolver->Resolve(*request);
}
asyncResolver->NotifyChipThread(request);
}
exit:
ChipLogDetail(Inet, "Async DNS worker thread exiting.");
return NULL;
}
void AsyncDNSResolverSockets::AsyncMutexLock(void)
{
int pthreadErr;
pthreadErr = pthread_mutex_lock(&mAsyncDNSMutex);
VerifyOrDie(pthreadErr == 0);
}
void AsyncDNSResolverSockets::AsyncMutexUnlock(void)
{
int pthreadErr;
pthreadErr = pthread_mutex_unlock(&mAsyncDNSMutex);
VerifyOrDie(pthreadErr == 0);
}
} // namespace Inet
} // namespace chip
#endif // INET_CONFIG_ENABLE_DNS_RESOLVER && INET_CONFIG_ENABLE_ASYNC_DNS_SOCKETS
#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS