/****************************************************************************
 * 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: forward.c,v 3.41 2005/01/28 12:24:34 dillema Exp $>
 */

#include "totd.h"

char *sprint_inet(struct sockaddr *sa, char *address_str) {
    char tmp[MAX_DNAME];

	address_str[0] = '\0';
#ifdef USE_INET4
    	if (sa->sa_family == AF_INET) {
		struct sockaddr_in *sin_p = (struct sockaddr_in *) sa;
		inet_ntop (sin_p->sin_family, (void *) &sin_p->sin_addr, tmp,
			   MAX_DNAME);
		snprintf (address_str, MAX_DNAME, "[%s]:%d", tmp,
			  ntohs(sin_p->sin_port));
    	}
#endif
#ifdef USE_INET6
    	if (sa->sa_family == AF_INET6) {
		struct sockaddr_in6 *sin6_p = (struct sockaddr_in6 *) sa;
		inet_ntop(sin6_p->sin6_family, (void *) &sin6_p->sin6_addr,
			  tmp, MAX_DNAME);
		snprintf (address_str, MAX_DNAME, "[%s]:%d", tmp,
			  ntohs(sin6_p->sin6_port));
    	}
#endif
    	return address_str;
}

struct sockaddr *parse_and_alloc_addr (char *caddr, int port, int *sa_len_ret) {
	char address[MAX_DNAME] = "";
	struct sockaddr *sa_p;
	int sa_len, af = 0;
	char *colon = NULL;

#ifdef USE_INET4
	sa_len = sizeof (struct sockaddr_in);
	af = AF_INET;
#endif

#ifdef USE_INET6
	colon = strchr (caddr, ':');
	if (colon || T.use_mapped) {
		sa_len = sizeof (struct sockaddr_in6);
		af = AF_INET6;
	}
#endif

	if (!af)
		return NULL;

	sa_p = malloc (sa_len);
	if (!sa_p)
		return NULL;
	memset ((void *) sa_p, 0, sa_len);
#ifdef HAVE_SA_LEN_FIELD
	sa_p->sa_len = sa_len;
#endif
	sa_p->sa_family = af;

	if (!colon && T.use_mapped)
		strcpy(address, "::ffff:");

	if (strlcat (address, caddr, MAX_DNAME) >= sizeof(address))
		return NULL;

#ifdef USE_INET4
	if (af == AF_INET) {
		struct sockaddr_in *sin_p;

		sin_p = (struct sockaddr_in *) sa_p;
		sin_p->sin_port = htons (port);
		if (!inet_aton (address, &sin_p->sin_addr)) {
			free(sa_p);
			return NULL;
		}
	}
#endif

#ifdef USE_INET6
	if (af == AF_INET6) {
		struct addrinfo hints, *res;
		char portstr[NI_MAXSERV];
		int error;

		snprintf(portstr, NI_MAXSERV, "%d", port);

		memset(&hints, 0, sizeof(hints));
           	hints.ai_family = af;
		hints.ai_socktype = SOCK_STREAM;
		hints.ai_flags = AI_NUMERICHOST;
           	error = getaddrinfo(address, portstr, &hints, &res);
           	if (error) {
                	syslog(LOG_ERR, "%s", gai_strerror(error));
			if (res)
				freeaddrinfo(res);
			return NULL;
		}
		memcpy(sa_p, res->ai_addr, sa_len);

		if (res)
			freeaddrinfo(res);
	}
#endif

	if (sa_len_ret)
		*sa_len_ret = sa_len;

	return sa_p;
}

void fwd_free (Fwd *fwd_ptr) {
    if (fwd_ptr) {
	if (fwd_ptr->sa)
	    free (fwd_ptr->sa);
	
	free (fwd_ptr);
    }
    return;
}

void fwd_freev (void *fwd_p) {
	fwd_free ((Fwd *) fwd_p);
	return;
}

Fwd *fwd_alloc (void) {
	Fwd *fwd_p = NULL;	/* alloc'ed */
	char *fn = "fwd_alloc()";

	fwd_p = malloc (sizeof (Fwd));
	if (!fwd_p) {
		syslog (LOG_ERR, "%s: Cannot allocate memory", fn);
		goto error;
	}
	fwd_p->sa = malloc (sizeof (struct sockaddr_storage));
	if (!fwd_p->sa) {
		syslog (LOG_ERR, "%s: Cannot allocate memory", fn);
		goto error;
	}

	return fwd_p;

error:
	fwd_free (fwd_p);
	return NULL;
}

void fwd_init (void) {
	G_List *gl_tmp;

	if (!T.Fwd_list)
		return;

	for (gl_tmp = T.Fwd_list->next; gl_tmp->list_data; gl_tmp = gl_tmp->next) {
        	struct sockaddr *sa_p;
        	int sa_p_len;
		Fwd *fwd_tmp;

		fwd_tmp = (Fwd *) (gl_tmp->list_data);
		fwd_tmp->went_down_at = 0;
		fwd_tmp->ticks = 0;

		sa_p = parse_and_alloc_addr (fwd_tmp->hostname, fwd_tmp->port, &sa_p_len);
		if (sa_p) {
			syslog (LOG_INFO, "Forwarder %s configured, port %d", fwd_tmp->hostname, fwd_tmp->port);
			memcpy (fwd_tmp->sa, sa_p, sa_p_len);
			fwd_tmp->sa_len = sa_p_len;
		} else {
			syslog (LOG_ERR, "Can't configure forwarder %s, port %d", fwd_tmp->hostname, fwd_tmp->port);
			if (fwd_tmp->sa)
	    			free (fwd_tmp->sa);
			fwd_tmp->sa = NULL;
			fwd_tmp->sa_len = 0;
		}
	}
}

int fwd_add (char *hostname, int port) {
	Fwd *fwd_p;

	syslog (LOG_DEBUG, "fwd_add(): start");

	if (!T.Fwd_list)
		return -1;

	fwd_p = fwd_alloc ();
	if (!fwd_p)
		return -1;

	strlcpy(fwd_p->hostname, hostname, MAX_DNAME);
	fwd_p->port = port;

	if (list_add_tail (T.Fwd_list, fwd_p) < 0) {
		fwd_free (fwd_p);
		return -1;
	}
	return 0;
}

/*
 * Selects nameserver to initially forward incoming requests to.
 */
void fwd_select (void) {
	char *fn = "fwd_select";
    	char astr[MAX_DNAME];
	G_List *list_tmp;
	Fwd *fwd_tmp;

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

	if (!T.current_fwd) {
	    /* No forwarder selected yet, just pick first valid one */
	    if (!T.Fwd_list)
		return;

	    T.current_fwd = T.Fwd_list->next;
	    fwd_tmp = (Fwd *)T.current_fwd->list_data;
	    while (fwd_tmp && !fwd_tmp->sa) {
	    	T.current_fwd = T.current_fwd->next;
	        fwd_tmp = (Fwd *)T.current_fwd->list_data;
	    }

	    if (!fwd_tmp || !fwd_tmp->sa) {
		/* we didn't find a valid forwarder at all */
		T.current_fwd = NULL;
	    	syslog (LOG_ERR, "No forwarder configured!");
		return;
	    }
	    syslog (LOG_DEBUG, "Use initial forwarder %s",
		    sprint_inet(fwd_tmp->sa, astr));
	} else if (T.current_fwd->prev->list_data) {
	    /* 
	     * We're not using the first nameserver listed, i.e.
	     * we are using a backup server. After a while we should
	     * try to go back to an earlier nameserver.
	     */
	    time_t waittime, downtime, current_time;

	    current_time = time(NULL);
	    list_tmp = T.current_fwd->prev;
	    fwd_tmp = (Fwd *)list_tmp->list_data;
	    while (fwd_tmp) {
		waittime = (current_time - fwd_tmp->went_down_at);
		downtime = (time_t) (T.retry_interval);

		if (fwd_tmp->sa && waittime > downtime) {
		    /* waited long enough, let's try again! */
		    syslog (LOG_NOTICE, "Enable forwarder %s again",
			    sprint_inet(fwd_tmp->sa, astr));
		    /* for the occasion, we mark it up again */
		    fwd_tmp->went_down_at = 0;
		    if (fwd_tmp->ticks > 0)
		    	fwd_tmp->ticks--;
		    T.current_fwd = list_tmp;
		    /* only one at a time, the rest will follow */
		    break;
		}
		list_tmp = list_tmp->prev;
	    	fwd_tmp = (Fwd *)list_tmp->list_data;
	    }
	}

	syslog (LOG_DEBUG, "Current forwarder %s",
		    sprint_inet(((Fwd *)T.current_fwd->list_data)->sa, astr));
	syslog (LOG_DEBUG, "%s: end()", fn);
	return;
}

/*
 * The fact that this routines gets called is a first hint that
 * the current forwarder/nameserver is down at this point in time.
 * Actually, it may just be slow, be overloaded, or the network may
 * be congested. In any way, from our point of view it is slow or
 * down and thus we may gain by trying a configured backup forwarder.
 * 
 * This routines marks the forwarder with the given address (if any)
 * with a `minus' point. If a forwarder has gathered `enough' minus points
 * it will be marked down, such that it will not be used for a while.
 *
 * Note that the current forwarder (T.current_fwd) is never marked down!
 */
void fwd_mark (struct sockaddr *sa, int up) {
	char *fn = "fwd_mark";
    	char astr[MAX_DNAME];
    	char bstr[MAX_DNAME];
	G_List *gl;
	Fwd *fwd;

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

	if (!T.Fwd_list || !T.current_fwd)
		return;

	fwd = NULL;
        for (gl = T.Fwd_list->next; gl->list_data; gl = gl->next) {
	        fwd = (Fwd *)gl->list_data;

		if (sa->sa_family != fwd->sa->sa_family)
			continue;

#ifdef USE_INET6
		if (sa->sa_family == AF_INET6) {
			struct sockaddr_in6 *sina, *sinb;

			sina = (struct sockaddr_in6 *) (fwd->sa);
			sinb = (struct sockaddr_in6 *) sa;
			if (IN6_ARE_ADDR_EQUAL(&sina->sin6_addr, &sinb->sin6_addr)
			    && sina->sin6_port == sinb->sin6_port) {
				fwd->ticks += up;
				break;
			}
		}
#endif
#ifdef USE_INET4
		if (sa->sa_family == AF_INET) {
			struct sockaddr_in *sina, *sinb;

			sina = (struct sockaddr_in *) (fwd->sa);
			sinb = (struct sockaddr_in *) sa;
			if (sina->sin_addr.s_addr == sinb->sin_addr.s_addr &&
			    sina->sin_port == sinb->sin_port) {
				fwd->ticks += up;
				break;
			}
		}
#endif
	}

	if (!fwd)
		return;

	if (fwd->ticks < 0)
		fwd->ticks = 0;

	if (gl->list_data)
		syslog (LOG_DEBUG, "Mark forwarder with %d: %s ", fwd->ticks,
		    sprint_inet(sa, astr));

	if (fwd->ticks < FORWARDER_DEATH_MARK)
		return;
	else
		fwd->went_down_at = time(NULL);

	if (((Fwd *)(T.current_fwd->list_data))->went_down_at) {
		G_List *new_fwd;
		Fwd *fwd_tmp;

		/*
		 * we marked current forwarder down, so
		 * select new next valid forwarder
		 */
		new_fwd = T.current_fwd->next;
		fwd_tmp = (Fwd *)new_fwd->list_data;
		while (fwd_tmp) {
			if (fwd_tmp->sa && !fwd_tmp->went_down_at) 
				break;
			new_fwd = new_fwd->next;
	        	fwd_tmp = (Fwd *)new_fwd->list_data;
		}

		if (!fwd_tmp || !fwd_tmp->sa) {
			/*
			 * we didn't find a next valid forwarder, game over!
			 * Note that we do not mark current forwarder down
			 * (the current one never is) nor do we change it.
			 *
			 * Actually, we mark all forwarders up again! No use
			 * discriminating between things that seem to behave
			 * the same ;)
			 */
			new_fwd = T.Fwd_list->next;
			fwd_tmp = (Fwd *)new_fwd->list_data;
			while (fwd_tmp) {
				/* mark 'em `up' again */
				fwd_tmp->ticks = 0;
				fwd_tmp->went_down_at = 0;
				new_fwd = new_fwd->next;
	        		fwd_tmp = (Fwd *)new_fwd->list_data;
			}
			return;
		}

		syslog (LOG_NOTICE, "Disabling forwarder %s (next %s)", 
			sprint_inet(((Fwd *)T.current_fwd->list_data)->sa, astr), 
			sprint_inet(fwd_tmp->sa, bstr));

		T.current_fwd = new_fwd;
	}
}

G_List *fwd_socketlist (void) {
	char *fn = "fwd_socketlist";
	G_List *socklist, *gl;
	Fwd *fwd;

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

	if (!T.Fwd_list || !T.current_fwd)
		return NULL;

	socklist = list_init();

	/*
	 * We cycle through all forwarders, starting with the `current' one.
	 * We skip all those that are currently marked down or without proper
	 * socket address.
	 */
        for (gl = T.current_fwd; gl->next != T.current_fwd; gl = gl->next) {
		if (!gl->list_data)
			continue;

	        fwd = (Fwd *)gl->list_data;
		if (fwd->sa && !fwd->went_down_at)  {
			struct sockaddr *sa;

			sa = malloc (sizeof(struct sockaddr_storage));
			if (!sa)
				return NULL;

			memcpy(sa, fwd->sa, SOCKADDR_SIZEOF(*fwd->sa));
			list_add_tail(socklist, sa);
		}
	}

	return socklist;
}


syntax highlighted by Code2HTML, v. 0.9.1