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

/*
 * Copyright (c) 2003, 2004 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/queue.h>

#include <endian.h>
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

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

/*
 * Prefix Table functions:
 * pt_add:    create new prefix and link it into the prefix table
 * pt_remove: Checks if there is no bgp prefix linked to the prefix,
 *            unlinks from the prefix table and frees the pt_entry.
 * pt_get:    get a prefix/prefixlen entry. While pt_lookup searches for the
 *            best matching prefix pt_get only finds the prefix/prefixlen
 *            entry. The speed of pt_get is important for the bgp updates.
 * pt_getaddr: convert the address into a struct bgpd_addr.
 * pt_lookup: lookup a IP in the prefix table. Mainly for "show ip bgp".
 * pt_empty:  returns true if there is no bgp prefix linked to the pt_entry.
 * pt_init:   initialize prefix table.
 * pt_alloc: allocate a AF specific pt_entry. Internal function.
 * pt_free:   free a pt_entry. Internal function.
 */

/* internal prototypes */
static struct pt_entry	*pt_alloc(struct pt_entry *, int len);
static void		 pt_free(struct pt_entry *);

struct pt_entry4 {
	RB_ENTRY(pt_entry)		pt_e;
	struct adjout_prefix		*adjout;
	uint32_t			adjoutlen;
	uint32_t			adjoutavail;
	uint8_t				aid;
	uint8_t				prefixlen;
	uint16_t			len;
	uint32_t			refcnt;
	struct in_addr			prefix4;
};

struct pt_entry6 {
	RB_ENTRY(pt_entry)		pt_e;
	struct adjout_prefix		*adjout;
	uint32_t			adjoutlen;
	uint32_t			adjoutavail;
	uint8_t				aid;
	uint8_t				prefixlen;
	uint16_t			len;
	uint32_t			refcnt;
	struct in6_addr			prefix6;
};

struct pt_entry_vpn4 {
	RB_ENTRY(pt_entry)		pt_e;
	struct adjout_prefix		*adjout;
	uint32_t			adjoutlen;
	uint32_t			adjoutavail;
	uint8_t				aid;
	uint8_t				prefixlen;
	uint16_t			len;
	uint32_t			refcnt;
	uint64_t			rd;
	struct in_addr			prefix4;
	uint8_t				labelstack[21];
	uint8_t				labellen;
	uint8_t				pad1;
	uint8_t				pad2;
};

struct pt_entry_vpn6 {
	RB_ENTRY(pt_entry)		pt_e;
	struct adjout_prefix		*adjout;
	uint32_t			adjoutlen;
	uint32_t			adjoutavail;
	uint8_t				aid;
	uint8_t				prefixlen;
	uint16_t			len;
	uint32_t			refcnt;
	uint64_t			rd;
	struct in6_addr			prefix6;
	uint8_t				labelstack[21];
	uint8_t				labellen;
	uint8_t				pad1;
	uint8_t				pad2;
};

struct pt_entry_evpn {
	RB_ENTRY(pt_entry)		pt_e;
	struct adjout_prefix		*adjout;
	uint32_t			adjoutlen;
	uint32_t			adjoutavail;
	uint8_t				aid;
	uint8_t				prefixlen;
	uint16_t			len;
	uint32_t			refcnt;
	uint64_t			rd;
	uint32_t			ethtag;
	uint8_t				esi[ESI_ADDR_LEN];
	uint8_t				mac[ETHER_ADDR_LEN];
	uint8_t				labelstack[6];
	uint8_t				labellen;
	uint8_t				type;
	uint8_t				vpnaid;
	union {
		struct in_addr	prefix4;
		struct in6_addr	prefix6;
	};
};

struct pt_entry_flow {
	RB_ENTRY(pt_entry)		pt_e;
	struct adjout_prefix		*adjout;
	uint32_t			adjoutlen;
	uint32_t			adjoutavail;
	uint8_t				aid;
	uint8_t				prefixlen;	/* unused ??? */
	uint16_t			len;
	uint32_t			refcnt;
	uint64_t			rd;
	uint8_t				flow[0];	/* NLRI */
};

#define PT_FLOW_SIZE		(offsetof(struct pt_entry_flow, flow))

RB_HEAD(pt_tree, pt_entry);
RB_PROTOTYPE(pt_tree, pt_entry, pt_e, pt_prefix_cmp);
RB_GENERATE(pt_tree, pt_entry, pt_e, pt_prefix_cmp);

struct pt_tree	pttable;

void
pt_init(void)
{
	RB_INIT(&pttable);
}

void
pt_shutdown(void)
{
	if (!RB_EMPTY(&pttable))
		log_debug("pt_shutdown: tree is not empty.");
}

void
pt_getaddr(struct pt_entry *pte, struct bgpd_addr *addr)
{
	struct pt_entry_flow	*pflow;
	struct pt_entry_evpn	*evpn;

	memset(addr, 0, sizeof(struct bgpd_addr));
	addr->aid = pte->aid;
	switch (addr->aid) {
	case AID_INET:
		addr->v4 = ((struct pt_entry4 *)pte)->prefix4;
		break;
	case AID_INET6:
		addr->v6 = ((struct pt_entry6 *)pte)->prefix6;
		/* XXX scope_id ??? */
		break;
	case AID_VPN_IPv4:
		addr->v4 = ((struct pt_entry_vpn4 *)pte)->prefix4;
		addr->rd = ((struct pt_entry_vpn4 *)pte)->rd;
		addr->labellen = ((struct pt_entry_vpn4 *)pte)->labellen;
		memcpy(addr->labelstack,
		    ((struct pt_entry_vpn4 *)pte)->labelstack,
		    addr->labellen);
		break;
	case AID_VPN_IPv6:
		addr->v6 = ((struct pt_entry_vpn6 *)pte)->prefix6;
		addr->rd = ((struct pt_entry_vpn6 *)pte)->rd;
		addr->labellen = ((struct pt_entry_vpn6 *)pte)->labellen;
		memcpy(addr->labelstack,
		    ((struct pt_entry_vpn6 *)pte)->labelstack,
		    addr->labellen);
		break;
	case AID_EVPN:
		evpn = (struct pt_entry_evpn *)pte;
		addr->evpn.type = evpn->type;
		addr->rd = evpn->rd;
		addr->evpn.ethtag = evpn->ethtag;
		addr->labellen = evpn->labellen;
		addr->evpn.aid = evpn->vpnaid;
		memcpy(addr->labelstack, evpn->labelstack, addr->labellen);
		memcpy(addr->evpn.esi, evpn->esi, sizeof(evpn->esi));
		memcpy(addr->evpn.mac, evpn->mac, sizeof(evpn->mac));
		switch (evpn->vpnaid) {
		case AID_INET:
			addr->evpn.v4 = evpn->prefix4;
			break;
		case AID_INET6:
			addr->evpn.v6 = evpn->prefix6;
			break;
		}
		break;
	case AID_FLOWSPECv4:
	case AID_FLOWSPECv6:
		pflow = (struct pt_entry_flow *)pte;
		flowspec_get_addr(pflow->flow, pflow->len - PT_FLOW_SIZE,
		    FLOWSPEC_TYPE_DEST, addr->aid == AID_FLOWSPECv6,
		    addr, &pflow->prefixlen, NULL);
		break;
	default:
		fatalx("pt_getaddr: unknown af");
	}
}

int
pt_getflowspec(struct pt_entry *pte, uint8_t **flow)
{
	struct pt_entry_flow	*pflow;

	switch (pte->aid) {
	case AID_FLOWSPECv4:
	case AID_FLOWSPECv6:
		pflow = (struct pt_entry_flow *)pte;
		*flow = pflow->flow;
		return pflow->len - PT_FLOW_SIZE;
	default:
		fatalx("pt_getflowspec: unknown af");
	}
}

struct pt_entry *
pt_fill(struct bgpd_addr *prefix, int prefixlen)
{
	static struct pt_entry4		pte4;
	static struct pt_entry6		pte6;
	static struct pt_entry_vpn4	pte_vpn4;
	static struct pt_entry_vpn6	pte_vpn6;
	static struct pt_entry_evpn	pte_evpn;

	switch (prefix->aid) {
	case AID_INET:
		memset(&pte4, 0, sizeof(pte4));
		pte4.len = sizeof(pte4);
		pte4.refcnt = UINT32_MAX;
		pte4.aid = prefix->aid;
		if (prefixlen > 32)
			fatalx("pt_fill: bad IPv4 prefixlen");
		inet4applymask(&pte4.prefix4, &prefix->v4, prefixlen);
		pte4.prefixlen = prefixlen;
		return ((struct pt_entry *)&pte4);
	case AID_INET6:
		memset(&pte6, 0, sizeof(pte6));
		pte6.len = sizeof(pte6);
		pte6.refcnt = UINT32_MAX;
		pte6.aid = prefix->aid;
		if (prefixlen > 128)
			fatalx("pt_fill: bad IPv6 prefixlen");
		inet6applymask(&pte6.prefix6, &prefix->v6, prefixlen);
		pte6.prefixlen = prefixlen;
		return ((struct pt_entry *)&pte6);
	case AID_VPN_IPv4:
		memset(&pte_vpn4, 0, sizeof(pte_vpn4));
		pte_vpn4.len = sizeof(pte_vpn4);
		pte_vpn4.refcnt = UINT32_MAX;
		pte_vpn4.aid = prefix->aid;
		if (prefixlen > 32)
			fatalx("pt_fill: bad IPv4 prefixlen");
		inet4applymask(&pte_vpn4.prefix4, &prefix->v4, prefixlen);
		pte_vpn4.prefixlen = prefixlen;
		pte_vpn4.rd = prefix->rd;
		pte_vpn4.labellen = prefix->labellen;
		memcpy(pte_vpn4.labelstack, prefix->labelstack,
		    prefix->labellen);
		return ((struct pt_entry *)&pte_vpn4);
	case AID_VPN_IPv6:
		memset(&pte_vpn6, 0, sizeof(pte_vpn6));
		pte_vpn6.len = sizeof(pte_vpn6);
		pte_vpn6.refcnt = UINT32_MAX;
		pte_vpn6.aid = prefix->aid;
		if (prefixlen > 128)
			fatalx("pt_fill: bad IPv6 prefixlen");
		inet6applymask(&pte_vpn6.prefix6, &prefix->v6, prefixlen);
		pte_vpn6.prefixlen = prefixlen;
		pte_vpn6.rd = prefix->rd;
		pte_vpn6.labellen = prefix->labellen;
		memcpy(pte_vpn6.labelstack, prefix->labelstack,
		    prefix->labellen);
		return ((struct pt_entry *)&pte_vpn6);
	case AID_EVPN:
		memset(&pte_evpn, 0, sizeof(pte_evpn));
		pte_evpn.len = sizeof(pte_evpn);
		pte_evpn.refcnt = UINT32_MAX;
		switch (prefix->evpn.aid) {
		case AID_UNSPEC:
			/* See rfc7432 section 7.2 */
			break;
		case AID_INET:
			pte_evpn.prefix4 = prefix->evpn.v4;
			break;
		case AID_INET6:
			pte_evpn.prefix6 = prefix->evpn.v6;
			break;
		default:
			fatalx("pt_fill: bad EVPN prefixlen");
		}
		pte_evpn.aid = prefix->aid;
		pte_evpn.vpnaid = prefix->evpn.aid;
		pte_evpn.prefixlen = prefixlen;
		pte_evpn.type = prefix->evpn.type;
		pte_evpn.rd = prefix->rd;
		pte_evpn.ethtag = prefix->evpn.ethtag;
		pte_evpn.labellen = prefix->labellen;
		memcpy(pte_evpn.labelstack, prefix->labelstack,
		    pte_evpn.labellen);
		memcpy(pte_evpn.esi, prefix->evpn.esi,
		    sizeof(prefix->evpn.esi));
		memcpy(pte_evpn.mac, prefix->evpn.mac,
		    sizeof(prefix->evpn.mac));
		return ((struct pt_entry *)&pte_evpn);
	default:
		fatalx("pt_fill: unknown af");
	}
}

struct pt_entry *
pt_get(struct bgpd_addr *prefix, int prefixlen)
{
	struct pt_entry	*pte;

	pte = pt_fill(prefix, prefixlen);
	return RB_FIND(pt_tree, &pttable, pte);
}

struct pt_entry *
pt_get_next(struct bgpd_addr *prefix, int prefixlen)
{
	struct pt_entry	*pte;

	pte = pt_fill(prefix, prefixlen);
	return RB_NFIND(pt_tree, &pttable, pte);
}

struct pt_entry *
pt_add(struct bgpd_addr *prefix, int prefixlen)
{
	struct pt_entry		*p = NULL;

	p = pt_fill(prefix, prefixlen);
	p = pt_alloc(p, p->len);

	if (RB_INSERT(pt_tree, &pttable, p) != NULL)
		fatalx("pt_add: insert failed");

	return (p);
}

struct pt_entry *
pt_get_flow(struct flowspec *f)
{
	struct pt_entry *needle;
	union {
		struct pt_entry_flow	flow;
		uint8_t			buf[4096];
	} x;

	needle = (struct pt_entry *)&x.flow;

	memset(needle, 0, PT_FLOW_SIZE);
	needle->aid = f->aid;
	needle->len = f->len + PT_FLOW_SIZE;
	memcpy(((struct pt_entry_flow *)needle)->flow, f->data, f->len);

	return RB_FIND(pt_tree, &pttable, (struct pt_entry *)needle);
}

struct pt_entry *
pt_add_flow(struct flowspec *f)
{
	struct pt_entry *p;
	int len = f->len + PT_FLOW_SIZE;

	p = malloc(len);
	if (p == NULL)
		fatal(__func__);
	rdemem.pt_cnt[f->aid]++;
	rdemem.pt_size[f->aid] += len;
	memset(p, 0, PT_FLOW_SIZE);

	p->len = len;
	p->aid = f->aid;
	memcpy(((struct pt_entry_flow *)p)->flow, f->data, f->len);

	if (RB_INSERT(pt_tree, &pttable, p) != NULL)
		fatalx("pt_add: insert failed");

	return (p);
}

struct pt_entry *
pt_first(uint8_t aid)
{
	struct pt_entry	*pte;
	struct bgpd_addr addr = { .aid = aid };

	if (aid == AID_UNSPEC)
		return RB_MIN(pt_tree, &pttable);

	pte = pt_fill(&addr, 0);
	return RB_NFIND(pt_tree, &pttable, pte);
}

struct pt_entry *
pt_next(struct pt_entry *pte)
{
	return RB_NEXT(pt_tree, &pttable, pte);
}

void
pt_remove(struct pt_entry *pte)
{
	if (pte->refcnt != 0)
		fatalx("pt_remove: entry still holds references");

	if (RB_REMOVE(pt_tree, &pttable, pte) == NULL)
		log_warnx("pt_remove: remove failed.");
	pt_free(pte);
}

struct pt_entry *
pt_lookup(struct bgpd_addr *addr)
{
	struct pt_entry	*p;
	int		 i;

	switch (addr->aid) {
	case AID_INET:
	case AID_VPN_IPv4:
		i = 32;
		break;
	case AID_INET6:
	case AID_VPN_IPv6:
		i = 128;
		break;
	default:
		fatalx("pt_lookup: unknown af");
	}
	for (; i >= 0; i--) {
		p = pt_get(addr, i);
		if (p != NULL)
			return (p);
	}
	return (NULL);
}

int
pt_prefix_cmp(const struct pt_entry *a, const struct pt_entry *b)
{
	const struct pt_entry4		*a4, *b4;
	const struct pt_entry6		*a6, *b6;
	const struct pt_entry_vpn4	*va4, *vb4;
	const struct pt_entry_vpn6	*va6, *vb6;
	const struct pt_entry_flow	*af, *bf;
	const struct pt_entry_evpn	*ea, *eb;
	int				 i;

	if (a->aid > b->aid)
		return (1);
	if (a->aid < b->aid)
		return (-1);

	switch (a->aid) {
	case AID_INET:
		a4 = (const struct pt_entry4 *)a;
		b4 = (const struct pt_entry4 *)b;
		if (ntohl(a4->prefix4.s_addr) > ntohl(b4->prefix4.s_addr))
			return (1);
		if (ntohl(a4->prefix4.s_addr) < ntohl(b4->prefix4.s_addr))
			return (-1);
		if (a4->prefixlen > b4->prefixlen)
			return (1);
		if (a4->prefixlen < b4->prefixlen)
			return (-1);
		return (0);
	case AID_INET6:
		a6 = (const struct pt_entry6 *)a;
		b6 = (const struct pt_entry6 *)b;

		i = memcmp(&a6->prefix6, &b6->prefix6, sizeof(struct in6_addr));
		if (i > 0)
			return (1);
		if (i < 0)
			return (-1);
		if (a6->prefixlen < b6->prefixlen)
			return (-1);
		if (a6->prefixlen > b6->prefixlen)
			return (1);
		return (0);
	case AID_VPN_IPv4:
		va4 = (const struct pt_entry_vpn4 *)a;
		vb4 = (const struct pt_entry_vpn4 *)b;
		if (be64toh(va4->rd) > be64toh(vb4->rd))
			return (1);
		if (be64toh(va4->rd) < be64toh(vb4->rd))
			return (-1);
		if (ntohl(va4->prefix4.s_addr) > ntohl(vb4->prefix4.s_addr))
			return (1);
		if (ntohl(va4->prefix4.s_addr) < ntohl(vb4->prefix4.s_addr))
			return (-1);
		if (va4->prefixlen > vb4->prefixlen)
			return (1);
		if (va4->prefixlen < vb4->prefixlen)
			return (-1);
		return (0);
	case AID_VPN_IPv6:
		va6 = (const struct pt_entry_vpn6 *)a;
		vb6 = (const struct pt_entry_vpn6 *)b;
		if (be64toh(va6->rd) > be64toh(vb6->rd))
			return (1);
		if (be64toh(va6->rd) < be64toh(vb6->rd))
			return (-1);
		i = memcmp(&va6->prefix6, &vb6->prefix6,
		    sizeof(struct in6_addr));
		if (i > 0)
			return (1);
		if (i < 0)
			return (-1);
		if (va6->prefixlen > vb6->prefixlen)
			return (1);
		if (va6->prefixlen < vb6->prefixlen)
			return (-1);
		return (0);
	case AID_EVPN:
		/* XXXX Need different comparator for different types */
		ea = (const struct pt_entry_evpn *)a;
		eb = (const struct pt_entry_evpn *)b;
		if (ea->ethtag > eb->ethtag)
			return (1);
		if (ea->ethtag < eb->ethtag)
			return (-1);
		/* MAC length is always 48 */
		i = memcmp(&ea->mac, &eb->mac, sizeof(ea->mac));
		if (i > 0)
			return (1);
		if (i < 0)
			return (-1);
		if (ea->prefixlen > eb->prefixlen)
			return (1);
		if (ea->prefixlen < eb->prefixlen)
			return (-1);
		switch (ea->vpnaid) {
		case AID_UNSPEC:
			break;
		case AID_INET:
			i = memcmp(&ea->prefix4, &eb->prefix4,
			    sizeof(struct in_addr));
			if (i > 0)
				return (1);
			if (i < 0)
				return (-1);
			break;
		case AID_INET6:
			i = memcmp(&ea->prefix6, &eb->prefix6,
			    sizeof(struct in6_addr));
			if (i > 0)
				return (1);
			if (i < 0)
				return (-1);
			break;
		default:
			fatalx("pt_prefix_cmp: unknown evpn af %d", ea->vpnaid);
		}
		return (0);
	case AID_FLOWSPECv4:
	case AID_FLOWSPECv6:
		af = (const struct pt_entry_flow *)a;
		bf = (const struct pt_entry_flow *)b;
		return flowspec_cmp(af->flow, af->len - PT_FLOW_SIZE,
		    bf->flow, bf->len - PT_FLOW_SIZE,
		    a->aid == AID_FLOWSPECv6);
	default:
		fatalx("pt_prefix_cmp: unknown af %d", a->aid);
	}
	return (-1);
}

/*
 * Returns a pt_entry cloned from the one passed in.
 * Function may not return on failure.
 */
static struct pt_entry *
pt_alloc(struct pt_entry *op, int len)
{
	struct pt_entry		*p;

	p = malloc(len);
	if (p == NULL)
		fatal("pt_alloc");
	rdemem.pt_cnt[op->aid]++;
	rdemem.pt_size[op->aid] += len;
	memcpy(p, op, len);
	p->refcnt = 0;

	return (p);
}

static void
pt_free(struct pt_entry *pte)
{
	rdemem.pt_cnt[pte->aid]--;
	rdemem.pt_size[pte->aid] -= pte->len;
	free(pte);
}

/* dump a prefix into specified buffer */
int
pt_writebuf(struct ibuf *buf, struct pt_entry *pte, int withdraw,
    int add_path, uint32_t pathid)
{
	struct pt_entry_vpn4	*pvpn4 = (struct pt_entry_vpn4 *)pte;
	struct pt_entry_vpn6	*pvpn6 = (struct pt_entry_vpn6 *)pte;
	struct pt_entry_flow	*pflow = (struct pt_entry_flow *)pte;
	struct pt_entry_evpn	*pevpn = (struct pt_entry_evpn *)pte;
	struct ibuf		*tmp;
	int			 flowlen, psize;
	uint16_t		 plen;

	if ((tmp = ibuf_dynamic(32, UINT16_MAX)) == NULL)
		goto fail;

	if (add_path) {
		if (ibuf_add_n32(tmp, pathid) == -1)
			goto fail;
	}

	switch (pte->aid) {
	case AID_INET:
	case AID_INET6:
		plen = pte->prefixlen;
		if (ibuf_add_n8(tmp, plen) == -1)
			goto fail;
		if (ibuf_add(tmp, pte->data, PREFIX_SIZE(plen) - 1) == -1)
			goto fail;
		break;
	case AID_VPN_IPv4:
		plen = pvpn4->prefixlen;
		psize = PREFIX_SIZE(plen) - 1;
		plen += sizeof(pvpn4->rd) * 8;
		if (withdraw) {
			/* withdraw have one compat label as placeholder */
			plen += 3 * 8;
		} else {
			plen += pvpn4->labellen * 8;
		}

		if (ibuf_add_n8(tmp, plen) == -1)
			goto fail;
		if (withdraw) {
			/* magic compatibility label as per rfc8277 */
			if (ibuf_add_n8(tmp, 0x80) == -1 ||
			    ibuf_add_zero(tmp, 2) == -1)
				goto fail;
		} else {
			if (ibuf_add(tmp, &pvpn4->labelstack,
			    pvpn4->labellen) == -1)
				goto fail;
		}
		if (ibuf_add(tmp, &pvpn4->rd, sizeof(pvpn4->rd)) == -1 ||
		    ibuf_add(tmp, &pvpn4->prefix4, psize) == -1)
			goto fail;
		break;
	case AID_VPN_IPv6:
		plen = pvpn6->prefixlen;
		psize = PREFIX_SIZE(plen) - 1;
		plen += sizeof(pvpn6->rd) * 8;
		if (withdraw) {
			/* withdraw have one compat label as placeholder */
			plen += 3 * 8;
		} else {
			plen += pvpn6->labellen * 8;
		}

		if (ibuf_add_n8(tmp, plen) == -1)
			goto fail;
		if (withdraw) {
			/* magic compatibility label as per rfc8277 */
			if (ibuf_add_n8(tmp, 0x80) == -1 ||
			    ibuf_add_zero(tmp, 2) == -1)
				goto fail;
		} else {
			if (ibuf_add(tmp, &pvpn6->labelstack,
			    pvpn6->labellen) == -1)
				goto fail;
		}
		if (ibuf_add(tmp, &pvpn6->rd, sizeof(pvpn6->rd)) == -1 ||
		    ibuf_add(tmp, &pvpn6->prefix6, psize) == -1)
			goto fail;
		break;
	case AID_EVPN:
		if (ibuf_add_n8(tmp, pevpn->type) == -1)
			goto fail;
		switch (pevpn->type) {
		case EVPN_ROUTE_TYPE_2:
			plen = sizeof(pevpn->rd) * 8;
			plen += sizeof(pevpn->esi) * 8;
			plen += sizeof(pevpn->ethtag) * 8;
			plen += 8;	/* MAC length */
			plen += sizeof(pevpn->mac) * 8;
			plen += 8;	/* IP length */
			plen += pevpn->prefixlen;
			plen += pevpn->labellen * 8;
			if (ibuf_add_n8(tmp, PREFIX_SIZE(plen) - 1) == -1)
				goto fail;
			if (ibuf_add_h64(tmp, pevpn->rd) == -1 ||
			    ibuf_add(tmp, pevpn->esi,
			    sizeof(pevpn->esi)) == -1 ||
			    ibuf_add_h32(tmp, pevpn->ethtag) == -1)
				goto fail;
			if (ibuf_add_n8(tmp, sizeof(pevpn->mac) * 8) == -1 ||
			    ibuf_add(tmp, pevpn->mac, sizeof(pevpn->mac)) == -1)
				goto fail;
			if (ibuf_add_n8(tmp, pevpn->prefixlen) == -1)
				goto fail;
			switch (pevpn->vpnaid) {
			case AID_UNSPEC:
				/* See rfc7432 section 7.2 */
				break;
			case AID_INET:
				if (ibuf_add(tmp, &pevpn->prefix4,
				    sizeof(pevpn->prefix4)) == -1)
					goto fail;
				break;
			case AID_INET6:
				if (ibuf_add(tmp, &pevpn->prefix6,
				sizeof(pevpn->prefix6)) == -1)
					goto fail;
				break;
			default:
				goto fail;
			}
			if (ibuf_add(tmp, pevpn->labelstack,
			    pevpn->labellen) == -1)
				goto fail;
			break;
		case EVPN_ROUTE_TYPE_3:
			plen = sizeof(pevpn->rd) * 8;
			plen += sizeof(pevpn->ethtag) * 8;
			plen += 8;	/* IP length */
			plen += pevpn->prefixlen;
			if (ibuf_add_n8(tmp, PREFIX_SIZE(plen) - 1) == -1)
				goto fail;
			if (ibuf_add_h64(tmp, pevpn->rd) == -1 ||
			    ibuf_add_h32(tmp, pevpn->ethtag) == -1)
				goto fail;
			if (ibuf_add_n8(tmp, pevpn->prefixlen) == -1)
				goto fail;
			switch (pevpn->vpnaid) {
			case AID_INET:
				if (ibuf_add(tmp, &pevpn->prefix4,
				    sizeof(pevpn->prefix4)) == -1)
					goto fail;
				break;
			case AID_INET6:
				if (ibuf_add(tmp, &pevpn->prefix6,
				    sizeof(pevpn->prefix6)) == -1)
					goto fail;
				break;
			default:
				goto fail;
			}
		}
		break;
	case AID_FLOWSPECv4:
	case AID_FLOWSPECv6:
		flowlen = pflow->len - PT_FLOW_SIZE;
		if (flowlen < FLOWSPEC_LEN_LIMIT) {
			if (ibuf_add_n8(tmp, flowlen) == -1)
				goto fail;
		} else {
			if (ibuf_add_n8(tmp, 0xf0 | (flowlen >> 8)) == -1 ||
			    ibuf_add_n8(tmp, flowlen) == -1)
				goto fail;
		}
		if (ibuf_add(tmp, &pflow->flow, flowlen) == -1)
			goto fail;
		break;
	default:
		fatalx("%s: unknown aid %d", __func__, pte->aid);
	}

	/* keep 2 bytes reserved in the withdraw case for IPv4 encoding */
	if (withdraw && ibuf_left(buf) < ibuf_size(tmp) + 2)
		goto fail;
	if (ibuf_add_ibuf(buf, tmp) == -1)
		goto fail;
	ibuf_free(tmp);
	return 0;

 fail:
	ibuf_free(tmp);
	return -1;
}
