/*
** SellaNMS
** $Id: icmp.cxx,v 1.40 2008/01/31 22:31:14 sella Exp $
** Copyright (c) 2001-2005 Digital Genesis Software, LLC. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
*/

static const char rcsid[] = "$Id: icmp.cxx,v 1.40 2008/01/31 22:31:14 sella Exp $";

#include "daemon.h"
#include "icmp.h"
#include "common/ip_util.h"
#include "state/state.h"
#include <math.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <values.h>
#include <unistd.h>
#include <pthread.h>
#include <linux/if.h>
#include <sys/timeb.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/poll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip_icmp.h>
#include <stl/algorithm>

#define BUF_SIZE 512
#define THREAD_WAIT_AND_KILL
#define AUTO_ADJUST_QUEUE_SIZE
//#define SKIP_EVALUATE

extern ConfigNode* config;
extern bool interrupt;

ICMP::ICMP():sleepIndex(0),calcFlags(0),tosDefault(MONITOR_TOS_DEF),knownAddresses(0) {
	pthread_mutex_init(&calc_queue_mutex, NULL);
	pthread_mutex_init(&calc_data_hash_mutex, NULL);
	pthread_mutex_init(&calc_data_list_mutex, NULL);
	pthread_mutex_init(&policy_queue_mutex, NULL);
	pthread_mutex_init(&transmit_mutex, NULL);
	pthread_mutex_init(&state_mutex, NULL);

	flag_init(calc_flag);
	flag_init(policy_flag);
}

ICMP::~ICMP() {
	pthread_mutex_destroy(&calc_queue_mutex);
	pthread_mutex_destroy(&calc_data_hash_mutex);
	pthread_mutex_destroy(&calc_data_list_mutex);
	pthread_mutex_destroy(&policy_queue_mutex);
	pthread_mutex_destroy(&transmit_mutex);
	pthread_mutex_destroy(&state_mutex);

	for (hash_map<u_int32_t, IPv4State *>::iterator status = state_hash.begin(); status != state_hash.end(); ++status) {
		delete status->second;
	}
	state_hash.clear();
}

RESULT ICMP::readConfig() {
	RESULT result = SUCCESS;
	string str;
	vector<string> v;
	vector<string>::iterator v_it;
	ConfigNode node;
	struct in_addr ip;
	unsigned long tmp = 0;

	if (!config->assignNode(getConfigPath(), node)) {
		LMSG("No '%s' section found within the config file.", getConfigPath());

		return FAILURE;
	}

	/* How often to start a polling inteval. */
	node.assignValue("interval", interval, MONITOR_ICMP_INTERVAL_DEF, MONITOR_ICMP_INTERVAL_MIN, MONITOR_ICMP_INTERVAL_MAX);

	/* How often to start a polling inteval. */
	node.assignValue("interval-adjust", intervalAdjust, MONITOR_ICMP_INTERVAL_ADJUST_DEF, MONITOR_ICMP_INTERVAL_ADJUST_MIN, MONITOR_ICMP_INTERVAL_ADJUST_MAX);

	/* Number of packets to send to each address per interval. */
	node.assignValue("transmit", transmit, MONITOR_ICMP_TRANSMIT_DEF, MONITOR_ICMP_TRANSMIT_MIN, MONITOR_ICMP_TRANSMIT_MAX);

	/* The message placed in the payload of the ICMP packet. Up to 36 characters. */
	node.assignValue("payload", payloadMessage, MONITOR_ICMP_PAYLOADMESSAGE_DEF);

	if (node.assignValue("source", str)) {
		if (inet_pton(AF_INET, str.c_str(), &ip) == 0) {
			this->source = htonl(INADDR_ANY);
		} else {
			this->source = ip.s_addr;
		}
	} else if (node.assignValue("interface", str)) {
		source_ip(str.c_str(), &ip);
		this->source = ip.s_addr;
	} else {
		this->source = htonl(INADDR_ANY);
	}

	/* Timemout value for an ICMP packet before it is considered lost. */
	node.assignValue("timeout", tmp, MONITOR_ICMP_TIMEOUT_DEF, MONITOR_ICMP_TIMEOUT_MIN, MONITOR_ICMP_TIMEOUT_MAX);
	this->timeout.tv_sec = tmp / 1000;
	this->timeout.tv_usec = (tmp % 1000) * 1000;

	/* This option should be removed. A policy should handle this. */
	this->ignoreEdge = node.isNode("ignore-edge");

	if (node.assignValue("tos", tmp)) {
		this->tosDefault = tmp;
	} else if (node.assignValue("dscp", tmp)) {
		this->tosDefault = tmp << 2;
	} else if (node.assignValue("precedence", tmp)) {
		this->tosDefault = tmp << 5;
	} else {
		this->tosDefault = MONITOR_ICMP_TOS_DEF;
	}

	/* The TTL to place in outgoing ICMP packets (how many hops they may go). */


	node.assignValue("ttl", ttl, MONITOR_ICMP_TTL_DEF, MONITOR_ICMP_TTL_MIN, MONITOR_ICMP_TTL_MAX);

	/* Set queue depths. */
	node.assignValue("queue-depth", tmp, MONITOR_ICMP_QUEUEDEPTH_DEF, MONITOR_ICMP_QUEUEDEPTH_MIN, MONITOR_ICMP_QUEUEDEPTH_MAX);
	node.assignValue("calc-queue-depth", calcQueueDepth, tmp, MONITOR_ICMP_QUEUEDEPTH_MIN, MONITOR_ICMP_QUEUEDEPTH_MAX);
	node.assignValue("policy-queue-depth", policyQueueDepth, calcQueueDepth * MONITOR_ICMP_POLICYQUEUEDEPTH_MLT, MONITOR_ICMP_QUEUEDEPTH_MIN, MONITOR_ICMP_QUEUEDEPTH_MAX);

	/* Number of policy queue entries allowed to be processed at a time. (impacts memory usage) */
	node.assignValue("policy-queue-limit", policyQueueLimit, policyQueueDepth, MONITOR_ICMP_POLICYQUEUELIMIT_MIN, MONITOR_ICMP_POLICYQUEUELIMIT_MAX);

	node.assignValue("policy-queue-lower-threshold", policyQueueLowerThreshold, MONITOR_ICMP_QUEUELOWERTHRESHOLD_DEF, MONITOR_ICMP_QUEUELOWERTHRESHOLD_MIN, MONITOR_ICMP_QUEUELOWERTHRESHOLD_MAX);

	node.assignValue("policy-queue-upper-threshold", policyQueueUpperThreshold, MONITOR_ICMP_QUEUEUPPERTHRESHOLD_DEF, MONITOR_ICMP_QUEUEUPPERTHRESHOLD_MIN, MONITOR_ICMP_QUEUEUPPERTHRESHOLD_MAX);

	node.assignValue("calc-queue-lower-threshold", calcQueueLowerThreshold, MONITOR_ICMP_QUEUELOWERTHRESHOLD_DEF, MONITOR_ICMP_QUEUELOWERTHRESHOLD_MIN, MONITOR_ICMP_QUEUELOWERTHRESHOLD_MAX);

	node.assignValue("calc-queue-upper-threshold", calcQueueUpperThreshold, MONITOR_ICMP_QUEUEUPPERTHRESHOLD_DEF, MONITOR_ICMP_QUEUEUPPERTHRESHOLD_MIN, MONITOR_ICMP_QUEUEUPPERTHRESHOLD_MAX);

	/* Interval (sec) that calc_queue_thread runs calculations on collected data and passes results to policy_thread. */
	node.assignValue("calc-interval", calcInterval, MONITOR_ICMP_CALCINTERVAL_DEF, MONITOR_ICMP_CALCINTERVAL_MIN, MONITOR_ICMP_CALCINTERVAL_MAX);

	/* Number of secs to auto adjust calcInterval by, when system is getting behind, or catches up. */
	node.assignValue("calc-interval-adjustment", calcIntervalAdj, MONITOR_ICMP_CALCINTERVAL_ADJUST_DEF, MONITOR_ICMP_CALCINTERVAL_ADJUST_MIN, calcInterval);

	suppression = !node.isNode("suppression disable");
	node.assignValue("suppression packet-loss", lossSupVar, MONITOR_ICMP_LOSSSUPVAR_DEF, 0, 100);
	node.assignValue("suppression rtt", rttSupVar, MONITOR_ICMP_RTTSUPVAR_DEF, MONITOR_ICMP_TIMEOUT_MIN, MONITOR_ICMP_TIMEOUT_MAX);
	node.assignValue("suppression jitter", jtrSupVar, MONITOR_ICMP_JTRSUPVAR_DEF, MONITOR_ICMP_TIMEOUT_MIN, MONITOR_ICMP_TIMEOUT_MAX);
	node.assignValue("suppression normalized-rtt", nrmSupVar, MONITOR_ICMP_NRMSUPVAR_DEF, MONITOR_ICMP_TIMEOUT_MIN, MONITOR_ICMP_TIMEOUT_MIN);

	/* Clear any existing policies. */
	inputPolicy.clear();
	outputPolicy.clear();

	/* Add and compile input policy. */
	if (node.assignValues("policy input", v)) {
		/* This is actually just added to the begining of the output policies. */
		if (inputPolicy.addPolicies(config, v) == FAILURE) {
			return FAILURE;
		}
	}

	/* Add and compile output policy. */
	if (node.assignValues("policy output", v)) {
		if (outputPolicy.addPolicies(config, v) == FAILURE) {
			return FAILURE;
		}
	}

	/* Verify we have required policies. */
	if (outputPolicy.empty()) {
		LMSG("an output policy is required for this module.");

		return FAILURE;
	}

	/* Configure Storage */
	if (storage.setConfig(*config) != SUCCESS) {
		result = FAILURE;
	}

	printConfig();
	return result;
}

void ICMP::printConfig() {
	string str;
	vector<string> v;
	vector<string>::iterator v_it;

	in_addr s;
	s.s_addr = this->source;
	char buf[INET_ADDRSTRLEN];

	if (!TRACE_ISSET(TRACE_CONFIG)) {
		return;
	}

	TRACE(TRACE_CONFIG, "interval: %lu (sec)", this->interval);
	TRACE(TRACE_CONFIG, "interval-adjust: %lu (sec)", this->intervalAdjust);
	TRACE(TRACE_CONFIG, "transmit: %lu", this->transmit);
	TRACE(TRACE_CONFIG, "payload: %s", this->payloadMessage.c_str());
	inet_ntop(AF_INET, &s, buf, INET_ADDRSTRLEN);
	TRACE(TRACE_CONFIG, "source: %s", buf);
	TRACE(TRACE_CONFIG, "timeout: %lu (msec)", tvtousec(this->timeout)/1000);
	TRACE(TRACE_CONFIG, "tos: %lu (0x%02x)", this->tosDefault, this->tosDefault);
	TRACE(TRACE_CONFIG, "dscp: %lu (0x%02x)", this->tosDefault >> 2, this->tosDefault);
	TRACE(TRACE_CONFIG, "precedence: %lu (0x%02x)", this->tosDefault >> 5, this->tosDefault);
	TRACE(TRACE_CONFIG, "ttl: %lu (hops)", this->ttl);
	TRACE(TRACE_CONFIG, "calc-interval: %lu (sec)", this->calcInterval);
	TRACE(TRACE_CONFIG, "calc-interval-adjustment: %lu (sec)", this->calcIntervalAdj);
	TRACE(TRACE_CONFIG, "calc-queue-depth: %lu", this->calcQueueDepth);
	TRACE(TRACE_CONFIG, "policy-queue-depth: %lu", this->policyQueueDepth);
	TRACE(TRACE_CONFIG, "policy-queue-limit: %lu", this->policyQueueLimit);
	TRACE(TRACE_CONFIG, "policy-queue-lower-threshold: %lu", this->policyQueueLowerThreshold);
	TRACE(TRACE_CONFIG, "policy-queue-upper-threshold: %lu", this->policyQueueUpperThreshold);
	TRACE(TRACE_CONFIG, "calc-queue-lower-threshold: %lu", this->calcQueueLowerThreshold);
	TRACE(TRACE_CONFIG, "calc-queue-upper-threshold: %lu", this->calcQueueUpperThreshold);
	TRACE(TRACE_CONFIG, "suppression disable: %s", (suppression) ? "not set" : "set");
	TRACE(TRACE_CONFIG, "suppression packet-loss: %lu (percentage)", this->lossSupVar);
	TRACE(TRACE_CONFIG, "suppression rtt: %lu (ms)", this->rttSupVar);
	TRACE(TRACE_CONFIG, "suppression jitter: %lu (ms)", this->jtrSupVar);
	TRACE(TRACE_CONFIG, "suppression normalized-rtt: %lu (ms)", this->nrmSupVar);

	if (config->assignValues("monitor icmp policy input", v)) {
		str.erase();
		for (v_it = v.begin(); v_it != v.end(); ++v_it) {
			str.append(*v_it + " ");
		}
		TRACE(TRACE_CONFIG, "policy input: %s", str.c_str());
	} else {
		TRACE(TRACE_CONFIG, "policy input: none");
	}

	if (config->assignValues("monitor icmp policy output", v)) {
		str.erase();
		for (v_it = v.begin(); v_it != v.end(); ++v_it) {
			str.append(*v_it + " ");
		}
		TRACE(TRACE_CONFIG, "policy output: %s", str.c_str());
	} else {
		TRACE(TRACE_CONFIG, "policy output: none");
	}

	return;
}

RESULT ICMP::execute() {
	RESULT result = SUCCESS;

	int err;
	last_id = 0;
	pthread_t thread_id = 0;
	this->module_id = (uint16_t) getpid();

	slist<pthread_t> t_list;
	slist<pthread_t>::iterator it;

	TRACE(TRACE_THREAD, "ICMP::execute() starting");

	/* Create the receive thread */
	if (result == SUCCESS) {
		if ((err = pthread_create(&thread_id, NULL, (void* (*)(void*))ICMP::receive_thread, this)) == 0) {
			TRACE(TRACE_THREAD, "receive thread created.");
			t_list.push_front(thread_id);
		}
	}

	/* Create the timeout thread */
	if (result == SUCCESS) {
		if ((err = pthread_create(&thread_id, NULL, (void* (*)(void*))ICMP::timeout_thread, this)) == 0) {
			TRACE(TRACE_THREAD, "timeout thread created.");
			t_list.push_front(thread_id);
		}
	}

	/* Create the calc_queue thread */
	if (result == SUCCESS) {
		if ((err = pthread_create(&thread_id, NULL, (void* (*)(void*))ICMP::calc_queue_thread, this)) == 0) {
			TRACE(TRACE_THREAD, "calc_queue thread created.");
			t_list.push_front(thread_id);
		}
	}

	/* Create the calculation thread */
	if (result == SUCCESS) {
		if ((err = pthread_create(&thread_id, NULL, (void* (*)(void*))ICMP::calculation_thread, this)) == 0) {
			TRACE(TRACE_THREAD, "calculation thread created.");
			t_list.push_front(thread_id);
		}
	}

	/* Create the policy thread */
	if ((err = pthread_create(&thread_id, NULL, (void* (*)(void*))ICMP::policy_thread, this)) == 0) {
		TRACE(TRACE_THREAD, "policy thread created.");
		t_list.push_front(thread_id);
	}

	/* Create the transmit thread */
	if (result == SUCCESS) {
		if ((err = pthread_create(&thread_id, NULL, (void* (*)(void*))ICMP::transmit_thread, this)) == 0) {
			TRACE(TRACE_THREAD, "transmit thread created.");
			t_list.push_front(thread_id);
		}
	}

	/* Join each thread to free its resources. */
	for (it = t_list.begin(); it != t_list.end(); ++it) {
		/* Wait for thread to complete. */
		TRACE(TRACE_THREAD, "joining thread %lu", *it);
		if ((err = pthread_join(*it, NULL)) != 0) {
			LMSG("pthread_join returned %d", err);
		}
	}

	TRACE(TRACE_THREAD, "ICMP::execute() exiting");

	return result;
}

RESULT ICMP::transmit_thread(void* args) {
	RESULT result = SUCCESS;	

	ICMP *instance = (ICMP*) args;
	unsigned long	packet_length = ICMP_MINLEN + sizeof(struct icmp_payload);
	char				request[packet_length], buf[INET_ADDRSTRLEN];
	unsigned long	calc_queue_fill;

	int				s = 0, ttl;
	sockaddr_in		src;

	timeval			interval;
	timeval			start;
	timeval			stop;

	interval.tv_sec = instance->interval;
	interval.tv_usec = 0;

	/* Initialize the template ICMP packet. */
	instance->initPacket(request, packet_length);

	TRACE(TRACE_THREAD, "ICMP::transmit_thread() starting main loop");

	grantPriv();

	/* Open a socket for packet transmission. */
	s = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);

	if (s < 0) {
		interrupt = true;

		LMSG("root privileges required for this module.");
		result = FAILURE;
	} else {
		fcntl(s, F_SETFL, O_NONBLOCK | O_ASYNC);

		bzero(&src, sizeof(struct sockaddr_in));

		src.sin_family = AF_INET;
		src.sin_port = 0;
		src.sin_addr.s_addr = instance->source;

		ttl = instance->ttl;
		if (setsockopt(s, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(ttl)) < 0) {
			LMSG("failed to set TTL value %lu.", ttl);
		}

		if (bind(s, (sockaddr *)&src, sizeof(sockaddr_in)) == -1) {
			interrupt = true;

			close(s);

			inet_ntop(AF_INET, &src.sin_addr, buf, INET_ADDRSTRLEN);
			LMSG("ICMP monitoring module unable to bind to local address '%s'.\n", buf);
			result = FAILURE;
		}
	}

	dropPriv();

	while (!interrupt) {
		gettimeofday(&start, NULL);

		if (instance->readKnownAddress(instance->ignoreEdge) != SUCCESS) {
			LMSG("failed to read IPs from database");

			interrupt = true;
			continue;
		}

		if (instance->netblocks.size() < 1) {
			TRACE(TRACE_DEBUG, "no IPs to monitor - sleeping for %lu sec.", instance->interval);

			sleep(instance->interval);
			continue;
		}

		//FIXME: These next two blocks should be functionalized.

#ifdef AUTO_ADJUST_QUEUE_SIZE
		/* Adjust calc and policy queue depth, if the number of IPs is too great. */
		if (instance->calcQueueDepth < instance->netblocks.size()) {
			LMSG("adjusting 'calc-queue-depth' from %lu to %lu (IPs * 1.5).", instance->calcQueueDepth, (unsigned long) (instance->netblocks.size() * 1.5));
			instance->calcQueueDepth = (unsigned long) (instance->netblocks.size() * 1.5);
			if (instance->calcQueueDepth > instance->policyQueueDepth) {
				LMSG("adjusting 'queue-queue-depth' from %lu to %lu (matching 'calc-queue-depth').", instance->policyQueueDepth, instance->calcQueueDepth);
				instance->policyQueueDepth = instance->calcQueueDepth;
			}
		}
#endif

		/* Adjust the polling interval if needed. */
		pthread_mutex_lock(&(instance->calc_queue_mutex));
		calc_queue_fill = 100 * instance->calc_queue.size() / instance->calcQueueDepth;
		pthread_mutex_unlock(&(instance->calc_queue_mutex));

		if (calc_queue_fill >= instance->calcQueueUpperThreshold) {
			/* Adjust the polling interval outwards. */
			if (interval.tv_sec + instance->intervalAdjust <= MONITOR_ICMP_INTERVAL_MAX) {
				LMSG("polling interval adjusted from %lu to %lu sec (calc_queue %lu%% full)", interval.tv_sec, interval.tv_sec + instance->intervalAdjust, calc_queue_fill);

				interval.tv_sec += instance->intervalAdjust;
			}

			LMSG("polling being delayed by %lu sec due to queue congestion (calc_queue %lu%% full). Lengthen the polling 'interval' or increase 'calc-queue-depth' to avoid this condition.", interval.tv_sec / 2, calc_queue_fill);

			/* Delay for half the current interval. */
			usleep((interval.tv_sec / 2) * 1000000);

			continue;
		} else if (instance->calc_queue.size() < instance->calcQueueLowerThreshold) {
			/* Adjust the polling interval back towards 'interval' setting. */
			if (interval.tv_sec - instance->intervalAdjust >= instance->interval) {
				LMSG("polling interval adjusted from %lu to %lu sec. (calc_queue %lu%% full)", interval.tv_sec, interval.tv_sec - instance->intervalAdjust, calc_queue_fill);

				interval.tv_sec -= instance->intervalAdjust;
			}
		}

		/* Begin polling targets. */
		if (instance->pollTargets(s, request, &interval) != SUCCESS) {
			LMSG("failed to poll all ICMP targets - shutting down");

			interrupt = true;
		}

		gettimeofday(&stop, NULL);
		subtract_timeval(stop, start);

		TRACE(TRACE_TIMER, "ICMP polling interval took: %d seconds, %d microseconds.", stop.tv_sec, stop.tv_usec);
	}

	if (s > 0) {
		close(s);
	}

	TRACE(TRACE_THREAD, "ICMP::transmit_thread() ending main loop");

	pthread_exit(NULL);
}

RESULT ICMP::pollTargets(int s, char *packet, timeval *interval) {
	RESULT			result = SUCCESS;

//	icmp				*header = (icmp *)packet;
	icmp_payload	*payload = (icmp_payload *)(packet + ICMP_MINLEN);

	int				tos, tos_prev = 0;

	unsigned long	packet_length = ICMP_MINLEN + sizeof(struct icmp_payload);

	char				*ip_address = NULL;
	Netblock			netblock;

	timeval			stop_time;
	timeval			timer_x;
	timeval			timer_y;
	timeval			timer_z;

	unsigned long	samples[8];
	unsigned long 	sleep_time = 0;
	unsigned long	sleep_accuracy = 0;
	unsigned long 	average_transmit;
	signed long 	remaining_time;

	unsigned long	total_time = 0;
	unsigned long	transmitted = 0;

	unsigned long	burst_rate = 1;
	unsigned long	burst_index = 0;
	unsigned long	packet_count = this->transmit * this->netblocks.size();

	/* Get current time of day and add polling interval to deterime polling stop time. */
	gettimeofday(&stop_time, NULL);
	add_timeval(stop_time, *interval);

	/* Calculate a sample set for sleep times for use in determining how many packets to burst,
		while keeping bandwidth usage balanced over the available polling time. */
	for (int i = 0; i < 8; i++) {
		this->transmitSleep(sleep_time, sleep_accuracy);
	}
	memset(samples, 0, 8 * sizeof(unsigned long));

	/* Send ICMP polling packets. */
	burst_index = 0;
	gettimeofday(&timer_x, NULL);

	for (unsigned long i = this->transmit; (i > 0) && (!interrupt); i--) {
		for (vector<string>::iterator j = this->netblocks.begin(); (j != this->netblocks.end()) && (!interrupt); ++j) {
			netblock.setNetblock(j->c_str());
			if (netblock.isValid()) {
				for (unsigned long k = 0; (ip_address = netblock[k]) != NULL; k++) {
					inet_pton(AF_INET, ip_address, &(payload->destination));

#ifdef IP_TOS
					/* Do we need a special TOS value for this IP? */
					if (this->tosMap.find(ip_address) != this->tosMap.end()) {
						tos = this->tosMap[ip_address];
					} else {
						tos = this->tosDefault;
					}

					/* Update the sockets TOS value, if it changed. */
					if (tos != tos_prev) {
							/* If they passed in precidence, then this output will appear wrong. */
						TRACE(TRACE_DEBUG, "changing socket ToS value from %d to %d (dscp: %lu, prec: %lu) for IP %s", tos_prev , tos, tos >> 2, tos >> 5, ip_address);
						tos_prev = tos;

						grantPriv();
						if (setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&tos, sizeof(tos)) < 0) {
							LMSG("failed to set TOS value %lu (dscp: %lu, prec: %lu) for IP %s.", tos, tos >> 2, tos >> 5, ip_address);
						}
						dropPriv();
					}
#endif

					if (this->transmitPacket(s, packet, packet_length) != SUCCESS) {
						LMSG("failed to send packet to %s", ip_address);
					}
					
					SAFE_DELETE_ARRAY(ip_address);

					/* Take a sample of the amount of time taken for the last single ping packet sent. */
					gettimeofday(&timer_y, NULL);
					memcpy(&timer_z, &timer_y, sizeof(struct timeval));
					subtract_timeval(timer_y, timer_x);	

					total_time += tvtousec(timer_y);
					transmitted++;

					/* Store the last time measurement for next packet. */
					memcpy(&timer_x, &timer_z, sizeof(struct timeval));

					/* Check to see how much time remains in the current polling interval. */
					memcpy(&timer_z, &stop_time, sizeof(timeval));
					subtract_timeval(timer_z, timer_x);

					/* Calculate the avaerage amount of time taken to transmit a packet. */
					average_transmit = total_time / transmitted;

					/* Calculate the burst size, and sleep time. */
					remaining_time = tvtousec(timer_z) / packet_count;
					if (remaining_time > average_transmit) {
						burst_rate = 1;
						sleep_time = remaining_time;
					} else {
						burst_rate = remaining_time > 0 ? (average_transmit * packet_count) / remaining_time : packet_count;
						sleep_time = 0;
					}

					/* Determine if it is time to sleep. */
					if (++burst_index >= burst_rate) {
						burst_index = 0;

						/* FIXME: Riggy Fix... probably fixes wraps on a long. */	
						if (sleep_time > tvtousec(*interval)) {
							sleep_time = 0;
						}
						this->transmitSleep(sleep_time, sleep_accuracy);
					}
					
					TRACE(TRACE_INTERNAL, "packets: %d (burst rate: %d)", packet_count, burst_rate);

					packet_count--;
				}	
			} else {
				TRACE(TRACE_DEBUG, "processed an invalid netblock.");

				result = FAILURE;
			}
		}
	}
	return result;
}

RESULT ICMP::transmitSleep(unsigned long t, unsigned long &accuracy) {
	timeval	start;
	timeval	stop;

	timespec	timer;

	gettimeofday(&start, NULL);

	memset(&timer, 0, sizeof(timespec));

	TRACE(TRACE_INTERNAL, "sleep: %u secs, %d nsec", t / 1000000, (t % 1000000) * 1000);

	timer.tv_sec = t / 1000000;
	timer.tv_nsec = (t % 1000000) * 1000;
	nanosleep(&timer, NULL);

	accuracy = 0;
	for (int i = 0; i < 8; i++) {
		accuracy += this->sleepSample[i];
	}
	accuracy <<= 3;

	gettimeofday(&stop, NULL);

	subtract_timeval(start, stop);
	this->sleepSample[this->sleepIndex++ % 8] = t - ((start.tv_sec * 1000000) + start.tv_usec);

	return SUCCESS;
}

RESULT ICMP::transmitPacket(int s, char *packet, unsigned long size) {
	RESULT				result = SUCCESS;
	icmp					*header = (icmp *)packet;
	icmp_payload		*payload = (icmp_payload *)(packet + ICMP_MINLEN);	
	static u_int16_t	seq = 0;

	transmit_entry_t		data;
	pair<u_int32_t, int>	key;
	
	sockaddr_in			dst;

	/* Icrement the static sequence. Not on a per IP basis. */
	seq += (u_int16_t) ((rand() % MONITOR_ICMP_SEQUENCEMAXADJ_DEF) + 1);

	/* Final packet modifications before checksum */
	header->icmp_seq = payload->sequence = data.sequence = seq;
	gettimeofday(&(payload->stamp), NULL);

	this->calculateChecksum(packet, size);

	/* Setup transmit_entry */
	data.address = payload->destination.s_addr;
	data.sequence = payload->sequence;

	memcpy(&(data.timestamp), &(payload->stamp), sizeof(struct timeval));

	key.first = data.address;
	key.second = data.sequence;

	/* Prepair to transmit packet. */
	bzero(&dst, sizeof(struct sockaddr_in));

	dst.sin_family = AF_INET;
	dst.sin_port = 0;
	dst.sin_addr = payload->destination;

	/* Locked to prevent receive_thread from getting a reply prior to the transmit_hash being updated. */
	pthread_mutex_lock(&(this->transmit_mutex));
	if (sendto(s, packet, ICMP_MINLEN + sizeof(struct icmp_payload), 0, (sockaddr *)&dst, sizeof(struct sockaddr_in)) == -1) {
		result = FAILURE;
	} else {
		this->transmit_hash[key] = this->transmit_list.insert(this->transmit_list.begin(), data);
	}
	pthread_mutex_unlock(&(this->transmit_mutex));

	return result;
}
					

RESULT ICMP::initPacket(char *packet, unsigned long size) {
	struct icmp				*header = (struct icmp *)packet;
	struct icmp_payload	*payload = (icmp_payload *)(packet + ICMP_MINLEN);

	/* Zero the ICMP Packet */
	bzero(packet, size);

	header->icmp_id = module_id;
	header->icmp_type = ICMP_ECHO;
	strncpy(payload->message, this->payloadMessage.c_str(), sizeof(payload->message));

	return SUCCESS;
}

RESULT ICMP::calculateChecksum(char *packet, unsigned long size) {
	struct icmp	*header = (struct icmp *)packet;

	/* Zero the packet length... required for checksum algorithm. */
	header->icmp_cksum = 0;

	/* Generate packet checksum. */
	header->icmp_cksum = in_cksum((unsigned short *)packet, size);

	return SUCCESS;
}

RESULT ICMP::timeout_thread(void* args) {
	ICMP 						*instance = (ICMP*) args;

	char						buf[INET_ADDRSTRLEN];

	timeval					stamp;
	time_t					delay;
	calc_queue_entry_t	entry;

	pair<u_int32_t, int>	key;
	bool						warn = false;
	unsigned long			calc_queue_size;

	bzero(&entry, sizeof(calc_queue_entry_t));
	entry.type = -1;
	entry.code = -1;

	struct in_addr ip;

	TRACE(TRACE_THREAD, "ICMP::timeout_thread() starting main loop");

	/* Wait 1/2 of max RTT between sweeps. */
	delay = tvtousec(instance->timeout) / 2;
	TRACE(TRACE_DEBUG, "timeout_thread using delay of %lu usec between scans", delay);

	while (!interrupt) {
		gettimeofday(&stamp, NULL);
		subtract_timeval(stamp, instance->timeout);

		/* See if the max RTT has been reached for any transmitted packets. */
		pthread_mutex_lock(&(instance->transmit_mutex)); /* This is a long lock.. don't like it. */
		for (transmit_list_t::iterator p = instance->transmit_list.begin(); !interrupt && p != instance->transmit_list.end(); ++p) {
			if (tvcmp(p->timestamp, stamp) < 0) {
				if (TRACE_ISSET(TRACE_INTERNAL)) {
					ip.s_addr = p->address;
					inet_ntop(AF_INET, &ip, buf, INET_ADDRSTRLEN);
					TRACE(TRACE_INTERNAL, "timeout for ip: %s (seq: %lu)", buf, p->sequence);
				}

				/* Assume this packet is lost. */
				entry.address = p->address;
				entry.timestamp = p->timestamp;

				/* Delete this packet reference from the hash. */
				key.first = p->address;
				key.second = p->sequence;
				instance->transmit_hash.erase(key);

				/* Delete this packet reference from the list. */
				p = instance->transmit_list.erase(p);

				/* Send a lost packet notification into the calc_queue. */
				pthread_mutex_lock(&(instance->calc_queue_mutex));
				calc_queue_size = instance->calc_queue.size();
				pthread_mutex_unlock(&(instance->calc_queue_mutex));

				if (calc_queue_size >= instance->calcQueueDepth) {
					if (warn) {
						LMSG("calc-queue has reached maximum depth of %lu entries - dropping data", instance->calcQueueDepth);
						warn = false;
					}
				} else {
					pthread_mutex_lock(&(instance->calc_queue_mutex));
					instance->calc_queue.push(entry);
					pthread_mutex_unlock(&(instance->calc_queue_mutex));

					/* Set the calculate flag. */
					flag_set(instance->calc_flag);
				}

				/* Did we erase the end of the list? */
				if (p == instance->transmit_list.end()) {
					break;
				}
			}
		}
		pthread_mutex_unlock(&(instance->transmit_mutex));

		/* Sleep until next scan. */
		usleep(delay);
	}

	TRACE(TRACE_THREAD, "ICMP::timeout_thread() ending main loop");
	
	pthread_exit(NULL);
}

RESULT ICMP::receive_thread(void* args) {
	RESULT result = SUCCESS;	

	ICMP					*instance = (ICMP*) args;

	int					s = 0;
	sockaddr_in			src;
	char					buf[INET_ADDRSTRLEN];

	TRACE(TRACE_THREAD, "ICMP::receive_thread() starting main loop");

	grantPriv();

	/* Open a socket for packet reciept. */
	TRACE(TRACE_INTERNAL, "ICMP::watchNetwork() opening raw ICMP socket.");
	s = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);

	if (s < 0) {
		interrupt = true;

		LMSG("root privileges required for this module");
		result = FAILURE;
	} else {
		fcntl(s, F_SETFL, O_NONBLOCK | O_ASYNC);

		bzero(&src, sizeof(struct sockaddr_in));

		src.sin_family = AF_INET;
		src.sin_port = 0;
		src.sin_addr.s_addr = instance->source;

		if (bind(s, (sockaddr *)&src, sizeof(sockaddr_in)) == -1) {
			interrupt = true;
			close(s);

			inet_ntop(AF_INET, &src.sin_addr, buf, INET_ADDRSTRLEN);
			LMSG("unable to bind to local address '%s'.\n", buf);
			result = FAILURE;
		}

	}
	dropPriv();

	while (!interrupt) {
		/* Look for incoming packets. */
		instance->watchNetwork(s);
	}
	if (s > 0) {
		close(s);
	}

	TRACE(TRACE_THREAD, "ICMP::receive_thread() ending main loop");

	pthread_exit(NULL);
}

RESULT ICMP::watchNetwork(int s) {
	RESULT			result = SUCCESS;

	char				packet[1024], buf[INET_ADDRSTRLEN];

	iphdr				*ip = (iphdr *)packet;
	icmp				*header = (icmp *)(packet + sizeof(struct iphdr));
	icmp_payload	*payload = (icmp_payload *)(packet + sizeof(struct iphdr) + ICMP_MINLEN);	
	iphdr				*error_ip = (iphdr *)(packet + sizeof(struct iphdr) + ICMP_MINLEN); /* IP header within an error payload. */
	icmp				*error_header = (icmp *)(packet + sizeof(struct iphdr) + ICMP_MINLEN + sizeof(struct iphdr)); /* The ICMP header within an error payload. */

	timeval			stamp;
	unsigned long	highwater = 0;
	unsigned long	calc_queue_size;
	bool				warn = false;

	fd_set					rfds;
	calc_queue_entry_t	entry;

	pair<u_int32_t, int>	key;

	struct in_addr tmp_ip;

	TRACE(TRACE_INTERNAL, "ICMP::watchNetwork() catching network packets");

	while (!interrupt) {
		/* Watch the ICMP socket. */
		FD_ZERO(&rfds);
		FD_SET(s, &rfds);

		/* Never wait for more than 1 second to make sure we catch program interrupts. */
		stamp.tv_sec = 1;
		stamp.tv_usec = 0;

		if (select(s + 1, &rfds, NULL, NULL, &stamp) > 0) {
			/* Get current time for use in RTT calculation. */
			gettimeofday(&(entry.timestamp), NULL);
			memcpy(&(entry.rtt), &(entry.timestamp), sizeof(struct timeval));

			/* Read the packet in. */
			read(s, packet, 1024);

			/* Is this our ICMP_ECHOREPLY? */
			if (header->icmp_type == ICMP_ECHOREPLY && header->icmp_id == this->module_id) {
				/* ICMP_ECHOREPLY matching our module_id. */
				TRACE(TRACE_INTERNAL, "ICMP::watchNetwork() received ICMP_ECHOREPLY packet.");

				memcpy(&(entry.address), &(payload->destination.s_addr), sizeof(u_int32_t));
				subtract_timeval(entry.rtt, payload->stamp);

				key.first = payload->destination.s_addr;
				key.second = payload->sequence;
			} else if (error_header->icmp_type == ICMP_ECHO && error_header->icmp_id == this->module_id) {
				/* ICMP error packet matching our module_id. */
				TRACE(TRACE_INTERNAL, "ICMP::watchNetwork() received ICMP error packet (type: %d, code: %d) - send type was: %d", header->icmp_type, header->icmp_code, error_header->icmp_type);

				memcpy(&(entry.address), &(error_ip->daddr), sizeof(u_int32_t));
				entry.rtt.tv_sec = 0;
				entry.rtt.tv_usec = 0;

				key.first = error_ip->daddr;
				key.second = error_header->icmp_seq;
			} else {
				/* This packet doesn't appear to be ours. */
				if (TRACE_ISSET(TRACE_INTERNAL)) {
					tmp_ip.s_addr = ip->saddr;
					inet_ntop(AF_INET, &tmp_ip, buf, INET_ADDRSTRLEN);
					TRACE(TRACE_INTERNAL, "recieved a packet that wasn't from the NMS (semi-normal). source: %s, seq: %lu, type: %d, err_type: %d, err_code: %d", buf, header->icmp_seq, header->icmp_type, error_header->icmp_type, error_header->icmp_code);
				}
				continue;
			}

			/* Capture the packets return tos value. */
			entry.tos = ip->tos;

			if (TRACE_ISSET(TRACE_INTERNAL)) {
				tmp_ip.s_addr = key.first;
				inet_ntop(AF_INET, &tmp_ip, buf, INET_ADDRSTRLEN);
				TRACE(TRACE_INTERNAL, "recieved a packet from ip: %s seq: %lu", buf, key.second);
			}

			/* Verify the packet's integrity. */
			pthread_mutex_lock(&(this->transmit_mutex));
			if (this->transmit_hash.find(key) != this->transmit_hash.end()) {
				/* Match. Remove from transmit_hash to prevent timeout. */
				TRACE(TRACE_INTERNAL, "ICMP::watchNetwork() delete ICMP packet from transmit list and hash.");

				this->transmit_list.erase(this->transmit_hash[key]);
				this->transmit_hash.erase(key);

				/* Release the transmit_hash lock */
				pthread_mutex_unlock(&(this->transmit_mutex));
			} else {
				/* Release the transmit_hash lock */
				pthread_mutex_unlock(&(this->transmit_mutex));

				tmp_ip.s_addr = key.first;
				inet_ntop(AF_INET, &tmp_ip, buf, INET_ADDRSTRLEN);
				/* The timeout_thread likely removed this entry before we were able to process the packet. */
				TRACE(TRACE_DEBUG, "received ICMP packet was not found within transmit_hash (ip: %s, seq: %lu) - timeout occured prior to processing reply.", buf, key.second);

				continue;
			}

			/* Capture icmp_type and icmp_code. */
			entry.type = header->icmp_type;
			entry.code = header->icmp_code;

			/* Push the reply into the queue. */
			if (TRACE_ISSET(TRACE_INTERNAL)) {
				tmp_ip.s_addr = key.first;
				inet_ntop(AF_INET, &tmp_ip, buf, INET_ADDRSTRLEN);
				TRACE(TRACE_INTERNAL, "ICMP::watchNetwork() push ICMP reply into queue (ip: %s).", buf);
			}

			pthread_mutex_lock(&calc_queue_mutex);
			calc_queue_size = calc_queue.size();
			pthread_mutex_unlock(&calc_queue_mutex);

			if (TRACE_ISSET(TRACE_DEBUG) && calc_queue_size > highwater) {
				TRACE(TRACE_INTERNAL, "calc_queue highwater moved to %lu.", calc_queue_size);
				highwater = calc_queue_size;
			}

			if (calc_queue_size >= calcQueueDepth) {
				if (warn) {
					LMSG("calc-queue depth has reached maximum of %lu entries - dropping data", calcQueueDepth);
					warn = false;
					result = FAILURE;
				}
			} else {
				pthread_mutex_lock(&calc_queue_mutex);
				this->calc_queue.push(entry);
				pthread_mutex_unlock(&calc_queue_mutex);

				/* Set the calculate flag. */
				flag_set(calc_flag);
			}
		}
	}

	return result;
}

RESULT ICMP::calculate(slist<unsigned int> &interval_list) {
	RESULT result = SUCCESS;

	char buf[512], buf2[512], ip_buf[INET_ADDRSTRLEN];

	bool warn_full = true, warn_threshold = true;
	long nrm_rtt_tmp;
	unsigned int processed = 0, suppressed = 0;
	unsigned long policy_queue_size, policy_queue_fill;

	policy_queue_entry_t policy_queue_entry;
	static nrm_rtt_hash_t nrm_rtt_data;
	pair<unsigned long, unsigned long> nrm_rtt_pair;

	slist<unsigned int>::iterator interval_it;
	calc_data_entry_list_t::iterator entry_it;
	unsigned long type, code, loss, min_rtt, max_rtt, sum_rtt, min_jtr, max_jtr, sum_jtr, jtr, rtt, prev_rtt, count, prev_count;
	timeval calc_start, calc_stop;
	static unsigned long highwater = 0;

	struct in_addr ip;
	char state[64];

	unsigned long calc_data_avg_len = 0;

	/* Grab the time when calculation started. */
	gettimeofday(&calc_start, NULL);

	/* Lock changes to calc_data (hash level). */
	pthread_mutex_lock(&calc_data_hash_mutex);

	/* Walk through all IP data, doing calcaulations for each interval. */
	for (calc_data_t::iterator it = calc_data.begin(); !interrupt && it != calc_data.end(); ++it) {
		if (TRACE_ISSET(TRACE_INTERNAL)) {
			ip.s_addr = it->first;
			inet_ntop(AF_INET, &ip, ip_buf, INET_ADDRSTRLEN);
			TRACE(TRACE_INTERNAL, "current ip: %s [%lu] (entries: %lu)", ip_buf, it->first, it->second.size());

		}

		/* Grab current state */
		ip.s_addr = it->first;
		inet_ntop(AF_INET, &ip, ip_buf, INET_ADDRSTRLEN);
		pthread_mutex_lock(&state_mutex);
		if (state_hash.find(it->first) == state_hash.end()) {
			state_hash[it->first] = new IPv4State(&storage, ip_buf, "icmp");
		}
		/* Check for an existing value. */
		strncpy(state, state_hash[it->first]->get(), sizeof(state));
		if (state[0] == '\0') {
			/* No entry, overwrite with normal. */
			strncpy(state, "normal", sizeof(state));
			state_hash[it->first]->set(state);
		}
		TRACE(TRACE_DUMP, "calculate() got state of %s as %s", ip_buf, state);
		pthread_mutex_unlock(&state_mutex);

		/* Lock calc_data (list level). */
		pthread_mutex_lock(&calc_data_list_mutex);

		/* Walk though list for this IP and preform calculations at each interval. */
		entry_it = it->second.begin();
		interval_it = interval_list.begin();
		min_rtt = min_jtr = (unsigned long) -1;
		type = code = loss = max_rtt = sum_rtt = rtt = prev_rtt = max_jtr = sum_jtr = count = prev_count = 0;

		calc_data_avg_len += it->second.size();

		while (entry_it != it->second.end() && interval_it != interval_list.end()) {
			/* Move to next interval if needed. */
			if (calc_start.tv_sec - *interval_it > entry_it->timestamp.tv_sec) {
				/* If any data samples for this interval (and queue isn't full), then submit data to policy_queue. */
				if (count > prev_count && result != FAILURE) {
					/* Fill in policy_queue_entry. */
					bzero(&policy_queue_entry, sizeof(policy_queue_entry_t));

					policy_queue_entry.address = it->first;
					policy_queue_entry.interval = *interval_it;

					strncpy(policy_queue_entry.state, state, sizeof(policy_queue_entry.state));

					/* Type and code (last non-zero type and code) */
					policy_queue_entry.type = (int8_t) type;
					policy_queue_entry.code = (int8_t) code;

					/* Current tos value. */
					policy_queue_entry.tos = entry_it->tos;

					/* Calculate values which are always used. */
					policy_queue_entry.loss = (int8_t) (count > 0) ? (100 * loss) / count : 0; /* Packet Loss */
					policy_queue_entry.avg_rtt = (u_int16_t) rint((sum_rtt / (double) (count - loss)) / 1000.0); /* Avg RTT */

					/* RTT Calculations */
					if (calcFlags & FLAG_CALC_LTCY) {
						policy_queue_entry.min_rtt = (u_int16_t) rint(min_rtt / 1000.0);
						policy_queue_entry.max_rtt = (u_int16_t) rint(max_rtt / 1000.0);
					}

					/* Jitter Calculations: Require at least 2 entries per interval. */
					if (calcFlags & FLAG_CALC_JTTR && (count - loss) > 1) {
						policy_queue_entry.min_jtr = (u_int16_t) rint(min_jtr / 1000.0);
						policy_queue_entry.max_jtr = (u_int16_t) rint(max_jtr / 1000.0);
						policy_queue_entry.avg_jtr = (u_int16_t) rint((sum_jtr / (double) ((count - loss) - 1)) / 1000.0);
					}

					/* Normalized RTT Calculations */
					if (calcFlags & FLAG_CALC_NORM) {
						policy_queue_entry.nrm_rtt = (u_int16_t) rint((nrm_rtt_data[it->first].first / (double) (nrm_rtt_data[it->first].second)) / 1000.0);

						nrm_rtt_tmp = policy_queue_entry.avg_rtt - policy_queue_entry.nrm_rtt;
						policy_queue_entry.nrm_var_abs = (u_int16_t) std::abs(nrm_rtt_tmp);
						policy_queue_entry.nrm_var_inc = (u_int16_t) (nrm_rtt_tmp > 0) ? nrm_rtt_tmp : 0;
						policy_queue_entry.nrm_var_dec = (u_int16_t) (nrm_rtt_tmp < 0) ? std::abs(nrm_rtt_tmp) : 0;

						nrm_rtt_tmp = (long) ((policy_queue_entry.avg_rtt / (double) policy_queue_entry.nrm_rtt) * 100.0 - 100);
						policy_queue_entry.nrm_per_abs = (u_int16_t) std::abs(nrm_rtt_tmp);
						policy_queue_entry.nrm_per_inc = (u_int16_t) (nrm_rtt_tmp > 0) ? nrm_rtt_tmp : 0;
						policy_queue_entry.nrm_per_dec = (u_int16_t) (nrm_rtt_tmp < 0) ? std::abs(nrm_rtt_tmp) : 0;

						/* Roll the average into the stored value, to protect against roll over. */					
						if (nrm_rtt_data[it->first].first > MONITOR_ICMP_MAXNRM_DEF) {
							nrm_rtt_data[it->first].first = policy_queue_entry.nrm_rtt;
							nrm_rtt_data[it->first].second = 1;
						}
					}

					processed++;
					if (!suppression || suppressDuplicate(policy_queue_entry) == false) {
						/* Insert policy_queue_entry into the policy_queue. */
						pthread_mutex_lock(&policy_queue_mutex);
						policy_queue_size = policy_queue.size();
						policy_queue_fill = 100 * policy_queue_size / policyQueueDepth;
						pthread_mutex_unlock(&policy_queue_mutex);

						if (TRACE_ISSET(TRACE_DEBUG) && policy_queue_size > highwater) {
							TRACE(TRACE_INTERNAL, "policy_queue highwater moved to %lu.", policy_queue_size);
							highwater = policy_queue_size;
						}

						if (policy_queue_size >= policyQueueDepth) {
							if (warn_full) {
								LMSG("policy_queue depth has reached maximum of %lu entries - dropping data", policyQueueDepth);
								warn_full = false;
								result = FAILURE;
							}
							break;
						} else if (policy_queue_fill >= policyQueueUpperThreshold) {
							if (warn_threshold) {
								LMSG("policy_queue depth has reached %lu%% utilization. Slowing inserts into policy_queue.", policy_queue_fill);
								warn_threshold = false;
							}
							/* Sleep longer and longer as we near congestion. */
							usleep(USLEEP_FASTLOOP_TIME / (100 - policy_queue_fill));
						}

						pthread_mutex_lock(&policy_queue_mutex);
						policy_queue.push(policy_queue_entry);
						pthread_mutex_unlock(&policy_queue_mutex);

						/* Set the policy flag. */
						flag_set(policy_flag);

						if (TRACE_ISSET(TRACE_INTERNAL)) {
							ip.s_addr = it->first;
							inet_ntop(AF_INET, &ip, ip_buf, INET_ADDRSTRLEN);
							TRACE(TRACE_INTERNAL, "policy_queue.push - ip: %s, int: %lu loss: %lu%%\nRTT-> min: %lu, max: %lu, avg: %lu\nJTR-> min: %lu, max: %lu, avg: %lu\nNRM-> %lu, var_abs: %lu, var_inc: %lu, var_dec: %lu, per_abs: %lu, per_inc: %lu, per_dec: %lu", ip_buf, *interval_it, policy_queue_entry.loss, policy_queue_entry.min_rtt, policy_queue_entry.max_rtt, policy_queue_entry.avg_rtt, policy_queue_entry.min_jtr, policy_queue_entry.max_jtr, policy_queue_entry.avg_jtr, policy_queue_entry.nrm_rtt, policy_queue_entry.nrm_var_abs, policy_queue_entry.nrm_var_inc, policy_queue_entry.nrm_var_dec, policy_queue_entry.nrm_per_abs, policy_queue_entry.nrm_per_inc, policy_queue_entry.nrm_per_dec);
							LMSG("- sample based on %lu entries. %lu more than last interval. (count: %lu, prev_count: %lu)", count, count - prev_count, count, prev_count);
						}
					} else {
						suppressed++;

						if (TRACE_ISSET(TRACE_INTERNAL)) {
							ip.s_addr = it->first;
							inet_ntop(AF_INET, &ip, ip_buf, INET_ADDRSTRLEN);
							TRACE(TRACE_INTERNAL, "policy_queue.push avoided. Entry for %s was a suppressed as a duplicate. int: %lu, %lu%% loss\nRTT-> avg: %lu JTR-> avg: %lu, NRM-> var_abs: %lu", ip_buf, *interval_it, policy_queue_entry.loss, policy_queue_entry.avg_rtt, policy_queue_entry.avg_jtr, policy_queue_entry.nrm_var_abs);
						}
					}
				}

				prev_count = count;
				++interval_it;
				continue;
			}

			/* Perform calculations for entry. */
			if (entry_it->type != 0) { /* Timeout. Didn't receive packet. */
				/* Loss */
				loss++;

				/* Type and code */
				type = entry_it->type;
				code = entry_it->code;

			} else { /* Recieved packet. */
				prev_rtt = rtt;
				rtt = tvtousec(entry_it->rtt);
				sum_rtt += rtt;

				/* Type and code */
				if (entry_it->type > 0) {
					type = entry_it->type;
					code = entry_it->code;
				}

				/* RTT calcs. */
				if (calcFlags & FLAG_CALC_LTCY) {
					if (rtt < min_rtt) {
						min_rtt = rtt;
					}

					if (rtt > max_rtt) {
						max_rtt = rtt;
					}
				}

				/* Jitter calcs. */
				if (calcFlags & FLAG_CALC_JTTR && (count - loss) > 0) { //Requires at least 2 entries for jitter calc.
					jtr = (unsigned long) std::abs((long)(prev_rtt - rtt));
					sum_jtr += jtr;
					
					if (jtr < min_jtr) {
						min_jtr = jtr;
					}

					if (jtr > max_jtr) {
						max_jtr = jtr;
					}
				}

				/* Normalized RTT calcs. */
				if (calcFlags & FLAG_CALC_NORM) {
					if (nrm_rtt_data.find(it->first) == nrm_rtt_data.end()) {
						nrm_rtt_pair.first = rtt; //RTT
						nrm_rtt_pair.second = 1; //Count
						nrm_rtt_data[it->first] = nrm_rtt_pair;
					} else {
						nrm_rtt_data[it->first].first += rtt;
						nrm_rtt_data[it->first].second++;
					}
				}
			}

			/* Inc Count */
			count++;

			if (TRACE_ISSET(TRACE_INTERNAL)) {
				snprintf(buf, 512, "- count: %lu, loss: %lu", count, (count > 0) ? (100 * loss) / count : 0);
				if (calcFlags & FLAG_CALC_LTCY) {
					strncpy(buf2, buf, 512);
					snprintf(buf, 512, "%s, min_rtt: %lu, max_rtt: %lu, sum_rtt: %lu", buf2, min_rtt, max_rtt, sum_rtt);
				}
				if (calcFlags & FLAG_CALC_JTTR) {
					strncpy(buf2, buf, 512);
					snprintf(buf, 512, "%s, min_jtr: %lu, max_jtr: %lu, sum_jtr: %lu", buf2, min_jtr, max_jtr, sum_jtr);
				} 
				strncpy(buf2, buf, 512);
				snprintf(buf, 512, "%s (%lus old)", buf2, calc_start.tv_sec - entry_it->timestamp.tv_sec);
				TRACE(TRACE_INTERNAL, buf);
			}

			/* Next entry in list. */
			++entry_it;
		}

		/* Move deeper into the entries, if the longest interval is shorter than the polling interval. */
		while (entry_it != it->second.end() && calc_start.tv_sec - entry_it->timestamp.tv_sec < interval) {
			++entry_it;
		}

		/* Trim data that is out of scope (leaving at least 1 entry) */
		if (entry_it != it->second.end() && !(it->second.empty())) {
			if (TRACE_ISSET(TRACE_INTERNAL)) {
				ip.s_addr = it->first;
				inet_ntop(AF_INET, &ip, ip_buf, INET_ADDRSTRLEN);
				TRACE(TRACE_INTERNAL, "calc_data[%s] list size: %lu - item at trimming location is %lus old.", ip_buf, it->second.size(), calc_start.tv_sec - entry_it->timestamp.tv_sec);
			}

			it->second.erase(entry_it, it->second.end());

			if (TRACE_ISSET(TRACE_INTERNAL)) {
				ip.s_addr = it->first;
				inet_ntop(AF_INET, &ip, ip_buf, INET_ADDRSTRLEN);
				TRACE(TRACE_INTERNAL, "calc_data[%s] list size: %lu (after trim)", ip_buf, it->second.size());
			}
		}

		/* Unlock calc_data (list level). */
		pthread_mutex_unlock(&calc_data_list_mutex);
	}

	/* Average lengh will be around "policy max interval" / "polling interval" * "transmit". */
	TRACE(TRACE_DEBUG, "calc_data width: %lu, avg list len: %0.1f, suppression: %0.1f%% (mem: %0.0fK)", calc_data.size(), calc_data_avg_len/(float) calc_data.size(), (processed < 1) ? 0 : suppressed/(float)processed*100.0, (calc_data.size() * (sizeof(u_int32_t) + (sizeof(calc_data_entry_t) * calc_data_avg_len/(float)calc_data.size())))/1024);

	/* Unlock calc_data (hash level). */
	pthread_mutex_unlock(&calc_data_hash_mutex);

	gettimeofday(&calc_stop, NULL);
	subtract_timeval(calc_stop, calc_start);

	TRACE(TRACE_DEBUG, "calculation took: %d seconds, %d microseconds.", calc_stop.tv_sec, calc_stop.tv_usec);

	return result;
}

bool ICMP::suppressDuplicate(const policy_queue_entry_t &new_entry) {
	hash_map<u_int32_t,policy_queue_entry_t> interval_entry;
	policy_queue_entry_t entry;
	u_int32_t source, interval;
	struct in_addr ip;
	char buf[INET_ADDRSTRLEN];

	interval = new_entry.interval;
	source = ip.s_addr = new_entry.address;

	/* Is there a interval_entry for this source? */
	if (calc_data_sup.find(source) == calc_data_sup.end()) {
		if (TRACE_ISSET(TRACE_INTERNAL)) {
			inet_ntop(AF_INET, &ip, buf, INET_ADDRSTRLEN);
			TRACE(TRACE_INTERNAL, "adding interval entry (%lu) for source %s to calc_data_sup.", interval, buf);
		}

		/* Insert current record and do not suppress. */
		interval_entry[interval] = new_entry;
		calc_data_sup[source] = interval_entry;
		return false;
	}

	/* Is there an interval for this source? */
	if (calc_data_sup[source].find(interval) == calc_data_sup[source].end()) {
		if (TRACE_ISSET(TRACE_INTERNAL)) {
			inet_ntop(AF_INET, &ip, buf, INET_ADDRSTRLEN);
			TRACE(TRACE_INTERNAL, "adding interval entry (%lu) for source %s to calc_data_sup.", interval, buf);
		}
		goto do_not_suppress;
	}

	/* Grab a copy of the current policy_queue_entry_t for this source/interval. */
	entry = calc_data_sup[source][interval];

	/* Don't suppress any changes in type or code. */
	if (new_entry.type != entry.type || new_entry.code != entry.code) {
		goto do_not_suppress;
	}

	/* Loss */
	if (std::abs(new_entry.loss - entry.loss) > lossSupVar) {
		goto do_not_suppress;
	}

	/* State */
	if (strncmp(new_entry.state, entry.state, sizeof(entry.state)) != 0) {
		goto do_not_suppress;
	}

	/* RTT */
	if (calcFlags & FLAG_CALC_LTCY) {
		if (std::abs(new_entry.avg_rtt - entry.avg_rtt) > rttSupVar) {
			if (TRACE_ISSET(TRACE_DUMP)) {
				inet_ntop(AF_INET, &ip, buf, INET_ADDRSTRLEN);
				TRACE(TRACE_DUMP, "avg_rtt: (%lu - %lu) = %lu > %lu => %s", new_entry.avg_rtt, entry.avg_rtt, std::abs(new_entry.avg_rtt - entry.avg_rtt), rttSupVar, buf);
			}
			goto do_not_suppress;
		}

		if (std::abs(new_entry.min_rtt - entry.min_rtt) > rttSupVar) {
			TRACE(TRACE_DUMP, "min_rtt: %lu > %lu", std::abs(new_entry.min_rtt - entry.min_rtt), rttSupVar);
			goto do_not_suppress;
		}

		if (std::abs(new_entry.max_rtt - entry.max_rtt) > rttSupVar) {
			TRACE(TRACE_DUMP, "max_rtt: %lu > %lu", std::abs(new_entry.max_rtt - entry.max_rtt), rttSupVar);
			goto do_not_suppress;
		}
	}

	/* Jitter */
	if (calcFlags & FLAG_CALC_JTTR) {
		if (std::abs(new_entry.avg_jtr - entry.avg_jtr) > jtrSupVar) {
			if (TRACE_ISSET(TRACE_DUMP)) {
				inet_ntop(AF_INET, &ip, buf, INET_ADDRSTRLEN);
				TRACE(TRACE_DUMP, "avg_jtr: (%lu - %lu) = %lu > %lu => %s", new_entry.avg_jtr, entry.avg_jtr, std::abs(new_entry.avg_jtr - entry.avg_jtr), jtrSupVar, buf);
			}
			goto do_not_suppress;
		}

		if (std::abs(new_entry.min_jtr - entry.min_jtr) > jtrSupVar) {
			TRACE(TRACE_DUMP, "min_jtr: %lu > %lu", std::abs(new_entry.min_jtr - entry.min_jtr), jtrSupVar);
			goto do_not_suppress;
		}

		if (std::abs(new_entry.max_jtr - entry.max_jtr) > jtrSupVar) {
			TRACE(TRACE_DUMP, "max_jtr: %lu > %lu", std::abs(new_entry.max_jtr - entry.max_jtr), jtrSupVar);
			goto do_not_suppress;
		}
	}

	/* Normalized RTT */
	if (calcFlags & FLAG_CALC_NORM) {
		if (new_entry.nrm_var_abs > nrmSupVar) {
			goto do_not_suppress;
		}

		if (new_entry.nrm_per_abs > nrmSupVar) {
			goto do_not_suppress;
		}
	}

	/* ToS Change */
	if (calcFlags & FLAG_CALC_TOSC) {
		if (new_entry.tos != entry.tos) {
			goto do_not_suppress;
		}
	}

	return true;

do_not_suppress:
	/* Update the suppression entry for this interval, and do not suppress. */
	calc_data_sup[source][interval] = new_entry;

	return false;
}

RESULT ICMP::calculation_thread(void *args) {
	RESULT calc_result;

	ICMP *instance = (ICMP*) args;

	time_t now, next_start;

	unsigned int calc_interval = instance->calcInterval;
	unsigned int policy_queue_size, policy_queue_fill;
	
	slist<unsigned int> interval_list;

	TRACE(TRACE_THREAD, "ICMP::calculation_thread() starting main loop");

	/* Get policy intervals and required data. */
	instance->getPolicyIntervals(interval_list);

	/* Set first next_start time. */
	next_start = time(NULL) + calc_interval;

	while (!interrupt) {
		now = time(NULL);

		usleep(USLEEP_LONGLOOP_TIME);

		/* Calculate if next_start timer has expired. */
		if (now >= next_start && !interrupt) {
			/* Update next_start to current (now) + calc_interval. */
			next_start = now + calc_interval;

			/* Run calculations and feed results into policy_queue. */
			calc_result = instance->calculate(interval_list);

			pthread_mutex_lock(&instance->policy_queue_mutex);
			policy_queue_size = instance->policy_queue.size();
			policy_queue_fill = 100 * policy_queue_size / instance->policyQueueDepth;
			pthread_mutex_unlock(&instance->policy_queue_mutex);

			/* Auto adjust calc_interval based on policy_queue health. */
			if (calc_result != SUCCESS || policy_queue_fill >= instance->policyQueueUpperThreshold) {
				/* Increase calc_interval by calc_inteval_adj */
				if (calc_interval + instance->calcIntervalAdj <= MONITOR_ICMP_CALCINTERVAL_MAX) {
					LMSG("calc-interval adjusted from %lu to %lu sec (policy_queue %lu%% full)", calc_interval, calc_interval + instance->calcIntervalAdj, policy_queue_fill);

					calc_interval += instance->calcIntervalAdj;
				}
			} else if (policy_queue_fill <= instance->policyQueueLowerThreshold) {
				/* Decrease calc_interval towards calcInterval */
				if (calc_interval - instance->calcIntervalAdj >= instance->calcInterval) {
					LMSG("calc-interval adjusted from %lu to %lu sec (policy_queue %lu%% full)", calc_interval, calc_interval - instance->calcIntervalAdj, policy_queue_fill);

					calc_interval -= instance->calcIntervalAdj;
				}
			}
		}
	}

	TRACE(TRACE_THREAD, "ICMP::calculation_thread() ending main loop");

	pthread_exit(NULL);
}

RESULT ICMP::calc_queue_thread(void* args) {
	ICMP *instance = (ICMP*) args;

	bool calc_queue_empty;

	calc_queue_t *calc_queue = &instance->calc_queue;
	calc_queue_entry_t calc_queue_entry;
	pthread_mutex_t *calc_queue_mutex = &(instance->calc_queue_mutex);

	calc_data_t *calc_data = &(instance->calc_data);
	calc_data_entry_t calc_data_entry;
	pthread_mutex_t *calc_data_list_mutex = &(instance->calc_data_list_mutex);

	TRACE(TRACE_THREAD, "ICMP::calc_queue_thread() starting main loop");

	while (!interrupt) {
		if (flag_wait(instance->calc_flag, &(interrupt)) == FAILURE) {
			TRACE(TRACE_INTERNAL, "calc_queue thread interrupted during wait");
			continue;
		}

		/* Update calc_queue_empty */
		pthread_mutex_lock(calc_queue_mutex);
		calc_queue_empty = calc_queue->empty();
		TRACE(TRACE_DUMP, "calc_queue_thread: calc_queue: %lu (empty: %s), policy_queue: %lu", instance->calc_queue.size(), calc_queue_empty ? "true" : "false", instance->policy_queue.size());
		pthread_mutex_unlock(calc_queue_mutex);

		while (!calc_queue_empty && !interrupt) {
			/* Pop calc_queue_entry off of queue. */
			pthread_mutex_lock(calc_queue_mutex);

			assert(!calc_queue->empty()); /* Sanity check. */

			calc_queue_entry = calc_queue->front();
			calc_queue->pop();

			calc_queue_empty = calc_queue->empty();

			pthread_mutex_unlock(calc_queue_mutex);

			/* Copy calc_queue_entry into calc_data_entry. */
			calc_data_entry.timestamp = calc_queue_entry.timestamp;
			calc_data_entry.rtt = calc_queue_entry.rtt;
			calc_data_entry.type = calc_queue_entry.type;
			calc_data_entry.code = calc_queue_entry.code;
			calc_data_entry.tos = calc_queue_entry.tos;

			/* Insert calc_data_entry into calc_data structure. */
			if (calc_data->find(calc_queue_entry.address) == calc_data->end()) {
				/* New entry in the hash. Lock the hash and list mutex. */
				pthread_mutex_lock(&instance->calc_data_hash_mutex);
				pthread_mutex_lock(calc_data_list_mutex);

				(*calc_data)[calc_queue_entry.address].push_front(calc_data_entry);

				pthread_mutex_unlock(calc_data_list_mutex);
				pthread_mutex_unlock(&instance->calc_data_hash_mutex);
			} else {
				/* New entry in only list only. Lock the list mutex. */
				pthread_mutex_lock(calc_data_list_mutex);

				(*calc_data)[calc_queue_entry.address].push_front(calc_data_entry);

				pthread_mutex_unlock(calc_data_list_mutex);
			}
		}
	}

	TRACE(TRACE_THREAD, "ICMP::calc_queue_thread() ending main loop");

	pthread_exit(NULL);
}

RESULT ICMP::policy_thread(void* args) {
	RESULT result = SUCCESS;
	ACTION action;

	char buf[BUF_SIZE], buf2[INET_ADDRSTRLEN];

	ICMP *instance = (ICMP*) args;

	policy_queue_t *policy_queue = &instance->policy_queue;
	pthread_mutex_t *policy_queue_mutex = &(instance->policy_queue_mutex);

	hash_map<u_int32_t, IPv4State *> *state_hash = &(instance->state_hash);
	pthread_mutex_t *state_mutex = &(instance->state_mutex);

	calc_data_t *calc_data = &(instance->calc_data);
	pthread_mutex_t *calc_data_list_mutex = &(instance->calc_data_list_mutex);

	policy_queue_entry_t policy_queue_entry;

	variable_binding_t policy_input;
	variable_binding_t policy_output;
	slist<variable_binding_t> input_set;

	struct in_addr ip;
	time_t t;

	TRACE(TRACE_THREAD, "ICMP::policy_thread() starting main loop");

	/* Until we are asked to stop, process policy data. */
	while (!interrupt) {
		/* Wait for the policy flag to get set. */
		if (flag_wait(instance->policy_flag, &(interrupt)) == FAILURE) {
			continue;
		}

		while (!policy_queue->empty() && !interrupt) {
			/* Grab policy_queue_entry from queue. */
			pthread_mutex_lock(policy_queue_mutex);
			policy_queue_entry = policy_queue->front();
			policy_queue->pop();
			pthread_mutex_unlock(policy_queue_mutex);

			/* Insert protocol/source. */
			policy_input["protocol"] = "icmp";
			policy_input["origin"] = "network";

			snprintf(buf, 132, "%u", policy_queue_entry.interval);
			policy_input["interval"] = buf;

			ip.s_addr = policy_queue_entry.address;
			inet_ntop(AF_INET, &ip, buf2, INET_ADDRSTRLEN);
			policy_input["ip"] = buf2;
			policy_input["ip-except"] = buf2;
			policy_input["source"] = buf2;
			policy_input["source-except"] = buf2;

			/* Insert the loss. */
			snprintf(buf, 132, "%d", policy_queue_entry.loss);
			policy_input["loss"] = buf;

			/* Insert the rtt. */
			snprintf(buf, 132, "%d", policy_queue_entry.avg_rtt);
			policy_input["rtt"] = buf;

			/* Insert type/code */
			instance->setPolicyInputTypeCode(&policy_input, policy_queue_entry.type, policy_queue_entry.code);

			/* Insert the tos at transmit and at receive. */
			if (instance->tosMap.find(policy_input["ip"]) != instance->tosMap.end()) { /* Transmitted TOS value. */
				snprintf(buf, 132, "%d", instance->tosMap[policy_input["ip"]]);
			} else {
				snprintf(buf, 132, "%lu", instance->tosDefault);
			}
			policy_input["tos"] = buf;
			policy_input["tos-tx"] = buf;
			snprintf(buf, 132, "%d", policy_queue_entry.tos); /* Recieved TOS value. */
			policy_input["tos-rx"] = buf;

			/* Insert the dscp at transmit and at receive. */
			if (instance->tosMap.find(policy_input["ip"]) != instance->tosMap.end()) { /* Transmitted TOS value. */
				snprintf(buf, 132, "%d", instance->tosMap[policy_input["ip"]] >> 2);
			} else {
				snprintf(buf, 132, "%lu", instance->tosDefault >> 2);
			}
			policy_input["dscp"] = buf;
			policy_input["dscp-tx"] = buf;
			snprintf(buf, 132, "%d", policy_queue_entry.tos >> 2); /* Recieved DSCP value. */
			policy_input["dscp-rx"] = buf;

			/* Insert the precedence at transmit and at receive. */
			if (instance->tosMap.find(policy_input["ip"]) != instance->tosMap.end()) { /* Transmitted TOS value. */
				snprintf(buf, 132, "%d", instance->tosMap[policy_input["ip"]] >> 5);
			} else {
				snprintf(buf, 132, "%lu", instance->tosDefault >> 5);
			}
			policy_input["precedence"] = buf;
			policy_input["precedence-tx"] = buf;
			snprintf(buf, 132, "%d", policy_queue_entry.tos >> 5); /* Recieved precedence value. */
			policy_input["precedence-rx"] = buf;

			/* Insert state */
			policy_input["state"] = policy_queue_entry.state;

			/* Event: Packet Loss */
			if (instance->calcFlags & FLAG_CALC_LOSS) {
				instance->setPolicyInputEventData(&policy_input, "packet-loss", policy_queue_entry.loss);
				input_set.push_front(policy_input);
			}

			/* Event: RTT */
			if (instance->calcFlags & FLAG_CALC_LTCY) {
				instance->setPolicyInputEventData(&policy_input, "rtt-minimum", policy_queue_entry.min_rtt);
				input_set.push_front(policy_input);

				instance->setPolicyInputEventData(&policy_input, "rtt-maximum", policy_queue_entry.max_rtt);
				input_set.push_front(policy_input);

				instance->setPolicyInputEventData(&policy_input, "rtt-average", policy_queue_entry.avg_rtt);
				input_set.push_front(policy_input);
			}

			/* Event: Jitter */
			if (instance->calcFlags & FLAG_CALC_JTTR) {
				instance->setPolicyInputEventData(&policy_input, "jitter-minimum", policy_queue_entry.min_jtr);
				input_set.push_front(policy_input);

				instance->setPolicyInputEventData(&policy_input, "jitter-maximum", policy_queue_entry.max_jtr);
				input_set.push_front(policy_input);

				instance->setPolicyInputEventData(&policy_input, "jitter-average", policy_queue_entry.avg_jtr);
				input_set.push_front(policy_input);
			}

			/* Event: Normalized RTT */
			if (instance->calcFlags & FLAG_CALC_NORM) {
				instance->setPolicyInputEventData(&policy_input, "normalized-rtt", policy_queue_entry.nrm_rtt);
				input_set.push_front(policy_input);

				instance->setPolicyInputEventData(&policy_input, "normalized-variation-absolute", policy_queue_entry.nrm_var_abs);
				input_set.push_front(policy_input);

				instance->setPolicyInputEventData(&policy_input, "normalized-variation-increase", policy_queue_entry.nrm_var_inc);
				input_set.push_front(policy_input);

				instance->setPolicyInputEventData(&policy_input, "normalized-variation-decrease", policy_queue_entry.nrm_var_dec);
				input_set.push_front(policy_input);

				instance->setPolicyInputEventData(&policy_input, "normalized-percent-absolute", policy_queue_entry.nrm_var_abs);
				input_set.push_front(policy_input);

				instance->setPolicyInputEventData(&policy_input, "normalized-percent-increase", policy_queue_entry.nrm_var_inc);
				input_set.push_front(policy_input);

				instance->setPolicyInputEventData(&policy_input, "normalized-percent-decrease", policy_queue_entry.nrm_var_dec);
				input_set.push_front(policy_input);
			}

			/* Event: TOS Change */
			if (instance->calcFlags & FLAG_CALC_TOSC) {
				instance->setPolicyInputEventData(&policy_input, "tos-change", policy_queue_entry.tos);
				input_set.push_front(policy_input);
			}

			/* Clear policy_input data. */
			policy_input.clear();

			/* Run evaulate if needed. */
			if (!input_set.empty()) {
				/* Apply output policy */
				t = time(NULL);
#ifndef SKIP_EVALUATE
				if ((action = instance->outputPolicy.evaluate(policy_output, input_set)) == ACTION_FAILURE) {
					TRACE(TRACE_DEBUG, "output policy evaulate returned in failure - terminating thread.");
					interrupt = true;
					result = FAILURE;
				} else if (action == ACTION_ACCEPT) {
					/* Update address status. */
					if (policy_output.find("state") != policy_output.end()) {
						pthread_mutex_lock(state_mutex);
						((*state_hash)[policy_queue_entry.address])->set(policy_output["state"].c_str());
						TRACE(TRACE_INTERNAL, "set state of %s to %s", policy_input["ip"].c_str(), policy_output["state"].c_str());
						pthread_mutex_unlock(state_mutex);
					}
					/* Handle statistics clear. */
					if (policy_output.find("statistics") != policy_output.end()) {
						if (policy_output["statistics"].compare("clear-history")) {
							// We don't clear the calculation() moving average. Do we need to?
							pthread_mutex_lock(calc_data_list_mutex);
							calc_data[policy_queue_entry.address].clear();
							pthread_mutex_unlock(calc_data_list_mutex);
						}
					}
				}

				/* Display policy run time, if needed. */
				TRACE(TRACE_INTERNAL, "outputPolicy.evaluate() ran %lu sec (with %lu policy_inputs)", time(NULL) - t, input_set.size());
#endif

				/* Clear policy data for next round. */
				input_set.clear();
				policy_output.clear();
			}
		}
	}

	TRACE(TRACE_THREAD, "ICMP::policy_thread() ending main loop");

	pthread_exit(NULL);
}

RESULT ICMP::setPolicyInputEventData(variable_binding_t *policy_input, char* event, unsigned long value) {
	char buf[132];

	snprintf(buf, 132, "%lu", value);

	(*policy_input)["event"] = event;
	(*policy_input)["not-equal"] = buf;
	(*policy_input)["equal-to"] = buf;
	(*policy_input)["at-least"] = buf;
	(*policy_input)["at-most"] = buf;
	(*policy_input)["less-than"] = buf;
	(*policy_input)["more-than"] = buf;

	/* Makes the value available as expected to users. */
	(*policy_input)[event] = buf;

	return SUCCESS;
}

RESULT ICMP::setPolicyInputTypeCode(variable_binding_t *policy_input, int8_t type, int8_t code) {
	char buf[132];

	switch (type) {
		case -1:
			(*policy_input)["type"] = "timeout";
			(*policy_input)["code"] = "none";
			break;

		case ICMP_ECHOREPLY:
			(*policy_input)["type"] = "echo-reply";
			(*policy_input)["code"] = "none";
			break;

		case ICMP_DEST_UNREACH:
			(*policy_input)["type"] = "destination-unreachable";
			switch (code) {
				case ICMP_NET_UNREACH:
					(*policy_input)["code"] = "network-unreachable";
					break;

				case ICMP_HOST_UNREACH:
					(*policy_input)["code"] = "host-unreachable";
					break;

				case ICMP_PROT_UNREACH:
					(*policy_input)["code"] = "protocol-unreachable";
					break;

				case ICMP_PORT_UNREACH:
					(*policy_input)["code"] = "port-unreachable";
					break;

				case ICMP_FRAG_NEEDED:
					(*policy_input)["code"] = "fragmentation-needed";
					break;

				case ICMP_PKT_FILTERED:
					(*policy_input)["code"] = "packet-filtered";
					break;

				case ICMP_SR_FAILED:
					(*policy_input)["code"] = "source-route-failed";
					break;

				default:
					snprintf(buf, 132, "unimplemented (%d)", code);
					(*policy_input)["code"] = buf;
					break;
			}
			break;

		case ICMP_SOURCE_QUENCH:
			(*policy_input)["type"] = "source-quench";
			break;

		case ICMP_REDIRECT:
			(*policy_input)["type"] = "redirect";
			switch (code) {
				case ICMP_REDIR_NET:
					(*policy_input)["code"] = "redirect-net";
					break;

				case ICMP_REDIR_HOST:
					(*policy_input)["code"] = "redirect-host";
					break;

				case ICMP_REDIR_NETTOS:
					(*policy_input)["code"] = "redirect-net-tos";
					break;

				case ICMP_REDIR_HOSTTOS:
					(*policy_input)["code"] = "redirect-host-tos";
					break;

				default:
					snprintf(buf, 132, "unimplemented (%d)", code);
					(*policy_input)["code"] = buf;
					break;
			}
			break;

		case ICMP_TIME_EXCEEDED:
			(*policy_input)["type"] = "time-exceeded";
			switch (code) {
				case ICMP_EXC_TTL:
					(*policy_input)["code"] = "ttl-count-exceeded";
					break;

				case ICMP_EXC_FRAGTIME:
					(*policy_input)["code"] = "fragment-reassembly-time-exceeded";
					break;

				default:
					snprintf(buf, 132, "unimplemented (%d)", code);
					(*policy_input)["code"] = buf;
					break;
			}
			break;

		default:
			snprintf(buf, 132, "unimplemented (%d)", type);
			(*policy_input)["type"] = buf;
			snprintf(buf, 132, "unimplemented (%d)", code);
			(*policy_input)["code"] = buf;
			break;
	}

	return SUCCESS;
}

RESULT ICMP::getPolicyIntervals(slist<unsigned int> &interval_list) {
	RESULT result = SUCCESS;

	unsigned long interval;
	slist<unsigned int>::iterator it;
	variable_reference_t referenced;

	if (this->outputPolicy.getReferencedVariables(referenced) != SUCCESS) {
		LMSG("unable to get referenced variables from output policies.");

		result = FAILURE;
	} else {
		/* Clear flags */
		calcFlags = 0;

		/* Walk policy terms. */
		for (variable_reference_t::iterator term = referenced.begin(); term != referenced.end(); ++term) {
			for (slist<variable_t>::iterator variable = term->begin(); variable != term->end(); ++variable) {
				if (variable->name.compare("interval") == 0) {
					if (!variable->values.empty()) {
						if ((interval = strtol((variable->values.begin())->c_str(), NULL, 10)) > 0) {
							interval_list.push_front(interval);
						}
					}
				} else if (variable->name.compare("event") == 0) {
					for (slist<string>::iterator event = variable->values.begin(); event != variable->values.end(); ++event) {
						if (event->compare("packet-loss") == 0) {
							if ((calcFlags & FLAG_CALC_LOSS) == 0) {
								calcFlags |= FLAG_CALC_LOSS;
								TRACE(TRACE_DEBUG, "set policy flag (%04x) for packet-loss (calcFlags: %04x).", FLAG_CALC_LOSS, calcFlags);
							}
						} else if (event->substr(0, 8).compare("rtt-") == 0) { //rtt-*
							if ((calcFlags & FLAG_CALC_LTCY) == 0) {
								calcFlags |= FLAG_CALC_LTCY;
								TRACE(TRACE_DEBUG, "set policy flag (%04x) for rtt (calcFlags: %04x).", FLAG_CALC_LTCY, calcFlags);
							}
						} else if (event->substr(0, 7).compare("jitter-") == 0) { //jitter-*
							if ((calcFlags & FLAG_CALC_JTTR) == 0) {
								calcFlags |= FLAG_CALC_JTTR;
								TRACE(TRACE_DEBUG, "set policy flag (%04x) for jitter (calcFlags: %04x).", FLAG_CALC_JTTR, calcFlags);
							}
						} else if (event->substr(0, 4).compare("tos-") == 0) { //tos-*
							if ((calcFlags & FLAG_CALC_TOSC) == 0) {
								calcFlags |= FLAG_CALC_TOSC;
								TRACE(TRACE_DEBUG, "set policy flag (%04x) for tos (calcFlags: %04x).", FLAG_CALC_TOSC, calcFlags);
							}
						} else if (event->substr(0, 11).compare("normalized-") == 0) { // normalized-*
							if ((calcFlags & FLAG_CALC_NORM) == 0) {
								calcFlags |= FLAG_CALC_NORM;
								TRACE(TRACE_DEBUG, "set policy flag (%04x) for normalized (calcFlags: %04x).", FLAG_CALC_NORM, calcFlags);
							}
						}
					}
				}
			}
		}

		/* Return a sorted unique list. */
		interval_list.sort();
		interval_list.unique();
	}

	if (interval_list.empty()) {
		LMSG("no policy intervals found in ICMP::getPolicyIntervals.");
	} else if (TRACE_ISSET(TRACE_DEBUG)) {
		for (slist<unsigned int>::iterator it = interval_list.begin(); it != interval_list.end(); ++it) {
			TRACE(TRACE_DEBUG, "set policy interval of %lu sec.", *it);
		}
	}

	return result;
}


RESULT ICMP::readKnownAddress(bool ignore_edge) {
	RESULT result = FAILURE;
	string sql;
	vector<string>::iterator v_it;
	StorageData data;

	variable_binding_t	policy_input;
	variable_binding_t	policy_output;

	unsigned long id, tos_tmp;

	/* Test for chagnes in IP dataset. */
	sql = "SELECT id FROM ip_data ORDER BY id DESC LIMIT 1";
	if ((result = storage.queryf(data, sql.c_str())) != SUCCESS) {
		LMSG("select of largest IP id from database failed.");
	} else if (data.getNumRow() < 1) {
		TRACE(TRACE_DEBUG, "discovery has not been complete - no IPs available to monitor");
	} else {
		data.nextRow();
		data.getULong(id, "id");

		if (id != last_id) {
			last_id = id;

			TRACE(TRACE_DEBUG, "reading in new set of IP addresses for monitoring");

			/* Ignore edge interfaces */
			if (ignore_edge) {
				sql = "SELECT DISTINCT ip_data.value AS ip FROM interface, interface_data, ip, ip_data, link_point WHERE (interface.id = interface_data.interface AND interface.id = ip.interface AND ip.id = ip_data.ip AND ip.id = link_point.point) AND interface_data.value = '1' AND interface_data.variable = '%s' AND ip_data.value NOT LIKE '0.%%' AND ip_data.variable = '%s' ORDER BY ip_data.value";
			} else {
				sql = "SELECT DISTINCT ip_data.value AS ip FROM interface, interface_data, ip, ip_data WHERE (interface.id = interface_data.interface AND interface.id = ip.interface AND ip.id = ip_data.ip) AND interface_data.value = '1' AND interface_data.variable = '%s' AND ip_data.value NOT LIKE '0.%%' AND ip_data.variable = '%s' ORDER BY ip_data.value";
			}

			if ((result = storage.queryf(data, sql.c_str(), ".1.3.6.1.2.1.2.2.1.7", ".1.3.6.1.2.1.4.20.1.1")) != SUCCESS) {
				/* The database is usually horked when this happens. */
				LMSG("select of IPs to monitor failed.");

				interrupt = true;
			} else {
				/* Remove previous contents. */
				netblocks.clear();
				tosMap.clear();

				/* NOTE: This loop is slow when STLPort debugging is on. */
				while (data.nextRow()) {
					data.getString(policy_input["ip"], "ip");
					policy_input["ip-except"] = policy_input["ip"];

					if (this->inputPolicy.evaluate(policy_output, policy_input) != ACTION_REJECT) {
						netblocks.insert(netblocks.begin(), policy_output["ip"]);

						/* Store all TOS values that are non-default. */
						if (policy_output.find("tos") != policy_output.end()) {
							tos_tmp = (u_int8_t) strtoul(policy_output["tos"].c_str(), NULL, 10);
							if (tos_tmp != tosDefault) {
								tosMap[policy_output["ip"]] = tos_tmp;
								TRACE(TRACE_DEBUG, "%s TOS value set to %lu (dscp: %lu, prec: %lu).", policy_output["ip"].c_str(), tos_tmp, (u_int8_t) (tos_tmp >> 2), (u_int8_t) (tos_tmp >> 5));
							}
						} else if (policy_output.find("dscp") != policy_output.end()) {
							tos_tmp = (u_int8_t) (strtoul(policy_output["dscp"].c_str(), NULL, 10) << 2);
							if (tos_tmp != tosDefault) {
								tosMap[policy_output["ip"]] = tos_tmp;
								TRACE(TRACE_DEBUG, "%s DSCP value set to %lu (%lu tos, prec: %lu).", policy_output["ip"].c_str(), (u_int8_t) (tos_tmp >> 2), tos_tmp, (u_int8_t) (tos_tmp >> 5));
							}
						} else if (policy_output.find("precedence") != policy_output.end()) {
						  	tos_tmp = (u_int8_t) (strtoul(policy_output["precedence"].c_str(), NULL, 10) << 5);
						  	if (tos_tmp != tosDefault) {
								tosMap[policy_output["ip"]] = tos_tmp;
								TRACE(TRACE_DEBUG, "%s precedence value set to %lu (tos: %lu, dscp: %lu).", policy_output["ip"].c_str(), (u_int8_t) (tos_tmp >> 5), tos_tmp, (u_int8_t) (tos_tmp >> 2));
							}
						}
					} else {
						TRACE(TRACE_DEBUG, "policy rejected ip: %s", policy_input["ip"].c_str());
					}
				}

				/* Remove duplicate IP addresses. */
				sort(netblocks.begin(), netblocks.end());
				v_it = unique(netblocks.begin(), netblocks.end());
				netblocks.erase(v_it, netblocks.end());

				/* Randomize to dispurse load across devices. */
				random_shuffle(netblocks.begin(), netblocks.end());

				this->knownAddresses = netblocks.size();

				TRACE(TRACE_INTERNAL, "netblocks.size() == %lu, tosMap.size() == %lu", this->knownAddresses, this->tosMap.size());

				if ((this->knownAddresses < 1) && TRACE_ISSET(TRACE_DEBUG)) {
					TRACE(TRACE_DEBUG, "no IPs passed the applied policy - verify your policy is properly configured");
				}
			}
		}
	}

	return result;
}

/*
** Local Variables:
** c-basic-offset: 3
** tab-width: 3
** End:
** vim:
** vim: noet ts=3 sw=3
*/
