blob: 0f918529968026165a7758e4a4954c76d4bdf162 [file] [log] [blame]
/*
*
* Copyright (c) 2017 Nest Labs, Inc.
* All rights reserved.
*
* 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 <Weave/Support/CodeUtils.h>
#include <Weave/Support/logging/WeaveLogging.h>
#include <InetLayer/InetLayer.h>
#if WEAVE_SYSTEM_CONFIG_USE_SOCKETS
#if INET_CONFIG_ENABLE_DNS_RESOLVER && INET_CONFIG_ENABLE_ASYNC_DNS_SOCKETS
#include <InetLayer/AsyncDNSResolverSockets.h>
namespace nl {
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 Weave 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] 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 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.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);
}
WeaveLogDetail(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 hints;
struct addrinfo *lookupRes = NULL;
int getaddrinfoRes;
INET_ERROR err = INET_NO_ERROR;
// Initialize getaddrinfo parameters.
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = 6;
hints.ai_flags = AI_ADDRCONFIG;
getaddrinfoRes = getaddrinfo(resolver.asyncHostNameBuf, NULL, &hints, &lookupRes);
// Mutex protects the read and write operation on resolver->mState
AsyncMutexLock();
if (resolver.mState != DNSResolver::kState_Canceled)
{
if (getaddrinfoRes == 0)
{
UpdateDNSResult(resolver, lookupRes);
}
else
{
switch (getaddrinfoRes)
{
case EAI_NODATA:
err = INET_NO_ERROR;
break;
case EAI_NONAME:
err = INET_ERROR_HOST_NOT_FOUND;
break;
case EAI_AGAIN:
err = INET_ERROR_DNS_TRY_AGAIN;
break;
case EAI_SYSTEM:
err = Weave::System::MapErrorPOSIX(errno);
break;
default:
err = INET_ERROR_DNS_NO_RECOVERY;
break;
}
// Set the result error code.
resolver.asyncDNSResolveResult = err;
}
// Set the DNS resolver state.
resolver.mState = DNSResolver::kState_Complete;
}
// Release lock.
AsyncMutexUnlock();
if (lookupRes != NULL)
{
freeaddrinfo(lookupRes);
}
return;
}
/* Event handler function for asynchronous DNS notification */
void AsyncDNSResolverSockets::DNSResultEventHandler (Weave::System::Layer* aLayer, void* aAppState, Weave::System::Error aError)
{
DNSResolver *resolver = static_cast<DNSResolver *>(aAppState);
if (resolver)
{
resolver->HandleAsyncResolveComplete();
}
}
void AsyncDNSResolverSockets::NotifyWeaveThread(DNSResolver *resolver)
{
// Post work item via Timer Event for the Weave thread
Weave::System::Layer& lSystemLayer = resolver->SystemLayer();
WeaveLogDetail(Inet, "Posting DNS completion event to Weave 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->NotifyWeaveThread(request);
}
exit:
WeaveLogDetail(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 nl
#endif // INET_CONFIG_ENABLE_DNS_RESOLVER && INET_CONFIG_ENABLE_ASYNC_DNS_SOCKETS
#endif // WEAVE_SYSTEM_CONFIG_USE_SOCKETS