/* SPDX-License-Identifier: LGPL-2.1-only */
/*
 * Copyright (c) 2013 Cong Wang <xiyou.wangcong@gmail.com>
 */

/**
 * @ingroup link
 * @defgroup veth VETH
 * Virtual Ethernet
 *
 * @details
 * \b Link Type Name: "veth"
 *
 * @route_doc{link_veth, VETH Documentation}
 *
 * @{
 */

#include <netlink-private/netlink.h>
#include <netlink/netlink.h>
#include <netlink/attr.h>
#include <netlink/utils.h>
#include <netlink/object.h>
#include <netlink/route/rtnl.h>
#include <netlink-private/route/link/api.h>
#include <netlink/route/link/veth.h>

#include <linux/if_link.h>
#include <linux/veth.h>

static struct nla_policy veth_policy[VETH_INFO_MAX+1] = {
	[VETH_INFO_PEER]	= { .minlen = sizeof(struct ifinfomsg) },
};

static int veth_parse(struct rtnl_link *link, struct nlattr *data,
		      struct nlattr *xstats)
{
	struct nlattr *tb[VETH_INFO_MAX+1];
	struct nlattr *peer_tb[IFLA_MAX + 1];
	struct rtnl_link *peer = link->l_info;
	int err;

	NL_DBG(3, "Parsing veth link info\n");

	if ((err = nla_parse_nested(tb, VETH_INFO_MAX, data, veth_policy)) < 0)
		goto errout;

	if (tb[VETH_INFO_PEER]) {
		struct nlattr *nla_peer;
		struct ifinfomsg *ifi;

		nla_peer = tb[VETH_INFO_PEER];
		ifi = nla_data(nla_peer);

		peer->l_family = ifi->ifi_family;
		peer->l_arptype = ifi->ifi_type;
		peer->l_index = ifi->ifi_index;
		peer->l_flags = ifi->ifi_flags;
		peer->l_change = ifi->ifi_change;
		err = nla_parse(peer_tb, IFLA_MAX, (struct nlattr *)
		                ((char *) nla_data(nla_peer) + sizeof(struct ifinfomsg)),
				nla_len(nla_peer) - sizeof(struct ifinfomsg),
				rtln_link_policy);
		if (err < 0)
			goto errout;

		err = rtnl_link_info_parse(peer, peer_tb);
		if (err < 0)
			goto errout;
	}

	err = 0;

errout:
	return err;
}

static void veth_dump_line(struct rtnl_link *link, struct nl_dump_params *p)
{
}

static void veth_dump_details(struct rtnl_link *link, struct nl_dump_params *p)
{
	struct rtnl_link *peer = link->l_info;
	char *name;
	name = rtnl_link_get_name(peer);
	nl_dump(p, "      peer ");
	if (name)
		nl_dump_line(p, "%s\n", name);
	else
		nl_dump_line(p, "%u\n", peer->l_index);
}

static int veth_clone(struct rtnl_link *dst, struct rtnl_link *src)
{
	struct rtnl_link *dst_peer = NULL, *src_peer = src->l_info;

	/* we are calling nl_object_clone() recursively, this should
	 * happen only once */
	if (src_peer) {
		src_peer->l_info = NULL;
		dst_peer = (struct rtnl_link *)nl_object_clone(OBJ_CAST(src_peer));
		if (!dst_peer)
			return -NLE_NOMEM;
		src_peer->l_info = src;
		dst_peer->l_info = dst;
	}
	dst->l_info = dst_peer;
	return 0;
}

static int veth_put_attrs(struct nl_msg *msg, struct rtnl_link *link)
{
	struct rtnl_link *peer = link->l_info;
	struct ifinfomsg ifi;
	struct nlattr *data, *info_peer;

	memset(&ifi, 0, sizeof ifi);
	ifi.ifi_family = peer->l_family;
	ifi.ifi_type = peer->l_arptype;
	ifi.ifi_index = peer->l_index;
	ifi.ifi_flags = peer->l_flags;
	ifi.ifi_change = peer->l_change;

	if (!(data = nla_nest_start(msg, IFLA_INFO_DATA)))
		return -NLE_MSGSIZE;
	if (!(info_peer = nla_nest_start(msg, VETH_INFO_PEER)))
		return -NLE_MSGSIZE;
	if (nlmsg_append(msg, &ifi, sizeof(ifi), NLMSG_ALIGNTO) < 0)
		return -NLE_MSGSIZE;
	rtnl_link_fill_info(msg, peer);
	nla_nest_end(msg, info_peer);
	nla_nest_end(msg, data);

	return 0;
}

static int veth_alloc(struct rtnl_link *link)
{
	struct rtnl_link *peer;
	int err;

	/* return early if we are in recursion */
	if (link->l_info)
		return 0;

	if (!(peer = rtnl_link_alloc()))
		return -NLE_NOMEM;

	/* We don't need to hold a reference here, as link and
	 * its peer should always be freed together.
	 */
	peer->l_info = link;
	if ((err = rtnl_link_set_type(peer, "veth")) < 0) {
		rtnl_link_put(peer);
		return err;
	}

	link->l_info = peer;
	return 0;
}

static void veth_free(struct rtnl_link *link)
{
	struct rtnl_link *peer = link->l_info;
	if (peer) {
		link->l_info = NULL;
		/* avoid calling this recursively */
		peer->l_info = NULL;
		rtnl_link_put(peer);
	}
	/* the caller should finally free link */
}

static struct rtnl_link_info_ops veth_info_ops = {
	.io_name		= "veth",
	.io_parse		= veth_parse,
	.io_dump = {
	    [NL_DUMP_LINE]	= veth_dump_line,
	    [NL_DUMP_DETAILS]	= veth_dump_details,
	},
	.io_alloc		= veth_alloc,
	.io_clone		= veth_clone,
	.io_put_attrs		= veth_put_attrs,
	.io_free		= veth_free,
};

/** @cond SKIP */

#define IS_VETH_LINK_ASSERT(link) \
	if ((link)->l_info_ops != &veth_info_ops) { \
		APPBUG("Link is not a veth link. set type \"veth\" first."); \
		return NULL; \
	}
/** @endcond */

/**
 * @name VETH Object
 * @{
 */

/**
 * Allocate link object of type veth
 *
 * @return Allocated link object or NULL.
 */
struct rtnl_link *rtnl_link_veth_alloc(void)
{
	struct rtnl_link *link;

	if (!(link = rtnl_link_alloc()))
		return NULL;
	if (rtnl_link_set_type(link, "veth") < 0) {
		rtnl_link_put(link);
		return NULL;
	}

	return link;
}

/**
 * Get the peer link of a veth link
 *
 * @return the peer link object.
 */
struct rtnl_link *rtnl_link_veth_get_peer(struct rtnl_link *link)
{
	IS_VETH_LINK_ASSERT(link);
	nl_object_get(OBJ_CAST(link->l_info));
	return link->l_info;
}

/**
 * Release a veth link and its peer
 *
 */
void rtnl_link_veth_release(struct rtnl_link *link)
{
	veth_free(link);
	rtnl_link_put(link);
}

/**
 * Check if link is a veth link
 * @arg link		Link object
 *
 * @return True if link is a veth link, otherwise false is returned.
 */
int rtnl_link_is_veth(struct rtnl_link *link)
{
	return link->l_info_ops && !strcmp(link->l_info_ops->io_name, "veth");
}

/**
 * Create a new kernel veth device
 * @arg sock		netlink socket
 * @arg name		name of the veth device or NULL
 * @arg peer_name	name of its peer or NULL
 * @arg pid		pid of the process in the new netns
 *
 * Creates a new veth device pair in the kernel and move the peer
 * to the network namespace where the process is. If no name is
 * provided, the kernel will automatically pick a name of the
 * form "veth%d" (e.g. veth0, veth1, etc.)
 *
 * @return 0 on success or a negative error code
 */
int rtnl_link_veth_add(struct nl_sock *sock, const char *name,
                       const char *peer_name, pid_t pid)
{
	struct rtnl_link *link, *peer;
	int err = -NLE_NOMEM;

	if (!(link = rtnl_link_veth_alloc()))
		return -NLE_NOMEM;
	peer = link->l_info;

	if (name)
		rtnl_link_set_name(link, name);
	if (peer_name)
		rtnl_link_set_name(peer, peer_name);

	rtnl_link_set_ns_pid(peer, pid);
	err = rtnl_link_add(sock, link, NLM_F_CREATE | NLM_F_EXCL);

	rtnl_link_put(link);
	return err;
}

/** @} */

static void __init veth_init(void)
{
	rtnl_link_register_info(&veth_info_ops);
}

static void __exit veth_exit(void)
{
	rtnl_link_unregister_info(&veth_info_ops);
}

/** @} */
