/*
** CBLM v3
** $Id: packet.c,v 1.15 2007/12/09 06:22:01 sella Exp $
** Copyright (c) 2004 James M. Sella. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
** 
** Cheap Bastard Latency Monitor v3
*/

#include "packet.h"
#include "cblm.h"
#include "probe.h"
#include "prefs.h"
#include "data.h"
#include "db.h"
#include "util.h"
#include "data_hash.h"

/* Global variables */
extern struct args_t args;
extern struct prefs_t prefs;
extern struct probe_t *probes;
extern int running, restart;
extern pthread_mutex_t probe_mutex, packet_list_mutex, db_mutex, hash_mutex;

extern MYSQL mysql;

struct packet_list_t *packets = NULL;
struct packet_list_t **pl_end = &packets;
long p_count = 0;

int transmit_packet(int s, struct in_addr *dst, unsigned int pending, struct tos_t *tos, unsigned int set) {
	int i, c, ret_val = 1, pattern_len;
	struct sockaddr_in addr;
	char buf[MAX_SIZE];
	struct packet_t* packet = (struct packet_t*) buf;
	size_t packet_len = (prefs.size < sizeof(struct packet_t)) ? sizeof(struct packet_t) : prefs.size;
	struct in_addr in;
	static unsigned int seq = 0, last_set = -1;
	fd_set fds;
	struct timeval timeout;
	unsigned int opt;

	if (args.debug >= DEBUG_DUMP) {
		syslog(LOG_DEBUG, "Transmitting %lu pkts to %s with ToS %lu (dscp: %u, prec: %u) and PRI %lu (size %lu).", pending, inet_ntoa(*dst), tos->tos, tos->tos >> 2, tos->tos >> 5, tos->pri, packet_len);
	}

	/* Reset the seq, if needed. */
	if (set != last_set) {
		seq = 0;
		last_set = set;
	}

	/* Initialize our addr. */
	bzero((char*) &addr, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_addr = *dst;
	addr.sin_port = prefs.port;

#ifdef IP_TOS
	opt = tos->tos & 0x0000000F;
	if (setsockopt(s, IPPROTO_IP, IP_TOS, (void*) &opt, sizeof(opt)) < 0) {
		syslog(LOG_CRIT, "Failed to set ToS value %u (dscp: %u, prec: %u): %s", opt, opt >> 2, opt >> 5, strerror(errno));
	}
#endif

#ifdef SO_PRIORITY
	opt = tos->pri & 0x00000007;
	if (setsockopt(s, SOL_SOCKET, SO_PRIORITY, (void*) &opt, sizeof(opt)) < 0) {
		syslog(LOG_CRIT, "Failed to set PRI value %u: %s", opt, strerror(errno));
	}
#endif

	/* Set generic portions of our packet. */
	memset(packet, 0, packet_len);

	packet->src = args.source;
	packet->dst = *dst;
	memcpy(packet->key, prefs.key, sizeof(packet->key));
	packet->tx_tos = tos->tos;
	packet->tx_pri = tos->pri;
	packet->set = set;
	packet->tot = htons(prefs.rate);

	pattern_len = strlen(prefs.pattern);
	for (i = 0; i < packet_len - sizeof(struct packet_t); i += pattern_len) {
		if (pattern_len + i < packet_len - sizeof(struct packet_t)) {
			memcpy(packet->pattern + i, prefs.pattern, pattern_len);
		} else {
			memcpy(packet->pattern + i, prefs.pattern, packet_len - sizeof(struct packet_t) - i + 1);
		}
	}

	/* Transmit our packets. */
	for (i = 0; i < pending; i++) {
		/* Copy our fdset, select screws with it. */
		FD_ZERO(&fds);
		FD_SET(s, &fds);

		/* Use a 3/4 sec timeout. */
		timeout.tv_sec = 0;
		timeout.tv_usec = 750000;

		/* Wait for a connection. */
		c = select(s + 1, 0, &fds, 0, &timeout);
		if (c == -1) {
			syslog(LOG_CRIT, "select() %s", strerror(errno));
			return;
		} else if (c > 0) {
			/* Set dynamic portions of packet. */
			gettimeofday(&packet->tx, NULL);
			packet->tx = htontv(packet->tx);
			packet->seq = htons(seq++);

			if (sendto(s, (void*) packet, packet_len, 0, (struct sockaddr*) &addr, sizeof(struct sockaddr_in)) == -1) {
				syslog(LOG_CRIT, "sendto() %s", strerror(errno));
				goto exit;
			}
		}
	}

	/* Success */
	ret_val = 0;

exit:
	return ret_val;
}

int receive_packet(int s) {
	int ret_val = 1, packet_len;
	unsigned int tos = 0, pri = 0;
	char buf[MAX_SIZE];
	struct sockaddr_in addr;
	socklen_t len = sizeof(struct sockaddr);
	struct packet_t *packet = (struct packet_t*) buf;
	struct timeval tv, tv2;
	socklen_t tos_len, pri_len;

	while ((packet_len = recvfrom(s, buf, MAX_SIZE, 0, (struct sockaddr*) &addr, &len)) > 0) {
		/* Capture receive time */
		gettimeofday(&tv, NULL);

		/* Discard packets that are invalid. */
		if (packet_len < sizeof(struct packet_t)) {
			/* Discard obviously incorrect packets. */
			if (args.debug >= DEBUG_NOTICE) {
				syslog(LOG_NOTICE, "Recieved packet was too small (%lu < %lu). Discarding.", packet_len, sizeof(struct packet_t));
			}

			return ret_val;
		} else if (strncmp(packet->key, prefs.key, sizeof(prefs.key))) {
			/* Discard packets without correct key (might not even be from CBLM) */
			packet->key[17] = '\0'; /* packet->key is not null terminated. */

			if (args.debug >= DEBUG_NOTICE) {
				syslog(LOG_NOTICE, "Keys do not match (%s != %s). Discarding.", packet->key, prefs.key);
			}

			return ret_val;
		} else if (packet->dst.s_addr == 0 || packet->src.s_addr == 0) {
			if (args.debug >= DEBUG_NOTICE) {
				syslog(LOG_NOTICE, "Source (%lu) or destionation (%lu) are not set. Discarding.\n", packet->src.s_addr, packet->dst.s_addr);
			}

			return ret_val;
		}

		if (args.debug >= DEBUG_DUMP) {
			syslog(LOG_DEBUG, "Received packet (len: %lu)", packet_len);
			display_packet(packet, packet_len);
		}

#ifdef IP_TOS
		if (getsockopt(s, IPPROTO_IP, IP_TOS, (void *) &tos, &tos_len) < 0) {
			syslog(LOG_CRIT, "Failed to get ToS value %lu (dscp: %lu, prec: %lu): %s", tos, tos >> 2, tos >> 5, strerror(errno));
		}
#endif

#ifdef SO_PRIORITY
		if (getsockopt(s, SOL_SOCKET, SO_PRIORITY, (void *) &pri, &pri_len) < 0) {
			syslog(LOG_CRIT, "Failed to get ToS value %lu (dscp: %lu, prec: %lu): %s", tos, tos >> 2, tos >> 5, strerror(errno));
		}
#endif

		if (packet->ox.tv_sec == 0) {
			/* This is a packet sourced from a remote probe. */
			packet->ox = htontv(tv);
			packet->ox_tos = tos;
			packet->ox_pri = pri;

			if (args.debug >= DEBUG_DUMP) {
				tv = ntohtv(packet->ox);
				tv2 = ntohtv(packet->tx);
				subtract_timeval(&tv, &tv2);
				syslog(LOG_DEBUG, "OTT: %0.2f ms", tvtousec(tv) / 1000.0);
			}

			reflect_packet(s, packet, packet_len, (struct sockaddr_in*) &addr);
		} else {
			/* This is a packet we originated returning to us. */
			packet->rx = htontv(tv);
			packet->rx_tos = tos;
			packet->rx_pri = pri;

			if (args.debug >= DEBUG_DUMP) {
				tv = ntohtv(packet->rx);
				tv2 = ntohtv(packet->tx);
				subtract_timeval(&tv, &tv2);
				syslog(DEBUG_DUMP, "RTT: %0.2f ms", tvtousec(tv) / 1000.0);
			}

			/* Add this packet to the packet_list. */
			pthread_mutex_lock(&packet_list_mutex);

			*pl_end = malloc(sizeof(struct packet_list_t));
			memcpy(&(*pl_end)->packet, packet, sizeof(struct packet_t));

			(*pl_end)->next = NULL;
			pl_end = &(*pl_end)->next;
			p_count++;

			pthread_mutex_unlock(&packet_list_mutex);
		}
	}

	if (packet_len == -1) {
		if (errno != EAGAIN && errno == EINTR) {
			syslog(LOG_CRIT, "recvfrom() %d: %s", errno, strerror(errno));
		}

		/* Failure */
		ret_val = 0;
	}

	return ret_val;
}

int reflect_packet(int s, struct packet_t *packet, size_t packet_len, struct sockaddr_in *addr) {
	int c, ret_val = 1, optval;
	unsigned int tos, pri;
	fd_set fds;
	struct timeval timeout;

	tos = packet->tx_tos & 0x0000000F;
	pri = packet->tx_pri & 0x00000007;

	if (args.debug >= DEBUG_DUMP) {
		syslog(DEBUG_DUMP, "Reflecting packet to %s with ToS %lu (size %lu).", inet_ntoa((struct in_addr) addr->sin_addr), tos, packet_len);
	}

	/* Fixup the port. */
	addr->sin_port = prefs.port;

#ifdef IP_TOS
	if (setsockopt(s, IPPROTO_IP, IP_TOS, (void*) &tos, sizeof(tos)) < 0) {
		syslog(LOG_CRIT, "Failed to set ToS value %u (dscp: %u, prec: %u): %s", tos, tos >> 2, tos >> 5, strerror(errno));
	}
#endif

#ifdef SO_PRIORITY
	if (setsockopt(s, SOL_SOCKET, SO_PRIORITY, (void*) &pri, sizeof(pri)) < 0) {
		syslog(LOG_CRIT, "Failed to set PRI value %u: %s", pri, strerror(errno));
	}
#endif

	FD_ZERO(&fds);
	FD_SET(s, &fds);

	/* Use a 3/4 sec timeout. */
	timeout.tv_sec = 0;
	timeout.tv_usec = 750000;

#if 0
	if (sendto(s, (void*) packet, packet_len, 0, (struct sockaddr*) addr, sizeof(struct sockaddr_in)) == -1) {
		syslog(LOG_CRIT, "sendto() error: %s", strerror(errno));
		goto exit;
	}
#else
	/* Wait for socket write. */
	c = select(s + 1, 0, &fds, 0, &timeout);
	if (c == -1) {
		syslog(LOG_CRIT, "select() %s", strerror(errno));
		return;
	} else if (c > 0) {
		if (sendto(s, (void*) packet, packet_len, 0, (struct sockaddr*) addr, sizeof(struct sockaddr_in)) == -1) {
			syslog(LOG_CRIT, "sendto() error: %s", strerror(errno));
			goto exit;
		}
	}
#endif

	/* Success */
	ret_val = 0;

exit:
	return ret_val;
}

int bind_port(fd_set *fdset) {
	int s = 0, port, optval, len, flags;
	struct protoent *protocol;
	struct sockaddr_in socketaddr;

	protocol = (struct protoent*) getprotobyname("udp");
	if (!protocol) {
		syslog(LOG_CRIT, "Failed to get protocol. Verify that /etc/services is readable from current root directory.");

		return -1;
	}

	if (args.debug >= DEBUG_INTERNAL) {
		syslog(LOG_INFO, "Binding %s:%d", inet_ntoa(args.interface), ntohs(prefs.port));
	}

	/* Zero and setup our sockaddr_in structure. */
	bzero((char*) &socketaddr, sizeof(socketaddr));
	socketaddr.sin_family = AF_INET;
	socketaddr.sin_addr = args.interface;
	socketaddr.sin_port = prefs.port;

	if ((s = socket(AF_INET, SOCK_DGRAM, protocol->p_proto)) < 0) {
		syslog(LOG_CRIT, "Failed to open socket: %s", strerror(errno));
		return -1;
	}

#ifdef SO_RCVBUF
	/* Increase the receive buffer. */
	optval = RCVBUF_SIZE;
	if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, (void*) &optval, sizeof(optval)) < 0) {
		syslog(LOG_CRIT, "Failed to setsockopt SO_RCVBUF to %d: %s", optval, strerror(errno));
	}
#endif

#ifdef SO_SNDBUF
	/* Increase the send buffer. */
	optval = SNDBUF_SIZE;
	if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, (void*) &optval, sizeof(optval)) < 0) {
		syslog(LOG_CRIT, "Failed to setsockopt SO_SNDBUF to %d: %s", optval, strerror(errno));
	}
#endif

#ifdef IP_TTL
	optval = prefs.ttl;
	if (setsockopt(s, IPPROTO_IP, IP_TTL, (void*) &optval, sizeof(optval)) < 0) {
		syslog(LOG_CRIT, "Failed to setsockopt IP_TTL to %d: %s", optval, strerror(errno));
	}
#endif

#ifdef IP_DONTFRAGMENT
	if (prefs.fragment == 1) {
		optval = 0;
		if (setsockopt(s, IPPROTO_IP, IP_DONTFRAGMENT, (void*) &optval, sizeof(optval)) < 0) {
			syslog(LOG_CRIT, "Failed to setsockopt IP_DONTFRAGMENT to %d: %s", optval, strerror(errno));
		}
	}
#endif

#ifdef SO_NO_CHECK /* Linux only feature */
	optval = 1; /* 1 == disable checking of checksum */
	if (setsockopt(s, SOL_SOCKET, SO_NO_CHECK, (void*) &optval, sizeof(optval)) < 0) {
		syslog(LOG_CRIT, "Failed to setsockopt SO_NO_CHECK to %d: %s", optval, strerror(errno));
	}
#endif

	/* Set non-blocking */
	flags = fcntl(s, F_GETFL);
	if (fcntl(s, F_SETFL, flags | O_NONBLOCK) == -1) {
		syslog(LOG_CRIT, "Failed to fcntl O_NONBLOCK: %s", strerror(errno));
	}

	if (bind(s, (struct sockaddr*) &socketaddr, sizeof(socketaddr)) < 0) {
		syslog(LOG_CRIT, "Failed to bind to %s:%d: %s", inet_ntoa(args.interface), ntohs(prefs.port), strerror(errno));
		return -1;
	}

	/* Add new socket to our FDSET */
	FD_ZERO(fdset);
	FD_SET(s, fdset);

	if (args.debug >= DEBUG_INTERNAL) {
		syslog(LOG_INFO, "Bound socket '%d'.", s);
	}

	return s;
}

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