/* SPDX-License-Identifier: GPL-2.0 */
#include <unistd.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>

#include <netinet/ip.h>
#include <netinet/ip6.h>

#include <linux/if.h>
#include <linux/if_tun.h>
#include <sys/ioctl.h>

#include <linux/netlink.h>
#include <netlink/socket.h>
#include <netlink/route/link.h>
#include <netlink/route/addr.h>
#include <netlink/route/route.h>
#include <netlink/route/nexthop.h>

#include <osmocom/core/utils.h>

/***********************************************************************
 * netlink helper functions
 ***********************************************************************/

static int _netdev_addr(struct nl_sock *nlsk, int ifindex, const struct sockaddr_storage *ss, bool add)
{
	const struct sockaddr_in6 *sin6;
	const struct sockaddr_in *sin;
	struct nl_addr *local = NULL;
	struct rtnl_addr *addr;
	int rc;

	switch (ss->ss_family) {
	case AF_INET:
		sin = (struct sockaddr_in *) ss;
		local = nl_addr_build(AF_INET, &sin->sin_addr, 4);
		break;
	case AF_INET6:
		sin6 = (struct sockaddr_in6 *) ss;
		local = nl_addr_build(AF_INET6, &sin6->sin6_addr, 16);
		break;
	}
	OSMO_ASSERT(local);

	addr = rtnl_addr_alloc();
	OSMO_ASSERT(addr);
	rtnl_addr_set_ifindex(addr, ifindex);
	OSMO_ASSERT(rtnl_addr_set_local(addr, local) == 0);

	if (add)
		rc = rtnl_addr_add(nlsk, addr, 0);
	else
		rc = rtnl_addr_delete(nlsk, addr, 0);

	rtnl_addr_put(addr);

	return rc;
}

int netdev_add_addr(struct nl_sock *nlsk, int ifindex, const struct sockaddr_storage *ss)
{
	return _netdev_addr(nlsk, ifindex, ss, true);
}

int netdev_del_addr(struct nl_sock *nlsk, int ifindex, const struct sockaddr_storage *ss)
{
	return _netdev_addr(nlsk, ifindex, ss, false);
}

int netdev_set_link(struct nl_sock *nlsk, int ifindex, bool up)
{
	struct rtnl_link *link, *change;
	int rc;

	rc = rtnl_link_get_kernel(nlsk, ifindex, NULL, &link);
	if (rc < 0)
		return rc;

	change = rtnl_link_alloc();
	OSMO_ASSERT(change);

	if (up)
		rtnl_link_set_flags(change, IFF_UP);
	else
		rtnl_link_unset_flags(change, IFF_UP);

	rc = rtnl_link_change(nlsk, link, change, 0);

	rtnl_link_put(change);
	rtnl_link_put(link);

	return rc;
}

int netdev_add_defaultroute(struct nl_sock *nlsk, int ifindex, uint8_t family)
{
	struct rtnl_route *route = rtnl_route_alloc();
	struct rtnl_nexthop *nhop = rtnl_route_nh_alloc();
	struct nl_addr *dst, *gw;
	uint8_t buf[16];
	int rc;

	OSMO_ASSERT(route);
	OSMO_ASSERT(nhop);

	/* destination address of route: all-zero */
	memset(buf, 0, sizeof(buf));
	dst = nl_addr_build(family, buf, family == AF_INET ? 4 : 16);
	OSMO_ASSERT(dst);
	nl_addr_set_prefixlen(dst, 0);

	/* gateway address of route: also all-zero */
	gw = nl_addr_clone(dst);
	OSMO_ASSERT(gw);

	/* nexthop for route */
	rtnl_route_nh_set_ifindex(nhop, ifindex);
	rtnl_route_nh_set_gateway(nhop, gw);

	/* tie everything together in the route */
	rtnl_route_set_dst(route, dst);
	rtnl_route_set_family(route, family);
	rtnl_route_add_nexthop(route, nhop);

	rc = rtnl_route_add(nlsk, route, NLM_F_CREATE);

	//rtnl_route_nh_free(nhop);
	nl_addr_put(gw);
	nl_addr_put(dst);
	rtnl_route_put(route);

	return rc;
}