/*
 * arping.c - send unicast ARP request.
 *
 * Author: johan.peeters@airties.com
 *
 * See LICENSE file for license related information.
 *
 */

#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <net/if.h>
#include <netinet/if_ether.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ether.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdint.h>

/*#define STANDALONE*/
#if !defined(STANDALONE)
#include "arping.h"
#endif

__attribute__((unused))
static int send_arp_request(char *ifname, char *smac, char *sip, char *dmac, char *dip)
{
	int rc = -1;

	int sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
	if (sock < 0) {
		fprintf(stderr, "Failed to create raw socket");
		return -1;
	}

	struct ifreq ifr = {0};
	strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
	if (ioctl(sock, SIOCGIFINDEX, &ifr) != 0) {
		fprintf(stderr, "Failed to get ifname(%s) index", ifname);
		goto out;
	}

	struct sockaddr_ll sa;
	memset(&sa, 0, sizeof(struct sockaddr_ll));
	sa.sll_family = AF_PACKET;
	sa.sll_protocol = htons(ETH_P_ARP);
	sa.sll_ifindex = ifr.ifr_ifindex;
	sa.sll_halen = ETH_ALEN;

	struct ether_header ether_hdr;
	memcpy(ether_hdr.ether_shost, ether_aton(smac), ETH_ALEN);
	memcpy(ether_hdr.ether_dhost, ether_aton(dmac), ETH_ALEN);
	ether_hdr.ether_type = htons(ETHERTYPE_ARP);

	struct ether_arp arp_hdr;
	arp_hdr.arp_hrd = htons(ARPHRD_ETHER);
	arp_hdr.arp_pro = htons(ETHERTYPE_IP);
	arp_hdr.arp_hln = ETH_ALEN;
	arp_hdr.arp_pln = 4;
	arp_hdr.arp_op = htons(ARPOP_REQUEST);

	memcpy(arp_hdr.arp_sha, ether_aton(smac), ETH_ALEN);
	inet_pton(AF_INET, sip, arp_hdr.arp_spa);

	memset(arp_hdr.arp_tha, 0, ETH_ALEN); /*target hwaddr is unknown*/
	inet_pton(AF_INET, dip, arp_hdr.arp_tpa);

	uint8_t packet[sizeof(struct ether_header) + sizeof(struct ether_arp)];
	memcpy(packet, &ether_hdr, sizeof(struct ether_header));
	memcpy(packet + sizeof(struct ether_header), &arp_hdr, sizeof(struct ether_arp));

	rc = sendto(sock, packet, sizeof(packet), 0,
			(struct sockaddr *)&sa, sizeof(struct sockaddr_ll));
	if (rc < 0) {
		fprintf(stderr, "Failed to send the arp packet\n");
		goto out;
	}

	rc = 0;
out:
	close(sock);
	return rc;
}

#if !defined(STANDALONE)
int arping_send_arp_request(struct neigh_entry *n, char dip[])
{
	dbg("[%s-%d] arping: send unicast ARP request\n", __func__, __LINE__);

	struct hostmngr_private *priv = (struct hostmngr_private *)n->priv;
	struct local_interface *iface = hostmngr_ifname_to_interface(priv, n->ifname);
	if (!iface) {
		dbg("Failed to find struct local_interface {} belonging to %s\n", n->ifname);
		return -1;
	}

	char *sifname = n->ifname;
	char smac[20] = {0};
	char sip[46] = {0};
	char dmac[20] = {0};

	char brifname[16] = {0};
	struct local_interface *briface = NULL;
	bool is_brip_find = false;
	int brifindex;

	/* Try to find bridge IP address in order to use as source IP */
	brifindex = if_isbridge_interface(n->ifname);
	if (brifindex > 0 && if_indextoname(brifindex, brifname))
		briface = hostmngr_ifname_to_interface(priv, brifname);

	if (briface) {
		for (int i = 0; i < briface->num_ipaddrs; i++) {
			if (briface->ipaddrs[i].family != AF_INET)
				continue;
			inet_ntop(AF_INET/*ipv4 only*/, &briface->ipaddrs[i].addr, sip, sizeof(sip));
			is_brip_find = true;
			hwaddr_ntoa(briface->macaddr, smac);
			sifname = brifname;
			break;
		}
	}
	if (!is_brip_find) {
		dbg("Failed to find a valid IP address for brifname %s to use as source IP, "
				"will be using 0.0.0.0\n", brifname);
		snprintf(sip, sizeof(sip), "0.0.0.0");
		hwaddr_ntoa(iface->macaddr, smac);
	}
	hwaddr_ntoa(n->macaddr, dmac);

	dbg("arping to {ifname: %s, smac: %s, sip: %s, dmac: %s, dip: %s}\n",
			sifname, smac, sip, dmac, dip);
	return send_arp_request(sifname, smac, sip, dmac, dip);
}
#else
/* Convert \x001122334455 --> "00:11:22:33:44:55" */
static inline char *mac_ntoa(const uint8_t *mac, char *macstr, size_t len)
{
	snprintf(macstr, len, "%02x:%02x:%02x:%02x:%02x:%02x",
			mac[0]&0xff, mac[1]&0xff,
			mac[2]&0xff, mac[3]&0xff,
			mac[4]&0xff, mac[5]&0xff);

	return macstr;
}

int main(int argc, char *argv[])
{
	if (argc != 6) {
		fprintf(stderr, "Usage: %s <interface> "
				"<src_mac> <src_ip> <dest_mac> <dest_ip>\n", argv[0]);
		return -1;
	}

	char *ifname = argv[1];
	char *smac = argv[2];
	char *sip = argv[3];
	char *dmac = argv[4];
	char *dip = argv[5];

	fprintf(stderr, "arping to {ifname: %s, smac: %s, sip: %s, dmac: %s, dip: %s}\n",
			ifname, smac, sip, dmac, dip);

	send_arp_request(ifname, smac, sip, dmac, dip);

	return 0;
}
#endif /* STANDALONE */
