root/sys/net/route/route_ddb.c
/*-
 * 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();
}