// SPDX-License-Identifier: MIT
/*
Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek
*/

/* include zebra library */
#include <zebra.h>
#include <fcntl.h>

#include "getopt.h"
#include "if.h"
#include "log.h"
#include "frrevent.h"
#include "privs.h"
#include "sigevent.h"
#include "lib/version.h"
#include "command.h"
#include "vty.h"
#include "memory.h"
#include "libfrr.h"
#include "lib_errors.h"
#include "plist.h"

#include "babel_main.h"
#include "babeld.h"
#include "util.h"
#include "kernel.h"
#include "babel_interface.h"
#include "neighbour.h"
#include "route.h"
#include "xroute.h"
#include "message.h"
#include "resend.h"
#include "babel_zebra.h"
#include "babel_errors.h"

static FRR_NORETURN void babel_fail(void);
static void babel_init_random(void);
static FRR_NORETURN void babel_exit_properly(void);
static void babel_save_state_file(void);


struct event_loop *master; /* quagga's threads handler */
struct timeval babel_now;  /* current time             */

unsigned char myid[8]; /* unique id (mac address of an interface) */
int debug = 0;

int resend_delay = -1;

const unsigned char zeroes[16] = { 0 };
const unsigned char ones[16] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
				 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };

static char state_file[1024];

unsigned char protocol_group[16]; /* babel's link-local multicast address */
int protocol_port;		  /* babel's port */
int protocol_socket = -1;	  /* socket: communicate with others babeld */

static char *babel_vty_addr = NULL;
static int babel_vty_port = BABEL_VTY_PORT;

/* babeld privileges */
static zebra_capabilities_t _caps_p[] = { ZCAP_NET_RAW, ZCAP_BIND };

struct zebra_privs_t babeld_privs = {
#if defined(FRR_USER)
	.user = FRR_USER,
#endif
#if defined FRR_GROUP
	.group = FRR_GROUP,
#endif
#ifdef VTY_GROUP
	.vty_group = VTY_GROUP,
#endif
	.caps_p = _caps_p,
	.cap_num_p = array_size(_caps_p),
	.cap_num_i = 0
};

static FRR_NORETURN void babel_sigexit(void)
{
	zlog_notice("Terminating on signal");

	babel_exit_properly();
}

static void babel_sigusr1(void)
{
	zlog_rotate();
}

static struct frr_signal_t babel_signals[] = {
	{
		.signal = SIGUSR1,
		.handler = &babel_sigusr1,
	},
	{
		.signal = SIGINT,
		.handler = &babel_sigexit,
	},
	{
		.signal = SIGTERM,
		.handler = &babel_sigexit,
	},
};

struct option longopts[] = { { 0 } };

static const struct frr_yang_module_info *const babeld_yang_modules[] = {
	&frr_filter_info,
	&frr_interface_info,
	&frr_vrf_info,
};

/* clang-format off */
FRR_DAEMON_INFO(babeld, BABELD,
	.vty_port = BABEL_VTY_PORT,
	.proghelp = "Implementation of the BABEL routing protocol.",

	.signals = babel_signals,
	.n_signals = array_size(babel_signals),

	.privs = &babeld_privs,

	.yang_modules = babeld_yang_modules,
	.n_yang_modules = array_size(babeld_yang_modules),
);
/* clang-format on */

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

	frr_preinit(&babeld_di, argc, argv);
	frr_opt_add("", longopts, "");

	babel_init_random();

	/* set the Babel's default link-local multicast address and Babel's port */
	parse_address("ff02:0:0:0:0:0:1:6", protocol_group, NULL);
	protocol_port = 6696;

	/* get options */
	while (1) {
		int opt;

		opt = frr_getopt(argc, argv, NULL);

		if (opt == EOF)
			break;

		switch (opt) {
		case 0:
			break;
		default:
			frr_help_exit(1);
		}
	}

	snprintf(state_file, sizeof(state_file), "%s/%s", frr_runstatedir, "babel-state");

	/* create the threads handler */
	master = frr_init();

	/* Library inits. */
	babel_error_init();

	resend_delay = BABEL_DEFAULT_RESEND_DELAY;
	change_smoothing_half_life(BABEL_DEFAULT_SMOOTHING_HALF_LIFE);

	/* init some quagga's dependencies, and babeld's commands */
	hook_register_prio(if_real, 0, babel_ifp_create);
	hook_register_prio(if_up, 0, babel_ifp_up);
	hook_register_prio(if_down, 0, babel_ifp_down);
	hook_register_prio(if_unreal, 0, babel_ifp_destroy);
	babeld_quagga_init();
	/* init zebra client's structure and it's commands */
	/* this replace kernel_setup && kernel_setup_socket */
	babelz_zebra_init();

	/* init buffer */
	rc = resize_receive_buffer(1500);
	if (rc < 0)
		babel_fail();

	schedule_neighbours_check(5000, 1);

	frr_config_fork();
	frr_run(master);

	return 0;
}

static FRR_NORETURN void babel_fail(void)
{
	exit(1);
}

/* initialize random value, and set 'babel_now' by the way. */
static void babel_init_random(void)
{
	gettime(&babel_now);
	int rc;
	unsigned int seed;

	rc = read_random_bytes(&seed, sizeof(seed));
	if (rc < 0) {
		flog_err_sys(EC_LIB_SYSTEM_CALL, "read(random): %s", safe_strerror(errno));
		seed = 42;
	}

	seed ^= (babel_now.tv_sec ^ babel_now.tv_usec);
	srandom(seed);
}

/*
 Load the state file: check last babeld's running state, usefull in case of
 "/etc/init.d/babeld restart"
 */
void babel_load_state_file(void)
{
	int fd;
	int rc;

	fd = open(state_file, O_RDONLY);
	if (fd < 0 && errno != ENOENT)
		flog_err_sys(EC_LIB_SYSTEM_CALL, "open(babel-state: %s)", safe_strerror(errno));
	rc = unlink(state_file);
	if (fd >= 0 && rc < 0) {
		flog_err_sys(EC_LIB_SYSTEM_CALL, "unlink(babel-state): %s", safe_strerror(errno));
		/* If we couldn't unlink it, it's probably stale. */
		goto fini;
	}
	if (fd >= 0) {
		char buf[100];
		char buf2[100];
		int s;
		long t;
		rc = read(fd, buf, 99);
		if (rc < 0) {
			flog_err_sys(EC_LIB_SYSTEM_CALL, "read(babel-state): %s",
				     safe_strerror(errno));
		} else {
			buf[rc] = '\0';
			rc = sscanf(buf, "%99s %d %ld\n", buf2, &s, &t);
			if (rc == 3 && s >= 0 && s <= 0xFFFF) {
				unsigned char sid[8];
				rc = parse_eui64(buf2, sid);
				if (rc < 0) {
					flog_err(EC_BABEL_CONFIG, "Couldn't parse babel-state.");
				} else {
					struct timeval realnow;
					debugf(BABEL_DEBUG_COMMON,
					       "Got %s %d %ld from babel-state.",
					       format_eui64(sid), s, t);
					gettimeofday(&realnow, NULL);
					if (memcmp(sid, myid, 8) == 0)
						myseqno = seqno_plus(s, 1);
					else
						flog_err(EC_BABEL_CONFIG,
							 "ID mismatch in babel-state. id=%s; old=%s",
							 format_eui64(myid), format_eui64(sid));
				}
			} else {
				flog_err(EC_BABEL_CONFIG, "Couldn't parse babel-state.");
			}
		}
		goto fini;
	}
fini:
	if (fd >= 0)
		close(fd);
}

static FRR_NORETURN void babel_exit_properly(void)
{
	debugf(BABEL_DEBUG_COMMON, "Exiting...");
	usleep(roughly(10000));
	gettime(&babel_now);

	/* Uninstall and flush all routes. */
	debugf(BABEL_DEBUG_COMMON, "Uninstall routes.");
	babel_clean_routing_process();
	babel_zebra_close_connexion();
	babel_if_terminate();
	babel_save_state_file();
	debugf(BABEL_DEBUG_COMMON, "Remove pid file.");
	debugf(BABEL_DEBUG_COMMON, "Done.");

	vrf_terminate();
	prefix_list_reset();
	frr_fini();

	exit(0);
}

static void babel_save_state_file(void)
{
	int fd;
	int rc;

	debugf(BABEL_DEBUG_COMMON, "Save state file.");
	fd = open(state_file, O_WRONLY | O_TRUNC | O_CREAT, 0644);
	if (fd < 0) {
		flog_err_sys(EC_LIB_SYSTEM_CALL, "creat(babel-state): %s", safe_strerror(errno));
		unlink(state_file);
	} else {
		struct timeval realnow;
		char buf[100];
		gettimeofday(&realnow, NULL);
		rc = snprintf(buf, 100, "%s %d %ld\n", format_eui64(myid), (int)myseqno,
			      (long)realnow.tv_sec);
		if (rc < 0 || rc >= 100) {
			flog_err(EC_BABEL_CONFIG, "write(babel-state): overflow.");
			unlink(state_file);
		} else {
			rc = write(fd, buf, rc);
			if (rc < 0) {
				flog_err(EC_BABEL_CONFIG, "write(babel-state): %s",
					 safe_strerror(errno));
				unlink(state_file);
			}
			fsync(fd);
		}
		close(fd);
	}
}

void show_babel_main_configuration(struct vty *vty)
{
	vty_out(vty,
		"state file              = %s\n"
		"configuration file      = %s\n"
		"protocol information:\n"
		"  multicast address     = %s\n"
		"  port                  = %d\n"
		"vty address             = %s\n"
		"vty port                = %d\n"
		"id                      = %s\n"
		"kernel_metric           = %d\n",
		state_file, babeld_di.config_file, format_address(protocol_group), protocol_port,
		babel_vty_addr ? babel_vty_addr : "None", babel_vty_port, format_eui64(myid),
		kernel_metric);
}
