root/usr.sbin/bgpctl/mrtparser.c
/*      $OpenBSD: mrtparser.c,v 1.23 2025/11/03 11:32:07 tb Exp $ */
/*
 * Copyright (c) 2011 Claudio Jeker <claudio@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <err.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include "mrt.h"
#include "mrtparser.h"

struct mrt_peer *mrt_parse_v2_peer(struct mrt_hdr *, struct ibuf *);
struct mrt_rib  *mrt_parse_v2_rib(struct mrt_hdr *, struct ibuf *, int);
int     mrt_parse_dump(struct mrt_hdr *, struct ibuf *, struct mrt_peer **,
            struct mrt_rib **);
int     mrt_parse_dump_mp(struct mrt_hdr *, struct ibuf *, struct mrt_peer **,
            struct mrt_rib **, int);
int     mrt_extract_attr(struct mrt_rib_entry *, struct ibuf *, uint8_t, int);

void    mrt_free_peers(struct mrt_peer *);
void    mrt_free_rib(struct mrt_rib *);

u_char *mrt_aspath_inflate(struct ibuf *, uint16_t *);
int     mrt_extract_addr(struct ibuf *, struct bgpd_addr *, uint8_t);
int     mrt_extract_prefix(struct ibuf *, uint8_t, struct bgpd_addr *,
            uint8_t *, int);

int     mrt_parse_state(struct mrt_bgp_state *, struct mrt_hdr *,
            struct ibuf *, int);
int     mrt_parse_msg(struct mrt_bgp_msg *, struct mrt_hdr *,
            struct ibuf *, int);

static size_t
mrt_read_buf(int fd, void *buf, size_t len)
{
        char *b = buf;
        ssize_t n;

        while (len > 0) {
                if ((n = read(fd, b, len)) == -1) {
                        if (errno == EINTR)
                                continue;
                        err(1, "read");
                }
                if (n == 0)
                        break;
                b += n;
                len -= n;
        }

        return (b - (char *)buf);
}

static struct ibuf *
mrt_read_msg(int fd, struct mrt_hdr *hdr)
{
        struct ibuf *buf;
        size_t len;

        memset(hdr, 0, sizeof(*hdr));
        if (mrt_read_buf(fd, hdr, sizeof(*hdr)) != sizeof(*hdr))
                return (NULL);

        len = ntohl(hdr->length);
        if ((buf = ibuf_open(len)) == NULL)
                err(1, "ibuf_open(%zu)", len);

        if (mrt_read_buf(fd, ibuf_reserve(buf, len), len) != len) {
                ibuf_free(buf);
                return (NULL);
        }
        return (buf);
}

void
mrt_parse(int fd, struct mrt_parser *p, int verbose)
{
        struct mrt_hdr          h;
        struct mrt_bgp_state    s;
        struct mrt_bgp_msg      m;
        struct mrt_peer         *pctx = NULL;
        struct mrt_rib          *r;
        struct ibuf             *msg;

        while ((msg = mrt_read_msg(fd, &h)) != NULL) {
                if (ibuf_size(msg) != ntohl(h.length))
                        errx(1, "corrupt message, %zu vs %u", ibuf_size(msg),
                            ntohl(h.length));
                switch (ntohs(h.type)) {
                case MSG_NULL:
                case MSG_START:
                case MSG_DIE:
                case MSG_I_AM_DEAD:
                case MSG_PEER_DOWN:
                case MSG_PROTOCOL_BGP:
                case MSG_PROTOCOL_IDRP:
                case MSG_PROTOCOL_BGP4PLUS:
                case MSG_PROTOCOL_BGP4PLUS1:
                        if (verbose)
                                printf("deprecated MRT type %d\n",
                                    ntohs(h.type));
                        break;
                case MSG_PROTOCOL_RIP:
                case MSG_PROTOCOL_RIPNG:
                case MSG_PROTOCOL_OSPF:
                case MSG_PROTOCOL_ISIS_ET:
                case MSG_PROTOCOL_ISIS:
                case MSG_PROTOCOL_OSPFV3_ET:
                case MSG_PROTOCOL_OSPFV3:
                        if (verbose)
                                printf("unsupported MRT type %d\n",
                                    ntohs(h.type));
                        break;
                case MSG_TABLE_DUMP:
                        switch (ntohs(h.subtype)) {
                        case MRT_DUMP_AFI_IP:
                        case MRT_DUMP_AFI_IPv6:
                                if (p->dump == NULL)
                                        break;
                                if (mrt_parse_dump(&h, msg, &pctx, &r) == 0) {
                                        if (p->dump)
                                                p->dump(r, pctx, p->arg);
                                        mrt_free_rib(r);
                                }
                                break;
                        default:
                                if (verbose)
                                        printf("unknown AFI %d in table dump\n",
                                            ntohs(h.subtype));
                                break;
                        }
                        break;
                case MSG_TABLE_DUMP_V2:
                        switch (ntohs(h.subtype)) {
                        case MRT_DUMP_V2_PEER_INDEX_TABLE:
                                if (p->dump == NULL)
                                        break;
                                if (pctx)
                                        mrt_free_peers(pctx);
                                pctx = mrt_parse_v2_peer(&h, msg);
                                break;
                        case MRT_DUMP_V2_RIB_IPV4_UNICAST:
                        case MRT_DUMP_V2_RIB_IPV4_MULTICAST:
                        case MRT_DUMP_V2_RIB_IPV6_UNICAST:
                        case MRT_DUMP_V2_RIB_IPV6_MULTICAST:
                        case MRT_DUMP_V2_RIB_GENERIC:
                        case MRT_DUMP_V2_RIB_IPV4_UNICAST_ADDPATH:
                        case MRT_DUMP_V2_RIB_IPV4_MULTICAST_ADDPATH:
                        case MRT_DUMP_V2_RIB_IPV6_UNICAST_ADDPATH:
                        case MRT_DUMP_V2_RIB_IPV6_MULTICAST_ADDPATH:
                        case MRT_DUMP_V2_RIB_GENERIC_ADDPATH:
                                if (p->dump == NULL)
                                        break;
                                r = mrt_parse_v2_rib(&h, msg, verbose);
                                if (r) {
                                        if (p->dump)
                                                p->dump(r, pctx, p->arg);
                                        mrt_free_rib(r);
                                }
                                break;
                        default:
                                if (verbose)
                                        printf("unhandled DUMP_V2 subtype %d\n",
                                            ntohs(h.subtype));
                                break;
                        }
                        break;
                case MSG_PROTOCOL_BGP4MP_ET:
                case MSG_PROTOCOL_BGP4MP:
                        switch (ntohs(h.subtype)) {
                        case BGP4MP_STATE_CHANGE:
                        case BGP4MP_STATE_CHANGE_AS4:
                                if (mrt_parse_state(&s, &h, msg,
                                    verbose) != -1) {
                                        if (p->state)
                                                p->state(&s, p->arg);
                                }
                                break;
                        case BGP4MP_MESSAGE:
                        case BGP4MP_MESSAGE_AS4:
                        case BGP4MP_MESSAGE_LOCAL:
                        case BGP4MP_MESSAGE_AS4_LOCAL:
                        case BGP4MP_MESSAGE_ADDPATH:
                        case BGP4MP_MESSAGE_AS4_ADDPATH:
                        case BGP4MP_MESSAGE_LOCAL_ADDPATH:
                        case BGP4MP_MESSAGE_AS4_LOCAL_ADDPATH:
                                if (mrt_parse_msg(&m, &h, msg, verbose) != -1) {
                                        if (p->message)
                                                p->message(&m, p->arg);
                                }
                                break;
                        case BGP4MP_ENTRY:
                                if (p->dump == NULL)
                                        break;
                                if (mrt_parse_dump_mp(&h, msg, &pctx, &r,
                                    verbose) == 0) {
                                        if (p->dump)
                                                p->dump(r, pctx, p->arg);
                                        mrt_free_rib(r);
                                }
                                break;
                        default:
                                if (verbose)
                                        printf("unhandled BGP4MP subtype %d\n",
                                            ntohs(h.subtype));
                                break;
                        }
                        break;
                default:
                        if (verbose)
                                printf("unknown MRT type %d\n", ntohs(h.type));
                        break;
                }
                ibuf_free(msg);
        }
        if (pctx)
                mrt_free_peers(pctx);
}

static int
mrt_afi2aid(int afi, int safi, int verbose)
{
        switch (afi) {
        case MRT_DUMP_AFI_IP:
                if (safi == -1 || safi == 1 || safi == 2)
                        return AID_INET;
                else if (safi == 128)
                        return AID_VPN_IPv4;
                break;
        case MRT_DUMP_AFI_IPv6:
                if (safi == -1 || safi == 1 || safi == 2)
                        return AID_INET6;
                else if (safi == 128)
                        return AID_VPN_IPv6;
                break;
        default:
                break;
        }
        if (verbose)
                printf("unhandled AFI/SAFI %d/%d\n", afi, safi);
        return AID_UNSPEC;
}

struct mrt_peer *
mrt_parse_v2_peer(struct mrt_hdr *hdr, struct ibuf *msg)
{
        struct mrt_peer_entry   *peers = NULL;
        struct mrt_peer *p;
        uint32_t        bid;
        uint16_t        cnt, i;

        if (ibuf_size(msg) < 8) /* min msg size */
                return NULL;

        p = calloc(1, sizeof(struct mrt_peer));
        if (p == NULL)
                err(1, "calloc");

        /* collector bgp id */
        if (ibuf_get_n32(msg, &bid) == -1 ||
            ibuf_get_n16(msg, &cnt) == -1)
                goto fail;

        /* view name */
        if (cnt != 0) {
                if ((p->view = malloc(cnt + 1)) == NULL)
                        err(1, "malloc");
                if (ibuf_get(msg, p->view, cnt) == -1)
                        goto fail;
                p->view[cnt] = 0;
        } else
                if ((p->view = strdup("")) == NULL)
                        err(1, "strdup");

        /* peer_count */
        if (ibuf_get_n16(msg, &cnt) == -1)
                goto fail;

        /* peer entries */
        if ((peers = calloc(cnt, sizeof(struct mrt_peer_entry))) == NULL)
                err(1, "calloc");
        for (i = 0; i < cnt; i++) {
                uint8_t type;

                if (ibuf_get_n8(msg, &type) == -1 ||
                    ibuf_get_n32(msg, &peers[i].bgp_id) == -1)
                        goto fail;

                if (type & MRT_DUMP_V2_PEER_BIT_I) {
                        if (mrt_extract_addr(msg, &peers[i].addr,
                            AID_INET6) == -1)
                                goto fail;
                } else {
                        if (mrt_extract_addr(msg, &peers[i].addr,
                            AID_INET) == -1)
                                goto fail;
                }

                if (type & MRT_DUMP_V2_PEER_BIT_A) {
                        if (ibuf_get_n32(msg, &peers[i].asnum) == -1)
                                goto fail;
                } else {
                        uint16_t as2;

                        if (ibuf_get_n16(msg, &as2) == -1)
                                goto fail;
                        peers[i].asnum = as2;
                }
        }
        p->peers = peers;
        p->npeers = cnt;
        return (p);
fail:
        mrt_free_peers(p);
        free(peers);
        return (NULL);
}

struct mrt_rib *
mrt_parse_v2_rib(struct mrt_hdr *hdr, struct ibuf *msg, int verbose)
{
        struct mrt_rib_entry *entries = NULL;
        struct mrt_rib  *r;
        uint16_t        i, afi;
        uint8_t         safi, aid;

        r = calloc(1, sizeof(struct mrt_rib));
        if (r == NULL)
                err(1, "calloc");

        /* seq_num */
        if (ibuf_get_n32(msg, &r->seqnum) == -1)
                goto fail;

        switch (ntohs(hdr->subtype)) {
        case MRT_DUMP_V2_RIB_IPV4_UNICAST_ADDPATH:
        case MRT_DUMP_V2_RIB_IPV4_MULTICAST_ADDPATH:
                r->add_path = 1;
                /* FALLTHROUGH */
        case MRT_DUMP_V2_RIB_IPV4_UNICAST:
        case MRT_DUMP_V2_RIB_IPV4_MULTICAST:
                /* prefix */
                if (mrt_extract_prefix(msg, AID_INET, &r->prefix,
                    &r->prefixlen, verbose) == -1)
                        goto fail;
                break;
        case MRT_DUMP_V2_RIB_IPV6_UNICAST_ADDPATH:
        case MRT_DUMP_V2_RIB_IPV6_MULTICAST_ADDPATH:
                r->add_path = 1;
                /* FALLTHROUGH */
        case MRT_DUMP_V2_RIB_IPV6_UNICAST:
        case MRT_DUMP_V2_RIB_IPV6_MULTICAST:
                /* prefix */
                if (mrt_extract_prefix(msg, AID_INET6, &r->prefix,
                    &r->prefixlen, verbose) == -1)
                        goto fail;
                break;
        case MRT_DUMP_V2_RIB_GENERIC_ADDPATH:
                /*
                 * RFC8050 handling for add-path has special handling for
                 * RIB_GENERIC_ADDPATH but nobody implements it that way.
                 * So just use the same way as for the other _ADDPATH types.
                 */
                r->add_path = 1;
                /* FALLTHROUGH */
        case MRT_DUMP_V2_RIB_GENERIC:
                /* fetch AFI/SAFI pair */
                if (ibuf_get_n16(msg, &afi) == -1 ||
                    ibuf_get_n8(msg, &safi) == -1)
                        goto fail;

                if ((aid = mrt_afi2aid(afi, safi, verbose)) == AID_UNSPEC)
                        goto fail;

                /* prefix */
                if (mrt_extract_prefix(msg, aid, &r->prefix,
                    &r->prefixlen, verbose) == -1)
                        goto fail;
                break;
        default:
                errx(1, "unknown subtype %hd", ntohs(hdr->subtype));
        }

        /* entries count */
        if (ibuf_get_n16(msg, &r->nentries) == -1)
                goto fail;

        /* entries */
        if ((entries = calloc(r->nentries, sizeof(struct mrt_rib_entry))) ==
            NULL)
                err(1, "calloc");
        for (i = 0; i < r->nentries; i++) {
                struct ibuf     abuf;
                uint32_t        otm;
                uint16_t        alen;

                /* peer index */
                if (ibuf_get_n16(msg, &entries[i].peer_idx) == -1)
                        goto fail;

                /* originated */
                if (ibuf_get_n32(msg, &otm) == -1)
                        goto fail;
                entries[i].originated = otm;

                if (r->add_path) {
                        if (ibuf_get_n32(msg, &entries[i].path_id) == -1)
                                goto fail;
                }

                /* attr_len */
                if (ibuf_get_n16(msg, &alen) == -1 ||
                    ibuf_get_ibuf(msg, alen, &abuf) == -1)
                        goto fail;

                /* attr */
                if (mrt_extract_attr(&entries[i], &abuf, r->prefix.aid,
                    1) == -1)
                        goto fail;
        }
        r->entries = entries;
        return (r);
fail:
        mrt_free_rib(r);
        free(entries);
        return (NULL);
}

int
mrt_parse_dump(struct mrt_hdr *hdr, struct ibuf *msg, struct mrt_peer **pp,
    struct mrt_rib **rp)
{
        struct ibuf              abuf;
        struct mrt_peer         *p;
        struct mrt_rib          *r;
        struct mrt_rib_entry    *re;
        uint32_t                 tmp32;
        uint16_t                 tmp16, alen;

        if (*pp == NULL) {
                *pp = calloc(1, sizeof(struct mrt_peer));
                if (*pp == NULL)
                        err(1, "calloc");
                (*pp)->peers = calloc(1, sizeof(struct mrt_peer_entry));
                if ((*pp)->peers == NULL)
                        err(1, "calloc");
                (*pp)->npeers = 1;
        }
        p = *pp;

        *rp = r = calloc(1, sizeof(struct mrt_rib));
        if (r == NULL)
                err(1, "calloc");
        re = calloc(1, sizeof(struct mrt_rib_entry));
        if (re == NULL)
                err(1, "calloc");
        r->nentries = 1;
        r->entries = re;

        if (ibuf_skip(msg, sizeof(uint16_t)) == -1 ||   /* view */
            ibuf_get_n16(msg, &tmp16) == -1)            /* seqnum */
                goto fail;
        r->seqnum = tmp16;

        switch (ntohs(hdr->subtype)) {
        case MRT_DUMP_AFI_IP:
                if (mrt_extract_addr(msg, &r->prefix, AID_INET) == -1)
                        goto fail;
                break;
        case MRT_DUMP_AFI_IPv6:
                if (mrt_extract_addr(msg, &r->prefix, AID_INET6) == -1)
                        goto fail;
                break;
        }
        if (ibuf_get_n8(msg, &r->prefixlen) == -1 ||    /* prefixlen */
            ibuf_skip(msg, 1) == -1 ||                  /* status */
            ibuf_get_n32(msg, &tmp32) == -1)            /* originated */
                goto fail;
        re->originated = tmp32;
        /* peer ip */
        switch (ntohs(hdr->subtype)) {
        case MRT_DUMP_AFI_IP:
                if (mrt_extract_addr(msg, &p->peers->addr, AID_INET) == -1)
                        goto fail;
                break;
        case MRT_DUMP_AFI_IPv6:
                if (mrt_extract_addr(msg, &p->peers->addr, AID_INET6) == -1)
                        goto fail;
                break;
        }
        if (ibuf_get_n16(msg, &tmp16) == -1)
                goto fail;
        p->peers->asnum = tmp16;

        if (ibuf_get_n16(msg, &alen) == -1 ||
            ibuf_get_ibuf(msg, alen, &abuf) == -1)
                goto fail;

        /* attr */
        if (mrt_extract_attr(re, &abuf, r->prefix.aid, 0) == -1)
                goto fail;
        return (0);
fail:
        mrt_free_rib(r);
        return (-1);
}

int
mrt_parse_dump_mp(struct mrt_hdr *hdr, struct ibuf *msg, struct mrt_peer **pp,
    struct mrt_rib **rp, int verbose)
{
        struct ibuf              abuf;
        struct mrt_peer         *p;
        struct mrt_rib          *r;
        struct mrt_rib_entry    *re;
        uint32_t                 tmp32;
        uint16_t                 asnum, alen, afi;
        uint8_t                  safi, nhlen, aid;

        if (*pp == NULL) {
                *pp = calloc(1, sizeof(struct mrt_peer));
                if (*pp == NULL)
                        err(1, "calloc");
                (*pp)->peers = calloc(1, sizeof(struct mrt_peer_entry));
                if ((*pp)->peers == NULL)
                        err(1, "calloc");
                (*pp)->npeers = 1;
        }
        p = *pp;

        *rp = r = calloc(1, sizeof(struct mrt_rib));
        if (r == NULL)
                err(1, "calloc");
        re = calloc(1, sizeof(struct mrt_rib_entry));
        if (re == NULL)
                err(1, "calloc");
        r->nentries = 1;
        r->entries = re;

        /* just ignore the microsec field for _ET header for now */
        if (ntohs(hdr->type) == MSG_PROTOCOL_BGP4MP_ET) {
                if (ibuf_skip(msg, sizeof(uint32_t)) == -1)
                        goto fail;
        }

        if (ibuf_skip(msg, sizeof(uint16_t)) == -1 ||   /* source AS */
            ibuf_get_n16(msg, &asnum) == -1 ||          /* dest AS */
            ibuf_skip(msg, sizeof(uint16_t)) == -1 ||   /* iface index */
            ibuf_get_n16(msg, &afi) == -1)
                goto fail;
        p->peers->asnum = asnum;

        /* source + dest ip */
        switch (afi) {
        case MRT_DUMP_AFI_IP:
                /* source IP */
                if (ibuf_skip(msg, sizeof(struct in_addr)) == -1)
                        goto fail;
                /* dest IP */
                if (mrt_extract_addr(msg, &p->peers->addr, AID_INET) == -1)
                        goto fail;
                break;
        case MRT_DUMP_AFI_IPv6:
                /* source IP */
                if (ibuf_skip(msg, sizeof(struct in6_addr)) == -1)
                        goto fail;
                /* dest IP */
                if (mrt_extract_addr(msg, &p->peers->addr, AID_INET6) == -1)
                        goto fail;
                break;
        }

        if (ibuf_skip(msg, sizeof(uint16_t)) == -1 ||   /* view */
            ibuf_skip(msg, sizeof(uint16_t)) == -1 ||   /* status */
            ibuf_get_n32(msg, &tmp32) == -1)            /* originated */
                goto fail;
        re->originated = tmp32;

        if (ibuf_get_n16(msg, &afi) == -1 ||            /* afi */
            ibuf_get_n8(msg, &safi) == -1)              /* safi */
                goto fail;
        if ((aid = mrt_afi2aid(afi, safi, verbose)) == AID_UNSPEC)
                goto fail;

        if (ibuf_get_n8(msg, &nhlen) == -1)             /* nhlen */
                goto fail;

        /* nexthop */
        if (mrt_extract_addr(msg, &re->nexthop, aid) == -1)
                goto fail;

        /* prefix */
        if (mrt_extract_prefix(msg, aid, &r->prefix, &r->prefixlen,
            verbose) == -1)
                goto fail;

        if (ibuf_get_n16(msg, &alen) == -1 ||
            ibuf_get_ibuf(msg, alen, &abuf) == -1)
                goto fail;
        if (mrt_extract_attr(re, &abuf, r->prefix.aid, 0) == -1)
                goto fail;

        return (0);
fail:
        mrt_free_rib(r);
        return (-1);
}

int
mrt_extract_attr(struct mrt_rib_entry *re, struct ibuf *buf, uint8_t aid,
    int as4)
{
        struct ibuf     abuf;
        struct mrt_attr *ap;
        size_t          alen, hlen;
        uint8_t         type, flags;

        do {
                ibuf_from_ibuf(&abuf, buf);
                if (ibuf_get_n8(&abuf, &flags) == -1 ||
                    ibuf_get_n8(&abuf, &type) == -1)
                        return (-1);

                if (flags & MRT_ATTR_EXTLEN) {
                        uint16_t tmp16;
                        if (ibuf_get_n16(&abuf, &tmp16) == -1)
                                return (-1);
                        alen = tmp16;
                        hlen = 4;
                } else {
                        uint8_t tmp8;
                        if (ibuf_get_n8(&abuf, &tmp8) == -1)
                                return (-1);
                        alen = tmp8;
                        hlen = 3;
                }
                if (ibuf_truncate(&abuf, alen) == -1)
                        return (-1);
                /* consume the attribute in buf before moving forward */
                if (ibuf_skip(buf, hlen + alen) == -1)
                        return (-1);

                switch (type) {
                case MRT_ATTR_ORIGIN:
                        if (alen != 1)
                                return (-1);
                        if (ibuf_get_n8(&abuf, &re->origin) == -1)
                                return (-1);
                        break;
                case MRT_ATTR_ASPATH:
                        if (as4) {
                                re->aspath_len = alen;
                                if ((re->aspath = malloc(alen)) == NULL)
                                        err(1, "malloc");
                                if (ibuf_get(&abuf, re->aspath, alen) == -1)
                                        return (-1);
                        } else {
                                re->aspath = mrt_aspath_inflate(&abuf,
                                    &re->aspath_len);
                                if (re->aspath == NULL)
                                        return (-1);
                        }
                        break;
                case MRT_ATTR_NEXTHOP:
                        if (alen != 4)
                                return (-1);
                        if (aid != AID_INET)
                                break;
                        if (ibuf_get(&abuf, &re->nexthop.v4,
                            sizeof(re->nexthop.v4)) == -1)
                                return (-1);
                        re->nexthop.aid = AID_INET;
                        break;
                case MRT_ATTR_MED:
                        if (alen != 4)
                                return (-1);
                        if (ibuf_get_n32(&abuf, &re->med) == -1)
                                return (-1);
                        break;
                case MRT_ATTR_LOCALPREF:
                        if (alen != 4)
                                return (-1);
                        if (ibuf_get_n32(&abuf, &re->local_pref) == -1)
                                return (-1);
                        break;
                case MRT_ATTR_MP_REACH_NLRI:
                        /*
                         * XXX horrible hack:
                         * Once again IETF and the real world differ in the
                         * implementation. In short the abbreviated MP_NLRI
                         * hack in the standard is not used in real life.
                         * Detect the two cases by looking at the first byte
                         * of the payload (either the nexthop addr length (RFC)
                         * or the high byte of the AFI (old form)). If the
                         * first byte matches the expected nexthop length it
                         * is expected to be the RFC 6396 encoding.
                         *
                         * Checking for the hack skips over the nhlen.
                         */
                        {
                                uint8_t hack;
                                if (ibuf_get_n8(&abuf, &hack) == -1)
                                        return (-1);
                                if (hack != alen - 1) {
                                        if (ibuf_skip(&abuf, 3) == -1)
                                                return (-1);
                                }
                        }
                        switch (aid) {
                        case AID_INET6:
                                if (ibuf_get(&abuf, &re->nexthop.v6,
                                    sizeof(re->nexthop.v6)) == -1)
                                        return (-1);
                                re->nexthop.aid = aid;
                                break;
                        case AID_VPN_IPv4:
                                if (ibuf_skip(&abuf, sizeof(uint64_t)) == -1 ||
                                    ibuf_get(&abuf, &re->nexthop.v4,
                                    sizeof(re->nexthop.v4)) == -1)
                                        return (-1);
                                re->nexthop.aid = aid;
                                break;
                        case AID_VPN_IPv6:
                                if (ibuf_skip(&abuf, sizeof(uint64_t)) == -1 ||
                                    ibuf_get(&abuf, &re->nexthop.v6,
                                    sizeof(re->nexthop.v6)) == -1)
                                        return (-1);
                                re->nexthop.aid = aid;
                                break;
                        }
                        break;
                case MRT_ATTR_AS4PATH:
                        if (!as4) {
                                free(re->aspath);
                                re->aspath_len = alen;
                                if ((re->aspath = malloc(alen)) == NULL)
                                        err(1, "malloc");
                                if (ibuf_get(&abuf, re->aspath, alen) == -1)
                                        return (-1);
                                break;
                        }
                        /* FALLTHROUGH */
                default:
                        re->nattrs++;
                        if (re->nattrs >= UCHAR_MAX)
                                err(1, "too many attributes");
                        ap = reallocarray(re->attrs,
                            re->nattrs, sizeof(struct mrt_attr));
                        if (ap == NULL)
                                err(1, "realloc");
                        re->attrs = ap;
                        ap = re->attrs + re->nattrs - 1;
                        ibuf_rewind(&abuf);
                        ap->attr_len = ibuf_size(&abuf);
                        if ((ap->attr = malloc(ap->attr_len)) == NULL)
                                err(1, "malloc");
                        if (ibuf_get(&abuf, ap->attr, ap->attr_len) == -1)
                                return (-1);
                        break;
                }
        } while (ibuf_size(buf) > 0);

        return (0);
}

void
mrt_free_peers(struct mrt_peer *p)
{
        free(p->peers);
        free(p->view);
        free(p);
}

void
mrt_free_rib(struct mrt_rib *r)
{
        uint16_t        i, j;

        for (i = 0; i < r->nentries && r->entries; i++) {
                for (j = 0; j < r->entries[i].nattrs; j++)
                         free(r->entries[i].attrs[j].attr);
                free(r->entries[i].attrs);
                free(r->entries[i].aspath);
        }

        free(r->entries);
        free(r);
}

u_char *
mrt_aspath_inflate(struct ibuf *buf, uint16_t *newlen)
{
        struct ibuf *asbuf;
        u_char *data;
        size_t len;

        *newlen = 0;
        asbuf = aspath_inflate(buf);
        if (asbuf == NULL)
                return NULL;

        len = ibuf_size(asbuf);
        if ((data = malloc(len)) == NULL)
                err(1, "malloc");
        if (ibuf_get(asbuf, data, len) == -1) {
                ibuf_free(asbuf);
                free(data);
                return (NULL);
        }
        ibuf_free(asbuf);
        *newlen = len;
        return (data);
}

int
mrt_extract_addr(struct ibuf *msg, struct bgpd_addr *addr, uint8_t aid)
{
        memset(addr, 0, sizeof(*addr));
        switch (aid) {
        case AID_INET:
                if (ibuf_get(msg, &addr->v4, sizeof(addr->v4)) == -1)
                        return (-1);
                break;
        case AID_INET6:
                if (ibuf_get(msg, &addr->v6, sizeof(addr->v6)) == -1)
                        return (-1);
                break;
        case AID_VPN_IPv4:
                /* XXX labelstack and rd missing */
                if (ibuf_skip(msg, sizeof(uint64_t)) == -1 ||
                    ibuf_get(msg, &addr->v4, sizeof(addr->v4)) == -1)
                        return (-1);
                break;
        case AID_VPN_IPv6:
                /* XXX labelstack and rd missing */
                if (ibuf_skip(msg, sizeof(uint64_t)) == -1 ||
                    ibuf_get(msg, &addr->v6, sizeof(addr->v6)) == -1)
                        return (-1);
                break;
        default:
                return (-1);
        }
        addr->aid = aid;
        return 0;
}

int
mrt_extract_prefix(struct ibuf *msg, uint8_t aid, struct bgpd_addr *prefix,
    uint8_t *prefixlen, int verbose)
{
        int r;

        switch (aid) {
        case AID_INET:
                r = nlri_get_prefix(msg, prefix, prefixlen);
                break;
        case AID_INET6:
                r = nlri_get_prefix6(msg, prefix, prefixlen);
                break;
        case AID_VPN_IPv4:
                r = nlri_get_vpn4(msg, prefix, prefixlen, 0);
                break;
        case AID_VPN_IPv6:
                r = nlri_get_vpn6(msg, prefix, prefixlen, 0);
                break;
        default:
                if (verbose)
                        printf("unknown prefix AID %d\n", aid);
                return -1;
        }
        if (r == -1 && verbose)
                printf("failed to parse prefix of AID %d\n", aid);
        return r;
}

int
mrt_parse_state(struct mrt_bgp_state *s, struct mrt_hdr *hdr, struct ibuf *msg,
    int verbose)
{
        struct timespec          t;
        uint32_t                 sas, das, usec;
        uint16_t                 sas16, das16, afi;
        uint8_t                  aid;

        t.tv_sec = ntohl(hdr->timestamp);
        t.tv_nsec = 0;

        /* handle the microsec field for _ET header */
        if (ntohs(hdr->type) == MSG_PROTOCOL_BGP4MP_ET) {
                if (ibuf_get_n32(msg, &usec) == -1)
                        return (-1);
                t.tv_nsec = usec * 1000;
        }

        switch (ntohs(hdr->subtype)) {
        case BGP4MP_STATE_CHANGE:
                if (ibuf_get_n16(msg, &sas16) == -1 ||  /* source as */
                    ibuf_get_n16(msg, &das16) == -1 ||  /* dest as */
                    ibuf_skip(msg, 2) == -1 ||          /* if_index */
                    ibuf_get_n16(msg, &afi) == -1)      /* afi */
                        return (-1);
                sas = sas16;
                das = das16;
                break;
        case BGP4MP_STATE_CHANGE_AS4:
                if (ibuf_get_n32(msg, &sas) == -1 ||    /* source as */
                    ibuf_get_n32(msg, &das) == -1 ||    /* dest as */
                    ibuf_skip(msg, 2) == -1 ||          /* if_index */
                    ibuf_get_n16(msg, &afi) == -1)      /* afi */
                        return (-1);
                break;
        default:
                errx(1, "mrt_parse_state: bad subtype");
        }

        /* src & dst addr */
        if ((aid = mrt_afi2aid(afi, -1, verbose)) == AID_UNSPEC)
                return (-1);

        memset(s, 0, sizeof(*s));
        s->time = t;
        s->src_as = sas;
        s->dst_as = das;

        if (mrt_extract_addr(msg, &s->src, aid) == -1)
                return (-1);
        if (mrt_extract_addr(msg, &s->dst, aid) == -1)
                return (-1);

        /* states */
        if (ibuf_get_n16(msg, &s->old_state) == -1 ||
            ibuf_get_n16(msg, &s->new_state) == -1)
                return (-1);

        return (0);
}

int
mrt_parse_msg(struct mrt_bgp_msg *m, struct mrt_hdr *hdr, struct ibuf *msg,
    int verbose)
{
        struct timespec          t;
        uint32_t                 sas, das, usec;
        uint16_t                 sas16, das16, afi;
        int                      addpath = 0;
        uint8_t                  aid;

        t.tv_sec = ntohl(hdr->timestamp);
        t.tv_nsec = 0;

        /* handle the microsec field for _ET header */
        if (ntohs(hdr->type) == MSG_PROTOCOL_BGP4MP_ET) {
                if (ibuf_get_n32(msg, &usec) == -1)
                        return (-1);
                t.tv_nsec = usec * 1000;
        }

        switch (ntohs(hdr->subtype)) {
        case BGP4MP_MESSAGE_ADDPATH:
        case BGP4MP_MESSAGE_LOCAL_ADDPATH:
                addpath = 1;
                /* FALLTHROUGH */
        case BGP4MP_MESSAGE:
        case BGP4MP_MESSAGE_LOCAL:
                if (ibuf_get_n16(msg, &sas16) == -1 ||  /* source as */
                    ibuf_get_n16(msg, &das16) == -1 ||  /* dest as */
                    ibuf_skip(msg, 2) == -1 ||          /* if_index */
                    ibuf_get_n16(msg, &afi) == -1)      /* afi */
                        return (-1);
                sas = sas16;
                das = das16;
                break;
        case BGP4MP_MESSAGE_AS4_ADDPATH:
        case BGP4MP_MESSAGE_AS4_LOCAL_ADDPATH:
                addpath = 1;
                /* FALLTHROUGH */
        case BGP4MP_MESSAGE_AS4:
        case BGP4MP_MESSAGE_AS4_LOCAL:
                if (ibuf_get_n32(msg, &sas) == -1 ||    /* source as */
                    ibuf_get_n32(msg, &das) == -1 ||    /* dest as */
                    ibuf_skip(msg, 2) == -1 ||          /* if_index */
                    ibuf_get_n16(msg, &afi) == -1)      /* afi */
                        return (-1);
                break;
        default:
                errx(1, "mrt_parse_msg: bad subtype");
        }

        /* src & dst addr */
        if ((aid = mrt_afi2aid(afi, -1, verbose)) == AID_UNSPEC)
                return (-1);

        memset(m, 0, sizeof(*m));
        m->time = t;
        m->src_as = sas;
        m->dst_as = das;
        m->add_path = addpath;

        if (mrt_extract_addr(msg, &m->src, aid) == -1 ||
            mrt_extract_addr(msg, &m->dst, aid) == -1)
                return (-1);

        /* msg */
        ibuf_from_ibuf(&m->msg, msg);
        return (0);
}