/******************************************************************************/
/*                                                                            */
/*   Copyright (c) International Business Machines  Corp., 2005, 2006         */
/*                                                                            */
/*   This program is free software;  you can redistribute it and/or modify    */
/*   it under the terms of the GNU General Public License as published by     */
/*   the Free Software Foundation; either version 2 of the License, or        */
/*   (at your option) any later version.                                      */
/*                                                                            */
/*   This program is distributed in the hope that it will be useful,          */
/*   but WITHOUT ANY WARRANTY;  without even the implied warranty of          */
/*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See                */
/*   the GNU General Public License for more details.                         */
/*                                                                            */
/*   You should have received a copy of the GNU General Public License        */
/*   along with this program;  if not, write to the Free Software             */
/*   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA  */
/*                                                                            */
/******************************************************************************/

/*
 * File:
 *	ns-common.c
 *
 * Description:
 *	Common functions and variables in the ns-tools
 *
 * Author:
 *	Mitsuru Chinen <mitch@jp.ibm.com>
 *
 * History:
 *	Oct 19 2005 - Created (Mitsuru Chinen)
 *	May  1 2006 - Added functions for broken_ip, route, multicast tests
 *---------------------------------------------------------------------------*/

/*
 * Fixed values
 */
#define PROC_RMEM_MAX	"/proc/sys/net/core/rmem_max"
#define PROC_WMEM_MAX	"/proc/sys/net/core/wmem_max"

/*
 * Standard Header Files
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_arp.h>

#include "ns-mcast.h"
#define NS_COMMON 1
#include "ns-traffic.h"

/*
 * Function: fatal_error()
 *
 * Description:
 *  Output an error message then exit the program with EXIT_FAILURE
 *
 * Argument:
 *  errmsg: message printed by perror()
 *
 * Return value:
 *  This function does not return.
 */
void fatal_error(char *errmsg)
{
	perror(errmsg);
	exit(EXIT_FAILURE);
}

/*
 * Function: maximize_sockbuf()
 *
 * Descripton:
 *  This function maximize the send and receive buffer size of a socket
 *
 * Argument:
 *  sd:	target socket descriptor
 *
 * Return value:
 *  None
 */
void maximize_sockbuf(int sd)
{
	size_t idx;
	int level[] = { SO_RCVBUF, SO_SNDBUF };
	char *procfile[] = { PROC_RMEM_MAX, PROC_WMEM_MAX };
	char *bufname[] = { "rcvbuf", "sndbuf" };

	for (idx = 0; idx < (sizeof(level) / sizeof(int)); idx++) {
		FILE *fp;	/* File pointer to a proc file */
		int bufsiz;	/* buffer size of socket */
		unsigned int optlen;	/* size of sd option parameter */

		if ((fp = fopen(procfile[idx], "r")) == NULL) {
			fprintf(stderr, "Failed to open %s\n", procfile[idx]);
			fatal_error("fopen()");
		}
		if ((fscanf(fp, "%d", &bufsiz)) != 1) {
			fprintf(stderr, "Failed to read from %s\n",
				procfile[idx]);
			fatal_error("fscanf()");
		}
		if (setsockopt
		    (sd, SOL_SOCKET, level[idx], &bufsiz, sizeof(int))) {
			fatal_error("setsockopt()");
		}
		if (fclose(fp)) {
			fprintf(stderr, "Failed to close to %s\n",
				procfile[idx]);
			fatal_error("fopen()");
		}

		if (debug) {
			optlen = sizeof(bufsiz);
			if (getsockopt
			    (sd, SOL_SOCKET, level[idx], &bufsiz,
			     &optlen) < 0) {
				fatal_error("getsockopt()");
			}
			fprintf(stderr, "socket %s size is %d\n", bufname[idx],
				bufsiz);
		}
	}
}

/*
 * Function: calc_checksum()
 *
 * Description:
 *  This function calculate the checksum of IPv4 or ICMP
 *
 * Argument:
 *  data: pointer to target data for checksum
 *  size: target data size
 *
 * Return value:
 *  None
 */
u_int16_t calc_checksum(u_int16_t * data, size_t size)
{
	u_int32_t sum;
	u_int16_t *pos;
	size_t rest;

	sum = 0;
	pos = data;
	for (rest = size; rest > 1; rest -= 2)
		sum += *(pos++);

	if (rest > 0)
		sum += (*pos) & 0xff00;

	sum = (sum & 0xffff) + (sum >> 16);
	sum = (sum & 0xffff) + (sum >> 16);
	sum = ~sum;

	return sum;
}

/*
 * Function: fill_payload()
 *
 * Description:
 *  This function fills the payload
 *
 * Argument:
 *  payload_p: pointer to data of payload
 *    size:    payload size
 *
 * Return value:
 *  None
 */
void fill_payload(unsigned char *payload_p, size_t size)
{
	size_t idx;

	for (idx = 0; idx < size; idx++)
		*(payload_p + idx) = idx % 0x100;
}

/*
 * Function: rand_within()
 *
 * Description:
 *  This function returns a presudo-random integer within specified range
 *
 * Argument:
 *  first: Fisrt value of the range. If negative, assumed 0
 *  last : Last value of the range. If bigger than RAND_MAX, assumed RAND_MAX
 *
 * Return value:
 *  integer value between first to last
 */
int rand_within(int first, int last)
{
	unsigned int num;
	int rand_val;

	first = first < 0 ? 0 : first;
	last = RAND_MAX < (unsigned int)last ? RAND_MAX : last;

	num = last - first + 1U;
	rand_val = rand() / ((RAND_MAX + 1U) / num) + first;

	return rand_val;
}

/*
 * Function: bit_change_seed
 *
 * Description:
 *  This function creates a seed to change 1 bit at random position
 *
 * Argument:
 *  bitsize : bit size of data whose bit would be changed
 *  oversize: This value controls whether a bit is changed or not
 *
 * Return value:
 *  seed of the bit for change.
 */
u_int32_t bit_change_seed(size_t bitsize, size_t oversize)
{
	int rand_val;
	u_int32_t seed;
	rand_val = rand() / ((RAND_MAX + 1U) / (bitsize + oversize));

	seed = (rand_val < bitsize) ? (0x00000001 << rand_val) : 0;

	if (debug)
		fprintf(stderr, "Bit seed is %08x\n", seed);

	return seed;
}

/*
 * Function: eth_pton()
 *
 * Description:
 *  This function convert a string to struct sockaddr_ll (Ethernet)
 *  Note) The ifindex is set to `any'.
 *
 * Argument:
 *   af : AF_INET or AF_INET6
 *   str: Pointer to a string which represents MAC address
 *   ll : pointer to struct sockaddr_ll
 *
 * Return value:
 *    0  : Success
 *    1  : Fail
 */
int eth_pton(int af, const char *str, struct sockaddr_ll *ll)
{
	size_t idx;
	unsigned char *addr_p;
	unsigned int val[ETH_ALEN];

	ll->sll_family = AF_PACKET;	/* Always AF_PACKET */
	if (af == AF_INET)
		ll->sll_protocol = htons(ETH_P_IP);	/* IPv4 */
	else
		ll->sll_protocol = htons(ETH_P_IPV6);	/* IPv6 */
	ll->sll_ifindex = 0;	/* any interface */
	ll->sll_hatype = ARPHRD_ETHER;	/* Header type */
	ll->sll_pkttype = PACKET_OTHERHOST;	/* Packet type */
	ll->sll_halen = ETH_ALEN;	/* Length of address */

	/* Physical layer address */
	if (sscanf(str, "%2x:%2x:%2x:%2x:%2x:%2x", &val[0], &val[1],
		   &val[2], &val[3], &val[4], &val[5]) != ETH_ALEN) {
		fprintf(stderr, "%s is not a valid MAC address", str);
		return 1;
	}

	addr_p = (unsigned char *)ll->sll_addr;
	for (idx = 0; idx < ETH_ALEN; idx++)
		addr_p[idx] = val[idx];

	return 0;
}

/*
 * Function: get_ifinfo()
 *
 * Description:
 *  This function gets the interface information with ioctl()
 *
 * Argument:
 *    ans   : ifreq structure to store the information
 *  sock_fd : socket file descriptor
 *  ifname  : interface name
 *   query  : ioctl request value
 *
 * Return value:
 *  None
 *
 */
void get_ifinfo(struct ifreq *ans, int sock_fd, const char *ifname, int query)
{
	memset(ans, '\0', sizeof(struct ifreq));
	strncpy(ans->ifr_name, ifname, (IFNAMSIZ - 1));

	if (ioctl(sock_fd, query, ans) < 0)
		fatal_error("ioctl()");
}

/*
 * Function: strtotimespec()
 *
 * Description:
 *  This function converts a string to timespec structure
 *
 * Argument:
 *    str   : nano second value in character representation
 *    ts_p  : pointer to a timespec structure
 *
 * Return value:
 *  0: Success
 *  1: Fail
 */
int strtotimespec(const char *str, struct timespec *ts_p)
{
	size_t len;
	char *sec_str;
	unsigned long sec = 0;
	unsigned long nsec = 0;

	len = strlen(str);
	if (len > 9) {		/* Check the specified value is bigger than 999999999 */
		sec_str = calloc((len - 9 + 1), sizeof(char));
		strncpy(sec_str, str, len - 9);
		sec = strtoul(sec_str, NULL, 0);
		if (sec > 0x7fffffff)
			return 1;
		free(sec_str);
		nsec = strtoul(str + len - 9, NULL, 0);
	} else {
		nsec = strtoul(str, NULL, 0);
	}

	ts_p->tv_sec = sec;
	ts_p->tv_nsec = nsec;

	return 0;
}

/*
 * Function: get_a_lla_byifindex()
 *
 * Description:
 *  This function gets one of the link-local addresses which is specified
 *  by interface index
 *
 * Argument:
 *   lla_p  : pointer to a sockaddr_in6 sturcture which stores the lla
 *  ifindex : index of the interface
 *
 * Return value:
 *  0: Success
 *  1: Fail
 */
int get_a_lla_byifindex(struct sockaddr_in6 *lla_p, int ifindex)
{
	FILE *fp;
	int ret;
	unsigned int oct[16];
	int ifidx, prefixlen, scope;
	char line[PROC_IFINET6_FILE_LINELENGTH];
	int pos;

	if ((fp = fopen(PROC_IFINET6_FILE, "r")) == NULL) {
		fprintf(stderr, "Faile to open %s\n", PROC_IFINET6_FILE);
		return 1;
	}

	while (fgets(line, PROC_IFINET6_FILE_LINELENGTH, fp) != NULL) {
		ret = sscanf(line,
			     "%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x %x %x %x",
			     &oct[0], &oct[1], &oct[2], &oct[3],
			     &oct[4], &oct[5], &oct[6], &oct[7],
			     &oct[8], &oct[9], &oct[10], &oct[11],
			     &oct[12], &oct[13], &oct[14], &oct[15],
			     &ifidx, &prefixlen, &scope);

		if (ret == EOF)
			fatal_error("scanf()");
		else if (ret != 19)
			fatal_error
			    ("The number of input item is less than the expected");

		if (ifidx != ifindex)
			continue;

		if (prefixlen != 64)
			continue;

		if (scope != PROC_IFINET6_LINKLOCAL)
			continue;

		/* Find a link-local address */
		lla_p->sin6_family = AF_INET6;
		lla_p->sin6_port = 0;
		lla_p->sin6_flowinfo = 0;
		lla_p->sin6_scope_id = ifindex;

		for (pos = 0; pos < 16; pos++)
			lla_p->sin6_addr.s6_addr[pos] = oct[pos];

		return 0;
	}

	fprintf(stderr, "No link-local address is found.\n");
	return 1;
}

/*
 * Function: get_maddrinfo()
 *
 * Description:
 *  This function translates multicast address informantion into the addrinfo
 *  structure
 *
 * Argument:
 *   family:    protocol family
 *   maddr:     multicast address in character string
 *   portnum:   port number in character string
 *
 * Return value:
 *  pointer to the addrinfo which stores the multicast address information
 */
struct addrinfo *get_maddrinfo(sa_family_t family, const char *maddr,
			       const char *portnum)
{
	struct addrinfo hints;	/* hints for getaddrinfo() */
	struct addrinfo *res;	/* pointer to addrinfo structure */
	int err;		/* return value of getaddrinfo */

	memset(&hints, '\0', sizeof(struct addrinfo));
	hints.ai_family = family;
	hints.ai_socktype = SOCK_DGRAM;
	hints.ai_protocol = IPPROTO_UDP;
	hints.ai_flags |= AI_NUMERICHOST;

	err = getaddrinfo(maddr, portnum, &hints, &res);
	if (err) {
		fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(err));
		exit(EXIT_FAILURE);
	}
	if (res->ai_next) {
		fprintf(stderr, "getaddrinfo(): multiple address is found.");
		exit(EXIT_FAILURE);
	}

	return res;
}

/*
 * Function: create_group_info()
 *
 * Description:
 *  This function create a group information to join the group
 *  This function calls malloc to store the information
 *
 * Argument:
 *   ifindex:   interface index
 *   mainfo_p:  pointer to addrinfo structure for multicast address
 *
 * Return value:
 *  pointer to allocated group_filter structure
 */
struct group_req *create_group_info(uint32_t ifindex, struct addrinfo *mainfo_p)
{
	struct group_req *greq;

	/* allocate memory for group_filter */
	greq = (struct group_req *)calloc(1, sizeof(struct group_req));
	if (greq == NULL)
		fatal_error("calloc()");

	/* substitute informations */
	greq->gr_interface = ifindex;
	memcpy(&greq->gr_group, mainfo_p->ai_addr, mainfo_p->ai_addrlen);

	return greq;
}

/*
 * Function: create_source_filter()
 *
 * Description:
 *  This function create a source filter.
 *  This function calls malloc to store the source filter.
 *
 * Argument:
 *   ifindex:   interface index
 *   mainfo_p:  pointer to addrinfo structure for multicast address
 *   fmode:     filter mode
 *   saddrs:    comma separated array of the source addresses
 *
 * Return value:
 *  pointer to allocated group_filter structure
 */
struct group_filter *create_source_filter(uint32_t ifindex,
					  struct addrinfo *mainfo_p,
					  uint32_t fmode, char *saddrs)
{
	struct group_filter *gsf;	/* pointer to group_filter structure */
	uint32_t numsrc;	/* number of source address */
	struct addrinfo hints;	/* hints for getaddrinfo() */
	struct addrinfo *res;	/* pointer to addrinfo structure */
	int err;		/* return value of getaddrinfo */
	uint32_t idx;
	char *sp, *ep;

	/* calculate the number of source address */
	numsrc = 1;
	for (sp = saddrs; *sp != '\0'; sp++)
		if (*sp == ',')
			numsrc++;

	if (debug)
		fprintf(stderr, "number of source address is %u\n", numsrc);

	/* allocate memory for group_filter */
	gsf = (struct group_filter *)calloc(1, GROUP_FILTER_SIZE(numsrc));
	if (gsf == NULL)
		fatal_error("calloc()");

	/* substitute interface index, multicast address, filter mode */
	gsf->gf_interface = ifindex;
	memcpy(&gsf->gf_group, mainfo_p->ai_addr, mainfo_p->ai_addrlen);
	gsf->gf_fmode = fmode;
	gsf->gf_numsrc = numsrc;

	/* extract source address aray and substitute the addersses */
	memset(&hints, '\0', sizeof(struct addrinfo));
	hints.ai_family = mainfo_p->ai_family;
	hints.ai_socktype = SOCK_DGRAM;
	hints.ai_protocol = IPPROTO_UDP;
	hints.ai_flags |= AI_NUMERICHOST;

	/* extract source address aray and substitute the addersses */
	memset(&hints, '\0', sizeof(struct addrinfo));
	hints.ai_family = mainfo_p->ai_family;
	hints.ai_socktype = SOCK_DGRAM;
	hints.ai_protocol = IPPROTO_UDP;
	hints.ai_flags |= AI_NUMERICHOST;

	sp = saddrs;
	for (idx = 0; idx < numsrc; idx++) {
		ep = strchr(sp, ',');
		if (ep != NULL)
			*ep = '\0';
		if (debug)
			fprintf(stderr, "source address[%u]: %s\n", idx, sp);

		err = getaddrinfo(sp, NULL, &hints, &res);
		if (err) {
			fprintf(stderr, "getaddrinfo(): %s\n",
				gai_strerror(err));
			exit(EXIT_FAILURE);
		}

		memcpy(&gsf->gf_slist[idx], res->ai_addr, res->ai_addrlen);
		freeaddrinfo(res);
		sp = ep + 1;
	}

	return gsf;
}