/*
** CBLM v3
** $Id: receive.c,v 1.14 2008/02/04 03:27:33 sella Exp $
** Copyright (c) 2004-2007 James M. Sella. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
** 
** Cheap Bastard Latency Monitor v3
*/

static const char rcsid[] = "$Id: receive.c,v 1.14 2008/02/04 03:27:33 sella Exp $";

#include "receive.h"
#include "util.h"

#include <errno.h>
#include <string.h>

/* Global variables */
extern struct args_t args;
extern struct prefs_t prefs;
extern pthread_mutex_t packet_list_mutex;

extern struct packet_list_t **pl_end;
extern long p_count;

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;
	uint64_t usec, usec2;
	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);
		usec = tvtousec(tv);

		/* 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 destination (%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
		tos_len = sizeof(tos);
		if (getsockopt(s, IPPROTO_IP, IP_TOS, (void *) &tos, &tos_len) < 0) {
			syslog(LOG_CRIT, "Failed to get ToS value %u (dscp: %u, prec: %u): %s", tos, tos >> 2, tos >> 5, strerror(errno));
		}
#endif

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

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

			if (args.debug >= DEBUG_DUMP) {
				usec = ntohll(packet->ox);
				usec2 = ntohll(packet->tx);
				syslog(LOG_DEBUG, "OTT: %0.2f ms", (usec - usec2) / 1000.0);
			}

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

			if (args.debug >= DEBUG_DUMP) {
				usec = ntohll(packet->rx);
				usec2 = ntohll(packet->tx);
				syslog(DEBUG_DUMP, "RTT: %0.2f ms", (usec - usec2) / 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) {
	char buf[INET_ADDRSTRLEN];
	int c, ret_val = 1, optval;
	unsigned int tos, pri;
	fd_set fds;
	struct timeval timeout;

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

	if (args.debug >= DEBUG_DUMP) {
		syslog(DEBUG_DUMP, "Reflecting packet to %s with ToS %u:%u (size %lu).", inet_ntop(AF_INET, &(addr->sin_addr), buf, sizeof(buf)), tos, pri, 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(): %s", strerror(errno));
			goto exit;
		}
	}
#endif

	/* Success */
	ret_val = 0;

exit:
	return ret_val;
}

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