/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright 2019 Conrad Meyer <cem@FreeBSD.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#include "opt_inet.h"
#include "opt_inet6.h"

#include <sys/ctype.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <sys/syslog.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/rmlock.h>

#include <ddb/ddb.h>
#include <ddb/db_lex.h>

#include <net/if.h>
#include <net/if_var.h>
#include <net/if_private.h>
#include <net/if_dl.h>
#include <net/route.h>
#include <net/route/nhop.h>
#include <net/route/route_ctl.h>
#include <net/route/route_var.h>

/*
 * Unfortunately, RTF_ values are expressed as raw masks rather than powers of
 * 2, so we cannot use them as nice C99 initializer indices below.
 */
static const char * const rtf_flag_strings[] = {
	"UP",
	"GATEWAY",
	"HOST",
	"REJECT",
	"DYNAMIC",
	"MODIFIED",
	"DONE",
	"UNUSED_0x80",
	"UNUSED_0x100",
	"XRESOLVE",
	"LLDATA",
	"STATIC",
	"BLACKHOLE",
	"UNUSED_0x2000",
	"PROTO2",
	"PROTO1",
	"UNUSED_0x10000",
	"UNUSED_0x20000",
	"PROTO3",
	"FIXEDMTU",
	"PINNED",
	"LOCAL",
	"BROADCAST",
	"MULTICAST",
	/* Big gap. */
	[28] = "STICKY",
	[30] = "RNH_LOCKED",
	[31] = "GWFLAG_COMPAT",
};

static const char * __pure
rt_flag_name(unsigned idx)
{
	if (idx >= nitems(rtf_flag_strings))
		return ("INVALID_FLAG");
	if (rtf_flag_strings[idx] == NULL)
		return ("UNKNOWN");
	return (rtf_flag_strings[idx]);
}

static void
rt_dumpaddr_ddb(const char *name, const struct sockaddr *sa)
{
	char buf[INET6_ADDRSTRLEN], *res;

	res = NULL;
	if (sa == NULL)
		res = "NULL";
	else if (sa->sa_family == AF_INET) {
		res = inet_ntop(AF_INET,
		    &((const struct sockaddr_in *)sa)->sin_addr,
		    buf, sizeof(buf));
	} else if (sa->sa_family == AF_INET6) {
		res = inet_ntop(AF_INET6,
		    &((const struct sockaddr_in6 *)sa)->sin6_addr,
		    buf, sizeof(buf));
	} else if (sa->sa_family == AF_LINK) {
		res = "on link";
	}

	if (res != NULL) {
		db_printf("%s <%s> ", name, res);
		return;
	}

	db_printf("%s <af:%d> ", name, sa->sa_family);
}

static int
rt_dumpentry_ddb(struct radix_node *rn, void *arg __unused)
{
	struct sockaddr_storage ss;
	struct rtentry *rt;
	struct nhop_object *nh;
	int flags, idx;

	/* If RNTORT is important, put it in a header. */
	rt = (void *)rn;
	nh = (struct nhop_object *)rt->rt_nhop;

	rt_dumpaddr_ddb("dst", rt_key(rt));
	rt_dumpaddr_ddb("gateway", &rt->rt_nhop->gw_sa);
	rt_dumpaddr_ddb("netmask", rtsock_fix_netmask(rt_key(rt), rt_mask(rt),
	    &ss));
	if ((nh->nh_ifp->if_flags & IFF_DYING) == 0) {
		rt_dumpaddr_ddb("ifp", nh->nh_ifp->if_addr->ifa_addr);
		rt_dumpaddr_ddb("ifa", nh->nh_ifa->ifa_addr);
	}

	db_printf("flags ");
	flags = rt->rte_flags | nhop_get_rtflags(nh);
	if (flags == 0)
		db_printf("none");

	while ((idx = ffs(flags)) > 0) {
		idx--;

		db_printf("%s", rt_flag_name(idx));
		flags &= ~(1ul << idx);
		if (flags != 0)
			db_printf(",");
	}

	db_printf("\n");
	return (0);
}

DB_SHOW_COMMAND(routetable, db_show_routetable)
{
	struct rib_head *rnh;
	int error, i, lim;

	if (have_addr)
		i = lim = addr;
	else {
		i = 1;
		lim = AF_MAX;
	}

	for (; i <= lim; i++) {
		rnh = rt_tables_get_rnh(0, i);
		if (rnh == NULL) {
			if (have_addr) {
				db_printf("%s: AF %d not supported?\n",
				    __func__, i);
				break;
			}
			continue;
		}

		if (!have_addr && i > 1)
			db_printf("\n");

		db_printf("Route table for AF %d%s%s%s:\n", i,
		    (i == AF_INET || i == AF_INET6) ? " (" : "",
		    (i == AF_INET) ? "INET" : (i == AF_INET6) ? "INET6" : "",
		    (i == AF_INET || i == AF_INET6) ? ")" : "");

		error = rnh->rnh_walktree(&rnh->head, rt_dumpentry_ddb, NULL);
		if (error != 0)
			db_printf("%s: walktree(%d): %d\n", __func__, i,
			    error);
	}
}

DB_SHOW_COMMAND_FLAGS(route, db_show_route, CS_OWN)
{
	char abuf[INET6_ADDRSTRLEN], *buf, *end;
	struct rib_head *rh;
	struct radix_node *rn;
	void *dst_addrp;
	struct rtentry *rt;
	union {
		struct sockaddr_in dest_sin;
		struct sockaddr_in6 dest_sin6;
	} u;
	int af;

	buf = db_get_line();

	/* Remove whitespaces from both ends */
	end = buf + strlen(buf) - 1;
	for (; (end >= buf) && (*end=='\n' || isspace(*end)); end--)
		*end = '\0';
	while (isspace(*buf))
		buf++;

	/* Determine AF */
	if (strchr(buf, ':') != NULL) {
		af = AF_INET6;
		u.dest_sin6.sin6_family = af;
		u.dest_sin6.sin6_len = sizeof(struct sockaddr_in6);
		dst_addrp = &u.dest_sin6.sin6_addr;
	} else {
		af = AF_INET;
		u.dest_sin.sin_family = af;
		u.dest_sin.sin_len = sizeof(struct sockaddr_in);
		dst_addrp = &u.dest_sin.sin_addr;
	}

	if (inet_pton(af, buf, dst_addrp) != 1)
		goto usage;

	if (inet_ntop(af, dst_addrp, abuf, sizeof(abuf)) != NULL)
		db_printf("Looking up route to destination '%s'\n", abuf);

	rt = NULL;
	CURVNET_SET(vnet0);

	rh = rt_tables_get_rnh(RT_DEFAULT_FIB, af);

	rn = rh->rnh_matchaddr(&u, &rh->head);
	if (rn && ((rn->rn_flags & RNF_ROOT) == 0))
		rt = (struct rtentry *)rn;

	CURVNET_RESTORE();

	if (rt == NULL) {
		db_printf("Could not get route for that server.\n");
		return;
	}

	rt_dumpentry_ddb((void *)rt, NULL);

	return;
usage:
	db_printf("Usage: 'show route <address>'\n"
	    "  Currently accepts only IPv4 and IPv6 addresses\n");
	db_skip_to_eol();
}