// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * NS functions.
 * Copyright (C) 2014 6WIND S.A.
 */

#include <zebra.h>
#include <fcntl.h>

#ifdef HAVE_NETNS
#undef _GNU_SOURCE
#define _GNU_SOURCE

#include <sched.h>
#endif

/* for basename */
#include <libgen.h>

#include "if.h"
#include "ns.h"
#include "log.h"
#include "memory.h"
#include "command.h"
#include "vty.h"
#include "vrf.h"
#include "lib_errors.h"

DEFINE_MTYPE_STATIC(LIB, NS, "NetNS Context");
DEFINE_MTYPE_STATIC(LIB, NS_NAME, "NetNS Name");

static inline int ns_compare(const struct ns *ns, const struct ns *ns2);
static struct ns *ns_lookup_name_internal(const char *name);

RB_GENERATE(ns_head, ns, entry, ns_compare)

static struct ns_head ns_tree = RB_INITIALIZER(&ns_tree);

static struct ns *default_ns;
static int ns_current_ns_fd;
static int ns_default_ns_fd;

static int ns_debug;

struct ns_map_nsid {
	RB_ENTRY(ns_map_nsid) id_entry;
	ns_id_t ns_id_external;
	ns_id_t ns_id;
};

static inline int ns_map_compare(const struct ns_map_nsid *a,
				   const struct ns_map_nsid *b)
{
	return (a->ns_id - b->ns_id);
}

RB_HEAD(ns_map_nsid_head, ns_map_nsid);
RB_PROTOTYPE(ns_map_nsid_head, ns_map_nsid, id_entry, ns_map_compare);
RB_GENERATE(ns_map_nsid_head, ns_map_nsid, id_entry, ns_map_compare);
static struct ns_map_nsid_head ns_map_nsid_list =
		RB_INITIALIZER(&ns_map_nsid_list);

static ns_id_t ns_id_external_numbering;


#ifndef CLONE_NEWNET
#define CLONE_NEWNET 0x40000000
/* New network namespace (lo, device, names sockets, etc) */
#endif

#ifndef HAVE_SETNS
static inline int setns(int fd, int nstype)
{
#ifdef __NR_setns
	return syscall(__NR_setns, fd, nstype);
#else
	errno = EINVAL;
	return -1;
#endif
}
#endif /* !HAVE_SETNS */

#ifdef HAVE_NETNS
static int have_netns_enabled = -1;
#endif /* HAVE_NETNS */

static int have_netns(void)
{
#ifdef HAVE_NETNS
	if (have_netns_enabled < 0) {
		int fd = open(NS_DEFAULT_NAME, O_RDONLY);

		if (fd < 0)
			have_netns_enabled = 0;
		else {
			have_netns_enabled = 1;
			close(fd);
		}
	}
	return have_netns_enabled;
#else
	return 0;
#endif
}

/* Holding NS hooks  */
static struct ns_master {
	int (*ns_new_hook)(struct ns *ns);
	int (*ns_delete_hook)(struct ns *ns);
	int (*ns_enable_hook)(struct ns *ns);
	int (*ns_disable_hook)(struct ns *ns);
} ns_master = {
	0,
};

static int ns_is_enabled(struct ns *ns);

static inline int ns_compare(const struct ns *a, const struct ns *b)
{
	return (a->ns_id - b->ns_id);
}

/* Look up a NS by identifier. */
static struct ns *ns_lookup_internal(ns_id_t ns_id)
{
	struct ns ns;

	ns.ns_id = ns_id;
	return RB_FIND(ns_head, &ns_tree, &ns);
}

/* Look up a NS by name */
static struct ns *ns_lookup_name_internal(const char *name)
{
	struct ns *ns = NULL;

	RB_FOREACH (ns, ns_head, &ns_tree) {
		if (ns->name != NULL) {
			if (strcmp(name, ns->name) == 0)
				return ns;
		}
	}
	return NULL;
}

static struct ns *ns_get_created_internal(struct ns *ns, char *name,
					  ns_id_t ns_id)
{
	int created = 0;
	/*
	 * Initialize interfaces.
	 */
	if (!ns && !name && ns_id != NS_UNKNOWN)
		ns = ns_lookup_internal(ns_id);
	if (!ns && name)
		ns = ns_lookup_name_internal(name);
	if (!ns) {
		ns = XCALLOC(MTYPE_NS, sizeof(struct ns));
		ns->ns_id = ns_id;
		if (name)
			ns->name = XSTRDUP(MTYPE_NS_NAME, name);
		ns->fd = -1;
		RB_INSERT(ns_head, &ns_tree, ns);
		created = 1;
	}
	if (ns_id != ns->ns_id) {
		RB_REMOVE(ns_head, &ns_tree, ns);
		ns->ns_id = ns_id;
		RB_INSERT(ns_head, &ns_tree, ns);
	}
	if (!created)
		return ns;
	if (ns_debug) {
		if (ns->ns_id != NS_UNKNOWN)
			zlog_info("NS %u is created.", ns->ns_id);
		else
			zlog_info("NS %s is created.", ns->name);
	}
	if (ns_master.ns_new_hook)
		(*ns_master.ns_new_hook)(ns);
	return ns;
}

/*
 * Enable a NS - that is, let the NS be ready to use.
 * The NS_ENABLE_HOOK callback will be called to inform
 * that they can allocate resources in this NS.
 *
 * RETURN: 1 - enabled successfully; otherwise, 0.
 */
static int ns_enable_internal(struct ns *ns, void (*func)(ns_id_t, void *))
{
	if (!ns_is_enabled(ns)) {
		if (have_netns()) {
			ns->fd = open(ns->name, O_RDONLY);
		} else {
			ns->fd = -2;
			/* Remember ns_enable_hook has been called */
			errno = -ENOTSUP;
		}

		if (!ns_is_enabled(ns)) {
			flog_err_sys(EC_LIB_SYSTEM_CALL,
				     "Can not enable NS %u: %s!", ns->ns_id,
				     safe_strerror(errno));
			return 0;
		}

		/* Non default NS. leave */
		if (ns->ns_id == NS_UNKNOWN) {
			flog_err(EC_LIB_NS,
				 "Can not enable NS %s %u: Invalid NSID",
				 ns->name, ns->ns_id);
			return 0;
		}
		if (func)
			func(ns->ns_id, (void *)ns->vrf_ctxt);
		if (ns_debug) {
			if (have_netns())
				zlog_info("NS %u is associated with NETNS %s.",
					  ns->ns_id, ns->name);
			zlog_info("NS %u is enabled.", ns->ns_id);
		}
		/* zebra first receives NS enable event,
		 * then VRF enable event
		 */
		if (ns_master.ns_enable_hook)
			(*ns_master.ns_enable_hook)(ns);
	}

	return 1;
}

/*
 * Check whether the NS is enabled - that is, whether the NS
 * is ready to allocate resources. Currently there's only one
 * type of resource: socket.
 */
static int ns_is_enabled(struct ns *ns)
{
	if (have_netns())
		return ns && ns->fd >= 0;
	else
		return ns && ns->fd == -2 && ns->ns_id == NS_DEFAULT;
}

/*
 * Disable a NS - that is, let the NS be unusable.
 * The NS_DELETE_HOOK callback will be called to inform
 * that they must release the resources in the NS.
 */
static void ns_disable_internal(struct ns *ns)
{
	if (ns_is_enabled(ns)) {
		if (ns_debug)
			zlog_info("NS %u is to be disabled.", ns->ns_id);

		if (ns_master.ns_disable_hook)
			(*ns_master.ns_disable_hook)(ns);

		if (have_netns() && ns->fd >= 0)
			close(ns->fd);

		ns->fd = -1;
	}
}

/* VRF list existence check by name. */
static struct ns_map_nsid *ns_map_nsid_lookup_by_nsid(ns_id_t ns_id)
{
	struct ns_map_nsid ns_map;

	ns_map.ns_id = ns_id;
	return RB_FIND(ns_map_nsid_head, &ns_map_nsid_list, &ns_map);
}

ns_id_t ns_map_nsid_with_external(ns_id_t ns_id, bool map)
{
	struct ns_map_nsid *ns_map;
	vrf_id_t ns_id_external;

	ns_map = ns_map_nsid_lookup_by_nsid(ns_id);
	if (ns_map && !map) {
		ns_id_external = ns_map->ns_id_external;
		RB_REMOVE(ns_map_nsid_head, &ns_map_nsid_list, ns_map);
		return ns_id_external;
	}
	if (ns_map)
		return ns_map->ns_id_external;
	ns_map = XCALLOC(MTYPE_NS, sizeof(struct ns_map_nsid));
	/* increase vrf_id
	 * default vrf is the first one : 0
	 */
	ns_map->ns_id_external = ns_id_external_numbering++;
	ns_map->ns_id = ns_id;
	RB_INSERT(ns_map_nsid_head, &ns_map_nsid_list, ns_map);
	return ns_map->ns_id_external;
}

struct ns *ns_get_created(struct ns *ns, char *name, ns_id_t ns_id)
{
	return ns_get_created_internal(ns, name, ns_id);
}

int ns_have_netns(void)
{
	return have_netns();
}

/* Delete a NS. This is called in ns_terminate(). */
void ns_delete(struct ns *ns)
{
	if (ns_debug)
		zlog_info("NS %u is to be deleted.", ns->ns_id);

	ns_disable(ns);

	if (ns_master.ns_delete_hook)
		(*ns_master.ns_delete_hook)(ns);

	/*
	 * I'm not entirely sure if the vrf->iflist
	 * needs to be moved into here or not.
	 */
	// if_terminate (&ns->iflist);

	RB_REMOVE(ns_head, &ns_tree, ns);
	XFREE(MTYPE_NS_NAME, ns->name);

	XFREE(MTYPE_NS, ns);
}

/* Look up the data pointer of the specified VRF. */
void *ns_info_lookup(ns_id_t ns_id)
{
	struct ns *ns = ns_lookup_internal(ns_id);

	return ns ? ns->info : NULL;
}

/* Look up a NS by name */
struct ns *ns_lookup_name(const char *name)
{
	return ns_lookup_name_internal(name);
}

int ns_enable(struct ns *ns, void (*func)(ns_id_t, void *))
{
	return ns_enable_internal(ns, func);
}

void ns_disable(struct ns *ns)
{
	ns_disable_internal(ns);
}

struct ns *ns_lookup(ns_id_t ns_id)
{
	return ns_lookup_internal(ns_id);
}

void ns_walk_func(int (*func)(struct ns *,
			      void *param_in,
			      void **param_out),
		  void *param_in,
		  void **param_out)
{
	struct ns *ns = NULL;
	int ret;

	RB_FOREACH (ns, ns_head, &ns_tree) {
		ret = func(ns, param_in, param_out);
		if (ret == NS_WALK_STOP)
			return;
	}
}

const char *ns_get_name(struct ns *ns)
{
	if (!ns)
		return NULL;
	return ns->name;
}

/* Add a NS hook. Please add hooks before calling ns_init(). */
void ns_add_hook(int type, int (*func)(struct ns *))
{
	switch (type) {
	case NS_NEW_HOOK:
		ns_master.ns_new_hook = func;
		break;
	case NS_DELETE_HOOK:
		ns_master.ns_delete_hook = func;
		break;
	case NS_ENABLE_HOOK:
		ns_master.ns_enable_hook = func;
		break;
	case NS_DISABLE_HOOK:
		ns_master.ns_disable_hook = func;
		break;
	default:
		break;
	}
}

/*
 * NS realization with NETNS
 */

char *ns_netns_pathname(struct vty *vty, const char *name)
{
	static char pathname[PATH_MAX];
	char *result;
	char *check_base;

	if (name[0] == '/') /* absolute pathname */
		result = realpath(name, pathname);
	else {
		/* relevant pathname */
		char tmp_name[PATH_MAX];

		snprintf(tmp_name, sizeof(tmp_name), "%s/%s", NS_RUN_DIR, name);
		result = realpath(tmp_name, pathname);
	}

	if (!result) {
		if (vty)
			vty_out(vty, "Invalid pathname for %s: %s\n",
				pathname,
				safe_strerror(errno));
		else
			flog_warn(EC_LIB_LINUX_NS,
				  "Invalid pathname for %s: %s", pathname,
				  safe_strerror(errno));
		return NULL;
	}
	check_base = basename(pathname);
	if (check_base != NULL && strlen(check_base) + 1 > NS_NAMSIZ) {
		if (vty)
			vty_out(vty, "NS name (%s) invalid: too long (>%d)\n",
				check_base, NS_NAMSIZ - 1);
		else
			flog_warn(EC_LIB_LINUX_NS,
				  "NS name (%s) invalid: too long (>%d)",
				  check_base, NS_NAMSIZ - 1);
		return NULL;
	}
	return pathname;
}

void ns_init(void)
{
	static int ns_initialised;

	ns_debug = 0;
	/* silently return as initialisation done */
	if (ns_initialised == 1)
		return;
	errno = 0;
	if (have_netns())
		ns_default_ns_fd = open(NS_DEFAULT_NAME, O_RDONLY);
	else {
		ns_default_ns_fd = -1;
		default_ns = NULL;
	}
	ns_current_ns_fd = -1;
	ns_initialised = 1;
}

/* Initialize NS module. */
void ns_init_management(ns_id_t default_ns_id, ns_id_t internal_ns)
{
	int fd;

	ns_init();
	default_ns = ns_get_created_internal(NULL, NULL, default_ns_id);
	if (!default_ns) {
		flog_err(EC_LIB_NS, "%s: failed to create the default NS!",
			 __func__);
		exit(1);
	}
	if (have_netns()) {
		fd = open(NS_DEFAULT_NAME, O_RDONLY);
		default_ns->fd = fd;
	}
	default_ns->internal_ns_id = internal_ns;

	/* Set the default NS name. */
	default_ns->name = XSTRDUP(MTYPE_NS_NAME, NS_DEFAULT_NAME);
	if (ns_debug)
		zlog_info("%s: default NSID is %u", __func__,
			  default_ns->ns_id);

	/* Enable the default NS. */
	if (!ns_enable(default_ns, NULL)) {
		flog_err(EC_LIB_NS, "%s: failed to enable the default NS!",
			 __func__);
		exit(1);
	}
}

/* Terminate NS module. */
void ns_terminate(void)
{
	struct ns *ns;
	struct ns_map_nsid *ns_map;

	while (!RB_EMPTY(ns_head, &ns_tree)) {
		ns = RB_ROOT(ns_head, &ns_tree);

		ns_delete(ns);
	}

	while (!RB_EMPTY(ns_map_nsid_head, &ns_map_nsid_list)) {
		ns_map = RB_ROOT(ns_map_nsid_head, &ns_map_nsid_list);
		RB_REMOVE(ns_map_nsid_head, &ns_map_nsid_list, ns_map);
		XFREE(MTYPE_NS, ns_map);
	}
}

int ns_switch_to_netns(const char *name)
{
	int ret;
	int fd;

	if (name == NULL)
		return -1;
	if (ns_default_ns_fd == -1)
		return -1;
	fd = open(name, O_RDONLY);
	if (fd == -1) {
		errno = EINVAL;
		return -1;
	}
	ret = setns(fd, CLONE_NEWNET);
	ns_current_ns_fd = fd;
	close(fd);
	return ret;
}

/* returns 1 if switch() was not called before
 * return status of setns() otherwise
 */
int ns_switchback_to_initial(void)
{
	if (ns_current_ns_fd != -1 && ns_default_ns_fd != -1) {
		int ret;

		ret = setns(ns_default_ns_fd, CLONE_NEWNET);
		ns_current_ns_fd = -1;
		return ret;
	}
	/* silently ignore if setns() is not called */
	return 1;
}

/* Create a socket for the NS. */
int ns_socket(int domain, int type, int protocol, ns_id_t ns_id)
{
	struct ns *ns = ns_lookup(ns_id);
	int ret;

	if (!ns || !ns_is_enabled(ns)) {
		errno = EINVAL;
		return -1;
	}
	if (have_netns()) {
		ret = (ns_id != NS_DEFAULT) ? setns(ns->fd, CLONE_NEWNET) : 0;
		if (ret >= 0) {
			ret = socket(domain, type, protocol);
			if (ns_id != NS_DEFAULT) {
				setns(ns_lookup(NS_DEFAULT)->fd, CLONE_NEWNET);
				ns_current_ns_fd = ns_id;
			}
		}
	} else
		ret = socket(domain, type, protocol);

	return ret;
}

/* if relative link_nsid matches default netns,
 * then return default absolute netns value
 * otherwise, return NS_UNKNOWN
 */
ns_id_t ns_id_get_absolute(ns_id_t ns_id_reference, ns_id_t link_nsid)
{
	struct ns *ns;

	ns = ns_lookup(ns_id_reference);
	if (!ns)
		return NS_UNKNOWN;

	if (ns->relative_default_ns != link_nsid)
		return NS_UNKNOWN;

	ns = ns_get_default();
	assert(ns);
	return ns->ns_id;
}

struct ns *ns_get_default(void)
{
	return default_ns;
}
