/*
 * Copyright (C) 2000-2008, Parallels, Inc. All rights reserved.
 *
 * This code is public domain.
 *
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>

#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <linux/types.h>
#include <linux/if.h>
#include <netinet/icmp6.h>

#include <sys/ioctl.h>
#include <arpa/inet.h>

#include <getopt.h>
#include <unistd.h>
#include <signal.h>

#define EXC_OK 0
#define EXC_USAGE	1
#define EXC_SYS		2
#define EXC_RECV	3
#define EXC_NORECV	4

#define NAME "ndsend: "

char* iface = NULL;

struct in6_addr src_ipaddr;
int ifindex;
__u8 real_hwaddr[6];

struct nd_packet {
	__u8		icmp6_type;
	__u8		icmp6_code;
	__u16		icmp6_cksum;

	__u32		reserved:5,
			override:1,
			solicited:1,
			router:1,
			reserved2:24;
	struct in6_addr target;
	__u8		otype;
	__u8		ospace;
	__u8		obits[6];
};

static int init_device_addresses(int sock, const char* device)
{
	struct ifreq ifr;

	memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, device, IFNAMSIZ-1);

	if (ioctl(sock, SIOCGIFINDEX, &ifr) != 0) {
		fprintf(stderr, NAME "Unknown network interface %s: %m\n",
				device);
		return -1;
	}
	ifindex = ifr.ifr_ifindex;

	/* get interface HW address */
	if (ioctl(sock, SIOCGIFHWADDR, &ifr) != 0) {
		fprintf(stderr, NAME "Can not get the MAC address of "
				"network interface %s: %m\n", device);
		return -1;
	}
	memcpy(real_hwaddr, ifr.ifr_hwaddr.sa_data, 6);

	return 0;
}

int sock;
struct nd_packet pkt;

static void create_nd_packet(struct nd_packet* pkt)
{
	pkt->icmp6_type = ND_NEIGHBOR_ADVERT;
	pkt->icmp6_code = 0;
	pkt->icmp6_cksum = 0;
	pkt->reserved = 0;
	pkt->override = 1;
	pkt->solicited = 0;
	pkt->router = 0;
	pkt->reserved = 0;
	memcpy(&pkt->target, &src_ipaddr, 16);
	pkt->otype = ND_OPT_SOURCE_LINKADDR;
	pkt->ospace = 1;
	memcpy(&pkt->obits, real_hwaddr, 6);
}

static void sender(void)
{
	struct sockaddr_in6 to;

	to.sin6_family = AF_INET6;
	to.sin6_port = 0;
	((__u32*)&to.sin6_addr)[0] = htonl(0xFF020000);
	((__u32*)&to.sin6_addr)[1] = 0;
	((__u32*)&to.sin6_addr)[2] = 0;
	((__u32*)&to.sin6_addr)[3] = htonl(0x1);
	to.sin6_scope_id = ifindex;

	if (sendto(sock, &pkt, sizeof(pkt), 0,
	    (struct sockaddr*) &to, sizeof(to)) < 0) {
		fprintf(stderr, NAME "Error in sendto(): %m\n");
		exit(EXC_SYS);
	}
}

static void usage()
{
	printf(
"ndsend sends an unsolicited Neighbor Advertisement ICMPv6 multicast packet\n"
"announcing a given IPv6 address to all IPv6 nodes as per RFC4861.\n\n"
"Usage: ndsend <address> <interface> (example: ndsend 2001:DB8::1 eth0)\n\n"
"Note: ndsend is called by arpsend -U -i, see arpsend(8) man page.\n"
	);
}

int main(int argc,char** argv)
{
	int value;

	if (argc != 3) {
		usage();
		exit(EXC_USAGE);
	}

	if (inet_pton(AF_INET6, argv[1], &src_ipaddr) <= 0)
		return -1;
	iface = argv[2];

	sock = socket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
	if (sock < 0) {
		fprintf(stderr, NAME "Error in socket(): %m\n");
		exit(EXC_SYS);
	}

	if (init_device_addresses(sock, iface) < 0)
		exit(EXC_SYS);

	value = 255;
	setsockopt (sock, SOL_IPV6, IPV6_MULTICAST_HOPS,
		    &value, sizeof (value));

	create_nd_packet(&pkt);
	sender();
	exit(EXC_OK);
}
