1 /*
2  * Copyright (c) 2006, Stefan Walter
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  *     * Redistributions of source code must retain the above
10  *       copyright notice, this list of conditions and the
11  *       following disclaimer.
12  *     * Redistributions in binary form must reproduce the
13  *       above copyright notice, this list of conditions and
14  *       the following disclaimer in the documentation and/or
15  *       other materials provided with the distribution.
16  *     * The names of contributors to this software may not be
17  *       used to endorse or promote products derived from this
18  *       software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
27  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
30  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
31  * DAMAGE.
32  */
33 
34 #include <sys/types.h>
35 #include <sys/socket.h>
36 #include <sys/select.h>
37 #include <netdb.h>
38 #include <unistd.h>
39 #include <fcntl.h>
40 #include <pthread.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <stdio.h>
44 #include <assert.h>
45 #include <errno.h>
46 
47 #include "async-resolver.h"
48 #include "server-mainloop.h"
49 
50 /* -----------------------------------------------------------------------------
51  * THREAD COMMUNICATION
52  */
53 
54 #define TSIGNAL_UNITITIALIZED  { -1, -1 }
55 
56 static int
tsignal_init(int * sig)57 tsignal_init(int* sig)
58 {
59     if(pipe(sig) == -1)
60         return -1;
61     fcntl(sig[0], F_SETFL, fcntl(sig[0], F_GETFL, 0) | O_NONBLOCK);
62     return 0;
63 }
64 
65 static int
tsignal_get_fd(int * sig)66 tsignal_get_fd(int* sig)
67 {
68     return sig[0];
69 }
70 
71 static void
tsignal_wake(int * sig)72 tsignal_wake(int* sig)
73 {
74     write(sig[1], "1", 1);
75 }
76 
77 static void
tsignal_clear(int * sig)78 tsignal_clear(int* sig)
79 {
80     char buf[16];
81     if(sig[0] == -1)
82         return;
83     while(read(sig[0], buf, sizeof(buf)) > 0);
84 }
85 
86 static void
tsignal_wait(int * sig,struct timeval * tv)87 tsignal_wait(int* sig, struct timeval* tv)
88 {
89     fd_set watch;
90     FD_ZERO(&watch);
91     FD_SET(sig[0], &watch);
92     select(sig[0], &watch, NULL, NULL, tv);
93 }
94 
95 static void
tsignal_uninit(int * sig)96 tsignal_uninit(int* sig)
97 {
98     if(sig[1] != -1)
99         close(sig[1]);
100     sig[1] = -1;
101     if(sig[0] != -1)
102         close(sig[0]);
103     sig[0] = -1;
104 }
105 
106 /* -----------------------------------------------------------------------------
107  * RESOLVER
108  */
109 
110 typedef struct _resolve_request
111 {
112     char hostname[256];
113     char servname[256];
114     struct addrinfo hints;
115     async_resolve_callback cb;
116     void *arg;
117 
118     int gaierr;
119     int errn;
120     struct addrinfo *ai;
121 
122     struct _resolve_request *next;
123 }
124 resolve_request;
125 
126 /* The queues */
127 static int res_quit = 0;
128 static resolve_request* res_requests = NULL;
129 static resolve_request* res_done = NULL;
130 
131 /* Thread communication */
132 static pthread_t res_thread = 0;
133 static pthread_mutex_t res_mutex = PTHREAD_MUTEX_INITIALIZER;
134 static int res_request_signal[2] = TSIGNAL_UNITITIALIZED;
135 static int res_done_signal[2] = TSIGNAL_UNITITIALIZED;
136 
137 static void*
resolver_thread(void * arg)138 resolver_thread(void* arg)
139 {
140     resolve_request* req;
141     resolve_request* r;
142     struct timeval tv;
143 
144     while(!res_quit)
145     {
146         pthread_mutex_lock(&res_mutex);
147 
148             /* Dig out any requests */
149             req = res_requests;
150             if(req)
151             {
152                 res_requests = req->next;
153                 req->next = NULL;
154             }
155 
156         pthread_mutex_unlock(&res_mutex);
157 
158         /* No requests, wait for a request */
159         if(!req)
160         {
161             tv.tv_sec = 0;
162             tv.tv_usec = 500000;
163             tsignal_wait(res_request_signal, &tv);
164             tsignal_clear(res_request_signal);
165             continue;
166         }
167 
168         /* The actual resolve */
169         req->gaierr = getaddrinfo(req->hostname, req->servname[0] ? req->servname : NULL,
170                                   &req->hints, &req->ai);
171         req->errn = errno;
172 
173         /* A timeout */
174         if(!req->gaierr && !req->ai)
175         {
176             req->gaierr = EAI_SYSTEM;
177             req->errn = ETIMEDOUT;
178         }
179 
180         /* Append the result to done */
181         pthread_mutex_lock(&res_mutex);
182 
183             if(!res_done)
184             {
185                 res_done = req;
186             }
187             else
188             {
189                 r = res_done;
190                 while(r->next)
191                     r = r->next;
192                 r->next = req;
193             }
194 
195         pthread_mutex_unlock(&res_mutex);
196 
197         /* Tell the main thread to check outbound */
198         tsignal_wake(res_done_signal);
199     }
200 
201     return NULL;
202 }
203 
204 static void
resolver_done(int fd,int type,void * arg)205 resolver_done(int fd, int type, void* arg)
206 {
207     resolve_request* req;
208     resolve_request* r;
209 
210     tsignal_clear(res_done_signal);
211 
212     pthread_mutex_lock(&res_mutex);
213 
214         req = res_done;
215         res_done = NULL;
216 
217     pthread_mutex_unlock(&res_mutex);
218 
219     while(req)
220     {
221         /* Send off the result */
222         errno = req->errn;
223         (req->cb)(req->gaierr, req->ai, req->arg);
224 
225         /* And free it all */
226         r = req->next;
227         if(req->ai)
228             freeaddrinfo(req->ai);
229         free(req);
230 
231         req = r;
232     }
233 }
234 
235 int
async_resolver_init()236 async_resolver_init()
237 {
238     int r;
239 
240     /* The signal pipes */
241     if(tsignal_init(res_request_signal) < 0)
242         return -1;
243     if(tsignal_init(res_done_signal) < 0)
244         return -1;
245 
246     if(server_watch(tsignal_get_fd(res_done_signal), SERVER_READ, resolver_done, NULL) == -1)
247         return -1;
248 
249     r = pthread_create(&res_thread, NULL, resolver_thread, NULL);
250     if(r != 0)
251     {
252         res_thread = 0;
253         return -1;
254     }
255 
256     return 0;
257 }
258 
259 void
async_resolver_queue(const char * hostname,const char * servname,struct addrinfo * hints,async_resolve_callback cb,void * arg)260 async_resolver_queue(const char* hostname, const char* servname,
261                      struct addrinfo* hints, async_resolve_callback cb, void* arg)
262 {
263     resolve_request* req;
264     resolve_request* r;
265     char* t;
266 
267     if(!res_thread)
268     {
269         /* All errors go to callback */
270         errno = ESRCH;
271         (cb)(EAI_SYSTEM, NULL, arg);
272         return;
273     }
274 
275     req = calloc(1, sizeof(resolve_request));
276     if(!req)
277     {
278         /* All errors go to callback */
279         (cb)(EAI_MEMORY, NULL, arg);
280         return;
281     }
282 
283     req->cb = cb;
284     req->arg = arg;
285 
286     strncpy(req->hostname, hostname, sizeof(req->hostname));
287     req->hostname[sizeof(req->hostname) - 1] = 0;
288 
289     /* A colon and we try to split */
290     t = strchr(req->hostname, ':');
291     if(t)
292     {
293         *t = 0;
294         strncpy(req->servname, t + 1, sizeof(req->servname));
295     }
296 
297     if(servname && !req->servname[0])
298         strncpy(req->servname, servname, sizeof(req->servname));
299     req->servname[sizeof(req->servname) - 1] = 0;
300 
301     if(hints)
302         memcpy(&(req->hints), hints, sizeof(req->hints));
303 
304     /* Append the result to requests */
305     pthread_mutex_lock(&res_mutex);
306 
307         if(!res_requests)
308         {
309             res_requests = req;
310         }
311         else
312         {
313             for(r = res_requests; r->next; r = r->next);
314             r->next = req;
315         }
316 
317     pthread_mutex_unlock(&res_mutex);
318 
319     tsignal_wake(res_request_signal);
320 }
321 
322 void
async_resolver_uninit()323 async_resolver_uninit()
324 {
325     resolve_request* req;
326 
327     /* No more responses from this point on */
328     if(tsignal_get_fd(res_done_signal) != -1)
329         server_unwatch(tsignal_get_fd(res_done_signal));
330 
331     pthread_mutex_lock(&res_mutex);
332 
333         while(res_requests)
334         {
335             req = res_requests->next;
336             if(res_requests->ai)
337                 freeaddrinfo(res_requests->ai);
338             free(res_requests);
339             res_requests = req;
340         }
341 
342         while(res_done)
343         {
344             req = res_done->next;
345             if(res_done->ai)
346                 freeaddrinfo(res_done->ai);
347             free(res_done);
348             res_done = req;
349         }
350 
351     pthread_mutex_unlock(&res_mutex);
352 
353     /* Wake up the resolver thread */
354     res_quit = 1;
355     tsignal_uninit(res_request_signal);
356 
357     /* Wait for it to finish */
358     if(res_thread)
359     {
360         pthread_join(res_thread, NULL);
361         res_thread = 0;
362     }
363 
364     /* And close up the signals in the other direction */
365     tsignal_uninit(res_done_signal);
366 }
367