/****************************************************************************
 * Copyright (C) 1998 WIDE Project. All rights reserved.
 * Copyright (C) 1999,2000,2001,2002 University of Tromso. All rights reserved.
 * Copyright (C) 2002 Invenia Innovation AS. All rights reserved.
 *
 * Author: Feike W. Dillema, feico@pasta.cs.uit.no.
 *         based on newbie code by Yusuke DOI, Keio Univ. Murai Lab.
 ****************************************************************************/


/*
 * <$Id: udp_request.c,v 3.53 2002/03/04 12:34:11 dillema Exp $>
 */

#include "totd.h"

/*
 * A request message is sent over the given socket, and a
 * timeout event registered:
 *	- if no response within timeout, retry routine is called. 
 * 	- if response is received, it replaces the request message in the
 *	  (child) context and the process routine is called.
 */

int udp_request_start (struct context *cont) {
	char *fn = "udp_request_start()";
	struct sockaddr *sa;
	int timeout;

	syslog (LOG_DEBUG, "%s: start", fn);

        cont->process = udp_request_process;
        cont->retry = udp_request_retry;

	if (cont->mesg_len > MAX_PACKET) {
		syslog (LOG_NOTICE, "Query to big for UDP datagram.");
		return (request_abort (cont, 1)); /* try TCP instead? */
	}

	sa = (struct sockaddr *) cont->current_ns->list_data;
	if (net_mesg_send (NULL, cont->mesg.p, cont->mesg_len, sa) < 0) {
		syslog (LOG_NOTICE, "send failed: %m");

		/* force immediate retry */
		timeout = 0;
		syslog (LOG_DEBUG, "%s: force retry at zero timeout", fn);
	} else {
		/* put me to input list */
		if (ev_udp_in_register (cont, sa,
					SOCKADDR_SIZEOF (*sa), cont->q_id) < 0)
			return (request_abort (cont, -1));

		timeout = cont->timeout;
		syslog (LOG_DEBUG, "Query times out in %d seconds", timeout);
	}

	/* put me on timeout event queue */
	if (context_timeout_register (cont, timeout) < 0) 
		return (request_abort (cont, -1));

	syslog (LOG_DEBUG, "%s: end", fn);
	/* SUCCESS */
	return 0;
}

int udp_request_process (Context *cont) {
	char *fn = "udp_request_process()";
	struct sockaddr *sa;

	syslog (LOG_DEBUG, "%s: start", fn);

	/*
	 * ev_udp_in() called us to process an incoming response
	 * to the request we sent. The response message has been
	 * put in cont->mesg.p
	 */

	/*
	 * Timeout will still go off later, so we render
	 * it harmless here by removing the handler.
	 */
	if (cont->tout)
		cont->tout->handler = NULL;

	/* remove me from I/O list */
	sa = (struct sockaddr *) cont->current_ns->list_data;
	if (ev_udp_in_remove (sa, cont->q_id) < 0)
		return (request_abort (cont, -1));
	else
		return request_finish (cont);
}

int udp_request_retry (Context *cont) {
	char *fn = "udp_request_retry()";
	struct sockaddr *sa;
	int len, timeout;

	syslog (LOG_DEBUG, "%s: start", fn);

	/* remove old I/O List (if it is still there) */
	sa = (struct sockaddr *) cont->current_ns->list_data;
	ev_udp_in_remove (sa, cont->q_id);

	/*
	 * Fast GiveUp Hack.
	 *
	 * decisions like this should only be made in request.c,
	 * so this hack is ugly too.
	 */
	if (cont->q_type == RT_AAAA || cont->q_type == RT_A6) {
		/*
		 * Some nameservers just do not like IPv6
		 * address records. We guess that is the case
		 * here and tell the parent to continue if it
		 * can.
		 *
		 * We perform the code of request_finish()
		 * here, but without the parsing of the
		 * response (we have none).
		 *
		 * Note that this means that a timeout for a IPv6
		 * address record request never causes a shift to
		 * the next forwarder! Seems reasonable in the
		 * for the majority of cases.
		 */

		syslog (LOG_DEBUG, "Giving up quickly on IPv6 address record");

		/* re-initialize answer, nameserver, additional record lists */
		list_destroy (cont->an_list, rrset_freev);
		list_destroy (cont->ns_list, rrset_freev);
		list_destroy (cont->ar_list, rrset_freev);
		cont->an_list = list_init ();
		cont->ns_list = list_init ();
		cont->ar_list = list_init ();
		if (!cont->an_list || !cont->ns_list || !cont->ar_list)
			return request_abort (cont, -1);

		if (cont->parent) {
			syslog (LOG_DEBUG, "%s: process parent context", fn);
			cont->parent->process (cont->parent);
		}

		/* ... so we cleanup ourselves in any case */
		context_destroy (cont);

		syslog (LOG_DEBUG, "%s: return success", fn);
		return 0; /* SUCCESS */
	}

	if (request_retry (cont) < 0)
		return (request_abort (cont, -2));

	/* send/forward the message/request again */
	sa = (struct sockaddr *) cont->current_ns->list_data;
	len = net_mesg_send (NULL, cont->mesg.p, cont->mesg_len, sa);
	if (len < cont->mesg_len) {
		if (len < 0)
			syslog (LOG_NOTICE, "retry failed(default socket): %m");
		else
			syslog (LOG_NOTICE, "can't send whole datagram");
		/* forcing immediate retry */
		timeout = 0;
	} else {
		/* we retried the request, now wait for response or timeout */
		timeout = cont->timeout;
		/* put me to input list */
		if (ev_udp_in_register (cont, sa,
		     SOCKADDR_SIZEOF (*sa), cont->q_id) < 0)
			return (request_abort (cont, -1));
	}

	/* put me to timeout list */
	if (context_timeout_register (cont, timeout) < 0)
		return (request_abort (cont, -1));

	/* no state change... */
	syslog (LOG_DEBUG, "%s: end", fn);
	return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1