/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * iplink_amt.c	AMT device support
 *
 * Authors:	Taehee Yoo <ap420073@gmail.com>
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <net/if.h>
#include <linux/ip.h>
#include <linux/if_link.h>
#include <arpa/inet.h>
#include <linux/amt.h>

#include "rt_names.h"
#include "utils.h"
#include "ip_common.h"

#define AMT_ATTRSET(attrs, type) (((attrs) & (1L << (type))) != 0)

static void print_usage(FILE *f)
{
	fprintf(f,
		"Usage: ... amt\n"
		"               [ discovery IP_ADDRESS ]\n"
		"               [ mode MODE ]\n"
		"               [ local ADDR ]\n"
		"               [ dev PHYS_DEV ]\n"
		"               [ relay_port PORT ]\n"
		"               [ gateway_port PORT ]\n"
		"               [ max_tunnels NUMBER ]\n"
		"\n"
		"Where: ADDR	:= { IP_ADDRESS }\n"
		"       MODE	:= { gateway | relay }\n"
		);
}

static char *modename[] = {"gateway", "relay"};

static void usage(void)
{
	print_usage(stderr);
}

static void check_duparg(__u64 *attrs, int type, const char *key,
		const char *argv)
{
	if (!AMT_ATTRSET(*attrs, type)) {
		*attrs |= (1L << type);
		return;
	}
	duparg2(key, argv);
}

static int amt_parse_opt(struct link_util *lu, int argc, char **argv,
			 struct nlmsghdr *n)
{
	unsigned int mode, max_tunnels;
	inet_prefix saddr, daddr;
	__u64 attrs = 0;
	__u16 port;

	saddr.family = daddr.family = AF_UNSPEC;

	inet_prefix_reset(&saddr);
	inet_prefix_reset(&daddr);

	while (argc > 0) {
		if (strcmp(*argv, "mode") == 0) {
			NEXT_ARG();
			if (strcmp(*argv, "gateway") == 0) {
				mode = 0;
			} else if (strcmp(*argv, "relay") == 0) {
				mode = 1;
			} else {
				usage();
				return -1;
			}
			addattr32(n, 1024, IFLA_AMT_MODE, mode);
		} else if (strcmp(*argv, "relay_port") == 0) {
			NEXT_ARG();
			if (get_u16(&port, *argv, 0))
				invarg("relay_port", *argv);
			addattr16(n, 1024, IFLA_AMT_RELAY_PORT, htons(port));
		} else if (strcmp(*argv, "gateway_port") == 0) {
			NEXT_ARG();
			if (get_u16(&port, *argv, 0))
				invarg("gateway_port", *argv);
			addattr16(n, 1024, IFLA_AMT_GATEWAY_PORT, htons(port));
		} else if (strcmp(*argv, "max_tunnels") == 0) {
			NEXT_ARG();
			if (get_u32(&max_tunnels, *argv, 0))
				invarg("max_tunnels", *argv);
			addattr32(n, 1024, IFLA_AMT_MAX_TUNNELS, max_tunnels);
		} else if (strcmp(*argv, "dev") == 0) {
			unsigned int link;

			NEXT_ARG();
			link = ll_name_to_index(*argv);
			if (!link)
				exit(nodev(*argv));
			addattr32(n, 1024, IFLA_AMT_LINK, link);
		} else if (strcmp(*argv, "local") == 0) {
			NEXT_ARG();
			check_duparg(&attrs, IFLA_AMT_LOCAL_IP, "local", *argv);
			get_addr(&saddr, *argv, daddr.family);

			if (is_addrtype_inet(&saddr))
				addattr_l(n, 1024, IFLA_AMT_LOCAL_IP,
					  saddr.data, saddr.bytelen);
		} else if (strcmp(*argv, "discovery") == 0) {
			NEXT_ARG();
			check_duparg(&attrs, IFLA_AMT_DISCOVERY_IP,
				     "discovery", *argv);
			get_addr(&daddr, *argv, daddr.family);
			if (is_addrtype_inet(&daddr))
				addattr_l(n, 1024, IFLA_AMT_DISCOVERY_IP,
					  daddr.data, daddr.bytelen);
		} else if (strcmp(*argv, "help") == 0) {
			usage();
			return -1;
		} else {
			fprintf(stderr, "amt: unknown command \"%s\"?\n", *argv);
			usage();
			return -1;
		}
		argc--, argv++;
	}

	return 0;
}

static void amt_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
{
	if (!tb)
		return;

	if (tb[IFLA_AMT_MODE])
		print_string(PRINT_ANY, "mode", "%s ",
			     modename[rta_getattr_u32(tb[IFLA_AMT_MODE])]);

	if (tb[IFLA_AMT_GATEWAY_PORT])
		print_uint(PRINT_ANY, "gateway_port", "gateway_port %u ",
			   rta_getattr_be16(tb[IFLA_AMT_GATEWAY_PORT]));

	if (tb[IFLA_AMT_RELAY_PORT])
		print_uint(PRINT_ANY, "relay_port", "relay_port %u ",
			   rta_getattr_be16(tb[IFLA_AMT_RELAY_PORT]));

	if (tb[IFLA_AMT_LOCAL_IP]) {
		__be32 addr = rta_getattr_u32(tb[IFLA_AMT_LOCAL_IP]);

		print_string(PRINT_ANY, "local", "local %s ",
			     format_host(AF_INET, 4, &addr));
	}

	if (tb[IFLA_AMT_REMOTE_IP]) {
		__be32 addr = rta_getattr_u32(tb[IFLA_AMT_REMOTE_IP]);

		print_string(PRINT_ANY, "remote", "remote %s ",
			     format_host(AF_INET, 4, &addr));
	}

	if (tb[IFLA_AMT_DISCOVERY_IP]) {
		__be32 addr = rta_getattr_u32(tb[IFLA_AMT_DISCOVERY_IP]);

		print_string(PRINT_ANY, "discovery", "discovery %s ",
			     format_host(AF_INET, 4, &addr));
	}

	if (tb[IFLA_AMT_LINK]) {
		unsigned int link = rta_getattr_u32(tb[IFLA_AMT_LINK]);

		print_string(PRINT_ANY, "link", "dev %s ",
			     ll_index_to_name(link));
	}

	if (tb[IFLA_AMT_MAX_TUNNELS])
		print_uint(PRINT_ANY, "max_tunnels", "max_tunnels %u ",
			   rta_getattr_u32(tb[IFLA_AMT_MAX_TUNNELS]));
}

static void amt_print_help(struct link_util *lu, int argc, char **argv, FILE *f)
{
	print_usage(f);
}

struct link_util amt_link_util = {
	.id		= "amt",
	.maxattr	= IFLA_AMT_MAX,
	.parse_opt	= amt_parse_opt,
	.print_opt	= amt_print_opt,
	.print_help	= amt_print_help,
};
