/*
** SellaNMS
** $Id: icmp.cxx,v 2.109 2005/08/22 02:25:42 sella Exp $
** Copyright (c) 2001-2005 Digital Genesis Software, LLC. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
*/

#ifdef HAVE_CONFIG_H
	#include "config.h"
#endif

#include "icmp.h"
#include "debug.h"
#include "defaults.h"
#include "ip_util.h"
#include <math.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <values.h>
#include <unistd.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>

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

ICMP::ICMP():calcFlags(0),sleepIndex(0),throttle(false) {
	this->name.append(" icmp");

	has_scheduler = false;
	has_execute = true;

	pthread_mutex_init(&calc_queue_mutex, NULL);
	pthread_mutex_init(&calc_data_mutex, NULL);
	pthread_mutex_init(&policy_queue_mutex, NULL);
	pthread_mutex_init(&transmit_mutex, NULL);
	pthread_mutex_init(&status_mutex, NULL);

	flag_init(calc_flag);
	flag_init(policy_flag);
}

ICMP::~ICMP() {
	pthread_mutex_destroy(&calc_queue_mutex);
	pthread_mutex_destroy(&calc_data_mutex);
	pthread_mutex_destroy(&policy_queue_mutex);
	pthread_mutex_destroy(&transmit_mutex);
	pthread_mutex_destroy(&status_mutex);
}

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;

	/* How often to start a polling inteval. */
	config->assignValue("interval", interval, MODULE_MONITOR_ICMP_INTERVAL_DEFAULT, 1);

	/* Number of packets to send to each address per interval. */
	config->assignValue("transmit", transmit, MODULE_MONITOR_ICMP_TRANSMIT_DEFAULT, 1);

	/* The message placed in the payload of the ICMP packet. Up to 36 characters. */
	config->assignValue("payload", payloadMessage, MODULE_MONITOR_ICMP_PAYLOADMESSAGE_DEFAULT);

	if (config->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 (config->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. */
	config->assignValue("max-rtt", tmp, MODULE_MONITOR_ICMP_MAXRTT_DEFAULT, 1, MODULE_MONITOR_ICMP_MAXRTT_MAXIMUM);
	this->maxRTT.tv_sec = tmp / 1000;
	this->maxRTT.tv_usec = (tmp % 1000) * 1000;

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

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

	/* The TTL to place in outgoing ICMP packets (how many hops they may go). */
	config->assignValue("ttl", ttl, MODULE_MONITOR_ICMP_TTL_DEFAULT, 1, MAXTTL);

	/* Maxiumum allowed depth of calc_queue. */
	if (!config->assignValue("calc-queue-depth", calcQueueDepth, MODULE_MONITOR_ICMP_CALCQUEUEDEPTH_DEFAULT, MODULE_MONITOR_ICMP_QUEUEDEPTHMIN_DEFAULT)) {
		config->assignValue("queue-depth", calcQueueDepth, MODULE_MONITOR_ICMP_CALCQUEUEDEPTH_DEFAULT, MODULE_MONITOR_ICMP_QUEUEDEPTHMIN_DEFAULT);
	}

	/* Maxiumum allowed depth of policy_queue. */
	config->assignValue("policy-queue-depth", policyQueueDepth, calcQueueDepth * MODULE_MONITOR_ICMP_POLICYQUEUEDEPTH_MULTIPLIER, MODULE_MONITOR_ICMP_QUEUEDEPTHMIN_DEFAULT);

	/* Number of policy queue entries allowed to be processed at a time. (impacts memory usage) */
	config->assignValue("policy-queue-limit-max", policyQueueLimitMax, policyQueueDepth / 2, 5, policyQueueDepth);

	/* Interval (sec) that calc_thread runs calculations on collected data and passes results to policy_thread. */
	config->assignValue("calc-interval", calcInterval, MODULE_MONITOR_ICMP_CALCINTERVAL_DEFAULT, 1, 3600);

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

	suppression = !config->isNode("suppression disable");
	config->assignValue("suppression packet-loss", lossSupVar, MODULE_MONITOR_ICMP_LOSSSUPVAR_DEFAULT, 0, 100);
	config->assignValue("suppression latency", rttSupVar, MODULE_MONITOR_ICMP_RTTSUPVAR_DEFAULT, 0, MODULE_MONITOR_ICMP_MAXRTT_MAXIMUM);
	config->assignValue("suppression jitter", jtrSupVar, MODULE_MONITOR_ICMP_JTRSUPVAR_DEFAULT, 0, MODULE_MONITOR_ICMP_MAXRTT_MAXIMUM);
	config->assignValue("suppression normalized-latency", nrmSupVar, MODULE_MONITOR_ICMP_NRMSUPVAR_DEFAULT, 0, MODULE_MONITOR_ICMP_MAXRTT_MAXIMUM);

	/* Add and compile input policy. */
	if (config->assignValues("policy input", v)) {
		/* This is actually just added to the begining of the output policies. */
		if (inputPolicy.addPolicies(config, v) == FAILURE) {
			LMSG("NOTICE: Failed to add/compile input policies.");

			return FAILURE;
		}
	}

	/* Add and compile output policy. */
	if (config->assignValues("policy output", v)) {
		if (outputPolicy.addPolicies(config, v) == FAILURE) {
			LMSG("NOTICE: Failed to add/compile output policies.");

			return FAILURE;
		}
	}

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

		return FAILURE;
	}

	/* Configure Storage */
	storage.setConfig(*config);

	printConfig();
	return result;
}

RESULT ICMP::printConfig() {
	in_addr s;
	s.s_addr = this->source;
	char buf[INET_ADDRSTRLEN];

	TRACE(TRACE_CONFIG, "interval: %lu (sec)", this->interval);
	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, "max-rtt: %lu (msec)", tvtousec(this->maxRTT)/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-max: %lu", this->policyQueueLimitMax);
	TRACE(TRACE_CONFIG, "suppression disable: %s", (suppression) ? "not set" : "set");
	TRACE(TRACE_CONFIG, "suppression packet-loss: %lu (percentage)", this->lossSupVar);
	TRACE(TRACE_CONFIG, "suppression latency: %lu (ms)", this->rttSupVar);
	TRACE(TRACE_CONFIG, "suppression jitter: %lu (ms)", this->jtrSupVar);
	TRACE(TRACE_CONFIG, "suppression normalized-latency: %lu (ms)", this->nrmSupVar);

	return Monitor::printConfig();
}

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

	pthread_t thread_id = 0;
	this->module_id = (u_int16_t) pthread_self();

	bool running = true;

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

	if (trace.thread) {
		LMSG("ICMP::execute() starting");
	}

	/* Create the calc thread */
	if ((result = createThread(thread_id, (void* (*)(void*))ICMP::policy_thread, this)) == SUCCESS) {
		t_list.push_front(thread_id);
	}

	/* Create the storage thread */
	if (result == SUCCESS) {
		if ((result = createThread(thread_id, (void* (*)(void*))ICMP::calc_thread, this)) == SUCCESS) {
			t_list.push_front(thread_id);
		}
	}

	/* Create the receiver thread */
	if (result == SUCCESS) {
		if ((result = createThread(thread_id, (void* (*)(void*))ICMP::receive_thread, this)) == SUCCESS) {
			t_list.push_front(thread_id);
		}
	}

	/* Create the transmit thread */
	if (result == SUCCESS) {
		if ((result = createThread(thread_id, (void* (*)(void*))ICMP::transmit_thread, this)) == SUCCESS) {
			t_list.push_front(thread_id);
		}
	}

	/* Create the timeout thread */
	if (result == SUCCESS) {
		if ((result = createThread(thread_id, (void* (*)(void*))ICMP::timeout_thread, this)) == SUCCESS) {
			t_list.push_front(thread_id);
		}
	}

#ifdef THREAD_WAIT_AND_KILL
	// Waits for first thread in list (last inserted). When that thread completes,
	// kills the next thread and waits for it to complete, until all complete.
	for (it = t_list.begin(); it != t_list.end();) {
		/* Wait for thread to complete. */
		if (this->getThreadResult(*it, t_result) != SUCCESS || t_result != SUCCESS) {
			result = FAILURE;
		}
		/* Kill the next thread. */
		if (++it != t_list.end()) {
			this->killThread(*it);
		}
	}
#else
	// Waits for all threads to complete, starting with the first thread (last inserted). */
	for (it = t_list.begin(); it != t_list.end(); ++it) {
		/* Wait for thread to complete. */
		if (this->getThreadResult(*it, t_result) != SUCCESS || t_result != SUCCESS) {
			result = FAILURE;
		}
	}
#endif

	if (trace.thread) {
		LMSG("ICMP::execute() exiting");
	}

	return result;
}

RESULT ICMP::updateStatus() {
	StorageData data;

	string ip_address;
	sockaddr_in address;

	string sql_lock = "LOCK TABLES status WRITE, ip_data READ, ip READ";
	string sql_unlock = "UNLOCK TABLES";
	string sql_delete = "DELETE FROM status WHERE protocol = 'icmp'";
	string sql_select = "SELECT status.status AS status, ip_data.value AS address FROM ip_data, ip, status WHERE ip_data.variable = '.1.3.6.1.2.1.4.20.1.1' AND ip_data.ip = ip.id AND ip.interface = status.interface AND status.protocol = 'icmp' ORDER BY timestamp";

	pthread_mutex_lock(&(this->status_mutex)); //Before the SQL lock;
	this->storage.query(data, sql_lock.c_str());
	this->storage.query(data, sql_select.c_str());

	while (data.nextRow()) {
		data.getString(ip_address, "address");
		inet_pton(AF_INET, ip_address.c_str(), &address.sin_addr);

		data.getString(this->status_hash[address.sin_addr.s_addr], "status");
		if (trace.internal) {
			LMSG("ICMP::updateStatus() setting %s for %s", this->status_hash[address.sin_addr.s_addr].c_str(), ip_address.c_str());
		}
	}

	this->storage.query(data, sql_delete.c_str());
	this->storage.query(data, sql_unlock.c_str());
	pthread_mutex_unlock(&(this->status_mutex)); //After the SQL unlock.

	return SUCCESS;
}

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

	pthread_t thread_id = pthread_self();

	ICMP *instance = (ICMP*) args;
	instance->setThreadStatus(thread_id, THREAD_RUNNING);

	unsigned long	packet_length = ICMP_MINLEN + sizeof(struct icmp_payload);
	char				request[packet_length], buf[INET_ADDRSTRLEN];

	int				s = 0, ttl;
	sockaddr_in		src;

	timeval			interval;
	timeval			start;
	timeval			stop;

	bool running = true;

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

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

	if (instance->trace.thread) {
		LMSG("ICMP::transmit_thread() starting main loop");
	}

	instance->grantPriv();

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

	if (s < 0) {
		running = false;

		LMSG("WARNING: the ICMP monitoring module requires root privileges.");
		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("WARNING: Failed to set TTL value %lu.", ttl);
		}

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

			close(s);

			LMSG("HAVE: %s", inet_ntoa(src.sin_addr));
			inet_ntop(AF_INET, &src.sin_addr, buf, INET_ADDRSTRLEN);
			LMSG("WARNING: ICMP monitoring module unable to bind to local address '%s'.\n", buf);
			result = FAILURE;
		}
	}
	instance->dropPriv();

	while (running && !instance->interrupt) {
		gettimeofday(&start, NULL);

		instance->getThreadStatus(thread_id, status);
		if (status == THREAD_KILLED) {
			running = false;
		} else {
			if (instance->calc_queue.size() >= instance->calcQueueDepth) {
				LMSG("WARNING: Skipping ICMP polling due to calc queue congestion (%lu/%lu). If this condition persists, extend the polling interval.", instance->calc_queue.size(), instance->calcQueueDepth);
				usleep(instance->interval * 1000000);
			} else {
				if (instance->readKnownAddress(instance->ignoreEdge) != SUCCESS) {
					LMSG("NOTICE: Failed to read known addresses.");
	
					result = FAILURE;
				}
				if (instance->updateStatus() != SUCCESS) {
					LMSG("NOTICE: Failed to update status.");
	
					result = FAILURE;
				}
				if (instance->pollTargets(s, request, &interval) != SUCCESS) {
					LMSG("NOTICE: Failed to poll all ICMP targets.");

					result = FAILURE;
				}
			}

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

			if (instance->trace.timer) {
				LMSG("ICMP Polling Interval Took: %d seconds, %d microseconds.", stop.tv_sec, stop.tv_usec);
			}
		}
	}
	if (s > 0) {
		close(s);
	}

	instance->setThreadStatus(thread_id, THREAD_COMPLETED);

	THREAD_RETURN(result);
}

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	si = 0;
	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();

	sockaddr_in		src;

	/* 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->sleep(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) && (!this->interrupt); i--) {
		for (vector<string>::iterator j = this->netblocks.begin(); (j != this->netblocks.end()) && (!this->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 (trace.debug) {
							/* If they passed in precidence, then this output will appear wrong. */
							LMSG("Changing socket ToS value from %d to %d (dscp: %lu, prec: %lu) for prefix %s", tos_prev , tos, tos >> 2, tos >> 5, ip_address);
						}
						tos_prev = tos;

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

					if (this->transmitPacket(s, packet, packet_length) != SUCCESS) {
						LMSG("WARNING: failed to send packet to %s", ip_address);
					}
					
					//delete[] 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->sleep(sleep_time, sleep_accuracy);
					}
					
					if (trace.internal) {
						LMSG("Packets: %d (Burst Rate: %d)", packet_count, burst_rate);
					}

					packet_count--;
				}	
			} else {
				if (trace.debug) {
					LMSG("WARNING: Processed an invalid netblock.");
				}

				result = FAILURE;
			}
		}
	}
	return result;
}

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

	timespec	timer;

	gettimeofday(&start, NULL);

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

	if (trace.internal) {
		LMSG("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() % MODULE_MONITOR_ICMP_SEQUENCEMAXADJ_DEFAULT) + 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) {
	RESULT result = SUCCESS;	
	STATUS status;

	pthread_t				thread_id = pthread_self();
	bool						running = true;
	char						buf[INET_ADDRSTRLEN];

	timeval					stamp;
	calc_queue_entry_t	entry;

	pair<u_int32_t, int>	key;

	ICMP 						*instance = (ICMP*) args;
	instance->setThreadStatus(thread_id, THREAD_RUNNING);

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

	struct in_addr ip;

	if (instance->trace.thread) {
		LMSG("ICMP::timeout_thread() starting main loop");
	}

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

		instance->getThreadStatus(thread_id, status);
		if (status == THREAD_KILLED) {
			running = false;
		} else {
			/* 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(); p != instance->transmit_list.end(); ++p) {
				if (tvcmp(p->timestamp, stamp) < 0) {
					if (instance->trace.internal) {
						ip.s_addr = p->address;
						inet_ntop(AF_INET, &ip, buf, INET_ADDRSTRLEN);
						LMSG("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 on to the monitoring system. */
					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));

			usleep(USLEEP_LONGLOOP_TIME);
		}
	}

	if (instance->trace.thread) {
		LMSG("ICMP::timeout_thread() ending main loop");
	}
	
	instance->setThreadStatus(thread_id, THREAD_COMPLETED);

	THREAD_RETURN(result);
}

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

	pthread_t 			thread_id = pthread_self();
	ICMP					*instance = (ICMP*) args;

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

	instance->setThreadStatus(thread_id, THREAD_RUNNING);

	if (instance->trace.thread) {
		LMSG("ICMP::recieve_thread() starting main loop");
	}

	/* Open a socket for packet reciept. */
	if (instance->trace.internal) {
		LMSG("ICMP::watchNetwork() opening raw ICMP socket.");
	}

	instance->grantPriv();

	s = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);

	if (s < 0) {
		running = false;

		LMSG("WARNING: the ICMP monitoring module requires root privileges.");
		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) {
			running = false;
			close(s);

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

	}
	instance->dropPriv();

	while (running && !instance->interrupt) {
		instance->getThreadStatus(thread_id, status);
		if (status == THREAD_KILLED) {
			running = false;
		} else {
			/* Look for incoming packets. */
			instance->watchNetwork(s);
		}
	}
	if (s > 0) {
		close(s);
	}

	if (instance->trace.thread) {
		LMSG("ICMP::recieve_thread() ending main loop");
	}

	instance->setThreadStatus(thread_id, THREAD_COMPLETED);

	THREAD_RETURN(result);
}

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

	char				packet[1024], buf[INET_ADDRSTRLEN];
	bool				running = true;
	pthread_t		thread_id = pthread_self();

	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;

	sockaddr_in		src, dst;

	fd_set					rfds;
	calc_queue_entry_t	entry;

	pair<u_int32_t, int>	key;

	struct in_addr tmp_ip;

	if (this->trace.internal) {
		LMSG("ICMP::watchNetwork() catching network packets.");
	}

	while (running && !this->interrupt) {
		this->getThreadStatus(thread_id, status);
		if (status == THREAD_KILLED) {
			running = false;

			continue;
		}

		/* 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. */
				if (this->trace.internal) {
					LMSG("ICMP::watchNetwork() recieved 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. */
				if (this->trace.internal) {
					LMSG("ICMP::watchNetwork() recieved 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.internal) {
					tmp_ip.s_addr = ip->saddr;
					inet_ntop(AF_INET, &tmp_ip, buf, INET_ADDRSTRLEN);
					LMSG("DEBUG: Recieved a packet that wasn't from the NMS (its 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.internal) {
				tmp_ip.s_addr = key.first;
				inet_ntop(AF_INET, &tmp_ip, buf, INET_ADDRSTRLEN);
				LMSG("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. */
				if (this->trace.internal) {
					LMSG("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);
				LMSG("DEBUG: Received ICMP packet was not found within transmit_hash (ip: %s, seq: %lu). This should rarely happen.", 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 (this->trace.internal) {
				tmp_ip.s_addr = key.first;
				inet_ntop(AF_INET, &tmp_ip, buf, INET_ADDRSTRLEN);
				LMSG("ICMP::watchNetwork() push ICMP reply into queue (ip: %s).", buf);
			}

			pthread_mutex_lock(&(this->calc_queue_mutex));
			this->calc_queue.push(entry);
			pthread_mutex_unlock(&(this->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], buf3[INET_ADDRSTRLEN];

	bool warn = true;
	u_int32_t address;
	long nrm_rtt_tmp;
	unsigned int processed = 0, suppressed = 0;

	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;

	struct in_addr ip;

	unsigned long calc_data_avg_len = 0;

#if 0
	/* Verify we have room in the queue. */
	if (policy_queue.size() >= policyQueueDepth) {
		LMSG("WARNING: ICMP policy queue depth (%lu) is over the maximum (%lu)", policy_queue.size(), policyQueueDepth);
		throttle = true;
		result = FAILURE;
	}
#endif

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

	/* Walk through all prefixes data, doing calcaulations for each interval. */
	for (calc_data_t::iterator it = calc_data.begin(); it != calc_data.end(); ++it) {
		/* Lock calc_data */
		pthread_mutex_lock(&calc_data_mutex);

		if (trace.internal) {
			ip.s_addr = it->first;
			inet_ntop(AF_INET, &ip, buf3, INET_ADDRSTRLEN);
			LMSG("Current ip: %s [%lu] (entries: %lu)", buf3, it->first, it->second.size());
		}

		/* Walk though list for this prefix 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()) {
			if (policy_queue.size() >= policyQueueDepth) {
				if (warn) {
					LMSG("WARNING: ICMP Policy Queue depth has reached maxium of %lu entries. Dropping data.", policyQueueDepth);
					warn = false;
				}
				break;
			}

			/* 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;

					/* 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 Latency */

					/* Latency 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 Latency 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 > MODULE_MONITOR_ICMP_MAXNRM_DEFAULT) {
							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.push(policy_queue_entry);
						pthread_mutex_unlock(&policy_queue_mutex);

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

						if (trace.internal) {
							ip.s_addr = it->first;
							inet_ntop(AF_INET, &ip, buf3, INET_ADDRSTRLEN);
							LMSG("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", buf3, *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.internal) {
							ip.s_addr = it->first;
							inet_ntop(AF_INET, &ip, buf3, INET_ADDRSTRLEN);
							LMSG("policy_queue.push avoided. Entry for %s was a supressed as a duplicate. int: %lu, %lu%% loss\nRTT-> avg: %lu JTR-> avg: %lu, NRM-> var_abs: %lu", buf3, *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 recieve 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.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);
				LMSG(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.internal) {
				ip.s_addr = it->first;
				inet_ntop(AF_INET, &ip, buf3, INET_ADDRSTRLEN);
				LMSG("calc_data[%s] list size: %lu. Item at trimming location is %lus old.", buf3, it->second.size(), calc_start.tv_sec - entry_it->timestamp.tv_sec);
			}

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

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

		/* Unlock calc_data */
		pthread_mutex_unlock(&calc_data_mutex);
	}

	if (trace.debug || policy_queue.size() > policyQueueDepth / 2) {
		/* Average lengh will be around "policy max interval" / "polling interval" * "transmit". */
		LMSG("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);
	}

	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.internal) {
			inet_ntop(AF_INET, &ip, buf, INET_ADDRSTRLEN);
			LMSG("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.internal) {
			inet_ntop(AF_INET, &ip, buf, INET_ADDRSTRLEN);
			LMSG("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;
	}

	/* Latency */
	if (calcFlags & FLAG_CALC_LTCY) {
		if (std::abs(new_entry.avg_rtt - entry.avg_rtt) > rttSupVar) {
//			inet_ntop(AF_INET, &ip, buf, INET_ADDRSTRLEN);
//			LMSG("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) {
//			LMSG("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) {
//			LMSG("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) {
//			inet_ntop(AF_INET, &ip, buf, INET_ADDRSTRLEN);
//			LMSG("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) {
//			LMSG("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) {
//			LMSG("max_jtr: %lu > %lu", std::abs(new_entry.max_jtr - entry.max_jtr), jtrSupVar);
			goto do_not_suppress;
		}
	}

	/* Normalized Latency */
	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::calc_thread(void* args) {
	RESULT result = SUCCESS, calc_result;	
	STATUS status;

	pthread_t thread_id = pthread_self();

	ICMP *instance = (ICMP*) args;
	instance->setThreadStatus(thread_id, THREAD_RUNNING);

	time_t now, next_start;

	calc_queue_entry_t calc_queue_entry;

	bool running = true;
	calc_queue_t *calc_queue = &instance->calc_queue;
	pthread_mutex_t *calc_queue_mutex = &(instance->calc_queue_mutex);
	pthread_mutex_t *calc_data_mutex = &(instance->calc_data_mutex);
	unsigned int calc_interval = instance->calcInterval;
	unsigned int calc_interval_adj = instance->calcIntervalAdj;
	unsigned int count, calc_queue_limit;

	/* Calc data structure. */
	calc_data_t *calc_data = &(instance->calc_data);

	/* Interval List */
	slist<unsigned int> interval_list;

	calc_data_entry_t calc_data_entry;

	if (instance->trace.thread) {
		LMSG("ICMP::calc_thread() starting main loop");
	}

	/* Drop root privileges */
	instance->dropPriv();

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

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

	while (running && !instance->interrupt) {
		/* Wait for the calculate flag to get set. */
		if (flag_wait(instance->calc_flag, &(instance->interrupt)) == FAILURE) {
			break;
		}
		instance->getThreadStatus(thread_id, status);

		if (instance->trace.internal) {
			LMSG("calc_queue: %lu, policy_queue: %lu", instance->calc_queue.size(), instance->policy_queue.size());
		}

		if (status == THREAD_KILLED) {
			running = false;
		} else {
			/* Update now. */
			now = time(NULL);

			/* Calculate if next_start timer has expired. */
			if (now >= next_start) {
				/* 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);

				if (instance->trace.timer) {
					LMSG("ICMP::calc_thread calculation loop ran for %d sec", time(NULL) - now);
				}

				/* Auto adjust calc_interval. If calc is taking too long, increase. If its on time, decrease. */
				if (calc_result != SUCCESS || now + calc_interval < time(NULL)) {
					/* Increase calc_interval by calc_inteval_adj */
					calc_interval += calc_interval_adj;

					if (instance->trace.debug) {
						LMSG("NOTICE: calc_interval has been increased to %d", calc_interval);
					}
				} else if (calc_interval > instance->calcInterval) { /* Only decrease up to original calc_interval */
					/* Decrease calc_interval by calc_inteval_adj */
					calc_interval -= calc_interval_adj;

					if (instance->trace.debug) {
						LMSG("NOTICE: calc_interval has been decreased to %d", calc_interval);
					}
				}
			}

			/* Build calc structure with data from calc_queue. */
			count = 0;
			calc_queue_limit = calc_queue->size();

			while (!instance->throttle && count++ < calc_queue_limit && !calc_queue->empty()) {
				/* Grab calc_queue_entry from queue. */
				pthread_mutex_lock(calc_queue_mutex);

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

				calc_queue_entry = calc_queue->front();
				calc_queue->pop();
				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. */
				pthread_mutex_lock(calc_data_mutex);
				(*calc_data)[calc_queue_entry.address].push_front(calc_data_entry);
				pthread_mutex_unlock(calc_data_mutex);
			}

			/* Sleep */
	//		usleep(USLEEP_LONGLOOP_TIME);
		}
	}

	if (instance->trace.thread) {
		LMSG("ICMP::calc_thread() ending main loop");
	}

	instance->setThreadStatus(thread_id, THREAD_COMPLETED);

	THREAD_RETURN(result);
}

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

	pthread_t thread_id = pthread_self();
	char buf[BUF_SIZE], buf2[INET_ADDRSTRLEN];

	ICMP *instance = (ICMP*) args;
	instance->dropPriv();
	instance->setThreadStatus(thread_id, THREAD_RUNNING);

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

	hash_map<u_int32_t, string> *status_hash = &(instance->status_hash);
	pthread_mutex_t *status_mutex = &(instance->status_mutex);
	pthread_mutex_t *calc_data_mutex = &(instance->calc_data_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;

	bool running = true;
	time_t t;
	unsigned long policy_queue_limit;

	unsigned int count = 0;

	if (instance->trace.thread) {
		LMSG("ICMP::policy_thread() starting main loop");
	}

	instance->dropPriv();

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

		instance->getThreadStatus(thread_id, status);
		if (status == THREAD_KILLED) {
			running = false;
		}

		/* Capture the current size of the policy queue. */
		policy_queue_limit = (policy_queue->size() > instance->policyQueueLimitMax) ? instance->policyQueueLimitMax : policy_queue->size();

		while (policy_queue_limit-- > 0 && !policy_queue->empty() && !instance->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["source"] = "network";

			snprintf(buf, 132, "%lu", 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["prefix"] = buf2;
			policy_input["prefix-except"] = buf2;

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

			/* Insert the rtt. */
			snprintf(buf, 132, "%lu", 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["prefix"]) != instance->tosMap.end()) { /* Transmitted TOS value. */
				snprintf(buf, 132, "%lu", instance->tosMap[policy_input["prefix"]]);
			} else {
				snprintf(buf, 132, "%lu", instance->tosDefault);
			}
			policy_input["tos"] = buf;
			policy_input["tos-tx"] = buf;
			snprintf(buf, 132, "%lu", 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["prefix"]) != instance->tosMap.end()) { /* Transmitted TOS value. */
				snprintf(buf, 132, "%lu", instance->tosMap[policy_input["prefix"]] >> 2);
			} else {
				snprintf(buf, 132, "%lu", instance->tosDefault >> 2);
			}
			policy_input["dscp"] = buf;
			policy_input["dscp-tx"] = buf;
			snprintf(buf, 132, "%lu", 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["prefix"]) != instance->tosMap.end()) { /* Transmitted TOS value. */
				snprintf(buf, 132, "%lu", instance->tosMap[policy_input["prefix"]] >> 5);
			} else {
				snprintf(buf, 132, "%lu", instance->tosDefault >> 5);
			}
			policy_input["precedence"] = buf;
			policy_input["precedence-tx"] = buf;
			snprintf(buf, 132, "%lu", policy_queue_entry.tos >> 5); /* Recieved precedence value. */
			policy_input["precedence-rx"] = buf;

			/* Grab status */
			pthread_mutex_lock(status_mutex);
			if (status_hash->find(policy_queue_entry.address) == status_hash->end()) {
				policy_input["status"] = "normal";
			} else {
				policy_input["status"] = (*status_hash)[policy_queue_entry.address];
			}
			pthread_mutex_unlock(status_mutex);

			/* 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: Latency */
			if (instance->calcFlags & FLAG_CALC_LTCY) {
				instance->setPolicyInputEventData(&policy_input, "latency-minimum", policy_queue_entry.min_rtt);
				input_set.push_front(policy_input);

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

				instance->setPolicyInputEventData(&policy_input, "latency-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 Latency */
			if (instance->calcFlags & FLAG_CALC_NORM) {
				instance->setPolicyInputEventData(&policy_input, "normalized-latency", 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) {
					if (instance->trace.debug) {
						LMSG("WARNING: Output policy evaulate returned in failure. Terminating thread.");
					}
					instance->interrupt = true;
					result = FAILURE;
				} else if (action == ACTION_ACCEPT) {
					/* Update address status. */
					if (policy_output.find("status") != policy_output.end()) {
						pthread_mutex_lock(status_mutex);
						(*status_hash)[policy_queue_entry.address] = policy_output["status"];
						pthread_mutex_unlock(status_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_mutex);
							instance->calc_data[policy_queue_entry.address].clear();
							pthread_mutex_unlock(calc_data_mutex);
						}
					}
				}
#endif

				/* Display policy run time, if needed. */
				if (instance->trace.timer) {
					LMSG("outputPolicy.evaluate() ran %lu sec (with %lu policy_inputs)", time(NULL) - t, input_set.size());
				}

				/* Increment count for our RRD. */
				count += input_set.size();

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

		/* Sleep */
//		if (!policy_queue->empty()) {
			/* Queue isn't empty. Give up only a few cycles. */
//			usleep(USLEEP_FASTLOOP_TIME);
//		} else {
			/* Queue is empty. Sleep longer. */
//			usleep(USLEEP_LONGLOOP_TIME);
//		}
	}

	if (instance->trace.thread) {
		LMSG("ICMP::policy_thread() ending main loop");
	}

	instance->setThreadStatus(thread_id, THREAD_COMPLETED);

	THREAD_RETURN(result);
}

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("ERROR: 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);
							if (trace.debug) {
								LMSG("Set policy interval of %lu sec.", 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) {
							calcFlags |= FLAG_CALC_LOSS;
							if (trace.internal) {
								LMSG("Set policy flag (%04x) for packet-loss (calcFlags: %04x).", FLAG_CALC_LOSS, calcFlags);
							}
#if !defined HAVE_STLPORT && (defined __GNUC__ && __GNUC__ < 3)
						} else if (event->substr(0, 8).compare("latency-") == 0) { //latency-*
#else
						} else if (event->compare(0, 8, "latency-") == 0) { //latency-*
#endif
							calcFlags |= FLAG_CALC_LTCY;
							if (trace.internal) {
								LMSG("Set policy flag (%04x) for latency (calcFlags: %04x).", FLAG_CALC_LTCY, calcFlags);
							}
#if !defined HAVE_STLPORT && (defined __GNUC__ && __GNUC__ < 3)
						} else if (event->substr(0, 7).compare("jitter-") == 0) { //jitter-*
#else
						} else if (event->compare(0, 7, "jitter-") == 0) { //jitter-*
#endif
							calcFlags |= FLAG_CALC_JTTR;
							if (trace.internal) {
								LMSG("Set policy flag (%04x) for jitter (calcFlags: %04x).", FLAG_CALC_JTTR, calcFlags);
							}
#if !defined HAVE_STLPORT && (defined __GNUC__ && __GNUC__ < 3)
						} else if (event->substr(0, 4).compare("tos-") == 0) { //tos-*
#else
						} else if (event->compare(0, 4, "tos-") == 0) { //tos-*
#endif
							calcFlags |= FLAG_CALC_TOSC;
							if (trace.internal) {
								LMSG("Set policy flag (%04x) for tos (calcFlags: %04x).", FLAG_CALC_TOSC, calcFlags);
							}
#if !defined HAVE_STLPORT && (defined __GNUC__ && __GNUC__ < 3)
						} else if (event->substr(0, 11).compare("normalized-") == 0) { // normalized*
#else
						} else if (event->compare(0, 11, "normalized-") == 0) { // normalized*
#endif
							calcFlags |= FLAG_CALC_NORM;
							if (trace.internal) {
								LMSG("Set policy flag (%04x) for normalized (calcFlags: %04x).", FLAG_CALC_NORM, calcFlags);
							}
						}
					}
				}
			}
		}

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

	if (trace.debug && interval_list.empty()) {
		LMSG("NOTICE: No policy intervals found in ICMP::getPolicyIntervals.");
	}

	return result;
}

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