/*	$OpenBSD: util.c,v 1.98 2025/12/24 07:59:55 claudio Exp $ */

/*
 * Copyright (c) 2006 Claudio Jeker <claudio@openbsd.org>
 * Copyright (c) 2003, 2004 Henning Brauer <henning@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 <arpa/inet.h>
#include <endian.h>
#include <errno.h>
#include <limits.h>
#include <netdb.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <vis.h>

#include "bgpd.h"
#include "rde.h"

const char *
log_addr(const struct bgpd_addr *addr)
{
	static char	buf[74];
	struct sockaddr *sa;
	socklen_t	len;

	sa = addr2sa(addr, 0, &len);
	switch (addr->aid) {
	case AID_INET:
	case AID_INET6:
		return log_sockaddr(sa, len);
	case AID_VPN_IPv4:
	case AID_VPN_IPv6:
		snprintf(buf, sizeof(buf), "%s %s", log_rd(addr->rd),
		    log_sockaddr(sa, len));
		return (buf);
	case AID_EVPN:
		return log_evpnaddr(addr, sa, len);
		break;
	}
	return ("???");
}

static const char *
log_mac(const uint8_t mac[ETHER_ADDR_LEN])
{
	static char buf[18];

	snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x", mac[0],
	    mac[1], mac[2], mac[3], mac[4], mac[5]);

	return (buf);
}

static const uint8_t zero_esi[ESI_ADDR_LEN];

static const char *
log_esi(const uint8_t esi[ESI_ADDR_LEN])
{
	static char buf[30];

	if (memcmp(esi, zero_esi, sizeof(zero_esi)) == 0)
		return ("");

	snprintf(buf, sizeof(buf),
	    "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", esi[0],
	    esi[1], esi[2], esi[3], esi[4], esi[5], esi[6], esi[7], esi[8],
	    esi[9]);

	return (buf);
}

const char *
log_evpnaddr(const struct bgpd_addr *addr, struct sockaddr *sa,
    socklen_t salen)
{
	static char	buf[138];
	uint32_t	vni;
	uint8_t		len;

	switch (addr->evpn.type) {
	case EVPN_ROUTE_TYPE_2:
		memcpy(&vni, addr->labelstack, addr->labellen);
		snprintf(buf, sizeof(buf), "[2]:[%s]:[%s]:[%d]:[48]:[%s]",
		    log_rd(addr->rd), log_esi(addr->evpn.esi), htonl(vni) >> 8,
		    log_mac(addr->evpn.mac));
		if (sa != NULL) {
			len = strlen(buf);
			snprintf(buf+len, sizeof(buf)-len, ":[%d]:[%s]",
			    sa->sa_family == AF_INET ? 32 : 128,
			    log_sockaddr(sa, salen));
		}
		break;
	case EVPN_ROUTE_TYPE_3:
		if (sa != NULL) {
			memcpy(&vni, addr->labelstack, addr->labellen);
			snprintf(buf, sizeof(buf), "[3]:[%s]:[%d]:[%s]",
			    log_rd(addr->rd),
			    sa->sa_family == AF_INET ? 32 : 128,
			    log_sockaddr(sa, salen));
		}
		break;
	default:
		break;
	}
	return (buf);
}

const char *
log_in6addr(const struct in6_addr *addr)
{
	struct sockaddr_in6	sa_in6;

	memset(&sa_in6, 0, sizeof(sa_in6));
	sa_in6.sin6_family = AF_INET6;
	memcpy(&sa_in6.sin6_addr, addr, sizeof(sa_in6.sin6_addr));

#ifdef __KAME__
	/* XXX thanks, KAME, for this ugliness... adopted from route/show.c */
	if ((IN6_IS_ADDR_LINKLOCAL(&sa_in6.sin6_addr) ||
	    IN6_IS_ADDR_MC_LINKLOCAL(&sa_in6.sin6_addr) ||
	    IN6_IS_ADDR_MC_NODELOCAL(&sa_in6.sin6_addr)) &&
	    sa_in6.sin6_scope_id == 0) {
		uint16_t tmp16;
		memcpy(&tmp16, &sa_in6.sin6_addr.s6_addr[2], sizeof(tmp16));
		sa_in6.sin6_scope_id = ntohs(tmp16);
		sa_in6.sin6_addr.s6_addr[2] = 0;
		sa_in6.sin6_addr.s6_addr[3] = 0;
	}
#endif

	return (log_sockaddr((struct sockaddr *)&sa_in6, sizeof(sa_in6)));
}

const char *
log_sockaddr(struct sockaddr *sa, socklen_t len)
{
	static char	buf[4][NI_MAXHOST];
	static int	bufidx;

	bufidx = (bufidx + 1) % 4;
	if (sa == NULL || getnameinfo(sa, len, buf[bufidx], sizeof(buf[0]),
	    NULL, 0, NI_NUMERICHOST))
		return ("(unknown)");
	else
		return (buf[bufidx]);
}

const char *
log_as(uint32_t as)
{
	static char	buf[11];	/* "4294967294\0" */

	if (snprintf(buf, sizeof(buf), "%u", as) < 0)
		return ("?");

	return (buf);
}

const char *
log_rd(uint64_t rd)
{
	static char	buf[32];
	struct in_addr	addr;
	uint32_t	u32;
	uint16_t	u16;

	rd = be64toh(rd);
	switch (rd >> 48) {
	case EXT_COMMUNITY_TRANS_TWO_AS:
		u32 = rd & 0xffffffff;
		u16 = (rd >> 32) & 0xffff;
		snprintf(buf, sizeof(buf), "rd %hu:%u", u16, u32);
		break;
	case EXT_COMMUNITY_TRANS_FOUR_AS:
		u32 = (rd >> 16) & 0xffffffff;
		u16 = rd & 0xffff;
		snprintf(buf, sizeof(buf), "rd %s:%hu", log_as(u32), u16);
		break;
	case EXT_COMMUNITY_TRANS_IPV4:
		u32 = (rd >> 16) & 0xffffffff;
		u16 = rd & 0xffff;
		addr.s_addr = htonl(u32);
		snprintf(buf, sizeof(buf), "rd %s:%hu", inet_ntoa(addr), u16);
		break;
	default:
		snprintf(buf, sizeof(buf), "rd #%016llx",
		    (unsigned long long)rd);
		break;
	}
	return (buf);
}

const struct ext_comm_pairs iana_ext_comms[] = IANA_EXT_COMMUNITIES;

/* NOTE: this function does not check if the type/subtype combo is
 * actually valid. */
const char *
log_ext_subtype(int type, uint8_t subtype)
{
	static char etype[16];
	const struct ext_comm_pairs *cp;

	for (cp = iana_ext_comms; cp->subname != NULL; cp++) {
		if ((type == cp->type || type == -1) && subtype == cp->subtype)
			return (cp->subname);
	}
	if (type == -1)
		return ("???");
	snprintf(etype, sizeof(etype), "[%hhx:%hhx]", (uint8_t)type, subtype);
	return (etype);
}

const char *
log_reason(const char *communication) {
	static char buf[(REASON_LEN - 1) * 4 + 1];

	strnvis(buf, communication, sizeof(buf), VIS_NL | VIS_OCTAL);

	return buf;
}

static const char *
log_expires(time_t expires)
{
	static char buf[32];

	buf[0] = '\0';
	if (expires != 0)
		snprintf(buf, sizeof(buf), " expires %lld", (long long)expires);
	return buf;
}

const char *
log_roa(struct roa *roa)
{
	static char buf[256];
	struct bgpd_addr addr = { .aid = roa->aid, .v6 = roa->prefix.inet6 };
	char maxbuf[32];

	maxbuf[0] = '\0';
	if (roa->prefixlen != roa->maxlen)
		snprintf(maxbuf, sizeof(maxbuf), " maxlen %u", roa->maxlen);
	snprintf(buf, sizeof(buf), "%s/%u%s source-as %u%s", log_addr(&addr),
	    roa->prefixlen, maxbuf, roa->asnum, log_expires(roa->expires));
	return buf;
}

const char *
log_aspa(struct aspa_set *aspa)
{
	static char errbuf[256];
	static char *buf;
	static size_t len;
	char asbuf[16];
	size_t needed;
	uint32_t i;

	/* include enough space for header and trailer */
	if ((uint64_t)aspa->num > (SIZE_MAX / sizeof(asbuf) - 72))
		goto fail;
	needed = aspa->num * sizeof(asbuf) + 72;
	if (needed > len) {
		char *nbuf;

		if ((nbuf = realloc(buf, needed)) == NULL)
			goto fail;
		len = needed;
		buf = nbuf;
	}

	snprintf(buf, len, "customer-as %s%s provider-as { ",
	    log_as(aspa->as), log_expires(aspa->expires));

	for (i = 0; i < aspa->num; i++) {
		snprintf(asbuf, sizeof(asbuf), "%s ", log_as(aspa->tas[i]));
		if (strlcat(buf, asbuf, len) >= len)
			goto fail;
	}
	if (strlcat(buf, "}", len) >= len)
		goto fail;
	return buf;

 fail:
	free(buf);
	buf = NULL;
	len = 0;
	snprintf(errbuf, sizeof(errbuf), "customer-as %s%s provider-as { ... }",
	    log_as(aspa->as), log_expires(aspa->expires));
	return errbuf;
}

const char *
log_aspath_error(int error)
{
	static char buf[20];

	switch (error) {
	case AS_ERR_LEN:
		return "inconsistent length";
	case AS_ERR_TYPE:
		return "unknown segment type";
	case AS_ERR_BAD:
		return "invalid encoding";
	case AS_ERR_SOFT:
		return "soft failure";
	default:
		snprintf(buf, sizeof(buf), "unknown %d", error);
		return buf;
	}
}

const char *
log_rtr_error(enum rtr_error err)
{
	static char buf[20];

	switch (err) {
	case NO_ERROR:
		return "No Error";
	case CORRUPT_DATA:
		return "Corrupt Data";
	case INTERNAL_ERROR:
		return "Internal Error";
	case NO_DATA_AVAILABLE:
		return "No Data Available";
	case INVALID_REQUEST:
		return "Invalid Request";
	case UNSUPP_PROTOCOL_VERS:
		return "Unsupported Protocol Version";
	case UNSUPP_PDU_TYPE:
		return "Unsupported PDU Type";
	case UNK_REC_WDRAWL:
		return "Withdrawal of Unknown Record";
	case DUP_REC_RECV:
		return "Duplicate Announcement Received";
	case UNEXP_PROTOCOL_VERS:
		return "Unexpected Protocol Version";
	default:
		snprintf(buf, sizeof(buf), "unknown %u", err);
		return buf;
	}
}

const char *
log_policy(enum role role)
{
	switch (role) {
	case ROLE_PROVIDER:
		return "provider";
	case ROLE_RS:
		return "rs";
	case ROLE_RS_CLIENT:
		return "rs-client";
	case ROLE_CUSTOMER:
		return "customer";
	case ROLE_PEER:
		return "peer";
	default:
		return "unknown";
	}
}

const char *
log_capability(uint8_t capa)
{
	static char buf[20];

	switch (capa) {
	case CAPA_MP:
		return "Multiprotocol Extensions";
	case CAPA_REFRESH:
		return "Route Refresh";
	case CAPA_EXT_NEXTHOP:
		return "Extended Nexhop Encoding";
	case CAPA_EXT_MSG:
		return "Extended Message";
	case CAPA_ROLE:
		return "BGP Role";
	case CAPA_RESTART:
		return "Graceful Restart";
	case CAPA_AS4BYTE:
		return "4-octet AS number";
	case CAPA_ADD_PATH:
		return "ADD-PATH";
	case CAPA_ENHANCED_RR:
		return "Enhanced Route Refresh";
	default:
		snprintf(buf, sizeof(buf), "unknown %u", capa);
		return buf;
	}
}

static const char *
aspath_delim(uint8_t seg_type, int closing)
{
	static char db[8];

	switch (seg_type) {
	case AS_SET:
		if (!closing)
			return ("{ ");
		else
			return (" }");
	case AS_SEQUENCE:
		return ("");
	case AS_CONFED_SEQUENCE:
		if (!closing)
			return ("( ");
		else
			return (" )");
	case AS_CONFED_SET:
		if (!closing)
			return ("[ ");
		else
			return (" ]");
	default:
		if (!closing)
			snprintf(db, sizeof(db), "!%u ", seg_type);
		else
			snprintf(db, sizeof(db), " !%u", seg_type);
		return (db);
	}
}

static int
aspath_snprint(char *buf, size_t size, struct ibuf *in)
{
#define UPDATE()						\
	do {							\
		if (r < 0 || (unsigned int)r >= size)		\
			return (-1);				\
		size -= r;					\
		buf += r;					\
	} while (0)

	struct ibuf	data;
	uint32_t	as;
	int		r, n = 0;
	uint8_t		i, seg_type, seg_len;

	ibuf_from_ibuf(&data, in);
	while (ibuf_size(&data) > 0) {
		if (ibuf_get_n8(&data, &seg_type) == -1 ||
		    ibuf_get_n8(&data, &seg_len) == -1 ||
		    seg_len == 0)
			return (-1);

		r = snprintf(buf, size, "%s%s", n++ != 0 ? " " : "",
		    aspath_delim(seg_type, 0));
		UPDATE();

		for (i = 0; i < seg_len; i++) {
			if (ibuf_get_n32(&data, &as) == -1)
				return -1;

			r = snprintf(buf, size, "%s", log_as(as));
			UPDATE();
			if (i + 1 < seg_len) {
				r = snprintf(buf, size, " ");
				UPDATE();
			}
		}
		r = snprintf(buf, size, "%s", aspath_delim(seg_type, 1));
		UPDATE();
	}
	/* ensure that we have a valid C-string especially for empty as path */
	*buf = '\0';
	return (0);
#undef UPDATE
}

static ssize_t
aspath_strsize(struct ibuf *in)
{
	struct ibuf	 buf;
	ssize_t		 total_size = 0;
	uint32_t	 as;
	uint8_t		 i, seg_type, seg_len;

	ibuf_from_ibuf(&buf, in);
	while (ibuf_size(&buf) > 0) {
		if (ibuf_get_n8(&buf, &seg_type) == -1 ||
		    ibuf_get_n8(&buf, &seg_len) == -1 ||
		    seg_len == 0)
			return (-1);

		if (total_size != 0)
			total_size += 1;
		total_size += strlen(aspath_delim(seg_type, 0));

		for (i = 0; i < seg_len; i++) {
			if (ibuf_get_n32(&buf, &as) == -1)
				return (-1);

			do {
				total_size++;
			} while ((as = as / 10) != 0);
		}
		total_size += seg_len - 1;

		total_size += strlen(aspath_delim(seg_type, 1));
	}
	return (total_size + 1);
}

int
aspath_asprint(char **ret, struct ibuf *data)
{
	ssize_t	slen;

	if ((slen = aspath_strsize(data)) == -1) {
		*ret = NULL;
		errno = EINVAL;
		return (-1);
	}

	*ret = malloc(slen);
	if (*ret == NULL)
		return (-1);

	if (aspath_snprint(*ret, slen, data) == -1) {
		free(*ret);
		*ret = NULL;
		errno = EINVAL;
		return (-1);
	}

	return (0);
}

/*
 * Extract the asnum out of the as segment at the specified position.
 * Direct access is not possible because of non-aligned reads.
 * Only works on verified 4-byte AS paths.
 */
uint32_t
aspath_extract(const void *seg, int pos)
{
	const u_char	*ptr = seg;
	uint32_t	 as;

	/* minimal pos check, return 0 since that is an invalid ASN */
	if (pos < 0 || pos >= ptr[1])
		return (0);
	ptr += 2 + sizeof(uint32_t) * pos;
	memcpy(&as, ptr, sizeof(uint32_t));
	return (ntohl(as));
}

/*
 * Verify that the aspath is correctly encoded.
 */
int
aspath_verify(struct ibuf *in, int as4byte, int permit_set)
{
	struct ibuf	 buf;
	int		 pos, error = 0;
	uint8_t		 seg_len, seg_type;

	ibuf_from_ibuf(&buf, in);
	if (ibuf_size(&buf) & 1) {
		/* odd length aspath are invalid */
		error = AS_ERR_BAD;
		goto done;
	}

	while (ibuf_size(&buf) > 0) {
		if (ibuf_get_n8(&buf, &seg_type) == -1 ||
		    ibuf_get_n8(&buf, &seg_len) == -1) {
			error = AS_ERR_LEN;
			goto done;
		}

		if (seg_len == 0) {
			/* empty aspath segments are not allowed */
			error = AS_ERR_BAD;
			goto done;
		}

		/*
		 * BGP confederations should not show up but consider them
		 * as a soft error which invalidates the path but keeps the
		 * bgp session running.
		 */
		if (seg_type == AS_CONFED_SEQUENCE || seg_type == AS_CONFED_SET)
			error = AS_ERR_SOFT;
		/*
		 * If AS_SET filtering (RFC6472) is on, error out on AS_SET
		 * as well.
		 */
		if (!permit_set && seg_type == AS_SET)
			error = AS_ERR_SOFT;
		if (seg_type != AS_SET && seg_type != AS_SEQUENCE &&
		    seg_type != AS_CONFED_SEQUENCE &&
		    seg_type != AS_CONFED_SET) {
			error = AS_ERR_TYPE;
			goto done;
		}

		/* RFC 7607 - AS 0 is considered malformed */
		for (pos = 0; pos < seg_len; pos++) {
			uint32_t as;

			if (as4byte) {
				if (ibuf_get_n32(&buf, &as) == -1) {
					error = AS_ERR_LEN;
					goto done;
				}
			} else {
				uint16_t tmp;
				if (ibuf_get_n16(&buf, &tmp) == -1) {
					error = AS_ERR_LEN;
					goto done;
				}
				as = tmp;
			}
			if (as == 0)
				error = AS_ERR_SOFT;
		}
	}

 done:
	return (error);	/* aspath is valid but probably not loop free */
}

/*
 * convert a 2 byte aspath to a 4 byte one.
 */
struct ibuf *
aspath_inflate(struct ibuf *in)
{
	struct ibuf	*out;
	uint16_t	 short_as;
	uint8_t		 seg_type, seg_len;

	/*
	 * Allocate enough space for the worst case.
	 * XXX add 1 byte for the empty ASPATH case since we can't
	 * allocate an ibuf of 0 length.
	 */
	if ((out = ibuf_open(ibuf_size(in) * 2 + 1)) == NULL)
		return (NULL);

	/* then copy the aspath */
	while (ibuf_size(in) > 0) {
		if (ibuf_get_n8(in, &seg_type) == -1 ||
		    ibuf_get_n8(in, &seg_len) == -1 ||
		    seg_len == 0)
			goto fail;
		if (ibuf_add_n8(out, seg_type) == -1 ||
		    ibuf_add_n8(out, seg_len) == -1)
			goto fail;

		for (; seg_len > 0; seg_len--) {
			if (ibuf_get_n16(in, &short_as) == -1)
				goto fail;
			if (ibuf_add_n32(out, short_as) == -1)
				goto fail;
		}
	}

	return (out);

fail:
	ibuf_free(out);
	return (NULL);
}

static const u_char	addrmask[] = { 0x00, 0x80, 0xc0, 0xe0, 0xf0,
			    0xf8, 0xfc, 0xfe, 0xff };

/* NLRI functions to extract prefixes from the NLRI blobs */
int
extract_prefix(const u_char *p, int len, void *va, uint8_t pfxlen, uint8_t max)
{
	u_char		*a = va;
	int		 plen;

	plen = PREFIX_SIZE(pfxlen) - 1;
	if (len < plen || max < plen)
		return -1;

	while (pfxlen > 0) {
		if (pfxlen < 8) {
			*a++ = *p++ & addrmask[pfxlen];
			break;
		} else {
			*a++ = *p++;
			pfxlen -= 8;
		}
	}
	return (plen);
}

static int
extract_prefix_buf(struct ibuf *buf, void *va, uint8_t pfxlen, uint8_t max)
{
	u_char		*a = va;
	unsigned int	 plen;
	uint8_t		 tmp;

	plen = PREFIX_SIZE(pfxlen) - 1;
	if (ibuf_size(buf) < plen || max < plen)
		return -1;

	while (pfxlen > 0) {
		if (ibuf_get_n8(buf, &tmp) == -1)
			return -1;

		if (pfxlen < 8) {
			*a++ = tmp & addrmask[pfxlen];
			break;
		} else {
			*a++ = tmp;
			pfxlen -= 8;
		}
	}
	return (0);
}

int
nlri_get_prefix(struct ibuf *buf, struct bgpd_addr *prefix, uint8_t *prefixlen)
{
	uint8_t	 pfxlen;

	if (ibuf_get_n8(buf, &pfxlen) == -1)
		return (-1);
	if (pfxlen > 32)
		return (-1);

	memset(prefix, 0, sizeof(struct bgpd_addr));
	prefix->aid = AID_INET;

	if (extract_prefix_buf(buf, &prefix->v4, pfxlen,
	    sizeof(prefix->v4)) == -1)
		return (-1);

	*prefixlen = pfxlen;
	return (0);
}

int
nlri_get_prefix6(struct ibuf *buf, struct bgpd_addr *prefix, uint8_t *prefixlen)
{
	uint8_t	pfxlen;

	if (ibuf_get_n8(buf, &pfxlen) == -1)
		return (-1);
	if (pfxlen > 128)
		return (-1);

	memset(prefix, 0, sizeof(struct bgpd_addr));
	prefix->aid = AID_INET6;

	if (extract_prefix_buf(buf, &prefix->v6, pfxlen,
	    sizeof(prefix->v6)) == -1)
		return (-1);

	*prefixlen = pfxlen;
	return (0);
}

int
nlri_get_vpn4(struct ibuf *buf, struct bgpd_addr *prefix,
    uint8_t *prefixlen, int withdraw)
{
	int		 done = 0;
	uint8_t		 pfxlen;

	if (ibuf_get_n8(buf, &pfxlen) == -1)
		return (-1);

	memset(prefix, 0, sizeof(struct bgpd_addr));
	prefix->aid = AID_VPN_IPv4;

	/* label stack */
	do {
		if (prefix->labellen + 3U > sizeof(prefix->labelstack) ||
		    pfxlen < 3 * 8)
			return (-1);
		if (withdraw) {
			/* on withdraw ignore the labelstack all together */
			if (ibuf_skip(buf, 3) == -1)
				return (-1);
			pfxlen -= 3 * 8;
			break;
		}
		if (ibuf_get(buf, &prefix->labelstack[prefix->labellen], 3) ==
		    -1)
			return -1;
		if (prefix->labelstack[prefix->labellen + 2] &
		    BGP_MPLS_BOS)
			done = 1;
		prefix->labellen += 3;
		pfxlen -= 3 * 8;
	} while (!done);

	/* RD */
	if (pfxlen < sizeof(uint64_t) * 8 ||
	    ibuf_get_h64(buf, &prefix->rd) == -1)
		return (-1);
	pfxlen -= sizeof(uint64_t) * 8;

	/* prefix */
	if (pfxlen > 32)
		return (-1);
	if (extract_prefix_buf(buf, &prefix->v4, pfxlen,
	    sizeof(prefix->v4)) == -1)
		return (-1);

	*prefixlen = pfxlen;
	return (0);
}

int
nlri_get_vpn6(struct ibuf *buf, struct bgpd_addr *prefix,
    uint8_t *prefixlen, int withdraw)
{
	int		done = 0;
	uint8_t		pfxlen;

	if (ibuf_get_n8(buf, &pfxlen) == -1)
		return (-1);

	memset(prefix, 0, sizeof(struct bgpd_addr));
	prefix->aid = AID_VPN_IPv6;

	/* label stack */
	do {
		if (prefix->labellen + 3U > sizeof(prefix->labelstack) ||
		    pfxlen < 3 * 8)
			return (-1);
		if (withdraw) {
			/* on withdraw ignore the labelstack all together */
			if (ibuf_skip(buf, 3) == -1)
				return (-1);
			pfxlen -= 3 * 8;
			break;
		}

		if (ibuf_get(buf, &prefix->labelstack[prefix->labellen], 3) ==
		    -1)
			return (-1);
		if (prefix->labelstack[prefix->labellen + 2] &
		    BGP_MPLS_BOS)
			done = 1;
		prefix->labellen += 3;
		pfxlen -= 3 * 8;
	} while (!done);

	/* RD */
	if (pfxlen < sizeof(uint64_t) * 8 ||
	    ibuf_get_h64(buf, &prefix->rd) == -1)
		return (-1);
	pfxlen -= sizeof(uint64_t) * 8;

	/* prefix */
	if (pfxlen > 128)
		return (-1);
	if (extract_prefix_buf(buf, &prefix->v6, pfxlen,
	    sizeof(prefix->v6)) == -1)
		return (-1);

	*prefixlen = pfxlen;
	return (0);
}

int
nlri_get_evpn(struct ibuf *buf, struct bgpd_addr *prefix,
    uint8_t *prefixlen)
{
	struct ibuf	evpnbuf;
	uint8_t		nlrilen, type, pfxlen = 0, maclen = 0;

	if (ibuf_get_n8(buf, &type) == -1)
		return (-1);
	if (ibuf_get_n8(buf, &nlrilen) == -1)
		return (-1);

	memset(prefix, 0, sizeof(struct bgpd_addr));
	prefix->aid = AID_EVPN;

	switch (type) {
	case EVPN_ROUTE_TYPE_2:
		if (ibuf_get_ibuf(buf, nlrilen, &evpnbuf) == -1)
			return (-1);
		prefix->evpn.type = EVPN_ROUTE_TYPE_2;
		/* RD */
		if (ibuf_get_h64(&evpnbuf, &prefix->rd) == -1)
			return (-1);
		/* ESI */
		if (ibuf_get(&evpnbuf, &prefix->evpn.esi,
		    sizeof(prefix->evpn.esi)) == -1)
			return (-1);
		/* Ethernet Tag */
		if (ibuf_get_h32(&evpnbuf, &prefix->evpn.ethtag) == -1)
			return (-1);
		/* MAC length */
		if (ibuf_get_n8(&evpnbuf, &maclen) == -1)
			return (-1);
		if (maclen != 48)
			return (-1);
		/* MAC address */
		if (ibuf_get(&evpnbuf, &prefix->evpn.mac,
		    sizeof(prefix->evpn.mac)) == -1)
			return (-1);
		/* Prefix length */
		if (ibuf_get_n8(&evpnbuf, &pfxlen) == -1)
			return (-1);
		/* Destination */
		if (pfxlen == 0) {
			/* nothing */
		} else if (pfxlen == 32) {
			prefix->evpn.aid = AID_INET;
			if (ibuf_get(&evpnbuf, &prefix->evpn.v4,
			    sizeof(prefix->evpn.v4)) == -1)
				return (-1);
		} else if (pfxlen == 128) {
			prefix->evpn.aid = AID_INET6;
			if (ibuf_get(&evpnbuf, &prefix->evpn.v6,
			    sizeof(prefix->evpn.v6)) == -1)
				return (-1);
		} else
			return (-1);
		/* VNI */
		if (ibuf_size(&evpnbuf) != 3 && ibuf_size(&evpnbuf) != 6)
			return (-1);
		prefix->labellen = ibuf_size(&evpnbuf);
		if (ibuf_get(&evpnbuf, prefix->labelstack,
		    prefix->labellen) == -1)
			return (-1);
		break;
	case EVPN_ROUTE_TYPE_3:
		if (ibuf_get_ibuf(buf, nlrilen, &evpnbuf) == -1)
			return (-1);
		prefix->evpn.type = EVPN_ROUTE_TYPE_3;
		/* RD */
		if (ibuf_get_h64(&evpnbuf, &prefix->rd) == -1)
			return (-1);
		/* Ethernet Tag */
		if (ibuf_get_h32(&evpnbuf, &prefix->evpn.ethtag) == -1)
			return (-1);
		/* Prefix length */
		if (ibuf_get_n8(&evpnbuf, &pfxlen) == -1)
			return (-1);
		/* Destination */
		if (pfxlen == 32) {
			prefix->evpn.aid = AID_INET;
			if (ibuf_get(&evpnbuf, &prefix->evpn.v4,
			    sizeof(prefix->evpn.v4)) == -1)
				return (-1);
		} else if (pfxlen == 128) {
			prefix->evpn.aid = AID_INET6;
			if (ibuf_get(&evpnbuf, &prefix->evpn.v6,
			    sizeof(prefix->evpn.v6)) == -1)
				return (-1);
		} else
			return (-1);
		if (ibuf_size(&evpnbuf) != 0)
			return (-1);
		break;
	default:
		return (-1);
	}

	*prefixlen = pfxlen;
	return (0);
}

static in_addr_t
prefixlen2mask(uint8_t prefixlen)
{
	if (prefixlen == 0)
		return (0);

	return (0xffffffff << (32 - prefixlen));
}

/*
 * This function will have undefined behaviour if the passed in prefixlen is
 * too large for the respective bgpd_addr address family.
 */
int
prefix_compare(const struct bgpd_addr *a, const struct bgpd_addr *b,
    int prefixlen)
{
	in_addr_t	mask, aa, ba;
	int		i;
	uint8_t		m;

	if (a->aid != b->aid)
		return (a->aid - b->aid);

	switch (a->aid) {
	case AID_VPN_IPv4:
		if (be64toh(a->rd) > be64toh(b->rd))
			return (1);
		if (be64toh(a->rd) < be64toh(b->rd))
			return (-1);
		/* FALLTHROUGH */
	case AID_INET:
		if (prefixlen == 0)
			return (0);
		if (prefixlen > 32)
			return (-1);
		mask = htonl(prefixlen2mask(prefixlen));
		aa = ntohl(a->v4.s_addr & mask);
		ba = ntohl(b->v4.s_addr & mask);
		if (aa > ba)
			return (1);
		if (aa < ba)
			return (-1);
		break;
	case AID_VPN_IPv6:
		if (be64toh(a->rd) > be64toh(b->rd))
			return (1);
		if (be64toh(a->rd) < be64toh(b->rd))
			return (-1);
		/* FALLTHROUGH */
	case AID_INET6:
		if (prefixlen == 0)
			return (0);
		if (prefixlen > 128)
			return (-1);
		for (i = 0; i < prefixlen / 8; i++)
			if (a->v6.s6_addr[i] != b->v6.s6_addr[i])
				return (a->v6.s6_addr[i] - b->v6.s6_addr[i]);
		i = prefixlen % 8;
		if (i) {
			m = 0xff00 >> i;
			if ((a->v6.s6_addr[prefixlen / 8] & m) !=
			    (b->v6.s6_addr[prefixlen / 8] & m))
				return ((a->v6.s6_addr[prefixlen / 8] & m) -
				    (b->v6.s6_addr[prefixlen / 8] & m));
		}
		break;
	default:
		return (-1);
	}

	if (a->aid == AID_VPN_IPv4 || a->aid == AID_VPN_IPv6) {
		if (a->labellen > b->labellen)
			return (1);
		if (a->labellen < b->labellen)
			return (-1);
		return (memcmp(a->labelstack, b->labelstack, a->labellen));
	}
	return (0);

}

void
inet4applymask(struct in_addr *dest, const struct in_addr *src, int prefixlen)
{
	struct in_addr mask;

	mask.s_addr = htonl(prefixlen2mask(prefixlen));
	dest->s_addr = src->s_addr & mask.s_addr;
}

void
inet6applymask(struct in6_addr *dest, const struct in6_addr *src, int prefixlen)
{
	struct in6_addr	mask;
	int		i;

	memset(&mask, 0, sizeof(mask));
	for (i = 0; i < prefixlen / 8; i++)
		mask.s6_addr[i] = 0xff;
	i = prefixlen % 8;
	if (i)
		mask.s6_addr[prefixlen / 8] = 0xff00 >> i;

	for (i = 0; i < 16; i++)
		dest->s6_addr[i] = src->s6_addr[i] & mask.s6_addr[i];
}

void
applymask(struct bgpd_addr *dest, const struct bgpd_addr *src, int prefixlen)
{
	*dest = *src;
	switch (src->aid) {
	case AID_INET:
	case AID_VPN_IPv4:
		inet4applymask(&dest->v4, &src->v4, prefixlen);
		break;
	case AID_INET6:
	case AID_VPN_IPv6:
		inet6applymask(&dest->v6, &src->v6, prefixlen);
		break;
	}
}

/* address family translation functions */
const struct aid aid_vals[AID_MAX] = AID_VALS;

const char *
aid2str(uint8_t aid)
{
	if (aid < AID_MAX)
		return (aid_vals[aid].name);
	return ("unknown AID");
}

int
aid2afi(uint8_t aid, uint16_t *afi, uint8_t *safi)
{
	if (aid != AID_UNSPEC && aid < AID_MAX) {
		*afi = aid_vals[aid].afi;
		*safi = aid_vals[aid].safi;
		return (0);
	}
	return (-1);
}

int
afi2aid(uint16_t afi, uint8_t safi, uint8_t *aid)
{
	uint8_t i;

	for (i = AID_MIN; i < AID_MAX; i++)
		if (aid_vals[i].afi == afi && aid_vals[i].safi == safi) {
			*aid = i;
			return (0);
		}

	return (-1);
}

sa_family_t
aid2af(uint8_t aid)
{
	if (aid < AID_MAX)
		return (aid_vals[aid].af);
	return (AF_UNSPEC);
}

int
af2aid(sa_family_t af, uint8_t safi, uint8_t *aid)
{
	uint8_t i;

	if (safi == 0) /* default to unicast subclass */
		safi = SAFI_UNICAST;

	for (i = AID_UNSPEC; i < AID_MAX; i++)
		if (aid_vals[i].af == af && aid_vals[i].safi == safi) {
			*aid = i;
			return (0);
		}

	return (-1);
}

static socklen_t
addr2sa_in6(struct sockaddr_in6 *sin6, struct in6_addr in6, uint16_t port,
    uint32_t scope_id)
{
	sin6->sin6_family = AF_INET6;
	memcpy(&sin6->sin6_addr, &in6, sizeof(sin6->sin6_addr));
	sin6->sin6_port = htons(port);
	sin6->sin6_scope_id = scope_id;
	return (sizeof(struct sockaddr_in6));
}

static socklen_t
addr2sa_in(struct sockaddr_in *sin, struct in_addr in, uint16_t port)
{
	sin->sin_family = AF_INET;
	sin->sin_addr.s_addr = in.s_addr;
	sin->sin_port = htons(port);
	return (sizeof(struct sockaddr_in));
}

/*
 * Convert a struct bgpd_addr into a struct sockaddr. For VPN addresses
 * the included label stack is ignored and needs to be handled by the caller.
 */
struct sockaddr *
addr2sa(const struct bgpd_addr *addr, uint16_t port, socklen_t *len)
{
	static struct sockaddr_storage	 ss;
	struct sockaddr_in		*sa_in = (struct sockaddr_in *)&ss;
	struct sockaddr_in6		*sa_in6 = (struct sockaddr_in6 *)&ss;

	if (addr == NULL || addr->aid == AID_UNSPEC)
		return (NULL);

	memset(&ss, 0, sizeof(ss));
	switch (addr->aid) {
	case AID_INET:
	case AID_VPN_IPv4:
		*len = addr2sa_in(sa_in, addr->v4, port);
		break;
	case AID_INET6:
	case AID_VPN_IPv6:
		*len = addr2sa_in6(sa_in6, addr->v6, port, addr->scope_id);
		break;
	case AID_EVPN:
		if (addr->evpn.aid == AID_INET)
			*len = addr2sa_in(sa_in, addr->evpn.v4, port);
		else if (addr->evpn.aid == AID_INET6)
			*len = addr2sa_in6(sa_in6, addr->evpn.v6, port,
			    addr->scope_id);
		else {
			*len = 0;
			return (NULL);
		}
		break;
	case AID_FLOWSPECv4:
	case AID_FLOWSPECv6:
	default:
		return (NULL);
	}

	return ((struct sockaddr *)&ss);
}

void
sa2addr(struct sockaddr *sa, struct bgpd_addr *addr, uint16_t *port)
{
	struct sockaddr_in		*sa_in = (struct sockaddr_in *)sa;
	struct sockaddr_in6		*sa_in6 = (struct sockaddr_in6 *)sa;

	memset(addr, 0, sizeof(*addr));
	switch (sa->sa_family) {
	case AF_INET:
		addr->aid = AID_INET;
		memcpy(&addr->v4, &sa_in->sin_addr, sizeof(addr->v4));
		if (port)
			*port = ntohs(sa_in->sin_port);
		break;
	case AF_INET6:
		addr->aid = AID_INET6;
#ifdef __KAME__
		/*
		 * XXX thanks, KAME, for this ugliness...
		 * adopted from route/show.c
		 */
		if ((IN6_IS_ADDR_LINKLOCAL(&sa_in6->sin6_addr) ||
		    IN6_IS_ADDR_MC_LINKLOCAL(&sa_in6->sin6_addr) ||
		    IN6_IS_ADDR_MC_NODELOCAL(&sa_in6->sin6_addr)) &&
		    sa_in6->sin6_scope_id == 0) {
			uint16_t tmp16;
			memcpy(&tmp16, &sa_in6->sin6_addr.s6_addr[2],
			    sizeof(tmp16));
			sa_in6->sin6_scope_id = ntohs(tmp16);
			sa_in6->sin6_addr.s6_addr[2] = 0;
			sa_in6->sin6_addr.s6_addr[3] = 0;
		}
#endif
		memcpy(&addr->v6, &sa_in6->sin6_addr, sizeof(addr->v6));
		addr->scope_id = sa_in6->sin6_scope_id; /* I hate v6 */
		if (port)
			*port = ntohs(sa_in6->sin6_port);
		break;
	}
}

const char *
get_baudrate(unsigned long long baudrate, char *unit)
{
	static char bbuf[16];
	const unsigned long long kilo = 1000;
	const unsigned long long mega = 1000ULL * kilo;
	const unsigned long long giga = 1000ULL * mega;

	if (baudrate > giga)
		snprintf(bbuf, sizeof(bbuf), "%llu G%s",
		    baudrate / giga, unit);
	else if (baudrate > mega)
		snprintf(bbuf, sizeof(bbuf), "%llu M%s",
		    baudrate / mega, unit);
	else if (baudrate > kilo)
		snprintf(bbuf, sizeof(bbuf), "%llu K%s",
		    baudrate / kilo, unit);
	else
		snprintf(bbuf, sizeof(bbuf), "%llu %s",
		    baudrate, unit);

	return (bbuf);
}

/* internal functions needed for bucket sizing, stolen from omalloc.c */

/* using built-in function version */
__attribute__((const)) static inline unsigned int
lb(unsigned int x)
{
	/* I need an extension just for integer-length (: */
	return (sizeof(x) * CHAR_BIT - 1) - __builtin_clz(x);
}

/*
 * https://pvk.ca/Blog/2015/06/27/linear-log-bucketing-fast-versatile-simple/
 * via Tony Finch
 */
static inline unsigned int
bin_of(unsigned int size, unsigned int linear, unsigned int subbin)
{
	unsigned int mask, rounded, rounded_size;
	unsigned int n_bits, shift;

	n_bits = lb(size | (1U << linear));
	shift = n_bits - subbin;
	mask = (1U << shift) - 1;
	rounded = size + mask; /* XXX: overflow. */

	rounded_size = rounded & ~mask;
	return rounded_size;
}

unsigned int
bin_of_attrs(unsigned int count)
{
	/* 4, 8, 12, ... 60, 64, 72, 80, ... */
	return bin_of(count, 5, 3);
}

unsigned int
bin_of_communities(unsigned int count)
{
	/* 8, 16, 24, ... 56, 64, 80, 96, ... */
	return bin_of(count, 5, 2);
}

unsigned int
bin_of_adjout_prefixes(unsigned int count)
{
	/* 1, 2, 3, 4, 6, 8, 12, 16, 24, ... */
	return bin_of(count, 1, 1);
}
