root/tests/sys/net/routing/rtsock_print.h
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2019 Alexander V. Chernikov
 *
 * 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.
 */

#ifndef _NET_ROUTING_RTSOCK_PRINT_H_
#define _NET_ROUTING_RTSOCK_PRINT_H_


#define RLOG(_fmt, ...) printf("%s: " _fmt "\n", __func__, ##__VA_ARGS__)
#define RLOG_ERRNO(_fmt, ...)   do {                    \
        printf("%s: " _fmt, __func__, ##__VA_ARGS__);   \
        printf(": %s\n", strerror(errno));              \
} while(0)

#define RTSOCK_ATF_REQUIRE_MSG(_rtm, _cond, _fmt, ...)   do {   \
        if (!(_cond)) {                                         \
                printf("-- CONDITION FAILED, rtm dump  --\n\n");\
                rtsock_print_message(_rtm);                     \
                rtsock_print_table(AF_INET);                    \
                rtsock_print_table(AF_INET6);                   \
                printf("===================================\n");\
        }                                                       \
        ATF_REQUIRE_MSG(_cond, _fmt, ##__VA_ARGS__);            \
} while (0);

#define RTSOCKHD_ATF_REQUIRE_MSG(_rtm, _cond, _fmt, ...) do {   \
        if (!(_cond)) {                                         \
                printf("-- CONDITION FAILED, rtm hexdump--\n\n");\
                rtsock_print_message_hd(_rtm);                          \
        }                                                       \
        ATF_REQUIRE_MSG(_cond, _fmt, ##__VA_ARGS__);            \
} while (0);


/* from route.c */
static const char *const msgtypes[] = {
        "",
        "RTM_ADD",
        "RTM_DELETE",
        "RTM_CHANGE",
        "RTM_GET",
        "RTM_LOSING",
        "RTM_REDIRECT",
        "RTM_MISS",
        "RTM_LOCK",
        "RTM_OLDADD",
        "RTM_OLDDEL",
        "RTM_RESOLVE",
        "RTM_NEWADDR",
        "RTM_DELADDR",
        "RTM_IFINFO",
        "RTM_NEWMADDR",
        "RTM_DELMADDR",
        "RTM_IFANNOUNCE",
        "RTM_IEEE80211",
};

static const char metricnames[] =
    "\011weight\010rttvar\7rtt\6ssthresh\5sendpipe\4recvpipe\3expire"
    "\1mtu";
static const char routeflags[] =
    "\1UP\2GATEWAY\3HOST\4REJECT\5DYNAMIC\6MODIFIED\7DONE"
    "\012XRESOLVE\013LLINFO\014STATIC\015BLACKHOLE"
    "\017PROTO2\020PROTO1\021PRCLONING\022WASCLONED\023PROTO3"
    "\024FIXEDMTU\025PINNED\026LOCAL\027BROADCAST\030MULTICAST\035STICKY";
static const char ifnetflags[] =
    "\1UP\2BROADCAST\3DEBUG\4LOOPBACK\5PTP\6b6\7RUNNING\010NOARP"
    "\011PPROMISC\012ALLMULTI\013OACTIVE\014SIMPLEX\015LINK0\016LINK1"
    "\017LINK2\020MULTICAST";
static const char addrnames[] =
    "\1DST\2GATEWAY\3NETMASK\4GENMASK\5IFP\6IFA\7AUTHOR\010BRD";

static int
_printb(char *buf, size_t bufsize, int b, const char *str)
{
        int i;
        int gotsome = 0;

        char *pbuf = buf;

        if (b == 0) {
                *pbuf = '\0';
                return (0);
        }
        while ((i = *str++) != 0) {
                if (b & (1 << (i-1))) {
                        if (gotsome == 0)
                                i = '<';
                        else
                                i = ',';
                        *pbuf++ = i;
                        gotsome = 1;
                        for (; (i = *str) > 32; str++)
                                *pbuf++ = i;
                } else
                        while (*str > 32)
                                str++;
        }
        if (gotsome)
                *pbuf++ = '>';
        *pbuf = '\0';

        return (int)(pbuf - buf);
}

const char *
rtsock_print_cmdtype(int cmd)
{

        return (msgtypes[cmd]);
}

char *
rtsock_print_rtm_flags(char *buf, int buflen, int rtm_flags)
{

        _printb(buf, buflen, rtm_flags, routeflags);
        return (buf);
}


#define _PRINTX(fmt, ...)       do {                            \
        one_len = snprintf(ptr, rem_len, fmt, __VA_ARGS__);     \
        ptr += one_len;                                         \
        rem_len -= one_len;                                     \
} while(0)


void
sa_print_hd(char *buf, int buflen, const char *data, int len)
{
        char *ptr;
        int one_len, rem_len;

        ptr = buf;
        rem_len = buflen;

        const char *last_char = NULL;
        unsigned char v;
        int repeat_count = 0;
        for (int i = 0; i < len; i++) {
                if (last_char && *last_char == data[i] && data[i] == 0x00) {
                        repeat_count++;
                        continue;
                }

                if (repeat_count > 1) {
                        _PRINTX("{%d}", repeat_count);
                        repeat_count = 0;
                }

                v = ((const unsigned char *)data)[i];
                if (last_char == NULL)
                        _PRINTX("x%02X", v);
                else
                        _PRINTX(", x%02X", v);

                last_char = &data[i];
                repeat_count = 1;
        }

        if (repeat_count > 1)
                snprintf(ptr, rem_len, "{%d}", repeat_count);
}

#undef _PRINTX

void
sa_print(const struct sockaddr *sa, int include_hexdump)
{
        char hdbuf[512], abuf[64];
        char ifbuf[128];
        const struct sockaddr_dl *sdl;
        const struct sockaddr_in6 *sin6;
        const struct sockaddr_in *sin;
        int i;

        switch (sa->sa_family) {
                case AF_INET:
                        sin = (struct sockaddr_in *)sa;
                        inet_ntop(AF_INET, &sin->sin_addr, abuf, sizeof(abuf));
                        printf(" af=inet len=%d addr=%s", sa->sa_len, abuf);
                        break;
                case AF_INET6:
                        sin6 = (struct sockaddr_in6 *)sa;
                        inet_ntop(AF_INET6, &sin6->sin6_addr, abuf, sizeof(abuf));
                        int scope_id = sin6->sin6_scope_id;
                        printf(" af=inet6 len=%d addr=%s", sa->sa_len, abuf);
                        if (scope_id != 0) {
                                memset(ifbuf, 0, sizeof(ifbuf));
                                if_indextoname(scope_id, ifbuf);
                                printf(" scope_id=%d if_name=%s", scope_id, ifbuf);
                        }
                        break;
                case AF_LINK:
                        sdl = (const struct sockaddr_dl *)sa;
                        int sdl_index = sdl->sdl_index;
                        if (sdl_index != 0) {
                                memset(ifbuf, 0, sizeof(ifbuf));
                                if_indextoname(sdl_index, ifbuf);
                                printf(" af=link len=%d sdl_index=%d if_name=%s", sdl->sdl_len, sdl_index, ifbuf);
                        }
                        if (sdl->sdl_nlen) {
                                char _ifname[IFNAMSIZ];
                                memcpy(_ifname, sdl->sdl_data, sdl->sdl_nlen);
                                _ifname[sdl->sdl_nlen] = '\0';
                                printf(" name=%s", _ifname);
                        }
                        if (sdl->sdl_alen) {
                                printf(" addr=");
                                const char *lladdr = LLADDR(sdl);
                                for (int i = 0; i < sdl->sdl_alen; i++) {
                                        if (i + 1 < sdl->sdl_alen)
                                                printf("%02X:", ((const unsigned char *)lladdr)[i]);
                                        else
                                                printf("%02X", ((const unsigned char *)lladdr)[i]);
                                }
                        }
                        break;
                default:
                        printf(" af=%d len=%d", sa->sa_family, sa->sa_len);
        }

        if (include_hexdump) {
                sa_print_hd(hdbuf, sizeof(hdbuf), ((char *)sa), sa->sa_len);
                printf(" hd={%s}", hdbuf);
        }
        printf("\n");
}

/*
got message of size 240 on Mon Dec 16 09:23:31 2019
RTM_ADD: Add Route: len 240, pid: 25534, seq 2, errno 0, flags:<HOST,DONE,LLINFO,STATIC>
locks:  inits:
sockaddrs: <DST,GATEWAY>
*/

void
rtsock_print_rtm(struct rt_msghdr *rtm)
{
        struct timeval tv;
        struct tm tm_res;
        char buf[64];

        gettimeofday(&tv, NULL);
        localtime_r(&tv.tv_sec, &tm_res);
        strftime(buf, sizeof(buf), "%F %T", &tm_res);
        printf("Got message of size %hu on %s\n", rtm->rtm_msglen, buf);

        char flags_buf[256];
        rtsock_print_rtm_flags(flags_buf, sizeof(flags_buf), rtm->rtm_flags);

        printf("%s: len %hu, pid: %d, seq %d, errno %d, flags: %s\n", msgtypes[rtm->rtm_type],
                rtm->rtm_msglen, rtm->rtm_pid, rtm->rtm_seq, rtm->rtm_errno, flags_buf);

        if (rtm->rtm_inits > 0) {
                _printb(flags_buf, sizeof(flags_buf), rtm->rtm_inits, metricnames);
                printf("metrics: %s\n", flags_buf);
                if (rtm->rtm_inits & RTV_MTU)
                        printf("mtu: %lu\n", rtm->rtm_rmx.rmx_mtu);
                if (rtm->rtm_inits & RTV_EXPIRE) {
                        struct timeval tv;
                        gettimeofday(&tv, NULL);
                        printf("expire: %d (%lu raw)\n",
                            (int)(rtm->rtm_rmx.rmx_expire - tv.tv_sec), rtm->rtm_rmx.rmx_expire);
                }
        }

        _printb(flags_buf, sizeof(flags_buf), rtm->rtm_addrs, addrnames);
        printf("sockaddrs: 0x%X %s\n", rtm->rtm_addrs, flags_buf);

        char *ptr = (char *)(rtm + 1);
        for (int i = 0; i < RTAX_MAX; i++) {
                if (rtm->rtm_addrs & (1 << i)) {
                        struct sockaddr *sa = (struct sockaddr *)ptr;
                        sa_print(sa, 1);

                        /* add */
                        ptr += ALIGN(((struct sockaddr *)ptr)->sa_len);
                }
        }

        printf("\n");

}

void
rtsock_print_ifa(struct ifa_msghdr *ifam)
{
        struct timeval tv;
        struct tm tm_res;
        char buf[64];

        gettimeofday(&tv, NULL);
        localtime_r(&tv.tv_sec, &tm_res);
        strftime(buf, sizeof(buf), "%F %T", &tm_res);
        printf("Got message of size %hu on %s\n", ifam->ifam_msglen, buf);

        char flags_buf[256];
        _printb(flags_buf, sizeof(flags_buf), ifam->ifam_flags, routeflags);

        printf("%s: len %hu, ifindex: %d, flags: %s\n", msgtypes[ifam->ifam_type],
                ifam->ifam_msglen, ifam->ifam_index, flags_buf);

        _printb(flags_buf, sizeof(flags_buf), ifam->ifam_addrs, addrnames);
        printf("sockaddrs: 0x%X %s\n", ifam->ifam_addrs, flags_buf);

        char *ptr = (char *)(ifam + 1);
        for (int i = 0; i < RTAX_MAX; i++) {
                if (ifam->ifam_addrs & (1 << i)) {
                        struct sockaddr *sa = (struct sockaddr *)ptr;
                        sa_print(sa, 1);

                        /* add */
                        ptr += ALIGN(((struct sockaddr *)ptr)->sa_len);
                }
        }

        printf("\n");

}

void
rtsock_print_message_hd(struct rt_msghdr *rtm)
{
        struct timeval tv;
        struct tm tm_res;
        char buf[64];
        char dumpbuf[2048];

        gettimeofday(&tv, NULL);
        localtime_r(&tv.tv_sec, &tm_res);
        strftime(buf, sizeof(buf), "%F %T", &tm_res);
        printf("Got message type %s of size %hu on %s\n",
            rtsock_print_cmdtype(rtm->rtm_type),
            rtm->rtm_msglen, buf);

        sa_print_hd(dumpbuf, sizeof(dumpbuf), (char *)rtm, rtm->rtm_msglen);
        printf(" %s\n", dumpbuf);
}

void
rtsock_print_message(struct rt_msghdr *rtm)
{

        switch (rtm->rtm_type) {
        case RTM_GET:
        case RTM_ADD:
        case RTM_DELETE:
        case RTM_CHANGE:
                rtsock_print_rtm(rtm);
                break;
        case RTM_DELADDR:
        case RTM_NEWADDR:
                rtsock_print_ifa((struct ifa_msghdr *)rtm);
                break;
        default:
                printf("unknown rt message type %X\n", rtm->rtm_type);
        }
}

static void
print_command(char *cmd)
{
        char line[1024];

        FILE *fp = popen(cmd, "r");
        if (fp != NULL) {
                while (fgets(line, sizeof(line), fp) != NULL)
                        printf("%s", line);
                pclose(fp);
        }
}

void
rtsock_print_table(int family)
{
        char cmdbuf[128];
        char *key = (family == AF_INET) ? "4" : "6";

        snprintf(cmdbuf, sizeof(cmdbuf), "/usr/bin/netstat -%srnW", key);
        printf("==== %s ===\n", cmdbuf);
        print_command(cmdbuf);
        snprintf(cmdbuf, sizeof(cmdbuf), "/usr/bin/netstat -%sonW", key);
        printf("==== %s ===\n", cmdbuf);
        print_command(cmdbuf);
}

#endif