1 /*
2  * uhub - A tiny ADC p2p connection hub
3  * Copyright (C) 2007-2014, Jan Vidar Krey
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19 
20 #include "uhub.h"
21 
22 static struct net_dns_job* find_and_remove_job(struct net_dns_job* job);
23 static struct net_dns_result* find_and_remove_result(struct net_dns_job* job);
24 
25 struct net_dns_job
26 {
27 	net_dns_job_cb callback;
28 	void* ptr;
29 
30 	char* host;
31 	int af;
32 
33 #ifdef DEBUG_LOOKUP_TIME
34 	struct timeval time_start;
35 	struct timeval time_finish;
36 #endif
37 
38 	uhub_thread_t* thread_handle;
39 };
40 
41 struct net_dns_result
42 {
43 	struct linked_list* addr_list;
44 	struct net_dns_job* job;
45 };
46 
free_job(struct net_dns_job * job)47 static void free_job(struct net_dns_job* job)
48 {
49 	if (job)
50 	{
51 		hub_free(job->host);
52 		hub_free(job);
53 	}
54 }
55 
shutdown_free_jobs(void * ptr)56 static void shutdown_free_jobs(void* ptr)
57 {
58 	struct net_dns_job* job = (struct net_dns_job*) ptr;
59 	uhub_thread_cancel(job->thread_handle);
60 	uhub_thread_join(job->thread_handle);
61 	free_job(job);
62 }
63 
shutdown_free_results(void * ptr)64 static void shutdown_free_results(void* ptr)
65 {
66 	struct net_dns_result* result = (struct net_dns_result*) ptr;
67 	uhub_thread_join(result->job->thread_handle);
68 	net_dns_result_free(result);
69 }
70 
notify_callback(struct uhub_notify_handle * handle,void * ptr)71 static void notify_callback(struct uhub_notify_handle* handle, void* ptr)
72 {
73 	net_dns_process();
74 }
75 
76 
77 // NOTE: Any job manipulating the members of this
78 // struct must lock the mutex!
79 struct net_dns_subsystem
80 {
81 	struct linked_list* jobs;    // currently running jobs
82 	struct linked_list* results; // queue of results that are awaiting being delivered to callback.
83 	uhub_mutex_t mutex;
84 
85 	struct uhub_notify_handle* notify_handle; // used to signal back to the event loop that there is something to process.
86 };
87 
88 static struct net_dns_subsystem* g_dns = NULL;
89 
net_dns_initialize()90 void net_dns_initialize()
91 {
92 	LOG_TRACE("net_dns_initialize()");
93 	g_dns = (struct net_dns_subsystem*) hub_malloc_zero(sizeof(struct net_dns_subsystem));
94 	g_dns->jobs = list_create();
95 	g_dns->results = list_create();
96 	uhub_mutex_init(&g_dns->mutex);
97 	g_dns->notify_handle = net_notify_create(notify_callback, g_dns);
98 }
99 
net_dns_destroy()100 void net_dns_destroy()
101 {
102 	uhub_mutex_lock(&g_dns->mutex);
103 	LOG_TRACE("net_dns_destroy(): jobs=%d", (int) list_size(g_dns->jobs));
104 	list_clear(g_dns->jobs, &shutdown_free_jobs);
105 
106 	LOG_TRACE("net_dns_destroy(): results=%d", (int) list_size(g_dns->results));
107 	list_clear(g_dns->results, &shutdown_free_results);
108 	uhub_mutex_unlock(&g_dns->mutex);
109 
110 	list_destroy(g_dns->jobs);
111 	list_destroy(g_dns->results);
112 	uhub_mutex_destroy(&g_dns->mutex);
113 	net_notify_destroy(g_dns->notify_handle);
114 	hub_free(g_dns);
115 	g_dns = NULL;
116 }
117 
net_dns_process()118 void net_dns_process()
119 {
120 	struct net_dns_result* result;
121 	uhub_mutex_lock(&g_dns->mutex);
122 	LOG_TRACE("net_dns_process(): jobs=%d, results=%d", (int) list_size(g_dns->jobs), (int) list_size(g_dns->results));
123 
124 	LIST_FOREACH(struct net_dns_result*, result, g_dns->results,
125 	{
126 		struct net_dns_job* job = result->job;
127 #ifdef DEBUG_LOOKUP_TIME
128 		struct timeval time_result;
129 		timersub(&result->job->time_finish, &result->job->time_start, &time_result);
130 		LOG_TRACE("DNS lookup took %d ms", (time_result.tv_sec * 1000) + (time_result.tv_usec / 1000));
131 #endif
132 
133 		// wait for the work thread to finish
134 		uhub_thread_join(job->thread_handle);
135 
136 		// callback - should we delete the data immediately?
137 		if (job->callback(job, result))
138 		{
139 			net_dns_result_free(result);
140 		}
141 		else
142 		{
143 			/* Caller wants to keep the result data, and
144 			 * thus needs to call net_dns_result_free() to release it later.
145 			 * We only clean up the job data here and keep the results intact.
146 			 */
147 			result->job = NULL;
148 			free_job(job);
149 		}
150 	});
151 
152 	list_clear(g_dns->results, NULL);
153 	uhub_mutex_unlock(&g_dns->mutex);
154 }
155 
job_thread_resolve_name(void * ptr)156 static void* job_thread_resolve_name(void* ptr)
157 {
158 	struct net_dns_job* job = (struct net_dns_job*) ptr;
159 	struct addrinfo hints, *result, *it;
160 	struct net_dns_result* dns_results;
161 	int ret;
162 
163 	memset(&hints, 0, sizeof(hints));
164 	hints.ai_family = job->af;
165 	hints.ai_protocol = IPPROTO_TCP;
166 
167 	ret = getaddrinfo(job->host, NULL, &hints, &result);
168 	if (ret != 0 && ret != EAI_NONAME)
169 	{
170 		LOG_TRACE("getaddrinfo() failed: %s", gai_strerror(ret));
171 		return NULL;
172 	}
173 
174 	dns_results = (struct net_dns_result*) hub_malloc(sizeof(struct net_dns_result));
175 	dns_results->addr_list = list_create();
176 	dns_results->job = job;
177 
178 	if (ret != EAI_NONAME)
179 	{
180 		for (it = result; it; it = it->ai_next)
181 		{
182 			struct ip_addr_encap* ipaddr = hub_malloc_zero(sizeof(struct ip_addr_encap));
183 			ipaddr->af = it->ai_family;
184 
185 			if (it->ai_family == AF_INET)
186 			{
187 				struct sockaddr_in* addr4 = (struct sockaddr_in*) it->ai_addr;
188 				memcpy(&ipaddr->internal_ip_data.in, &addr4->sin_addr, sizeof(struct in_addr));
189 			}
190 			else if (it->ai_family == AF_INET6)
191 			{
192 				struct sockaddr_in6* addr6 = (struct sockaddr_in6*) it->ai_addr;
193 				memcpy(&ipaddr->internal_ip_data.in6, &addr6->sin6_addr, sizeof(struct in6_addr));
194 			}
195 			else
196 			{
197 				LOG_TRACE("getaddrinfo() returned result with unknown address family: %d", it->ai_family);
198 				hub_free(ipaddr);
199 				continue;
200 			}
201 
202 			LOG_DUMP("getaddrinfo() - Address (%d) %s for \"%s\"", ret++, ip_convert_to_string(ipaddr), job->host);
203 			list_append(dns_results->addr_list, ipaddr);
204 		}
205 		freeaddrinfo(result);
206 	}
207 	else
208 	{
209 		/* hm */
210 	}
211 
212 #ifdef DEBUG_LOOKUP_TIME
213 	gettimeofday(&job->time_finish, NULL);
214 #endif
215 
216 	uhub_mutex_lock(&g_dns->mutex);
217 	list_remove(g_dns->jobs, job);
218 	list_append(g_dns->results, dns_results);
219 	net_notify_signal(g_dns->notify_handle, 1);
220 	uhub_mutex_unlock(&g_dns->mutex);
221 
222 	return dns_results;
223 }
224 
225 
net_dns_gethostbyname(const char * host,int af,net_dns_job_cb callback,void * ptr)226 extern struct net_dns_job* net_dns_gethostbyname(const char* host, int af, net_dns_job_cb callback, void* ptr)
227 {
228 	struct net_dns_job* job = (struct net_dns_job*) hub_malloc_zero(sizeof(struct net_dns_job));
229 	job->host = strdup(host);
230 	job->af = af;
231 	job->callback = callback;
232 	job->ptr = ptr;
233 
234 #ifdef DEBUG_LOOKUP_TIME
235 	gettimeofday(&job->time_start, NULL);
236 #endif
237 
238 	// FIXME - scheduling - what about a max number of threads?
239 	uhub_mutex_lock(&g_dns->mutex);
240 	job->thread_handle = uhub_thread_create(job_thread_resolve_name, job);
241 	if (!job->thread_handle)
242 	{
243 		LOG_WARN("Unable to create thread");
244 		free_job(job);
245 		job = NULL;
246 	}
247 	else
248 	{
249 		list_append(g_dns->jobs, job);
250 	}
251 	uhub_mutex_unlock(&g_dns->mutex);
252 	return job;
253 }
254 
255 
256 
net_dns_gethostbyaddr(struct ip_addr_encap * ipaddr,net_dns_job_cb callback,void * ptr)257 extern struct net_dns_job* net_dns_gethostbyaddr(struct ip_addr_encap* ipaddr, net_dns_job_cb callback, void* ptr)
258 {
259 	struct net_dns_job* job = (struct net_dns_job*) hub_malloc_zero(sizeof(struct net_dns_job));
260 // 	job->host = strdup(addr);
261 	job->af = ipaddr->af;
262 	job->callback = callback;
263 	job->ptr = ptr;
264 
265 
266 // 	if (pthread_create(&job->thread_handle, NULL, start_job, job))
267 // 	{
268 // 		free_job(job);
269 // 		return NULL;
270 // 	}
271 	return job;
272 }
273 
274 // NOTE: mutex must be locked first!
find_and_remove_job(struct net_dns_job * job)275 static struct net_dns_job* find_and_remove_job(struct net_dns_job* job)
276 {
277 	struct net_dns_job* it;
278 	LIST_FOREACH(struct net_dns_job*, it, g_dns->jobs,
279 	{
280 		if (it == job)
281 		{
282 			list_remove(g_dns->jobs, it);
283 			return job;
284 		}
285 	});
286 	return NULL;
287 }
288 
289 // NOTE: mutex must be locked first!
find_and_remove_result(struct net_dns_job * job)290 static struct net_dns_result* find_and_remove_result(struct net_dns_job* job)
291 {
292 	struct net_dns_result* it;
293 	LIST_FOREACH(struct net_dns_result*, it, g_dns->results,
294 	{
295 		if (it->job == job)
296 		{
297 			list_remove(g_dns->results, it);
298 			return it;
299 		}
300 	});
301 	return NULL;
302 }
303 
304 
net_dns_job_cancel(struct net_dns_job * job)305 extern int net_dns_job_cancel(struct net_dns_job* job)
306 {
307 	int retval = 0;
308 	struct net_dns_result* res;
309 
310 	LOG_TRACE("net_dns_job_cancel(): job=%p, name=%s", job, job->host);
311 
312 	/*
313 	 * This function looks up the job in the jobs queue (which contains only active jobs)
314 	 * If that is found then the thread is cancelled, and the object is deleted.
315 	 * If the job was not found, that is either because it was an invalid job, or because
316 	 * it was already finished. At which point it was not deleted.
317 	 * If the job is already finished, but the result has not been delivered, then this
318 	 * deletes the result and the job.
319 	 */
320 	uhub_mutex_lock(&g_dns->mutex);
321 	if (find_and_remove_job(job))
322 	{
323 		// job still active - cancel it, then close it.
324 		uhub_thread_cancel(job->thread_handle);
325 		uhub_thread_join(job->thread_handle);
326 		free_job(job);
327 		retval = 1;
328 	}
329 	else if ((res = find_and_remove_result(job)))
330 	{
331 		// job already finished - close it.
332 		uhub_thread_join(job->thread_handle);
333 		net_dns_result_free(res);
334 	}
335 	uhub_mutex_unlock(&g_dns->mutex);
336 	return retval;
337 }
338 
net_dns_job_sync_wait(struct net_dns_job * job)339 extern struct net_dns_result* net_dns_job_sync_wait(struct net_dns_job* job)
340 {
341 	struct net_dns_result* res = NULL;
342 
343 	// Wait for job to finish (if not already)
344 	// This should make sure the job is removed from jobs and a result is
345 	// present in results.
346 	uhub_thread_join(job->thread_handle);
347 
348 	// Remove the result in order to prevent the callback from being called.
349 	uhub_mutex_lock(&g_dns->mutex);
350 	res = find_and_remove_result(job);
351 	uhub_assert(res != NULL);
352 	res->job = NULL;
353 	free_job(job);
354 	uhub_mutex_unlock(&g_dns->mutex);
355 	return res;
356 }
357 
net_dns_job_get_ptr(const struct net_dns_job * job)358 void* net_dns_job_get_ptr(const struct net_dns_job* job)
359 {
360 	return job->ptr;
361 }
362 
net_dns_result_size(const struct net_dns_result * res)363 extern size_t net_dns_result_size(const struct net_dns_result* res)
364 {
365 	return list_size(res->addr_list);
366 }
367 
net_dns_result_first(const struct net_dns_result * res)368 extern struct ip_addr_encap* net_dns_result_first(const struct net_dns_result* res)
369 {
370 	struct ip_addr_encap* ipaddr = list_get_first(res->addr_list);
371 	LOG_TRACE("net_dns_result_first() - Address: %s", ipaddr ? ip_convert_to_string(ipaddr) : "(no address)");
372 	return ipaddr;
373 }
374 
net_dns_result_next(const struct net_dns_result * res)375 extern struct ip_addr_encap* net_dns_result_next(const struct net_dns_result* res)
376 {
377 	struct ip_addr_encap* ipaddr = list_get_next(res->addr_list);
378 	LOG_TRACE("net_dns_result_next() - Address: %s", ipaddr ? ip_convert_to_string(ipaddr) : "(no more addresses)");
379 	return ipaddr;
380 }
381 
net_dns_result_free(const struct net_dns_result * res)382 extern void net_dns_result_free(const struct net_dns_result* res)
383 {
384 	if (!res)
385 		return;
386 
387 	list_clear(res->addr_list, &hub_free);
388 	list_destroy(res->addr_list);
389 	free_job(res->job);
390 	hub_free((struct net_dns_result*) res);
391 }
392