
/*
 * $Id: net_db.c,v 1.31.2.28 1998/02/05 03:33:07 wessels Exp $
 *
 * DEBUG: section 37    Network Measurement Database
 * AUTHOR: Duane Wessels
 *
 * SQUID Internet Object Cache  http://squid.nlanr.net/Squid/
 * --------------------------------------------------------
 *
 *  Squid is the result of efforts by numerous individuals from the
 *  Internet community.  Development is led by Duane Wessels of the
 *  National Laboratory for Applied Network Research and funded by
 *  the National Science Foundation.
 *
 *  This program is free software; you can redistribute it 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.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *  
 */

#include "squid.h"

#if USE_ICMP

static HashID addr_table = 0;
static HashID host_table = 0;

static struct in_addr networkFromInaddr _PARAMS((struct in_addr a));
static void netdbRelease _PARAMS((netdbEntry * n));
static netdbEntry *netdbGetFirst _PARAMS((HashID table));
static netdbEntry *netdbGetNext _PARAMS((HashID table));
static void netdbHashInsert _PARAMS((netdbEntry * n, struct in_addr addr));
static void netdbHashDelete _PARAMS((const char *key));
static void netdbHostInsert _PARAMS((netdbEntry * n, const char *hostname));
static void netdbHostDelete _PARAMS((const net_db_name *));
static void netdbPurgeLRU _PARAMS((void));
static net_db_peer *netdbPeerByName _PARAMS((const netdbEntry * n, const char *));
static net_db_peer *netdbPeerAdd _PARAMS((netdbEntry * n, peer * e));
static char *netdbPeerName _PARAMS((const char *name));
static netdbEntry *netdbAdd _PARAMS((struct in_addr addr));

/* We have to keep a local list of peer names.  The Peers structure
 * gets freed during a reconfigure.  We want this database to
 * remain persisitent, so _net_db_peer->peername points into this
 * linked list */
static wordlist *peer_names = NULL;
static wordlist **peer_names_tail = &peer_names;
static const char *const w_space = " \t\n\r";

static void
netdbHashInsert(netdbEntry * n, struct in_addr addr)
{
    xstrncpy(n->network, inet_ntoa(networkFromInaddr(addr)), 16);
    if (hash_lookup(addr_table, n->network))
	fatal_dump("netdbHashInsert: dup!");
    n->key = n->network;
    hash_join(addr_table, (hash_link *) n);
    meta_data.netdb_addrs++;
}

static void
netdbHashDelete(const char *key)
{
    hash_link *hptr = hash_lookup(addr_table, key);
    if (hptr == NULL) {
	debug_trap("netdbHashDelete: key not found");
	return;
    }
    hash_remove_link(addr_table, hptr);
    meta_data.netdb_addrs--;
}

static void
netdbHostInsert(netdbEntry * n, const char *hostname)
{
    net_db_name *x = xcalloc(1, sizeof(net_db_name));
    x->name = xstrdup(hostname);
    x->next = n->hosts;
    n->hosts = x;
    x->net_db_entry = n;
    if (hash_lookup(host_table, hostname))
	fatal_dump("netdbHostInsert: double insertion");
    hash_join(host_table, (hash_link *) x);
    n->link_count++;
    meta_data.netdb_hosts++;
}

static void
netdbHostDelete(const net_db_name * x)
{
    net_db_name **X;
    netdbEntry *n;
    if (x == NULL) {
	debug_trap("netdbHostDelete: NULL parameter");
	return;
    }
    if (x->net_db_entry == NULL)
	fatal_dump("netdbHostDelete: NULL net_db_entry");
    n = x->net_db_entry;
    n->link_count--;
    for (X = &n->hosts; *X; X = &(*X)->next) {
	if (*X == x) {
	    *X = x->next;
	    break;
	}
    }
    hash_remove_link(host_table, (hash_link *) x);
    meta_data.netdb_hosts--;
    xfree(x->name);
    xfree((void *) x);
}

static netdbEntry *
netdbLookupHost(const char *key)
{
    net_db_name *x = (net_db_name *) hash_lookup(host_table, key);
    return x ? x->net_db_entry : NULL;
}

static netdbEntry *
netdbGetFirst(HashID table)
{
    return (netdbEntry *) hash_first(table);
}

static netdbEntry *
netdbGetNext(HashID table)
{
    return (netdbEntry *) hash_next(table);
}

static void
netdbRelease(netdbEntry * n)
{
    net_db_name *x;
    net_db_name *next;
    for (x = n->hosts; x; x = next) {
	next = x->next;
	netdbHostDelete(x);
    }
    n->hosts = NULL;
    safe_free(n->peers);
    meta_data.netdb_peers -= n->n_peers_alloc;
    n->peers = NULL;
    n->n_peers = 0;
    n->n_peers_alloc = 0;
    if (n->link_count == 0) {
	netdbHashDelete(n->network);
	xfree(n);
    }
}

static int
netdbLRU(netdbEntry ** n1, netdbEntry ** n2)
{
    if ((*n1)->last_use_time > (*n2)->last_use_time)
	return (1);
    if ((*n1)->last_use_time < (*n2)->last_use_time)
	return (-1);
    return (0);
}

static void
netdbPurgeLRU(void)
{
    netdbEntry *n;
    netdbEntry **list;
    int k = 0;
    int list_count = 0;
    int removed = 0;
    list = xcalloc(meta_data.netdb_addrs, sizeof(netdbEntry *));
    for (n = netdbGetFirst(addr_table); n; n = netdbGetNext(addr_table)) {
	if (n->rtt == 0 && n->next_ping_time < squid_curtime) {
	    netdbRelease(n);
	    removed++;
	    continue;
	}
	*(list + list_count) = n;
	list_count++;
	if (list_count > meta_data.netdb_addrs)
	    fatal_dump("netdbPurgeLRU: list_count overflow");
    }
    qsort((char *) list,
	list_count,
	sizeof(netdbEntry *),
	(QS) netdbLRU);
    for (k = 0; k < list_count; k++) {
	if (meta_data.netdb_addrs < Config.Netdb.low)
	    break;
	netdbRelease(*(list + k));
	removed++;
    }
    xfree(list);
}

static netdbEntry *
netdbLookupAddr(struct in_addr addr)
{
    netdbEntry *n;
    char *key = inet_ntoa(networkFromInaddr(addr));
    n = (netdbEntry *) hash_lookup(addr_table, key);
    return n;
}

static netdbEntry *
netdbAdd(struct in_addr addr)
{
    netdbEntry *n;
    if (meta_data.netdb_addrs > Config.Netdb.high)
	netdbPurgeLRU();
    if ((n = netdbLookupAddr(addr)) == NULL) {
	n = xcalloc(1, sizeof(netdbEntry));
	netdbHashInsert(n, addr);
    }
    return n;
}

static void
netdbSendPing(int fdunused, const ipcache_addrs * ia, void *data)
{
    struct in_addr addr;
    char *hostname = data;
    netdbEntry *n;
    netdbEntry *na;
    net_db_name *x;
    net_db_name **X;
    if (ia == NULL) {
	xfree(hostname);
	return;
    }
    addr = ia->in_addrs[ia->cur];
    if ((n = netdbLookupHost(hostname)) == NULL) {
	n = netdbAdd(addr);
	netdbHostInsert(n, hostname);
    } else if ((na = netdbLookupAddr(addr)) != n) {
	/*
	 * hostname moved from 'network n' to 'network na'!
	 */
	if (na == NULL)
	    na = netdbAdd(addr);
	debug(37, 3, "netdbSendPing: NOTE: %s moved from %s to %s\n",
	    hostname, n->network, na->network);
	x = (net_db_name *) hash_lookup(host_table, hostname);
	if (x == NULL) {
	    debug(37, 1, "netdbSendPing: net_db_name list bug: %s not found", hostname);
	    xfree(hostname);
	    return;
	}
	/* remove net_db_name from 'network n' linked list */
	for (X = &n->hosts; *X; X = &(*X)->next) {
	    if (*X == x) {
		*X = x->next;
		break;
	    }
	}
	n->link_count--;
	/* point to 'network na' from host entry */
	x->net_db_entry = na;
	/* link net_db_name to 'network na' */
	x->next = na->hosts;
	na->hosts = x;
	na->link_count++;
	n = na;
    }
    debug(37, 3, "netdbSendPing: pinging %s\n", hostname);
    icmpDomainPing(addr, hostname);
    n->pings_sent++;
    n->next_ping_time = squid_curtime + Config.Netdb.period;
    n->last_use_time = squid_curtime;
    xfree(hostname);
}

static struct in_addr
networkFromInaddr(struct in_addr a)
{
    struct in_addr b;
    b.s_addr = ntohl(a.s_addr);
#if USE_CLASSFUL
    if (IN_CLASSC(b.s_addr))
	b.s_addr &= IN_CLASSC_NET;
    else if (IN_CLASSB(b.s_addr))
	b.s_addr &= IN_CLASSB_NET;
    else if (IN_CLASSA(b.s_addr))
	b.s_addr &= IN_CLASSA_NET;
#else
    /* use /24 for everything */
    b.s_addr &= IN_CLASSC_NET;
#endif
    b.s_addr = htonl(b.s_addr);
    return b;
}

static int
sortByRtt(netdbEntry ** n1, netdbEntry ** n2)
{
    if ((*n1)->rtt > (*n2)->rtt)
	return 1;
    else if ((*n1)->rtt < (*n2)->rtt)
	return -1;
    else
	return 0;
}

static net_db_peer *
netdbPeerByName(const netdbEntry * n, const char *peername)
{
    int i;
    net_db_peer *p = n->peers;
    for (i = 0; i < n->n_peers; i++, p++) {
	if (!strcmp(p->peername, peername))
	    return p;
    }
    return NULL;
}

static net_db_peer *
netdbPeerAdd(netdbEntry * n, peer * e)
{
    net_db_peer *p;
    net_db_peer *o;
    int osize;
    int i;
    if (n->n_peers == n->n_peers_alloc) {
	o = n->peers;
	osize = n->n_peers_alloc;
	if (n->n_peers_alloc == 0)
	    n->n_peers_alloc = 2;
	else
	    n->n_peers_alloc <<= 1;
	debug(37, 3, "netdbPeerAdd: Growing peer list for '%s' to %d\n",
	    n->network, n->n_peers_alloc);
	n->peers = xcalloc(n->n_peers_alloc, sizeof(net_db_peer));
	meta_data.netdb_peers += n->n_peers_alloc;
	for (i = 0; i < osize; i++)
	    *(n->peers + i) = *(o + i);
	if (osize) {
	    safe_free(o);
	    meta_data.netdb_peers -= osize;
	}
    }
    p = n->peers + n->n_peers;
    p->peername = netdbPeerName(e->host);
    n->n_peers++;
    return p;
}

static int
sortPeerByRtt(net_db_peer * p1, net_db_peer * p2)
{
    if (p1->rtt > p2->rtt)
	return 1;
    else if (p1->rtt < p2->rtt)
	return -1;
    else
	return 0;
}

static void
netdbSaveState(void *foo)
{
    LOCAL_ARRAY(char, path, SQUID_MAXPATHLEN);
    FILE *fp;
    netdbEntry *n;
    netdbEntry *next;
    net_db_name *x;
    struct timeval start = current_time;
    int count = 0;
    sprintf(path, "%s/netdb_state", swappath(0));
    fp = fopen(path, "w");
    if (fp == NULL) {
	debug(50, 1, "netdbSaveState: %s: %s\n", path, xstrerror());
	return;
    }
    next = (netdbEntry *) hash_first(addr_table);
    while ((n = next)) {
	next = (netdbEntry *) hash_next(addr_table);
	if (n->pings_recv == 0)
	    continue;
	fprintf(fp, "%s %d %d %10.5f %10.5f %d %d",
	    n->network,
	    n->pings_sent,
	    n->pings_recv,
	    n->hops,
	    n->rtt,
	    (int) n->next_ping_time,
	    (int) n->last_use_time);
	for (x = n->hosts; x; x = x->next)
	    fprintf(fp, " %s", x->name);
	fprintf(fp, "\n");
	count++;
    }
    fclose(fp);
    getCurrentTime();
    debug(37, 0, "NETDB state saved; %d entries, %d msec\n",
	count, tvSubMsec(start, current_time));
    eventAdd("netdbSaveState", netdbSaveState, NULL, 3617);
}

static void
netdbReloadState(void)
{
    LOCAL_ARRAY(char, path, SQUID_MAXPATHLEN);
    char *buf = get_free_4k_page();
    char *t;
    FILE *fp;
    netdbEntry *n;
    netdbEntry N;
    struct in_addr addr;
    int count = 0;
    struct timeval start = current_time;
    sprintf(path, "%s/netdb_state", swappath(0));
    fp = fopen(path, "r");
    if (fp == NULL)
	return;
    while (fgets(buf, 4095, fp)) {
	memset(&N, '\0', sizeof(netdbEntry));
	if ((t = strtok(buf, w_space)) == NULL)
	    continue;
	if (!safe_inet_addr(t, &addr))
	    continue;
	if (netdbLookupAddr(addr) != NULL)	/* no dups! */
	    continue;
	if ((t = strtok(NULL, w_space)) == NULL)
	    continue;
	N.pings_sent = atoi(t);
	if ((t = strtok(NULL, w_space)) == NULL)
	    continue;
	N.pings_recv = atoi(t);
	if (N.pings_recv == 0)
	    continue;
	/* give this measurement low weight */
	N.pings_sent = 1;
	N.pings_recv = 1;
	if ((t = strtok(NULL, w_space)) == NULL)
	    continue;
	N.hops = atof(t);
	if ((t = strtok(NULL, w_space)) == NULL)
	    continue;
	N.rtt = atof(t);
	if ((t = strtok(NULL, w_space)) == NULL)
	    continue;
	N.next_ping_time = (time_t) atoi(t);
	if ((t = strtok(NULL, w_space)) == NULL)
	    continue;
	N.last_use_time = (time_t) atoi(t);
	n = xcalloc(1, sizeof(netdbEntry));
	memcpy(n, &N, sizeof(netdbEntry));
	netdbHashInsert(n, addr);
	while ((t = strtok(NULL, w_space)) != NULL) {
	    if (netdbLookupHost(t) != NULL)	/* no dups! */
		continue;
	    netdbHostInsert(n, t);
	}
	count++;
    }
    put_free_4k_page(buf);
    fclose(fp);
    getCurrentTime();
    debug(37, 0, "NETDB state reloaded; %d entries, %d msec\n",
	count, tvSubMsec(start, current_time));
}

static char *
netdbPeerName(const char *name)
{
    wordlist *w;
    for (w = peer_names; w; w = w->next) {
	if (!strcmp(w->key, name))
	    return w->key;
    }
    w = xcalloc(1, sizeof(wordlist));
    w->key = xstrdup(name);
    *peer_names_tail = w;
    peer_names_tail = &w->next;
    return w->key;
}



#endif /* USE_ICMP */

/* PUBLIC FUNCTIONS */

void
netdbInit(void)
{
#if USE_ICMP
    if (addr_table)
	return;
    addr_table = hash_create((int (*)_PARAMS((const char *, const char *))) strcmp, 229, hash_string);
    host_table = hash_create((int (*)_PARAMS((const char *, const char *))) strcmp, 467, hash_string);
    eventAdd("netdbSaveState", netdbSaveState, NULL, 3617);
    netdbReloadState();
#endif
}

void
netdbPingSite(const char *hostname)
{
#if USE_ICMP
    netdbEntry *n;
    if ((n = netdbLookupHost(hostname)) != NULL)
	if (n->next_ping_time > squid_curtime)
	    return;
    ipcache_nbgethostbyname(hostname,
	-1,
	netdbSendPing,
	(void *) xstrdup(hostname));
#endif
}

void
netdbHandlePingReply(const struct sockaddr_in *from, int hops, int rtt)
{
#if USE_ICMP
    netdbEntry *n;
    int N;
    debug(37, 3, "netdbHandlePingReply: from %s\n", inet_ntoa(from->sin_addr));
    if ((n = netdbLookupAddr(from->sin_addr)) == NULL)
	return;
    N = ++n->pings_recv;
    if (N > 5)
	N = 5;
    n->hops = ((n->hops * (N - 1)) + hops) / N;
    n->rtt = ((n->rtt * (N - 1)) + rtt) / N;
    debug(37, 3, "netdbHandlePingReply: %s; rtt=%5.1f  hops=%4.1f\n",
	n->network,
	n->rtt,
	n->hops);
#endif
}

void
netdbFreeMemory(void)
{
#if USE_ICMP
    netdbEntry *n;
    netdbEntry **L1;
    net_db_name *x;
    net_db_name **L2;
    int i = 0;
    int j;
    L1 = xcalloc(meta_data.netdb_addrs, sizeof(netdbEntry *));
    n = (netdbEntry *) hash_first(addr_table);
    while (n && i < meta_data.netdb_addrs) {
	*(L1 + i) = n;
	i++;
	n = (netdbEntry *) hash_next(addr_table);
    }
    for (j = 0; j < i; j++) {
	n = *(L1 + j);
	safe_free(n->peers);
	xfree(n);
    }
    xfree(L1);
    i = 0;
    L2 = xcalloc(meta_data.netdb_hosts, sizeof(net_db_name *));
    x = (net_db_name *) hash_first(host_table);
    while (x && i < meta_data.netdb_hosts) {
	*(L2 + i) = x;
	i++;
	x = (net_db_name *) hash_next(host_table);
    }
    for (j = 0; j < i; j++) {
	x = *(L2 + j);
	xfree(x->name);
	xfree(x);
    }
    xfree(L2);
    hashFreeMemory(addr_table);
    hashFreeMemory(host_table);
    wordlistDestroy(&peer_names);
    peer_names = NULL;
    peer_names_tail = &peer_names;
#endif
}

int
netdbHops(struct in_addr addr)
{
#if USE_ICMP
    netdbEntry *n = netdbLookupAddr(addr);
    if (n && n->pings_recv) {
	n->last_use_time = squid_curtime;
	return (int) (n->hops + 0.5);
    }
#endif
    return 256;
}

void
netdbDump(StoreEntry * sentry)
{
#if USE_ICMP
    netdbEntry *n;
    netdbEntry **list;
    net_db_name *x;
    int k;
    int i;
    int j;
    net_db_peer *p;
    storeAppendPrintf(sentry, "{Network DB Statistics:\n");	/* } */
    storeAppendPrintf(sentry, "{%-16.16s %9s %7s %5s %s}\n",
	"Network",
	"recv/sent",
	"RTT",
	"Hops",
	"Hostnames");
    list = xcalloc(meta_data.netdb_addrs, sizeof(netdbEntry *));
    i = 0;
    for (n = netdbGetFirst(addr_table); n; n = netdbGetNext(addr_table))
	*(list + i++) = n;
    if (i != meta_data.netdb_addrs)
	debug(37, 0, "WARNING: netdb_addrs count off, found %d, expected %d\n",
	    i, meta_data.netdb_addrs);
    qsort((char *) list,
	i,
	sizeof(netdbEntry *),
	(QS) sortByRtt);
    for (k = 0; k < i; k++) {
	n = *(list + k);
	storeAppendPrintf(sentry, "{%-16.16s %4d/%4d %7.1f %5.1f",	/* } */
	    n->network,
	    n->pings_recv,
	    n->pings_sent,
	    n->rtt,
	    n->hops);
	for (x = n->hosts; x; x = x->next)
	    storeAppendPrintf(sentry, " %s", x->name);
	storeAppendPrintf(sentry, close_bracket);
	p = n->peers;
	for (j = 0; j < n->n_peers; j++, p++) {
	    storeAppendPrintf(sentry, "{    %-22.22s %7.1f %5.1f}\n",
		p->peername,
		p->rtt,
		p->hops);
	}
    }
    storeAppendPrintf(sentry, close_bracket);
    xfree(list);
#endif
}

int
netdbHostHops(const char *host)
{
#if USE_ICMP
    netdbEntry *n = netdbLookupHost(host);
    if (n) {
	n->last_use_time = squid_curtime;
	return (int) (n->hops + 0.5);
    }
#endif
    return 0;
}

int
netdbHostRtt(const char *host)
{
#if USE_ICMP
    netdbEntry *n = netdbLookupHost(host);
    if (n) {
	n->last_use_time = squid_curtime;
	return (int) (n->rtt + 0.5);
    }
#endif
    return 0;
}

void
netdbUpdatePeer(request_t * r, peer * e, int irtt, int ihops)
{
#if USE_ICMP
    netdbEntry *n;
    double rtt = (double) irtt;
    double hops = (double) ihops;
    net_db_peer *p;
    debug(37, 3, "netdbUpdatePeer: '%s', %d hops, %d rtt\n", r->host, ihops, irtt);
    n = netdbLookupHost(r->host);
    if (n == NULL) {
	debug(37, 3, "netdbUpdatePeer: host '%s' not found\n", r->host);
	return;
    }
    if ((p = netdbPeerByName(n, e->host)) == NULL)
	p = netdbPeerAdd(n, e);
    p->rtt = rtt;
    p->hops = hops;
    p->expires = squid_curtime + 3600;
    if (n->n_peers < 2)
	return;
    qsort((char *) n->peers,
	n->n_peers,
	sizeof(net_db_peer),
	(QS) sortPeerByRtt);
#endif
}

void
netdbDeleteAddrNetwork(struct in_addr addr)
{
#if USE_ICMP
    netdbEntry *n = netdbLookupAddr(addr);
    if (n == NULL)
	return;
    debug(37, 1, "netdbDeleteAddrNetwork: %s\n", n->network);
    netdbRelease(n);
#endif
}
