/* 
 * send_arp
 * 
 * This program sends out one ARP packet with source/target IP and Ethernet
 * hardware addresses suuplied by the user.  It uses the libnet libary from
 * Packet Factory (http://www.packetfactory.net/libnet/ ). It has been tested
 * on Linux, FreeBSD, and on Solaris.
 * 
 * This inspired by the sample application supplied by Packet Factory.

 * Matt Soffen

 * Copyright (C) 2001 Matt Soffen <matt@soffen.com>
 *
 * 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 St, Fifth Floor, Boston, MA  02110-1301  USA
 */

/* Needs to be defined before any other includes, otherwise some system
 * headers do not behave as expected! Major black magic... */
#undef _GNU_SOURCE  /* in case it was defined on the command line */
#define _GNU_SOURCE

#include <config.h>
#include <sys/param.h>

#define USE_GNU
#if defined(ANSI_ONLY) && !defined(inline)
#	define inline	/* nothing */
#endif

#include <limits.h>
#include <libnet.h>
#include <libgen.h>
#include <clplumbing/timers.h>
#include <clplumbing/cl_signal.h>
#include <clplumbing/cl_log.h>

#ifdef HAVE_LIBNET_1_0_API
#	define	LTYPE	struct libnet_link_int
	static u_char *mk_packet(u_int32_t ip, u_char *device, u_char *macaddr, u_char *broadcast, u_char *netmask, u_short arptype);
	static int send_arp(struct libnet_link_int *l, u_char *device, u_char *buf);
#endif
#ifdef HAVE_LIBNET_1_1_API
#	define	LTYPE	libnet_t
	static libnet_t *mk_packet(libnet_t* lntag, u_int32_t ip, u_char *device, u_char macaddr[6], u_char *broadcast, u_char *netmask, u_short arptype);
	int send_arp(libnet_t* lntag);
#endif

#define PIDDIR       HA_VARRUNDIR "/" PACKAGE
#define PIDFILE_BASE PIDDIR "/send_arp-"

static char print_usage[]={
"send_arp: sends out custom ARP packet.\n"
"  usage: send_arp [-i repeatinterval-ms] [-r repeatcount] [-p pidfile] \\\n"
"              device src_ip_addr src_hw_addr broadcast_ip_addr netmask\n"
"\n"
"  where:\n"
"    repeatinterval-ms: timing, in milliseconds of sending arp packets\n"
"      For each ARP announcement requested, a pair of ARP packets is sent,\n"
"      an ARP request, and an ARP reply. This is because some systems\n"
"      ignore one or the other, and this combination gives the greatest\n"
"      chance of success.\n"
"\n"
"      Each time an ARP is sent, if another ARP will be sent then\n"
"      the code sleeps for half of repeatinterval-ms.\n"
"\n"
"    repeatcount: how many pairs of ARP packets to send.\n"
"                 See above for why pairs are sent\n"
"\n"
"    pidfile: pid file to use\n"
"\n"
"    device: network interface to use\n"
"\n"
"    src_ip_addr: source ip address\n"
"\n"
"    src_hw_addr: source hardware address.\n"
"                 If \"auto\" then the address of device\n"
"\n"
"    broadcast_ip_addr: ignored\n"
"\n"
"    netmask: ignored\n"
};

static const char * SENDARPNAME = "send_arp";

static void convert_macaddr (u_char *macaddr, u_char enet_src[6]);
static int get_hw_addr(char *device, u_char mac[6]);
int write_pid_file(const char *pidfilename);
int create_pid_directory(const char *piddirectory);

#define AUTO_MAC_ADDR "auto"


#ifndef LIBNET_ERRBUF_SIZE
#	define LIBNET_ERRBUF_SIZE 256
#endif


/*
 * For use logd, should keep identical with the same const variables defined
 * in heartbeat.h.
 */
#define ENV_PREFIX "HA_"
#define KEY_LOGDAEMON   "use_logd"

static void
byebye(int nsig)
{
	(void)nsig;
	/* Avoid an "error exit" log message if we're killed */
	exit(0);
}


int
main(int argc, char *argv[])
{
	int	c = -1;
	char	errbuf[LIBNET_ERRBUF_SIZE];
	char*	device;
	char*	ipaddr;
	char*	macaddr;
	char*	broadcast;
	char*	netmask;
	u_int32_t	ip;
	u_char	src_mac[6];
	int	repeatcount = 1;
	int	j;
	long	msinterval = 1000;
	int	flag;
	char	*pidfilename = NULL;
	int	pidflen;
	struct sigaction act;

#ifdef HAVE_LIBNET_1_0_API
	LTYPE*	l;
	u_char *request, *reply;
#elif defined(HAVE_LIBNET_1_1_API)
	LTYPE *request, *reply;
#endif

	memset(&act, 0, sizeof(struct sigaction));
	act.sa_flags &= ~SA_RESTART; /* redundant - to stress syscalls should fail */
	act.sa_handler = byebye;
	if ((sigemptyset(&act.sa_mask) < 0) || (sigaction(SIGTERM, &act, NULL) < 0)) {
		cl_log(LOG_ERR, "Could not set handler for signal: %s", strerror(errno));
		return 1;
	}

        cl_log_set_entity(SENDARPNAME);
        cl_log_enable_stderr(TRUE);
        cl_log_set_facility(LOG_USER);
	cl_inherit_logging_environment(0);

	while ((flag = getopt(argc, argv, "i:r:p:")) != EOF) {
		switch(flag) {

		case 'i':	msinterval= atol(optarg);
				break;

		case 'r':	repeatcount= atoi(optarg);
				break;

		case 'p':	pidfilename= optarg;
				break;

		default:	fprintf(stderr, "%s\n\n", print_usage);
				return 1;
				break;
		}
	}
	if (argc-optind != 5) {
		fprintf(stderr, "%s\n\n", print_usage);
		return 1;
	}

	/*
	 *	argv[optind+1] DEVICE		dc0,eth0:0,hme0:0,
	 *	argv[optind+2] IP		192.168.195.186
	 *	argv[optind+3] MAC ADDR		00a0cc34a878
	 *	argv[optind+4] BROADCAST	192.168.195.186
	 *	argv[optind+5] NETMASK		ffffffffffff
	 */

	device    = argv[optind];
	ipaddr    = argv[optind+1];
	macaddr   = argv[optind+2];
	broadcast = argv[optind+3];
	netmask   = argv[optind+4];

	if (!pidfilename) {
		pidflen = strlen(PIDFILE_BASE) + strlen(ipaddr);
		pidfilename = calloc(1, pidflen);
		snprintf(pidfilename, pidflen, "%s%s",
				PIDFILE_BASE, ipaddr);
	}

	if(write_pid_file(pidfilename) < 0) {
		return EXIT_FAILURE;
	}

	if (!strcasecmp(macaddr, AUTO_MAC_ADDR)) {
		if (get_hw_addr(device, src_mac) < 0) {
			 cl_log(LOG_ERR, "Cannot find mac address for %s",
					 device);
			 unlink(pidfilename);
			 return EXIT_FAILURE;
		}
	}
	else {
		convert_macaddr((unsigned char *)macaddr, src_mac);
	}

/*
 * We need to send both a broadcast ARP request as well as the ARP response we
 * were already sending.  All the interesting research work for this fix was
 * done by Masaki Hasegawa <masaki-h@pp.iij4u.or.jp> and his colleagues.
 */

#if defined(HAVE_LIBNET_1_0_API)
#ifdef ON_DARWIN
	if ((ip = libnet_name_resolve((unsigned char*)ipaddr, 1)) == -1UL) {
#else
	if ((ip = libnet_name_resolve(ipaddr, 1)) == -1UL) {
#endif
		cl_log(LOG_ERR, "Cannot resolve IP address [%s]", ipaddr);
		unlink(pidfilename);
		return EXIT_FAILURE;
	}

	l = libnet_open_link_interface(device, errbuf);
	if (!l) {
		cl_log(LOG_ERR, "libnet_open_link_interface on %s: %s"
		,	device, errbuf);
		unlink(pidfilename);
		return EXIT_FAILURE;
	}
	request = mk_packet(ip, (unsigned char*)device, src_mac
		, (unsigned char*)broadcast, (unsigned char*)netmask
		, ARPOP_REQUEST);
	reply = mk_packet(ip, (unsigned char*)device, src_mac
		, (unsigned char *)broadcast
		, (unsigned char *)netmask, ARPOP_REPLY);
	if (!request || !reply) {
		cl_log(LOG_ERR, "could not create packets");
		unlink(pidfilename);
		return EXIT_FAILURE;
	}
	for (j=0; j < repeatcount; ++j) {
		c = send_arp(l, (unsigned char*)device, request);
		if (c < 0) {
			break;
		}
		mssleep(msinterval / 2);
		c = send_arp(l, (unsigned char*)device, reply);
		if (c < 0) {
			break;
		}
		if (j != repeatcount-1) {
			mssleep(msinterval / 2);
		}
	}
#elif defined(HAVE_LIBNET_1_1_API)
	if ((request=libnet_init(LIBNET_LINK, device, errbuf)) == NULL) {
		cl_log(LOG_ERR, "libnet_init failure on %s: %s", device, errbuf);
		unlink(pidfilename);
		return EXIT_FAILURE;
	}
	if ((reply=libnet_init(LIBNET_LINK, device, errbuf)) == NULL) {
		cl_log(LOG_ERR, "libnet_init failure on %s: %s", device, errbuf);
		unlink(pidfilename);
		return EXIT_FAILURE;
	}
	if ((signed)(ip = libnet_name2addr4(request, ipaddr, 1)) == -1) {
		cl_log(LOG_ERR, "Cannot resolve IP address [%s]", ipaddr);
		unlink(pidfilename);
		return EXIT_FAILURE;
	}
	request = mk_packet(request, ip, (unsigned char*)device, src_mac
		, (unsigned char*)broadcast, (unsigned char*)netmask
		, ARPOP_REQUEST);
	reply = mk_packet(reply, ip, (unsigned char*)device, src_mac
		, (unsigned char *)broadcast
		, (unsigned char *)netmask, ARPOP_REPLY);
	if (!request || !reply) {
		cl_log(LOG_ERR, "could not create packets");
		unlink(pidfilename);
		return EXIT_FAILURE;
	}
	for (j=0; j < repeatcount; ++j) {
		c = send_arp(request);
		if (c < 0) {
			break;
		}
		mssleep(msinterval / 2);
		c = send_arp(reply);
		if (c < 0) {
			break;
		}
		if (j != repeatcount-1) {
			mssleep(msinterval / 2);
		}
	}
#else
#	error "Must have LIBNET API version defined."
#endif

	unlink(pidfilename);
	return c < 0  ? EXIT_FAILURE : EXIT_SUCCESS;
}


void
convert_macaddr (u_char *macaddr, u_char enet_src[6])
{
	int i, pos;
	u_char bits[3];

	pos = 0;
	for (i = 0; i < 6; i++) {
		/* Inserted to allow old-style MAC addresses */
		if (*macaddr == ':') {
			pos++;
		}
		bits[0] = macaddr[pos++];
		bits[1] = macaddr[pos++];
		bits[2] = '\0';

		enet_src[i] = strtol((const char *)bits, (char **)NULL, 16);
	}

}

#ifdef HAVE_LIBNET_1_0_API
int
get_hw_addr(char *device, u_char mac[6])
{
	struct ether_addr	*mac_address;
	struct libnet_link_int  *network;
	char                    err_buf[LIBNET_ERRBUF_SIZE];

	network = libnet_open_link_interface(device, err_buf);
	if (!network) {
		fprintf(stderr, "libnet_open_link_interface: %s\n", err_buf);
		return -1;
	}

	mac_address = libnet_get_hwaddr(network, device, err_buf);
	if (!mac_address) {
		fprintf(stderr, "libnet_get_hwaddr: %s\n", err_buf);
		return -1;
	}

	memcpy(mac, mac_address->ether_addr_octet, 6);

	return 0;
}
#endif

#ifdef HAVE_LIBNET_1_1_API
int
get_hw_addr(char *device, u_char mac[6])
{
	struct libnet_ether_addr	*mac_address;
	libnet_t		*ln;
	char			err_buf[LIBNET_ERRBUF_SIZE];

	ln = libnet_init(LIBNET_LINK, device, err_buf);
	if (!ln) {
		fprintf(stderr, "libnet_open_link_interface: %s\n", err_buf);
		return -1;
	}

	mac_address = libnet_get_hwaddr(ln);
	if (!mac_address) {
		fprintf(stderr,  "libnet_get_hwaddr: %s\n", err_buf);
		return -1;
	}

	memcpy(mac, mac_address->ether_addr_octet, 6);

	return 0;
}
#endif


/*
 * Notes on send_arp() behaviour. Horms, 15th June 2004
 *
 * 1. Target Hardware Address
 *    (In the ARP portion of the packet)
 *
 *    a) ARP Reply
 *
 *       Set to the MAC address we want associated with the VIP,
 *       as per RFC2002 (4.6).
 *
 *       Previously set to ff:ff:ff:ff:ff:ff
 *
 *    b) ARP Request
 *
 *       Set to 00:00:00:00:00:00. According to RFC2002 (4.6)
 *       this value is not used in an ARP request, so the value should
 *       not matter. However, I observed that typically (always?) this value
 *       is set to 00:00:00:00:00:00. It seems harmless enough to follow
 *       this trend.
 *
 *       Previously set to ff:ff:ff:ff:ff:ff
 *
 *  2. Source Hardware Address
 *     (Ethernet Header, not in the ARP portion of the packet)
 *
 *     Set to the MAC address of the interface that the packet is being
 *     sent to. Actually, due to the way that send_arp is called this would
 *     usually (always?) be the case anyway. Although this value should not
 *     really matter, it seems sensible to set the source address to where
 *     the packet is really coming from.  The other obvious choice would be
 *     the MAC address that is being associated for the VIP. Which was the
 *     previous values.  Again, these are typically the same thing.
 *
 *     Previously set to MAC address being associated with the VIP
 */

#ifdef HAVE_LIBNET_1_0_API
u_char *
mk_packet(u_int32_t ip, u_char *device, u_char *macaddr, u_char *broadcast, u_char *netmask, u_short arptype)
{
	u_char *buf;
	u_char *target_mac;
	u_char device_mac[6];
	u_char bcast_mac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
	u_char zero_mac[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};


	if (libnet_init_packet(LIBNET_ARP_H + LIBNET_ETH_H, &buf) == -1) {
	cl_log(LOG_ERR, "libnet_init_packet memory:");
		return NULL;
	}

	/* Convert ASCII Mac Address to 6 Hex Digits. */

	/* Ethernet header */
	if (get_hw_addr((char*)device, device_mac) < 0) {
		cl_log(LOG_ERR, "Cannot find mac address for %s",
				device);
		return NULL;
	}

	if (libnet_build_ethernet(bcast_mac, device_mac, ETHERTYPE_ARP, NULL, 0
	,	buf) == -1) {
		cl_log(LOG_ERR, "libnet_build_ethernet failed:");
		libnet_destroy_packet(&buf);
		return NULL;
	}

	if (arptype == ARPOP_REQUEST) {
		target_mac = zero_mac;
	}
	else if (arptype == ARPOP_REPLY) {
		target_mac = macaddr;
	}
	else {
		cl_log(LOG_ERR, "unknown arptype");
		return NULL;
	}

	/*
	 *  ARP header
	 */
	if (libnet_build_arp(ARPHRD_ETHER,	/* Hardware address type */
		ETHERTYPE_IP,			/* Protocol address type */
		6,				/* Hardware address length */
		4,				/* Protocol address length */
		arptype,			/* ARP operation */
		macaddr,			/* Source hardware addr */
		(u_char *)&ip,			/* Target hardware addr */
		target_mac,			/* Destination hw addr */
		(u_char *)&ip,			/* Target protocol address */
		NULL,				/* Payload */
		0,				/* Payload length */
		buf + LIBNET_ETH_H) == -1) {
	        cl_log(LOG_ERR, "libnet_build_arp failed:");
		libnet_destroy_packet(&buf);
		return NULL;
	}
	return buf;
}
#endif /* HAVE_LIBNET_1_0_API */




#ifdef HAVE_LIBNET_1_1_API
libnet_t*
mk_packet(libnet_t* lntag, u_int32_t ip, u_char *device, u_char macaddr[6], u_char *broadcast, u_char *netmask, u_short arptype)
{
	u_char *target_mac;
	u_char device_mac[6];
	u_char bcast_mac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
	u_char zero_mac[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

	if (arptype == ARPOP_REQUEST) {
		target_mac = zero_mac;
	}
	else if (arptype == ARPOP_REPLY) {
		target_mac = macaddr;
	}
	else {
		cl_log(LOG_ERR, "unkonwn arptype:");
		return NULL;
	}

	/*
	 *  ARP header
	 */
	if (libnet_build_arp(ARPHRD_ETHER,	/* hardware address type */
		ETHERTYPE_IP,	/* protocol address type */
		6,		/* Hardware address length */
		4,		/* protocol address length */
		arptype,	/* ARP operation type */
		macaddr,	/* sender Hardware address */
		(u_int8_t *)&ip,	/* sender protocol address */
		target_mac,	/* target hardware address */
		(u_int8_t *)&ip,	/* target protocol address */
		NULL,		/* Payload */
		0,		/* Length of payload */
		lntag,		/* libnet context pointer */
		0		/* packet id */
	) == -1 ) {
		cl_log(LOG_ERR, "libnet_build_arp failed:");
		return NULL;
	}

	/* Ethernet header */
	if (get_hw_addr((char *)device, device_mac) < 0) {
		cl_log(LOG_ERR, "Cannot find mac address for %s",
				device);
		return NULL;
	}

	if (libnet_build_ethernet(bcast_mac, device_mac, ETHERTYPE_ARP, NULL, 0
	,	lntag, 0) == -1 ) {
		cl_log(LOG_ERR, "libnet_build_ethernet failed:");
		return NULL;
	}
	return lntag;
}
#endif /* HAVE_LIBNET_1_1_API */

#ifdef HAVE_LIBNET_1_0_API
int
send_arp(struct libnet_link_int *l, u_char *device, u_char *buf)
{
	int n;

	n = libnet_write_link_layer(l, (char*)device, buf, LIBNET_ARP_H + LIBNET_ETH_H);
	if (n == -1) {
		cl_log(LOG_ERR, "libnet_write_link_layer failed");
	}
	return (n);
}
#endif /* HAVE_LIBNET_1_0_API */

#ifdef HAVE_LIBNET_1_1_API
int
send_arp(libnet_t* lntag)
{
	int n;

	n = libnet_write(lntag);
	if (n == -1) {
		cl_log(LOG_ERR, "libnet_write failed");
	}
	return (n);
}
#endif /* HAVE_LIBNET_1_1_API */


int
create_pid_directory(const char *pidfilename)  
{
	int	status;
	struct stat stat_buf;
	char    *pidfilename_cpy;
	char    *dir;

	pidfilename_cpy = strdup(pidfilename);
	if (!pidfilename_cpy) {
		cl_log(LOG_INFO, "Memory allocation failure: %s\n",
				strerror(errno));
		return -1;
	}
	
	dir = dirname(pidfilename_cpy);

	status = stat(dir, &stat_buf); 

	if (status < 0 && errno != ENOENT && errno != ENOTDIR) {
		cl_log(LOG_INFO, "Could not stat pid-file directory "
				"[%s]: %s", dir, strerror(errno));
		free(pidfilename_cpy);
		return -1;
	}
	
	if (status >= 0) {
		if (S_ISDIR(stat_buf.st_mode)) {
			return 0;
		}
		cl_log(LOG_INFO, "Pid-File directory exists but is "
				"not a directory [%s]", dir);
		free(pidfilename_cpy);
		return -1;
        }

	if (mkdir(dir, S_IRUSR|S_IWUSR|S_IXUSR | S_IRGRP|S_IXGRP) < 0) {
		/* Did someone else make it while we were trying ? */
		if (errno == EEXIST && stat(dir, &stat_buf) >= 0
		&&	S_ISDIR(stat_buf.st_mode)) {
			return 0;
		}
		cl_log(LOG_INFO, "Could not create pid-file directory "
				"[%s]: %s", dir, strerror(errno));
		free(pidfilename_cpy);
		return -1;
	}

	free(pidfilename_cpy);
	return 0;
}


int
write_pid_file(const char *pidfilename)  
{

	int     	pidfilefd;
	char    	pidbuf[11];
	unsigned long   pid;
	ssize_t 	bytes;

	if (*pidfilename != '/') {
		cl_log(LOG_INFO, "Invalid pid-file name, must begin with a "
				"'/' [%s]\n", pidfilename);
		return -1;
	}

	if (create_pid_directory(pidfilename) < 0) {
		return -1;
	}

	while (1) {
		pidfilefd = open(pidfilename, O_CREAT|O_EXCL|O_RDWR, 
				S_IRUSR|S_IWUSR);
		if (pidfilefd < 0) {
			if (errno != EEXIST) { /* Old PID file */
				cl_log(LOG_INFO, "Could not open pid-file "
						"[%s]: %s", pidfilename, 
						strerror(errno));
				return -1;
			}
		}
		else {
			break;
		}

		pidfilefd = open(pidfilename, O_RDONLY, S_IRUSR|S_IWUSR);
		if (pidfilefd < 0) {
			cl_log(LOG_INFO, "Could not open pid-file " 
					"[%s]: %s", pidfilename, 
					strerror(errno));
			return -1;
		}

		while (1) {
			bytes = read(pidfilefd, pidbuf, sizeof(pidbuf)-1);
			if (bytes < 0) {
				if (errno == EINTR) {
					continue;
				}
				cl_log(LOG_INFO, "Could not read pid-file " 
						"[%s]: %s", pidfilename, 
						strerror(errno));
				return -1;
			}
			pidbuf[bytes] = '\0';
			break;
		}

		if(unlink(pidfilename) < 0) {
			cl_log(LOG_INFO, "Could not delete pid-file "
	 				"[%s]: %s", pidfilename, 
					strerror(errno));
			return -1;
		}

		if (!bytes) {
			cl_log(LOG_INFO, "Invalid pid in pid-file "
	 				"[%s]: %s", pidfilename, 
					strerror(errno));
			return -1;
		}

		close(pidfilefd);

		pid = strtoul(pidbuf, NULL, 10);
		if (pid == ULONG_MAX && errno == ERANGE) {
			cl_log(LOG_INFO, "Invalid pid in pid-file "
	 				"[%s]: %s", pidfilename, 
					strerror(errno));
			return -1;
		}

		if (kill(pid, SIGKILL) < 0 && errno != ESRCH) {
			cl_log(LOG_INFO, "Error killing old process [%lu] "
	 				"from pid-file [%s]: %s", pid,
					pidfilename, strerror(errno));
			return -1;
		}

		cl_log(LOG_INFO, "Killed old send_arp process [%lu]\n",
				pid);
	}

	if (snprintf(pidbuf, sizeof(pidbuf), "%u"
	,	getpid()) >= (int)sizeof(pidbuf)) {
		cl_log(LOG_INFO, "Pid too long for buffer [%u]", getpid());
		return -1;
	}

	while (1) {
		bytes = write(pidfilefd, pidbuf, strlen(pidbuf));
		if (bytes != (ssize_t)strlen(pidbuf)) {
			if (bytes < 0 && errno == EINTR) {
				continue;
			}
			cl_log(LOG_INFO, "Could not write pid-file "
					"[%s]: %s", pidfilename,
					strerror(errno));
			return -1;
		}
		break;
	}

	close(pidfilefd);

	return 0;
}


