#ifdef __linux__
#define _GNU_SOURCE 1		/* strdup() prototype, broken arpa/inet.h */
#endif

#include "../config.h"

#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <net/if.h>

#include <libgtpnl/gtp.h>
#include <libgtpnl/gtpnl.h>
#include <libmnl/libmnl.h>

#include <errno.h>

#include <time.h>

#include "../lib/tun.h"
#include "../lib/syserr.h"
#include "../gtp/pdp.h"
#include "../gtp/gtp.h"

#include <libgtpnl/gtp.h>
#include <libgtpnl/gtpnl.h>
#include <libmnl/libmnl.h>

#include "gtp-kernel.h"

static void pdp_debug(struct pdp_t *pdp)
{
	int i;
	uint64_t teid;

	if (!debug)
		return;

	printf("version %u\n", pdp->version);
	if (pdp->version == 0) {
		teid = pdp_gettid(pdp->imsi, pdp->nsapi);
		printf("flowid %u\n", pdp->flru);
	} else {
		teid = pdp->teid_gn; /* GTPIE_TEI_DI */
	}

	printf("teid %llx\n", teid);
	printf("address (%u)\n", pdp->eua.l);

	/* Byte 0: 0xf1 == IETF */
	/* Byte 1: 0x21 == IPv4 */
	/* Byte 2-6: IPv4 address */

	for (i = 0; i < 6; i++)
		printf("%x ", pdp->eua.v[i] & 0xff); /* GTPIE_EUA */

	printf("\n");
	printf("sgsn-addr (%u)\n", pdp->gsnrc.l);

	for (i = 0; i < 4; i++)
		printf("%x ", pdp->gsnrc.v[i] & 0xff); /* GTPIE_GSN_ADDR */

	printf("\n");
}

static struct {
	int			genl_id;
	struct mnl_socket	*nl;
	bool			enabled;
} gtp_nl;

/* Always forces the kernel to allocate gtp0. If it exists it hits EEXIST */
#define GTP_DEVNAME	"gtp0"

int gtp_kernel_init(struct gsn_t *gsn, struct in_addr *net,
		    size_t prefixlen, const char *net_arg)
{
	if (gtp_dev_create(-1, GTP_DEVNAME, gsn->fd0, gsn->fd1u) < 0) {
		SYS_ERR(DGGSN, LOGL_ERROR, 0,
			"cannot create GTP tunnel device: %s\n",
			strerror(errno));
		return -1;
	}
	gtp_nl.enabled = true;

	gtp_nl.nl = genl_socket_open();
	if (gtp_nl.nl == NULL) {
		SYS_ERR(DGGSN, LOGL_ERROR, 0,
			"cannot create genetlink socket\n");
		return -1;
	}
	gtp_nl.genl_id = genl_lookup_family(gtp_nl.nl, "gtp");
	if (gtp_nl.genl_id < 0) {
		SYS_ERR(DGGSN, LOGL_ERROR, 0,
			"cannot lookup GTP genetlink ID\n");
		return -1;
	}
	if (debug) {
		SYS_ERR(DGGSN, LOGL_NOTICE, 0,
			"Using the GTP kernel mode (genl ID is %d)\n",
			gtp_nl.genl_id);
	}

	DEBUGP(DGGSN, "Setting route to reach %s via %s\n",
	       net_arg, GTP_DEVNAME);

	if (gtp_dev_config(GTP_DEVNAME, net, prefixlen) < 0) {
		SYS_ERR(DGGSN, LOGL_ERROR, 0,
			"Cannot add route to reach network %s\n",
			net_arg);
	}

	/* launch script if it is set to bring up the route to reach
	 * the MS, eg. ip ro add 10.0.0.0/8 dev gtp0. Better add this
	 * using native rtnetlink interface given that we know the
	 * MS network mask, later.
	 */
	if (ipup) {
		char cmd[1024];
		int err;

		/* eg. /home/ggsn/ipup gtp0 10.0.0.0/8 */
		snprintf(cmd, sizeof(cmd), "%s %s %s",
			 ipup, GTP_DEVNAME, net_arg);
		cmd[sizeof(cmd)-1] = '\0';

		err = system(cmd);
		if (err < 0) {
			SYS_ERR(DGGSN, LOGL_ERROR, 0,
				"Failed to launch script `%s'", ipup);
			return -1;
		}
	}
	SYS_ERR(DGGSN, LOGL_NOTICE, 0, "GTP kernel configured\n");

	return 0;
}

void gtp_kernel_stop(void)
{
	if (!gtp_nl.enabled)
		return;

	gtp_dev_destroy(GTP_DEVNAME);
}

int gtp_kernel_tunnel_add(struct pdp_t *pdp)
{
	struct in_addr ms, sgsn;
	struct gtp_tunnel *t;
	int ret;

	if (!gtp_nl.enabled)
		return 0;

	pdp_debug(pdp);

	t = gtp_tunnel_alloc();
	if (t == NULL)
		return -1;

	memcpy(&ms, &pdp->eua.v[2], sizeof(struct in_addr));
	memcpy(&sgsn, &pdp->gsnrc.v[0], sizeof(struct in_addr));

	gtp_tunnel_set_ifidx(t, if_nametoindex(GTP_DEVNAME));
	gtp_tunnel_set_version(t, pdp->version);
	gtp_tunnel_set_ms_ip4(t, &ms);
	gtp_tunnel_set_sgsn_ip4(t, &sgsn);
	if (pdp->version == 0) {
		gtp_tunnel_set_tid(t, pdp_gettid(pdp->imsi, pdp->nsapi));
		gtp_tunnel_set_flowid(t, pdp->flru);
	} else {
		gtp_tunnel_set_i_tei(t, pdp->teid_own);
		/* use the TEI advertised by SGSN when sending packets
		 * towards the SGSN */
		gtp_tunnel_set_o_tei(t, pdp->teid_gn);
	}

	ret = gtp_add_tunnel(gtp_nl.genl_id, gtp_nl.nl, t);
	gtp_tunnel_free(t);

	return ret;
}

int gtp_kernel_tunnel_del(struct pdp_t *pdp)
{
	struct gtp_tunnel *t;
	int ret;

	if (!gtp_nl.enabled)
		return 0;

	pdp_debug(pdp);

	t = gtp_tunnel_alloc();
	if (t == NULL)
		return -1;

	gtp_tunnel_set_ifidx(t, if_nametoindex(GTP_DEVNAME));
	gtp_tunnel_set_version(t, pdp->version);
	if (pdp->version == 0) {
		gtp_tunnel_set_tid(t, pdp_gettid(pdp->imsi, pdp->nsapi));
		gtp_tunnel_set_flowid(t, pdp->flru);
	} else {
		gtp_tunnel_set_i_tei(t, pdp->teid_own);
	}

	ret = gtp_del_tunnel(gtp_nl.genl_id, gtp_nl.nl, t);
	gtp_tunnel_free(t);

	return ret;
}

int gtp_kernel_enabled(void)
{
	return gtp_nl.enabled;
}
