3 * Copyright (c) 2020 Project CHIP Authors
4 * Copyright (c) 2017 Nest Labs, Inc.
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
21 * This file implements AsyncDNSResolverSockets, the object that implements
22 * Asynchronous Domain Name System (DNS) resolution in InetLayer.
25 #include <inet/InetLayer.h>
26 #include <support/CodeUtils.h>
27 #include <support/logging/CHIPLogging.h>
33 #if CHIP_SYSTEM_CONFIG_USE_SOCKETS
34 #if INET_CONFIG_ENABLE_DNS_RESOLVER && INET_CONFIG_ENABLE_ASYNC_DNS_SOCKETS
36 #include "AsyncDNSResolverSockets.h"
42 * The explicit initializer for the AsynchronousDNSResolverSockets class.
43 * This initializes the mutex and semaphore variables and creates the
44 * threads for handling the asynchronous DNS resolution.
46 * @param[in] aInet A pointer to the InetLayer object.
48 * @retval #INET_NO_ERROR if initialization is
50 * @retval other appropriate POSIX network or OS error.
52 INET_ERROR AsyncDNSResolverSockets::Init(InetLayer * aInet)
54 INET_ERROR err = INET_NO_ERROR;
59 mAsyncDNSQueueHead = nullptr;
60 mAsyncDNSQueueTail = nullptr;
62 pthreadErr = pthread_cond_init(&mAsyncDNSCondVar, nullptr);
63 VerifyOrDie(pthreadErr == 0);
65 pthreadErr = pthread_mutex_init(&mAsyncDNSMutex, nullptr);
66 VerifyOrDie(pthreadErr == 0);
68 // Create the thread pool for asynchronous DNS resolution.
69 for (int i = 0; i < INET_CONFIG_DNS_ASYNC_MAX_THREAD_COUNT; i++)
71 pthreadErr = pthread_create(&mAsyncDNSThreadHandle[i], nullptr, &AsyncDNSThreadRun, this);
72 VerifyOrDie(pthreadErr == 0);
79 * This is the explicit deinitializer of the AsyncDNSResolverSockets class
80 * and it takes care of shutting the threads down and destroying the mutex
81 * and semaphore variables.
83 * @retval #INET_NO_ERROR if shutdown is successful.
84 * @retval other appropriate POSIX network or OS error.
86 INET_ERROR AsyncDNSResolverSockets::Shutdown()
88 INET_ERROR err = INET_NO_ERROR;
93 mInet->State = InetLayer::kState_ShutdownInProgress;
95 pthreadErr = pthread_cond_broadcast(&mAsyncDNSCondVar);
96 VerifyOrDie(pthreadErr == 0);
100 // Have the CHIP thread join the thread pool for asynchronous DNS resolution.
101 for (pthread_t thread : mAsyncDNSThreadHandle)
103 pthreadErr = pthread_join(thread, nullptr);
104 VerifyOrDie(pthreadErr == 0);
107 pthreadErr = pthread_mutex_destroy(&mAsyncDNSMutex);
108 VerifyOrDie(pthreadErr == 0);
110 pthreadErr = pthread_cond_destroy(&mAsyncDNSCondVar);
111 VerifyOrDie(pthreadErr == 0);
117 * This method prepares a DNSResolver object prior to asynchronous resolution.
119 * @param[in] resolver A reference to an allocated DNSResolver object.
121 * @param[in] hostName A pointer to a C string representing the host name
123 * @param[in] hostNameLen The string length of host name.
124 * @param[in] options An integer value controlling how host name address
125 * resolution is performed. Values are from the #DNSOptions
127 * @param[in] maxAddrs The maximum number of addresses to store in the DNS
129 * @param[in] addrArray A pointer to the DNS table.
130 * @param[in] onComplete A pointer to the callback function when a DNS
131 * request is complete.
132 * @param[in] appState A pointer to the application state to be passed to
133 * onComplete when a DNS request is complete.
135 * @retval INET_NO_ERROR if a DNS request is handled
139 INET_ERROR AsyncDNSResolverSockets::PrepareDNSResolver(DNSResolver & resolver, const char * hostName, uint16_t hostNameLen,
140 uint8_t options, uint8_t maxAddrs, IPAddress * addrArray,
141 DNSResolver::OnResolveCompleteFunct onComplete, void * appState)
143 INET_ERROR err = INET_NO_ERROR;
145 memcpy(resolver.asyncHostNameBuf, hostName, hostNameLen);
146 resolver.asyncHostNameBuf[hostNameLen] = 0;
147 resolver.MaxAddrs = maxAddrs;
148 resolver.NumAddrs = 0;
149 resolver.DNSOptions = options;
150 resolver.AddrArray = addrArray;
151 resolver.AppState = appState;
152 resolver.OnComplete = onComplete;
153 resolver.asyncDNSResolveResult = INET_NO_ERROR;
154 resolver.mState = DNSResolver::kState_Active;
155 resolver.pNextAsyncDNSResolver = nullptr;
161 * Enqueue a DNSResolver object for asynchronous IP address resolution of a specified hostname.
163 * @param[in] resolver A reference to the DNSResolver object.
165 * @retval #INET_NO_ERROR if a DNS request is queued
167 * @retval #INET_ERROR_NO_MEMORY if the Inet layer resolver pool
169 * @retval other appropriate POSIX network or OS error.
172 INET_ERROR AsyncDNSResolverSockets::EnqueueRequest(DNSResolver & resolver)
174 INET_ERROR err = INET_NO_ERROR;
179 // Add the DNSResolver object to the queue.
180 if (mAsyncDNSQueueHead == nullptr)
182 mAsyncDNSQueueHead = &resolver;
185 if (mAsyncDNSQueueTail != nullptr)
187 mAsyncDNSQueueTail->pNextAsyncDNSResolver = &resolver;
190 mAsyncDNSQueueTail = &resolver;
192 pthreadErr = pthread_cond_signal(&mAsyncDNSCondVar);
193 VerifyOrDie(pthreadErr == 0);
201 * Dequeue a DNSResolver object from the queue if there is one.
202 * Make the worker thread block if there is no item in the queue.
205 INET_ERROR AsyncDNSResolverSockets::DequeueRequest(DNSResolver ** outResolver)
207 INET_ERROR err = INET_NO_ERROR;
212 // block until there is work to do or we detect a shutdown
213 while ((mAsyncDNSQueueHead == nullptr) && (mInet->State == InetLayer::kState_Initialized))
215 pthreadErr = pthread_cond_wait(&mAsyncDNSCondVar, &mAsyncDNSMutex);
216 VerifyOrDie(pthreadErr == 0);
219 ChipLogDetail(Inet, "Async DNS worker thread woke up.");
221 // on shutdown, return NULL. Otherwise, pop the head of the DNS request queue
222 if (mInet->State != InetLayer::kState_Initialized)
224 *outResolver = nullptr;
228 *outResolver = const_cast<DNSResolver *>(mAsyncDNSQueueHead);
230 mAsyncDNSQueueHead = mAsyncDNSQueueHead->pNextAsyncDNSResolver;
232 if (mAsyncDNSQueueHead == nullptr)
235 mAsyncDNSQueueTail = nullptr;
245 * Cancel an outstanding DNS query that may still be active.
247 * @param[in] resolver A reference to the DNSResolver object.
249 INET_ERROR AsyncDNSResolverSockets::Cancel(DNSResolver & resolver)
251 INET_ERROR err = INET_NO_ERROR;
255 resolver.mState = DNSResolver::kState_Canceled;
262 void AsyncDNSResolverSockets::UpdateDNSResult(DNSResolver & resolver, struct addrinfo * inLookupRes)
264 resolver.NumAddrs = 0;
266 for (struct addrinfo * addr = inLookupRes; addr != nullptr && resolver.NumAddrs < resolver.MaxAddrs;
267 addr = addr->ai_next, resolver.NumAddrs++)
269 resolver.AddrArray[resolver.NumAddrs] = IPAddress::FromSockAddr(*addr->ai_addr);
273 void AsyncDNSResolverSockets::Resolve(DNSResolver & resolver)
275 struct addrinfo gaiHints;
276 struct addrinfo * gaiResults = nullptr;
279 // Configure the hints argument for getaddrinfo()
280 resolver.InitAddrInfoHints(gaiHints);
282 // Call getaddrinfo() to perform the name resolution.
283 gaiReturnCode = getaddrinfo(resolver.asyncHostNameBuf, nullptr, &gaiHints, &gaiResults);
285 // Mutex protects the read and write operation on resolver->mState
288 // Process the return code and results list returned by getaddrinfo(). If the call
289 // was successful this will copy the resultant addresses into the caller's array.
290 resolver.asyncDNSResolveResult = resolver.ProcessGetAddrInfoResult(gaiReturnCode, gaiResults);
292 // Set the DNS resolver state.
293 resolver.mState = DNSResolver::kState_Complete;
299 /* Event handler function for asynchronous DNS notification */
300 void AsyncDNSResolverSockets::DNSResultEventHandler(chip::System::Layer * aLayer, void * aAppState, chip::System::Error aError)
302 DNSResolver * resolver = static_cast<DNSResolver *>(aAppState);
306 resolver->HandleAsyncResolveComplete();
310 void AsyncDNSResolverSockets::NotifyChipThread(DNSResolver * resolver)
312 // Post work item via Timer Event for the CHIP thread
313 chip::System::Layer & lSystemLayer = resolver->SystemLayer();
315 ChipLogDetail(Inet, "Posting DNS completion event to CHIP thread.");
316 lSystemLayer.ScheduleWork(AsyncDNSResolverSockets::DNSResultEventHandler, resolver);
319 void * AsyncDNSResolverSockets::AsyncDNSThreadRun(void * args)
322 INET_ERROR err = INET_NO_ERROR;
323 AsyncDNSResolverSockets * asyncResolver = static_cast<AsyncDNSResolverSockets *>(args);
327 DNSResolver * request = nullptr;
329 // Dequeue a DNSResolver for resolution. This function would block until there
330 // is an item in the queue or shutdown has been called.
331 err = asyncResolver->DequeueRequest(&request);
333 // If shutdown has been called, DeQueue would return with an empty request.
334 // In that case, break out of the loop and exit thread.
335 VerifyOrExit(err == INET_NO_ERROR && request != nullptr, );
337 if (request->mState != DNSResolver::kState_Canceled)
339 asyncResolver->Resolve(*request);
342 asyncResolver->NotifyChipThread(request);
346 ChipLogDetail(Inet, "Async DNS worker thread exiting.");
350 void AsyncDNSResolverSockets::AsyncMutexLock()
354 pthreadErr = pthread_mutex_lock(&mAsyncDNSMutex);
355 VerifyOrDie(pthreadErr == 0);
358 void AsyncDNSResolverSockets::AsyncMutexUnlock()
362 pthreadErr = pthread_mutex_unlock(&mAsyncDNSMutex);
363 VerifyOrDie(pthreadErr == 0);
368 #endif // INET_CONFIG_ENABLE_DNS_RESOLVER && INET_CONFIG_ENABLE_ASYNC_DNS_SOCKETS
369 #endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS