mirror of
				https://git.proxmox.com/git/mirror_frr
				synced 2025-10-31 13:03:19 +00:00 
			
		
		
		
	 50cdb6cf95
			
		
	
	
		50cdb6cf95
		
	
	
	
	
		
			
			The callback itself might want to reschedule the resolver, so it is useful to clear out the callback field before making the call instead of after. Signed-off-by: David Lamparter <equinox@diac24.net>
		
			
				
	
	
		
			248 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			248 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* C-Ares integration to Quagga mainloop
 | |
|  * Copyright (c) 2014-2015 Timo Teräs
 | |
|  *
 | |
|  * This file is free software: you may copy, redistribute and/or modify
 | |
|  * it under the terms of the GNU General Public License as published by
 | |
|  * the Free Software Foundation, either version 2 of the License, or
 | |
|  * (at your option) any later version.
 | |
|  */
 | |
| 
 | |
| #ifdef HAVE_CONFIG_H
 | |
| #include "config.h"
 | |
| #endif
 | |
| 
 | |
| #include <ares.h>
 | |
| #include <ares_version.h>
 | |
| 
 | |
| #include "vector.h"
 | |
| #include "thread.h"
 | |
| #include "lib_errors.h"
 | |
| #include "resolver.h"
 | |
| #include "command.h"
 | |
| 
 | |
| struct resolver_state {
 | |
| 	ares_channel channel;
 | |
| 	struct thread_master *master;
 | |
| 	struct thread *timeout;
 | |
| 	vector read_threads, write_threads;
 | |
| };
 | |
| 
 | |
| static struct resolver_state state;
 | |
| static bool resolver_debug;
 | |
| 
 | |
| #define THREAD_RUNNING ((struct thread *)-1)
 | |
| 
 | |
| static void resolver_update_timeouts(struct resolver_state *r);
 | |
| 
 | |
| static int resolver_cb_timeout(struct thread *t)
 | |
| {
 | |
| 	struct resolver_state *r = THREAD_ARG(t);
 | |
| 
 | |
| 	r->timeout = THREAD_RUNNING;
 | |
| 	ares_process(r->channel, NULL, NULL);
 | |
| 	r->timeout = NULL;
 | |
| 	resolver_update_timeouts(r);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int resolver_cb_socket_readable(struct thread *t)
 | |
| {
 | |
| 	struct resolver_state *r = THREAD_ARG(t);
 | |
| 	int fd = THREAD_FD(t);
 | |
| 
 | |
| 	vector_set_index(r->read_threads, fd, THREAD_RUNNING);
 | |
| 	ares_process_fd(r->channel, fd, ARES_SOCKET_BAD);
 | |
| 	if (vector_lookup(r->read_threads, fd) == THREAD_RUNNING) {
 | |
| 		t = NULL;
 | |
| 		thread_add_read(r->master, resolver_cb_socket_readable, r, fd,
 | |
| 				&t);
 | |
| 		vector_set_index(r->read_threads, fd, t);
 | |
| 	}
 | |
| 	resolver_update_timeouts(r);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int resolver_cb_socket_writable(struct thread *t)
 | |
| {
 | |
| 	struct resolver_state *r = THREAD_ARG(t);
 | |
| 	int fd = THREAD_FD(t);
 | |
| 
 | |
| 	vector_set_index(r->write_threads, fd, THREAD_RUNNING);
 | |
| 	ares_process_fd(r->channel, ARES_SOCKET_BAD, fd);
 | |
| 	if (vector_lookup(r->write_threads, fd) == THREAD_RUNNING) {
 | |
| 		t = NULL;
 | |
| 		thread_add_write(r->master, resolver_cb_socket_writable, r, fd,
 | |
| 				 &t);
 | |
| 		vector_set_index(r->write_threads, fd, t);
 | |
| 	}
 | |
| 	resolver_update_timeouts(r);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void resolver_update_timeouts(struct resolver_state *r)
 | |
| {
 | |
| 	struct timeval *tv, tvbuf;
 | |
| 
 | |
| 	if (r->timeout == THREAD_RUNNING)
 | |
| 		return;
 | |
| 
 | |
| 	THREAD_OFF(r->timeout);
 | |
| 	tv = ares_timeout(r->channel, NULL, &tvbuf);
 | |
| 	if (tv) {
 | |
| 		unsigned int timeoutms = tv->tv_sec * 1000 + tv->tv_usec / 1000;
 | |
| 		thread_add_timer_msec(r->master, resolver_cb_timeout, r,
 | |
| 				      timeoutms, &r->timeout);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void ares_socket_cb(void *data, ares_socket_t fd, int readable,
 | |
| 			   int writable)
 | |
| {
 | |
| 	struct resolver_state *r = (struct resolver_state *)data;
 | |
| 	struct thread *t;
 | |
| 
 | |
| 	if (readable) {
 | |
| 		t = vector_lookup_ensure(r->read_threads, fd);
 | |
| 		if (!t) {
 | |
| 			thread_add_read(r->master, resolver_cb_socket_readable,
 | |
| 					r, fd, &t);
 | |
| 			vector_set_index(r->read_threads, fd, t);
 | |
| 		}
 | |
| 	} else {
 | |
| 		t = vector_lookup(r->read_threads, fd);
 | |
| 		if (t) {
 | |
| 			if (t != THREAD_RUNNING) {
 | |
| 				THREAD_OFF(t);
 | |
| 			}
 | |
| 			vector_unset(r->read_threads, fd);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (writable) {
 | |
| 		t = vector_lookup_ensure(r->write_threads, fd);
 | |
| 		if (!t) {
 | |
| 			thread_add_read(r->master, resolver_cb_socket_writable,
 | |
| 					r, fd, &t);
 | |
| 			vector_set_index(r->write_threads, fd, t);
 | |
| 		}
 | |
| 	} else {
 | |
| 		t = vector_lookup(r->write_threads, fd);
 | |
| 		if (t) {
 | |
| 			if (t != THREAD_RUNNING) {
 | |
| 				THREAD_OFF(t);
 | |
| 			}
 | |
| 			vector_unset(r->write_threads, fd);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| static void ares_address_cb(void *arg, int status, int timeouts,
 | |
| 			    struct hostent *he)
 | |
| {
 | |
| 	struct resolver_query *query = (struct resolver_query *)arg;
 | |
| 	union sockunion addr[16];
 | |
| 	void (*callback)(struct resolver_query *, int, union sockunion *);
 | |
| 	size_t i;
 | |
| 
 | |
| 	callback = query->callback;
 | |
| 	query->callback = NULL;
 | |
| 
 | |
| 	if (status != ARES_SUCCESS) {
 | |
| 		if (resolver_debug)
 | |
| 			zlog_debug("[%p] Resolving failed", query);
 | |
| 
 | |
| 		callback(query, -1, NULL);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	for (i = 0; i < array_size(addr) && he->h_addr_list[i] != NULL; i++) {
 | |
| 		memset(&addr[i], 0, sizeof(addr[i]));
 | |
| 		addr[i].sa.sa_family = he->h_addrtype;
 | |
| 		switch (he->h_addrtype) {
 | |
| 		case AF_INET:
 | |
| 			memcpy(&addr[i].sin.sin_addr,
 | |
| 			       (uint8_t *)he->h_addr_list[i], he->h_length);
 | |
| 			break;
 | |
| 		case AF_INET6:
 | |
| 			memcpy(&addr[i].sin6.sin6_addr,
 | |
| 			       (uint8_t *)he->h_addr_list[i], he->h_length);
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (resolver_debug)
 | |
| 		zlog_debug("[%p] Resolved with %d results", query, (int)i);
 | |
| 
 | |
| 	callback(query, i, &addr[0]);
 | |
| }
 | |
| 
 | |
| void resolver_resolve(struct resolver_query *query, int af,
 | |
| 		      const char *hostname,
 | |
| 		      void (*callback)(struct resolver_query *, int,
 | |
| 				       union sockunion *))
 | |
| {
 | |
| 	if (query->callback != NULL) {
 | |
| 		flog_err(
 | |
| 			EC_LIB_RESOLVER,
 | |
| 			"Trying to resolve '%s', but previous query was not finished yet",
 | |
| 			hostname);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (resolver_debug)
 | |
| 		zlog_debug("[%p] Resolving '%s'", query, hostname);
 | |
| 
 | |
| 	query->callback = callback;
 | |
| 	ares_gethostbyname(state.channel, hostname, af, ares_address_cb, query);
 | |
| 	resolver_update_timeouts(&state);
 | |
| }
 | |
| 
 | |
| DEFUN(debug_resolver,
 | |
|       debug_resolver_cmd,
 | |
|       "[no] debug resolver",
 | |
|       NO_STR
 | |
|       DEBUG_STR
 | |
|       "Debug DNS resolver actions\n")
 | |
| {
 | |
| 	resolver_debug = (argc == 2);
 | |
| 	return CMD_SUCCESS;
 | |
| }
 | |
| 
 | |
| static struct cmd_node resolver_debug_node = {RESOLVER_DEBUG_NODE, "", 1};
 | |
| 
 | |
| static int resolver_config_write_debug(struct vty *vty)
 | |
| {
 | |
| 	if (resolver_debug)
 | |
| 		vty_out(vty, "debug resolver\n");
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| void resolver_init(struct thread_master *tm)
 | |
| {
 | |
| 	struct ares_options ares_opts;
 | |
| 
 | |
| 	state.master = tm;
 | |
| 	state.read_threads = vector_init(1);
 | |
| 	state.write_threads = vector_init(1);
 | |
| 
 | |
| 	ares_opts = (struct ares_options){
 | |
| 		.sock_state_cb = &ares_socket_cb,
 | |
| 		.sock_state_cb_data = &state,
 | |
| 		.timeout = 2,
 | |
| 		.tries = 3,
 | |
| 	};
 | |
| 
 | |
| 	ares_init_options(&state.channel, &ares_opts,
 | |
| 			  ARES_OPT_SOCK_STATE_CB | ARES_OPT_TIMEOUT
 | |
| 				  | ARES_OPT_TRIES);
 | |
| 
 | |
| 	install_node(&resolver_debug_node, resolver_config_write_debug);
 | |
| 	install_element(CONFIG_NODE, &debug_resolver_cmd);
 | |
| 	install_element(ENABLE_NODE, &debug_resolver_cmd);
 | |
| }
 |