Index | Thread | Search

From:
Theo Buehler <tb@theobuehler.org>
Subject:
Re: bgpd: rework the rde side of filter_sets
To:
tech@openbsd.org
Date:
Wed, 4 Feb 2026 10:23:33 +0100

Download raw body.

Thread
On Tue, Feb 03, 2026 at 04:01:57PM +0100, Claudio Jeker wrote:
> rde_apply_set() is very inefficent at the moment. The code is hunting
> for memory all the time since there are just too many objects all over the
> place.
> 
> This switches this away from a linked list to an array of filter_set
> elements. On top of this the new rde_filter_set_elm structs are much
> smaller then the regular filter_sets. These changes make rde_apply_set()
> a lot more efficent.
> 
> At the same time this brings in a new way to send and recv the imsgs with
> the filter sets in them. The goal is to add more of those send / recv
> functions to better validate this internal structs on receive.
> Also since IMSG_FILTER_SET is also sent via the control socket add a
> imsg_check_filterset() function that validates these messages before
> passing them on.
> 
> There is a lot of churn because of the new rde_filter_set object. Also the
> diff of rde_filter.c is a big mess and so is hard to read. It is probably
> better to look at the result.

It's a quite a bit less messy if you hoist filterset_name() to the right
place in a separate commit.

> 
> Next step is to do a similar cleanup for filter rules but that is a bit
> more involved.

This one's straightforward enough. Some minor comments/questions below.

> -- 
> :wq Claudio
> 
> Index: bgpctl/Makefile
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpctl/Makefile,v
> diff -u -p -r1.20 Makefile
> --- bgpctl/Makefile	20 Feb 2025 19:48:14 -0000	1.20
> +++ bgpctl/Makefile	8 Dec 2025 13:00:31 -0000
> @@ -5,7 +5,7 @@
>  PROG=	bgpctl
>  SRCS=	bgpctl.c output.c output_json.c output_ometric.c parser.c \
>  	mrtparser.c json.c ometric.c
> -SRCS+=	util.c flowspec.c monotime.c
> +SRCS+=	util.c flowspec.c monotime.c bgpd_imsg.c
>  CFLAGS+= -Wall
>  CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes
>  CFLAGS+= -Wmissing-declarations
> Index: bgpctl/bgpctl.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpctl/bgpctl.c,v
> diff -u -p -r1.319 bgpctl.c
> --- bgpctl/bgpctl.c	4 Nov 2025 15:30:50 -0000	1.319
> +++ bgpctl/bgpctl.c	6 Dec 2025 21:38:31 -0000
> @@ -49,7 +49,6 @@
>  
>  int		 main(int, char *[]);
>  int		 show(struct imsg *, struct parse_result *);
> -void		 send_filterset(struct imsgbuf *, struct filter_set_head *);
>  void		 show_mrt_dump_neighbors(struct mrt_rib *, struct mrt_peer *,
>  		    void *);
>  void		 show_mrt_dump(struct mrt_rib *, struct mrt_peer *, void *);
> @@ -329,7 +328,7 @@ main(int argc, char *argv[])
>  		if (res->action == NETWORK_ADD) {
>  			imsg_compose(imsgbuf, IMSG_NETWORK_ADD, 0, 0, -1,
>  			    &net, sizeof(net));
> -			send_filterset(imsgbuf, &res->set);
> +			imsg_send_filterset(imsgbuf, &res->set);
>  			imsg_compose(imsgbuf, IMSG_NETWORK_DONE, 0, 0, -1,
>  			    NULL, 0);
>  		} else
> @@ -373,7 +372,7 @@ main(int argc, char *argv[])
>  		if (res->action == FLOWSPEC_ADD) {
>  			imsg_compose(imsgbuf, IMSG_FLOWSPEC_ADD, 0, 0, -1,
>  			    f, FLOWSPEC_SIZE + f->len);
> -			send_filterset(imsgbuf, &res->set);
> +			imsg_send_filterset(imsgbuf, &res->set);
>  			imsg_compose(imsgbuf, IMSG_FLOWSPEC_DONE, 0, 0, -1,
>  			    NULL, 0);
>  		} else
> @@ -1134,19 +1133,6 @@ fmt_set_type(struct ctl_show_set *set)
>  		return "ASNUM";
>  	default:
>  		return "BULA";
> -	}
> -}
> -
> -void
> -send_filterset(struct imsgbuf *i, struct filter_set_head *set)
> -{
> -	struct filter_set	*s;
> -
> -	while ((s = TAILQ_FIRST(set)) != NULL) {
> -		imsg_compose(i, IMSG_FILTER_SET, 0, 0, -1, s,
> -		    sizeof(struct filter_set));
> -		TAILQ_REMOVE(set, s, entry);
> -		free(s);
>  	}
>  }
>  
> Index: bgpctl/output.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpctl/output.c,v
> diff -u -p -r1.67 output.c
> --- bgpctl/output.c	29 Dec 2025 07:49:05 -0000	1.67
> +++ bgpctl/output.c	3 Feb 2026 13:58:19 -0000
> @@ -1104,6 +1104,10 @@ show_rib_mem(struct rde_memstats *stats)
>  	printf("%10lld pending prefix entries using %s of memory\n",
>  	    stats->pend_prefix_cnt, fmt_mem(stats->pend_prefix_cnt *
>  	    sizeof(struct pend_prefix)));
> +	printf("%10lld filter-sets using %s of memory\n",
> +	    stats->filter_set_cnt, fmt_mem(stats->filter_set_size));
> +	printf("\t   and holding %lld references\n",
> +	    stats->filter_set_refs);
>  	printf("%10lld as-set elements in %lld tables using "
>  	    "%s of memory\n", stats->aset_nmemb, stats->aset_cnt,
>  	    fmt_mem(stats->aset_size));
> @@ -1119,8 +1123,9 @@ show_rib_mem(struct rde_memstats *stats)
>  	    stats->path_cnt * sizeof(struct rde_aspath) +
>  	    stats->aspath_size + stats->attr_cnt * sizeof(struct attr) +
>  	    stats->attr_data));
> -	printf("Sets using %s of memory\n", fmt_mem(stats->aset_size +
> -	    stats->pset_size));
> +	printf("Sets and filters using %s of memory\n",
> +	    fmt_mem(stats->aset_size + stats->pset_size +
> +	    stats->filter_set_size));
>  
>  	printf("\nRDE timing statistics\n");
>  	printf("%10lld usec spent in the event loop for %llu rounds\n",
> Index: bgpctl/output_json.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpctl/output_json.c,v
> diff -u -p -r1.58 output_json.c
> --- bgpctl/output_json.c	29 Dec 2025 07:49:05 -0000	1.58
> +++ bgpctl/output_json.c	3 Feb 2026 14:12:15 -0000
> @@ -937,6 +937,13 @@ json_rib_mem(struct rde_memstats *stats)
>  	    stats->attr_data, UINT64_MAX);
>  	json_do_end();
>  
> +	json_do_object("filters", 0);
> +	json_rib_mem_element("filter_set", stats->filter_set_cnt,
> +	    stats->filter_set_size, stats->filter_set_refs);
> +	json_rib_mem_element("total", UINT64_MAX,
> +	    stats->filter_set_size, UINT64_MAX);
> +	json_do_end();
> +
>  	json_do_object("sets", 0);
>  	json_rib_mem_element("as_set", stats->aset_nmemb,
>  	    stats->aset_size, UINT64_MAX);
> Index: bgpctl/output_ometric.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpctl/output_ometric.c,v
> diff -u -p -r1.22 output_ometric.c
> --- bgpctl/output_ometric.c	29 Dec 2025 07:49:05 -0000	1.22
> +++ bgpctl/output_ometric.c	3 Feb 2026 14:11:14 -0000
> @@ -327,6 +327,11 @@ ometric_rib_mem(struct rde_memstats *sta
>  	    stats->aspath_size + stats->attr_cnt * sizeof(struct attr) +
>  	    stats->attr_data, UINT64_MAX);
>  
> +	ometric_rib_mem_element("filter_set", stats->filter_set_cnt,
> +	    stats->filter_set_size, stats->filter_set_refs);
> +	ometric_rib_mem_element("filter_total", UINT64_MAX,
> +	    stats->filter_set_size, UINT64_MAX);
> +
>  	ometric_set_int(rde_table_count, stats->aset_cnt, NULL);
>  
>  	ometric_set_int_with_labels(rde_set_size, stats->aset_size,
> Index: bgpd/Makefile
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpd/Makefile,v
> diff -u -p -r1.45 Makefile
> --- bgpd/Makefile	11 Dec 2025 12:18:27 -0000	1.45
> +++ bgpd/Makefile	13 Dec 2025 21:47:20 -0000
> @@ -2,6 +2,7 @@
>  
>  PROG=	bgpd
>  SRCS=	bgpd.c
> +SRCS+=	bgpd_imsg.c
>  SRCS+=	bitmap.c
>  SRCS+=	carp.c
>  SRCS+=	chash.c
> Index: bgpd/bgpd.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpd/bgpd.c,v
> diff -u -p -r1.286 bgpd.c
> --- bgpd/bgpd.c	3 Dec 2025 12:20:19 -0000	1.286
> +++ bgpd/bgpd.c	6 Dec 2025 21:32:44 -0000
> @@ -672,7 +672,7 @@ send_config(struct bgpd_config *conf)
>  			if (imsg_compose(ibuf_rde, IMSG_FLOWSPEC_ADD, 0, 0, -1,
>  			    f->flow, FLOWSPEC_SIZE + f->flow->len) == -1)
>  				return (-1);
> -			if (filterset_send(ibuf_rde, &f->attrset) == -1)
> +			if (imsg_send_filterset(ibuf_rde, &f->attrset) == -1)
>  				return (-1);
>  			if (imsg_compose(ibuf_rde, IMSG_FLOWSPEC_DONE, 0, 0, -1,
>  			    NULL, 0) == -1)
> @@ -781,7 +781,7 @@ send_config(struct bgpd_config *conf)
>  	/* filters for the RDE */
>  	while ((r = TAILQ_FIRST(conf->filters)) != NULL) {
>  		TAILQ_REMOVE(conf->filters, r, entry);
> -		if (filterset_send(ibuf_rde, &r->set) == -1)
> +		if (imsg_send_filterset(ibuf_rde, &r->set) == -1)
>  			return (-1);
>  		if (imsg_compose(ibuf_rde, IMSG_RECONF_FILTER, 0, 0, -1,
>  		    r, sizeof(struct filter_rule)) == -1)
> @@ -806,7 +806,7 @@ send_config(struct bgpd_config *conf)
>  			return (-1);
>  
>  		/* export targets */
> -		if (filterset_send(ibuf_rde, &vpn->export) == -1)
> +		if (imsg_send_filterset(ibuf_rde, &vpn->export) == -1)
>  			return (-1);
>  		if (imsg_compose(ibuf_rde, IMSG_RECONF_VPN_EXPORT, 0, 0,
>  		    -1, NULL, 0) == -1)
> @@ -814,7 +814,7 @@ send_config(struct bgpd_config *conf)
>  		filterset_free(&vpn->export);
>  
>  		/* import targets */
> -		if (filterset_send(ibuf_rde, &vpn->import) == -1)
> +		if (imsg_send_filterset(ibuf_rde, &vpn->import) == -1)
>  			return (-1);
>  		if (imsg_compose(ibuf_rde, IMSG_RECONF_VPN_IMPORT, 0, 0,
>  		    -1, NULL, 0) == -1)
> @@ -1170,7 +1170,7 @@ send_network(int type, struct network_co
>  	/* networks that get deleted don't need to send the filter set */
>  	if (type == IMSG_NETWORK_REMOVE)
>  		return (0);
> -	if (filterset_send(ibuf_rde, h) == -1)
> +	if (imsg_send_filterset(ibuf_rde, h) == -1)
>  		return (-1);
>  	if (imsg_compose(ibuf_rde, IMSG_NETWORK_DONE, 0, 0, -1, NULL, 0) == -1)
>  		return (-1);
> Index: bgpd/bgpd.h
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpd/bgpd.h,v
> diff -u -p -r1.530 bgpd.h
> --- bgpd/bgpd.h	3 Feb 2026 12:25:16 -0000	1.530
> +++ bgpd/bgpd.h	3 Feb 2026 13:56:43 -0000
> @@ -252,6 +252,7 @@ TAILQ_HEAD(timer_head, timer);
>  
>  TAILQ_HEAD(listen_addrs, listen_addr);
>  TAILQ_HEAD(filter_set_head, filter_set);
> +struct rde_filter_set;
>  
>  struct bitmap {
>  	uint64_t	data[2];
> @@ -566,6 +567,7 @@ enum network_type {
>  struct network_config {
>  	struct bgpd_addr	 prefix;
>  	struct filter_set_head	 attrset;
> +	struct rde_filter_set	*rde_attrset;
>  	char			 psname[SET_NAME_LEN];
>  	uint64_t		 rd;
>  	enum network_type	 type;
> @@ -591,6 +593,7 @@ struct flowspec {
>  struct flowspec_config {
>  	RB_ENTRY(flowspec_config)	 entry;
>  	struct filter_set_head		 attrset;
> +	struct rde_filter_set		*rde_attrset;
>  	struct flowspec			*flow;
>  	enum reconf_action		 reconf_action;
>  };
> @@ -1265,6 +1268,7 @@ struct filter_rule {
>  	struct filter_peers		peer;
>  	struct filter_match		match;
>  	struct filter_set_head		set;
> +	struct rde_filter_set		*rde_set;
>  #define RDE_FILTER_SKIP_PEERID		0
>  #define RDE_FILTER_SKIP_GROUPID		1
>  #define RDE_FILTER_SKIP_REMOTE_AS	2
> @@ -1363,6 +1367,8 @@ struct l3vpn {
>  	char				ifmpe[IFNAMSIZ];
>  	struct filter_set_head		import;
>  	struct filter_set_head		export;
> +	struct rde_filter_set		*rde_import;
> +	struct rde_filter_set		*rde_export;
>  	struct network_head		net_l;
>  	uint64_t			rd;
>  	u_int				rtableid;
> @@ -1420,6 +1426,9 @@ struct rde_memstats {
>  	long long	aset_nmemb;
>  	long long	pset_cnt;
>  	long long	pset_size;
> +	long long	filter_set_cnt;
> +	long long	filter_set_size;
> +	long long	filter_set_refs;
>  	long long	rde_event_loop_count;
>  	long long	rde_event_loop_usec;
>  	long long	rde_event_io_usec;
> @@ -1581,13 +1590,12 @@ int	pftable_commit(void);
>  
>  /* rde_filter.c */
>  void	filterset_free(struct filter_set_head *);
> +void	rde_filterset_unref(struct rde_filter_set *);
>  int	filterset_cmp(struct filter_set *, struct filter_set *);
>  void	filterset_move(struct filter_set_head *, struct filter_set_head *);
>  void	filterset_copy(const struct filter_set_head *,
>  	    struct filter_set_head *);
>  const char	*filterset_name(enum action_types);
> -int	filterset_send(struct imsgbuf *, struct filter_set_head *);
> -void	filterset_recv(struct imsg *, struct filter_set_head *);
>  
>  /* bitmap.c */
>  int		 bitmap_set(struct bitmap *, uint32_t);
> @@ -1680,6 +1688,12 @@ const char	*get_baudrate(unsigned long l
>  unsigned int	 bin_of_attrs(unsigned int);
>  unsigned int	 bin_of_communities(unsigned int);
>  unsigned int	 bin_of_adjout_prefixes(unsigned int);
> +
> +/* bgpd_imsg.c */
> +int	imsg_send_filterset(struct imsgbuf *, struct filter_set_head *);
> +int	imsg_check_filterset(struct imsg *);
> +int	ibuf_recv_filterset_count(struct ibuf *);
> +int	ibuf_recv_one_filterset(struct ibuf *, struct filter_set *);
>  
>  /* flowspec.c */
>  int	flowspec_valid(const uint8_t *, int, int);
> Index: bgpd/bgpd_imsg.c
> ===================================================================
> RCS file: bgpd/bgpd_imsg.c
> diff -N bgpd/bgpd_imsg.c
> --- /dev/null	1 Jan 1970 00:00:00 -0000
> +++ bgpd/bgpd_imsg.c	3 Feb 2026 13:06:04 -0000
> @@ -0,0 +1,211 @@
> +/*	$OpenBSD$	*/
> +/*
> + * Copyright (c) 2026 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 <errno.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include "bgpd.h"
> +#include "rde.h"
> +#include "log.h"
> +
> +int
> +imsg_send_filterset(struct imsgbuf *imsgbuf, struct filter_set_head *set)
> +{
> +	struct filter_set	*s;
> +	struct ibuf		*msg;
> +	int			 nsets = 0;
> +
> +	msg = imsg_create(imsgbuf, IMSG_FILTER_SET, 0, 0, 0);
> +	if (msg == NULL)
> +		return -1;
> +
> +	TAILQ_FOREACH(s, set, entry)
> +		nsets++;
> +	if (ibuf_add_n16(msg, nsets) == -1)
> +		goto fail;
> +
> +	TAILQ_FOREACH(s, set, entry) {
> +		if (ibuf_add_n32(msg, s->type) == -1)
> +			goto fail;
> +
> +		switch (s->type) {
> +		case ACTION_SET_PREPEND_SELF:
> +		case ACTION_SET_PREPEND_PEER:
> +			if (ibuf_add_n8(msg, s->action.prepend) == -1)
> +				goto fail;
> +			break;
> +		case ACTION_SET_AS_OVERRIDE:
> +			break;
> +		case ACTION_SET_LOCALPREF:
> +		case ACTION_SET_MED:
> +		case ACTION_SET_WEIGHT:
> +			if (ibuf_add_n32(msg, s->action.metric) == -1)
> +				goto fail;
> +			break;
> +		case ACTION_SET_RELATIVE_LOCALPREF:
> +		case ACTION_SET_RELATIVE_MED:
> +		case ACTION_SET_RELATIVE_WEIGHT:
> +			if (ibuf_add_n32(msg, s->action.relative) == -1)
> +				goto fail;
> +			break;
> +		case ACTION_SET_NEXTHOP:
> +			if (ibuf_add(msg, &s->action.nexthop,
> +			    sizeof(s->action.nexthop)) == -1)
> +				goto fail;
> +			break;
> +		case ACTION_SET_NEXTHOP_BLACKHOLE:
> +		case ACTION_SET_NEXTHOP_REJECT:
> +		case ACTION_SET_NEXTHOP_NOMODIFY:
> +		case ACTION_SET_NEXTHOP_SELF:
> +			break;
> +		case ACTION_DEL_COMMUNITY:
> +		case ACTION_SET_COMMUNITY:
> +			if (ibuf_add(msg, &s->action.community,
> +			    sizeof(s->action.community)) == -1)
> +				goto fail;
> +			break;
> +		case ACTION_PFTABLE:
> +			if (ibuf_add_strbuf(msg, s->action.pftable,
> +			    sizeof(s->action.pftable)) == -1)
> +				goto fail;
> +			break;
> +		case ACTION_RTLABEL:
> +			if (ibuf_add_strbuf(msg, s->action.rtlabel,
> +			    sizeof(s->action.rtlabel)) == -1)
> +				goto fail;
> +			break;
> +		case ACTION_SET_ORIGIN:
> +			if (ibuf_add_n8(msg, s->action.origin) == -1)
> +				goto fail;
> +			break;
> +		case ACTION_SET_NEXTHOP_REF:
> +		case ACTION_RTLABEL_ID:
> +		case ACTION_PFTABLE_ID:
> +			goto fail;
> +		}
> +	}
> +
> +	imsg_close(imsgbuf, msg);
> +	return 0;
> +
> +fail:

I'd indent the label.

> +	ibuf_free(msg);
> +	return -1;
> +}
> +
> +int
> +imsg_check_filterset(struct imsg *imsg)
> +{
> +	struct ibuf ibuf;
> +	uint16_t count, i;
> +
> +	if (imsg_get_ibuf(imsg, &ibuf) == -1)
> +		return -1;
> +	if (ibuf_get_n16(&ibuf, &count) == -1)
> +		return -1;

Should this use ibuf_recv_filterset_count()? But see below.

> +	for (i = 0; i < count; i++) {
> +		struct filter_set set;
> +		if (ibuf_recv_one_filterset(&ibuf, &set) == -1)
> +			return -1;
> +	}
> +	if (ibuf_size(&ibuf) != 0) {
> +		errno = EBADMSG;
> +		return -1;
> +	}
> +	return 0;
> +}
> +
> +int
> +ibuf_recv_filterset_count(struct ibuf *ibuf)
> +{
> +	uint16_t count;
> +
> +	if (ibuf_get_n16(ibuf, &count) == -1)
> +		return -1;
> +	return count;
> +}

I'm not entirely sold on the ibuf_recv_filterset_count() interface.
It doesn't really seem to make the caller simpler. The in-band error
with a signed type also isn't great in the caller, but that's probably
just me :)

> +
> +int
> +ibuf_recv_one_filterset(struct ibuf *ibuf, struct filter_set *set)
> +{
> +	uint32_t type;
> +
> +	memset(set, 0, sizeof(*set));
> +
> +	if (ibuf_get_n32(ibuf, &type) == -1)
> +		return -1;
> +	set->type = type;
> +
> +	switch (set->type) {
> +	case ACTION_SET_PREPEND_SELF:
> +	case ACTION_SET_PREPEND_PEER:
> +		if (ibuf_get_n8(ibuf, &set->action.prepend) == -1)
> +			return -1;
> +		break;
> +	case ACTION_SET_AS_OVERRIDE:
> +		break;
> +	case ACTION_SET_LOCALPREF:
> +	case ACTION_SET_MED:
> +	case ACTION_SET_WEIGHT:
> +		if (ibuf_get_n32(ibuf, &set->action.metric) == -1)
> +			return -1;
> +		break;
> +	case ACTION_SET_RELATIVE_LOCALPREF:
> +	case ACTION_SET_RELATIVE_MED:
> +	case ACTION_SET_RELATIVE_WEIGHT:
> +		if (ibuf_get_n32(ibuf, &set->action.relative) == -1)

I'm a bit surprised that gcc doesn't complain about int32_t * vs uint32_t *.

> +			return -1;
> +		break;
> +	case ACTION_SET_NEXTHOP:
> +		if (ibuf_get(ibuf, &set->action.nexthop,
> +		    sizeof(set->action.nexthop)) == -1)
> +			return -1;
> +		break;
> +	case ACTION_SET_NEXTHOP_BLACKHOLE:
> +	case ACTION_SET_NEXTHOP_REJECT:
> +	case ACTION_SET_NEXTHOP_NOMODIFY:
> +	case ACTION_SET_NEXTHOP_SELF:
> +		break;
> +	case ACTION_DEL_COMMUNITY:
> +	case ACTION_SET_COMMUNITY:
> +		if (ibuf_get(ibuf, &set->action.community,
> +		    sizeof(set->action.community)) == -1)
> +			return -1;
> +		break;
> +	case ACTION_PFTABLE:
> +		if (ibuf_get_strbuf(ibuf, set->action.pftable,
> +		    sizeof(set->action.pftable)) == -1)
> +			return -1;
> +		break;
> +	case ACTION_RTLABEL:
> +		if (ibuf_get_strbuf(ibuf, set->action.rtlabel,
> +		    sizeof(set->action.rtlabel)) == -1)
> +			return -1;
> +		break;
> +	case ACTION_SET_ORIGIN:
> +		if (ibuf_get_n8(ibuf, &set->action.origin) == -1)
> +			return -1;
> +		break;
> +	case ACTION_SET_NEXTHOP_REF:
> +	case ACTION_RTLABEL_ID:
> +	case ACTION_PFTABLE_ID:
> +		errno = EBADMSG;
> +		return -1;
> +	}
> +	return 0;
> +}
> Index: bgpd/control.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpd/control.c,v
> diff -u -p -r1.135 control.c
> --- bgpd/control.c	10 Mar 2025 14:11:38 -0000	1.135
> +++ bgpd/control.c	3 Feb 2026 13:05:51 -0000
> @@ -513,7 +513,14 @@ control_dispatch_msg(struct pollfd *pfd,
>  		case IMSG_FLOWSPEC_REMOVE:
>  		case IMSG_FLOWSPEC_DONE:
>  		case IMSG_FLOWSPEC_FLUSH:
> +			imsg_ctl_rde(&imsg);
> +			break;
>  		case IMSG_FILTER_SET:
> +			if (imsg_check_filterset(&imsg) == -1) {
> +				/* malformed request */
> +				control_result(c, CTL_RES_PARSE_ERROR);
> +				break;
> +			}
>  			imsg_ctl_rde(&imsg);
>  			break;
>  		case IMSG_CTL_LOG_VERBOSE:
> Index: bgpd/rde.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpd/rde.c,v
> diff -u -p -r1.681 rde.c
> --- bgpd/rde.c	3 Feb 2026 12:25:16 -0000	1.681
> +++ bgpd/rde.c	3 Feb 2026 12:52:48 -0000
> @@ -101,7 +101,7 @@ static void	 network_dump_upcall(struct 
>  static void	 network_flush_upcall(struct rib_entry *, void *);
>  
>  void		 flowspec_add(struct flowspec *, struct filterstate *,
> -		    struct filter_set_head *);
> +		    struct rde_filter_set *);
>  void		 flowspec_delete(struct flowspec *);
>  static void	 flowspec_flush_upcall(struct rib_entry *, void *);
>  static void	 flowspec_dump_upcall(struct rib_entry *, void *);
> @@ -400,8 +400,8 @@ rde_main(int debug, int verbose)
>  
>  struct network_config	netconf_s, netconf_p;
>  struct filterstate	netconf_state;
> -struct filter_set_head	session_set = TAILQ_HEAD_INITIALIZER(session_set);
> -struct filter_set_head	parent_set = TAILQ_HEAD_INITIALIZER(parent_set);
> +struct rde_filter_set	*session_set;
> +struct rde_filter_set	*parent_set;
>  
>  void
>  rde_dispatch_imsg_session(struct imsgbuf *imsgbuf)
> @@ -563,7 +563,8 @@ rde_dispatch_imsg_session(struct imsgbuf
>  			}
>  			break;
>  		case IMSG_NETWORK_DONE:
> -			TAILQ_CONCAT(&netconf_s.attrset, &session_set, entry);
> +			netconf_s.rde_attrset = session_set;
> +			session_set = NULL;
>  			switch (netconf_s.prefix.aid) {
>  			case AID_INET:
>  				if (netconf_s.prefixlen > 32)
> @@ -658,10 +659,11 @@ badnetdel:
>  				    "from bgpctl");
>  			else
>  				flowspec_add(curflow, &netconf_state,
> -				    &session_set);
> +				    session_set);
>  
>  			rde_filterstate_clean(&netconf_state);
> -			filterset_free(&session_set);
> +			rde_filterset_unref(session_set);
> +			session_set = NULL;
>  			free(curflow);
>  			curflow = NULL;
>  			break;
> @@ -702,7 +704,7 @@ badnetdel:
>  			    flowspec_flush_upcall, NULL);
>  			break;
>  		case IMSG_FILTER_SET:
> -			filterset_recv(&imsg, &session_set);
> +			session_set = rde_filterset_imsg_recv(&imsg);
>  			break;
>  		case IMSG_CTL_SHOW_NETWORK:
>  		case IMSG_CTL_SHOW_RIB:
> @@ -920,7 +922,8 @@ rde_dispatch_imsg_parent(struct imsgbuf 
>  			TAILQ_INIT(&netconf_p.attrset);
>  			break;
>  		case IMSG_NETWORK_DONE:
> -			TAILQ_CONCAT(&netconf_p.attrset, &parent_set, entry);
> +			netconf_p.rde_attrset = parent_set;
> +			parent_set = NULL;
>  
>  			rde_filterstate_init(&state);
>  			asp = &state.aspath;
> @@ -982,10 +985,11 @@ rde_dispatch_imsg_parent(struct imsgbuf 
>  				log_warnx("invalid flowspec update received "
>  				    "from parent");
>  			else
> -				flowspec_add(curflow, &state, &parent_set);
> +				flowspec_add(curflow, &state, parent_set);
>  
>  			rde_filterstate_clean(&state);
> -			filterset_free(&parent_set);
> +			rde_filterset_unref(parent_set);
> +			parent_set = NULL;
>  			free(curflow);
>  			curflow = NULL;
>  			break;
> @@ -1090,11 +1094,12 @@ rde_dispatch_imsg_parent(struct imsgbuf 
>  				}
>  			}
>  			TAILQ_INIT(&r->set);
> -			TAILQ_CONCAT(&r->set, &parent_set, entry);
> +			r->rde_set = parent_set;
> +			parent_set = NULL;
>  			if ((rib = rib_byid(rib_find(r->rib))) == NULL) {
>  				log_warnx("IMSG_RECONF_FILTER: filter rule "
>  				    "for nonexistent rib %s", r->rib);
> -				filterset_free(&r->set);
> +				rde_filterset_unref(r->rde_set);
>  				free(r);
>  				break;
>  			}
> @@ -1192,7 +1197,8 @@ rde_dispatch_imsg_parent(struct imsgbuf 
>  				    "IMSG_RECONF_VPN_EXPORT unexpected");
>  				break;
>  			}
> -			TAILQ_CONCAT(&vpn->export, &parent_set, entry);
> +			vpn->rde_export = parent_set;
> +			parent_set = NULL;
>  			break;
>  		case IMSG_RECONF_VPN_IMPORT:
>  			if (vpn == NULL) {
> @@ -1200,7 +1206,8 @@ rde_dispatch_imsg_parent(struct imsgbuf 
>  				    "IMSG_RECONF_VPN_IMPORT unexpected");
>  				break;
>  			}
> -			TAILQ_CONCAT(&vpn->import, &parent_set, entry);
> +			vpn->rde_import = parent_set;
> +			parent_set = NULL;
>  			break;
>  		case IMSG_RECONF_VPN_DONE:
>  			break;
> @@ -1221,7 +1228,7 @@ rde_dispatch_imsg_parent(struct imsgbuf 
>  			nexthop_update(&knext);
>  			break;
>  		case IMSG_FILTER_SET:
> -			filterset_recv(&imsg, &parent_set);
> +			parent_set = rde_filterset_imsg_recv(&imsg);
>  			break;
>  		case IMSG_MRT_OPEN:
>  		case IMSG_MRT_REOPEN:
> @@ -4545,7 +4552,7 @@ void
>  network_add(struct network_config *nc, struct filterstate *state)
>  {
>  	struct l3vpn		*vpn;
> -	struct filter_set_head	*vpnset = NULL;
> +	struct rde_filter_set	*vpnset = NULL;
>  	struct in_addr		 prefix4;
>  	struct in6_addr		 prefix6;
>  	uint32_t		 path_id_tx;
> @@ -4571,7 +4578,7 @@ network_add(struct network_config *nc, s
>  				nc->prefix.labelstack[2] =
>  				    (vpn->label << 4) & 0xf0;
>  				nc->prefix.labelstack[2] |= BGP_MPLS_BOS;
> -				vpnset = &vpn->export;
> +				vpnset = vpn->rde_export;
>  				break;
>  			case AID_INET6:
>  				prefix6 = nc->prefix.v6;
> @@ -4587,11 +4594,11 @@ network_add(struct network_config *nc, s
>  				nc->prefix.labelstack[2] =
>  				    (vpn->label << 4) & 0xf0;
>  				nc->prefix.labelstack[2] |= BGP_MPLS_BOS;
> -				vpnset = &vpn->export;
> +				vpnset = vpn->rde_export;
>  				break;
>  			default:
>  				log_warnx("unable to VPNize prefix");
> -				filterset_free(&nc->attrset);
> +				rde_filterset_unref(nc->rde_attrset);
>  				return;
>  			}
>  			break;
> @@ -4605,7 +4612,8 @@ network_add(struct network_config *nc, s
>  		}
>  	}
>  
> -	rde_apply_set(&nc->attrset, peerself, peerself, state, nc->prefix.aid);
> +	rde_apply_set(nc->rde_attrset, peerself, peerself, state,
> +	    nc->prefix.aid);
>  	if (vpnset)
>  		rde_apply_set(vpnset, peerself, peerself, state,
>  		    nc->prefix.aid);
> @@ -4628,7 +4636,7 @@ network_add(struct network_config *nc, s
>  		prefix_update(rib, peerself, 0, path_id_tx, state, 0,
>  		    &nc->prefix, nc->prefixlen);
>  	}
> -	filterset_free(&nc->attrset);
> +	rde_filterset_unref(nc->rde_attrset);
>  }
>  
>  void
> @@ -4761,7 +4769,7 @@ network_flush_upcall(struct rib_entry *r
>   */
>  void
>  flowspec_add(struct flowspec *f, struct filterstate *state,
> -    struct filter_set_head *attrset)
> +    struct rde_filter_set *attrset)
>  {
>  	struct pt_entry *pte;
>  	uint32_t path_id_tx;
> Index: bgpd/rde.h
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpd/rde.h,v
> diff -u -p -r1.339 rde.h
> --- bgpd/rde.h	3 Feb 2026 12:25:16 -0000	1.339
> +++ bgpd/rde.h	3 Feb 2026 12:55:30 -0000
> @@ -546,17 +546,19 @@ void		 prefix_evaluate_nexthop(struct pr
>  		    enum nexthop_state);
>  
>  /* rde_filter.c */
> -void	rde_apply_set(struct filter_set_head *, struct rde_peer *,
> +void	rde_apply_set(const struct rde_filter_set *, struct rde_peer *,
>  	    struct rde_peer *, struct filterstate *, u_int8_t);
> -int	 rde_l3vpn_import(struct rde_community *, struct l3vpn *);
> +int	rde_l3vpn_import(struct rde_community *, struct l3vpn *);
>  struct filter_rule     *rde_filter_dup(const struct filter_rule *);
>  void	rde_filterstate_init(struct filterstate *);
>  void	rde_filterstate_prep(struct filterstate *, struct prefix *);
>  void	rde_filterstate_copy(struct filterstate *, struct filterstate *);
>  void	rde_filterstate_set_vstate(struct filterstate *, uint8_t, uint8_t);
>  void	rde_filterstate_clean(struct filterstate *);
> +uint64_t	rde_filterset_calc_hash(const struct rde_filter_set *);
>  int	rde_filter_skip_rule(struct rde_peer *, struct filter_rule *);
>  int	rde_filter_equal(struct filter_head *, struct filter_head *);
> +struct rde_filter_set	*rde_filterset_imsg_recv(struct imsg *);
>  void	rde_filter_calc_skip_steps(struct filter_head *);
>  enum filter_actions rde_filter(struct filter_head *, struct rde_peer *,
>  	    struct rde_peer *, struct bgpd_addr *, uint8_t,
> Index: bgpd/rde_filter.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpd/rde_filter.c,v
> diff -u -p -r1.139 rde_filter.c
> --- bgpd/rde_filter.c	3 Feb 2026 12:25:16 -0000	1.139
> +++ bgpd/rde_filter.c	3 Feb 2026 14:53:38 -0000
> @@ -21,27 +21,56 @@
>  #include <sys/types.h>
>  #include <sys/queue.h>
>  
> +#include <errno.h>
>  #include <limits.h>
>  #include <stdlib.h>
>  #include <string.h>
> +#include <siphash.h>
>  
>  #include "bgpd.h"
>  #include "rde.h"
>  #include "log.h"
> +#include "chash.h"
>  
> -int	filterset_equal(struct filter_set_head *, struct filter_set_head *);
> +static int	rde_filterset_equal(const struct rde_filter_set *,
> +	    const struct rde_filter_set *);
> +static void	rde_filterset_ref(struct rde_filter_set *);
> +
> +struct rde_filter_set_elm {
> +	enum action_types	type;
> +	union {
> +		uint8_t				 prepend;
> +		uint8_t				 origin;
> +		uint16_t			 id;
> +		uint32_t			 metric;
> +		int32_t				 relative;
> +		struct nexthop			*nh_ref;
> +		struct community		 community;
> +	}			action;
> +};
> +
> +struct rde_filter_set {
> +	uint64_t			hash;
> +	size_t				len;
> +	int				refcnt;
> +	struct rde_filter_set_elm	set[0];
> +};
>  
>  void
> -rde_apply_set(struct filter_set_head *sh, struct rde_peer *peer,
> +rde_apply_set(const struct rde_filter_set *rfs, struct rde_peer *peer,
>      struct rde_peer *from, struct filterstate *state, uint8_t aid)
>  {
> -	struct filter_set	*set;
>  	u_char			*np;
> +	size_t			 i;
>  	uint32_t		 prep_as;
>  	uint16_t		 nl;
>  	uint8_t			 prepend;
>  
> -	TAILQ_FOREACH(set, sh, entry) {
> +	if (rfs == NULL)
> +		return;
> +	for (i = 0; i < rfs->len; i++) {
> +		const struct rde_filter_set_elm *set = &rfs->set[i];
> +
>  		switch (set->type) {
>  		case ACTION_SET_LOCALPREF:
>  			state->aspath.lpref = set->action.metric;
> @@ -169,12 +198,16 @@ rde_apply_set(struct filter_set_head *sh
>  	}
>  }
>  
> +/* use to match the import filters for vpn imports */
>  int
>  rde_l3vpn_import(struct rde_community *comm, struct l3vpn *rd)
>  {
> -	struct filter_set	*s;
> +	size_t i;
>  
> -	TAILQ_FOREACH(s, &rd->import, entry) {
> +	if (rd->rde_import == NULL)
> +		return (0);
> +	for (i = 0; i < rd->rde_import->len; i++) {
> +		struct rde_filter_set_elm *s = &rd->rde_import->set[i];
>  		if (community_match(comm, &s->action.community, 0))
>  			return (1);
>  	}
> @@ -414,7 +447,7 @@ rde_filter_equal(struct filter_head *a, 
>  			return (0);
>  		}
>  
> -		if (!filterset_equal(&fa->set, &fb->set))
> +		if (!rde_filterset_equal(fa->rde_set, fb->rde_set))
>  			return (0);
>  
>  		fa = TAILQ_NEXT(fa, entry);
> @@ -431,7 +464,8 @@ rde_filter_dup(const struct filter_rule 
>  	if ((new = malloc(sizeof(*new))) == NULL)
>  		fatal(NULL);
>  	*new = *fr;
> -	filterset_copy(&fr->set, &new->set);
> +	/* XXX think about skip table */
> +	rde_filterset_ref(new->rde_set);
>  	return new;
>  }
>  
> @@ -511,6 +545,8 @@ filterlist_free(struct filter_head *fh)
>  	while ((r = TAILQ_FIRST(fh)) != NULL) {
>  		TAILQ_REMOVE(fh, r, entry);
>  		filterset_free(&r->set);
> +		if (r->rde_set != NULL)
> +			rde_filterset_unref(r->rde_set);
>  		free(r);
>  	}
>  	free(fh);
> @@ -537,6 +573,49 @@ filterset_free(struct filter_set_head *s
>  	}
>  }
>  
> +const char *
> +filterset_name(enum action_types type)
> +{
> +	switch (type) {
> +	case ACTION_SET_LOCALPREF:
> +	case ACTION_SET_RELATIVE_LOCALPREF:
> +		return ("localpref");
> +	case ACTION_SET_MED:
> +	case ACTION_SET_RELATIVE_MED:
> +		return ("metric");
> +	case ACTION_SET_WEIGHT:
> +	case ACTION_SET_RELATIVE_WEIGHT:
> +		return ("weight");
> +	case ACTION_SET_PREPEND_SELF:
> +		return ("prepend-self");
> +	case ACTION_SET_PREPEND_PEER:
> +		return ("prepend-peer");
> +	case ACTION_SET_AS_OVERRIDE:
> +		return ("as-override");
> +	case ACTION_SET_NEXTHOP:
> +	case ACTION_SET_NEXTHOP_REF:
> +	case ACTION_SET_NEXTHOP_REJECT:
> +	case ACTION_SET_NEXTHOP_BLACKHOLE:
> +	case ACTION_SET_NEXTHOP_NOMODIFY:
> +	case ACTION_SET_NEXTHOP_SELF:
> +		return ("nexthop");
> +	case ACTION_SET_COMMUNITY:
> +		return ("community");
> +	case ACTION_DEL_COMMUNITY:
> +		return ("community delete");
> +	case ACTION_PFTABLE:
> +	case ACTION_PFTABLE_ID:
> +		return ("pftable");
> +	case ACTION_RTLABEL:
> +	case ACTION_RTLABEL_ID:
> +		return ("rtlabel");
> +	case ACTION_SET_ORIGIN:
> +		return ("origin");
> +	}
> +
> +	fatalx("filterset_name: got lost");
> +}
> +
>  /*
>   * this function is a bit more complicated than a memcmp() because there are
>   * types that need to be considered equal e.g. ACTION_SET_MED and
> @@ -610,201 +689,248 @@ filterset_copy(const struct filter_set_h
>  	}
>  }
>  
> -int
> -filterset_equal(struct filter_set_head *ah, struct filter_set_head *bh)
> +static int
> +rde_filterset_equal(const struct rde_filter_set *afs,
> +    const struct rde_filter_set *bfs)
>  {
> -	struct filter_set	*a, *b;
> -	const char		*as, *bs;
> +	const struct rde_filter_set_elm *a, *b;
> +	size_t i;
> +
> +	if (afs->len != bfs->len)
> +		return 0;
> +
> +	a = afs->set;
> +	b = bfs->set;
> +	for (i = 0; i < afs->len; i++, a++, b++) {
> +		if (a->type != b->type)
> +			return 0;
>  
> -	for (a = TAILQ_FIRST(ah), b = TAILQ_FIRST(bh);
> -	    a != NULL && b != NULL;
> -	    a = TAILQ_NEXT(a, entry), b = TAILQ_NEXT(b, entry)) {
>  		switch (a->type) {
>  		case ACTION_SET_PREPEND_SELF:
>  		case ACTION_SET_PREPEND_PEER:
> -			if (a->type == b->type &&
> -			    a->action.prepend == b->action.prepend)
> +			if (a->action.prepend == b->action.prepend)
>  				continue;
>  			break;
>  		case ACTION_SET_AS_OVERRIDE:
> -			if (a->type == b->type)
> -				continue;
> -			break;
> +			continue;
>  		case ACTION_SET_LOCALPREF:
>  		case ACTION_SET_MED:
>  		case ACTION_SET_WEIGHT:
> -			if (a->type == b->type &&
> -			    a->action.metric == b->action.metric)
> +			if (a->action.metric == b->action.metric)
>  				continue;
>  			break;
>  		case ACTION_SET_RELATIVE_LOCALPREF:
>  		case ACTION_SET_RELATIVE_MED:
>  		case ACTION_SET_RELATIVE_WEIGHT:
> -			if (a->type == b->type &&
> -			    a->action.relative == b->action.relative)
> -				continue;
> -			break;
> -		case ACTION_SET_NEXTHOP:
> -			if (a->type == b->type &&
> -			    memcmp(&a->action.nexthop, &b->action.nexthop,
> -			    sizeof(a->action.nexthop)) == 0)
> +			if (a->action.relative == b->action.relative)
>  				continue;
>  			break;
>  		case ACTION_SET_NEXTHOP_REF:
> -			if (a->type == b->type &&
> -			    a->action.nh_ref == b->action.nh_ref)
> +			if (a->action.nh_ref == b->action.nh_ref)

I was somewhat surprised by a check for pointer equality. I guess
it makes sense given that they reference objects in a global tree.

>  				continue;
>  			break;
>  		case ACTION_SET_NEXTHOP_BLACKHOLE:
>  		case ACTION_SET_NEXTHOP_REJECT:
>  		case ACTION_SET_NEXTHOP_NOMODIFY:
>  		case ACTION_SET_NEXTHOP_SELF:

I don't follow why these ACTION_SET_NEXTHOP_* are ignored wile the
ACTION_SET_NEXTHOP_REF is an error.

> -			if (a->type == b->type)
> -				continue;
> -			break;
> +			continue;
>  		case ACTION_DEL_COMMUNITY:
>  		case ACTION_SET_COMMUNITY:
> -			if (a->type == b->type &&
> -			    memcmp(&a->action.community, &b->action.community,
> +			if (memcmp(&a->action.community, &b->action.community,
>  			    sizeof(a->action.community)) == 0)
>  				continue;
>  			break;
> -		case ACTION_PFTABLE:
> -		case ACTION_PFTABLE_ID:
> -			if (b->type == ACTION_PFTABLE)
> -				bs = b->action.pftable;
> -			else if (b->type == ACTION_PFTABLE_ID)
> -				bs = pftable_id2name(b->action.id);
> -			else
> -				break;
> -
> -			if (a->type == ACTION_PFTABLE)
> -				as = a->action.pftable;
> -			else
> -				as = pftable_id2name(a->action.id);
> -
> -			if (strcmp(as, bs) == 0)
> -				continue;
> -			break;
> -		case ACTION_RTLABEL:
>  		case ACTION_RTLABEL_ID:
> -			if (b->type == ACTION_RTLABEL)
> -				bs = b->action.rtlabel;
> -			else if (b->type == ACTION_RTLABEL_ID)
> -				bs = rtlabel_id2name(b->action.id);
> -			else
> -				break;
> -
> -			if (a->type == ACTION_RTLABEL)
> -				as = a->action.rtlabel;
> -			else
> -				as = rtlabel_id2name(a->action.id);
> -
> -			if (strcmp(as, bs) == 0)
> +		case ACTION_PFTABLE_ID:
> +			if (a->action.id == b->action.id)
>  				continue;
>  			break;
>  		case ACTION_SET_ORIGIN:
> -			if (a->type == b->type &&
> -			    a->action.origin == b->action.origin)
> +			if (a->action.origin == b->action.origin)
>  				continue;
>  			break;
> +		case ACTION_SET_NEXTHOP:
> +		case ACTION_RTLABEL:
> +		case ACTION_PFTABLE:
> +			fatalx("unexpected filter action in RDE");
>  		}
>  		/* compare failed */
> -		return (0);
> +		return 0;
>  	}
> -	if (a != NULL || b != NULL)
> -		return (0);
> -	return (1);
> +	return 1;
>  }
>  
> -const char *
> -filterset_name(enum action_types type)
> +static SIPHASH_KEY	fskey;
> +
> +static inline uint64_t
> +rde_filterset_hash(const struct rde_filter_set *rfs)
>  {
> -	switch (type) {
> +        return rfs->hash;

use a tab, not 8 spaces

> +}
> +
> +uint64_t
> +rde_filterset_calc_hash(const struct rde_filter_set *rfs)
> +{
> +	return SipHash24(&fskey, rfs->set, rfs->len * sizeof(*rfs->set));
> +}
> +
> +CH_HEAD(rde_filterset, rde_filter_set);
> +CH_PROTOTYPE(rde_filterset, rde_filter_set, rde_filterset_hash);
> +
> +static struct rde_filterset filterset = CH_INITIALIZER(&filterset);
> +
> +static void
> +rde_filterset_free(struct rde_filter_set *rfs)
> +{
> +	struct rde_filter_set_elm *rfse;
> +	size_t i;
> +
> +	if (rfs == NULL)
> +		return;
> +
> +	rdemem.filter_set_size -= sizeof(*rfs) + rfs->len * sizeof(*rfse);
> +	rdemem.filter_set_cnt--;
> +
> +	rfse = rfs->set;
> +	for (i = 0; i < rfs->len; i++, rfse++) {
> +		if (rfse->type == ACTION_RTLABEL_ID)
> +			rtlabel_unref(rfse->action.id);
> +		else if (rfse->type == ACTION_PFTABLE_ID)
> +			pftable_unref(rfse->action.id);
> +		else if (rfse->type == ACTION_SET_NEXTHOP_REF)
> +			nexthop_unref(rfse->action.nh_ref);
> +	}
> +	free(rfs);
> +}
> +
> +static void
> +rde_filterset_ref(struct rde_filter_set *rfs)
> +{
> +	rfs->refcnt++;
> +	rdemem.filter_set_refs++;
> +}
> +
> +void
> +rde_filterset_unref(struct rde_filter_set *rfs)
> +{
> +	rfs->refcnt--;
> +	rdemem.filter_set_refs--;
> +	if (rfs->refcnt <= 0) {
> +		CH_REMOVE(rde_filterset, &filterset, rfs);
> +		rde_filterset_free(rfs);
> +	}
> +}
> +
> +static void
> +rde_filterset_conv(const struct filter_set *set,
> +    struct rde_filter_set_elm *rfse)
> +{
> +	rfse->type = set->type;
> +	switch (set->type) {
> +	case ACTION_SET_PREPEND_SELF:
> +	case ACTION_SET_PREPEND_PEER:
> +		rfse->action.prepend = set->action.prepend;
> +		break;
> +	case ACTION_SET_AS_OVERRIDE:
> +		break;
>  	case ACTION_SET_LOCALPREF:
> -	case ACTION_SET_RELATIVE_LOCALPREF:
> -		return ("localpref");
>  	case ACTION_SET_MED:
> -	case ACTION_SET_RELATIVE_MED:
> -		return ("metric");
>  	case ACTION_SET_WEIGHT:
> +		rfse->action.metric = set->action.metric;
> +		break;
> +	case ACTION_SET_RELATIVE_LOCALPREF:
> +	case ACTION_SET_RELATIVE_MED:
>  	case ACTION_SET_RELATIVE_WEIGHT:
> -		return ("weight");
> -	case ACTION_SET_PREPEND_SELF:
> -		return ("prepend-self");
> -	case ACTION_SET_PREPEND_PEER:
> -		return ("prepend-peer");
> -	case ACTION_SET_AS_OVERRIDE:
> -		return ("as-override");
> -	case ACTION_SET_NEXTHOP:
> -	case ACTION_SET_NEXTHOP_REF:
> -	case ACTION_SET_NEXTHOP_REJECT:
> +		rfse->action.relative = set->action.relative;
> +		break;
>  	case ACTION_SET_NEXTHOP_BLACKHOLE:
> +	case ACTION_SET_NEXTHOP_REJECT:
>  	case ACTION_SET_NEXTHOP_NOMODIFY:
>  	case ACTION_SET_NEXTHOP_SELF:
> -		return ("nexthop");
> -	case ACTION_SET_COMMUNITY:
> -		return ("community");
> +		break;
>  	case ACTION_DEL_COMMUNITY:
> -		return ("community delete");
> -	case ACTION_PFTABLE:
> -	case ACTION_PFTABLE_ID:
> -		return ("pftable");
> +	case ACTION_SET_COMMUNITY:
> +		rfse->action.community = set->action.community;
> +		break;
> +	case ACTION_SET_ORIGIN:
> +		rfse->action.origin = set->action.origin;
> +		break;
> +	case ACTION_SET_NEXTHOP:
> +		rfse->action.nh_ref = nexthop_get(&set->action.nexthop);
> +		rfse->type = ACTION_SET_NEXTHOP_REF;
> +		break;
>  	case ACTION_RTLABEL:
> +		rfse->action.id = rtlabel_name2id(set->action.rtlabel);
> +		rfse->type = ACTION_RTLABEL_ID;
> +		break;
> +	case ACTION_PFTABLE:
> +		rfse->action.id = pftable_name2id(set->action.pftable);
> +		rfse->type = ACTION_PFTABLE_ID;
> +		break;
> +	case ACTION_SET_NEXTHOP_REF:
>  	case ACTION_RTLABEL_ID:
> -		return ("rtlabel");
> -	case ACTION_SET_ORIGIN:
> -		return ("origin");
> +	case ACTION_PFTABLE_ID:
> +		fatalx("unexpected filter action in RDE");
>  	}
> -
> -	fatalx("filterset_name: got lost");
>  }
>  
> -int
> -filterset_send(struct imsgbuf *imsgbuf, struct filter_set_head *set)
> +struct rde_filter_set *
> +rde_filterset_imsg_recv(struct imsg *imsg)
>  {
> -	struct filter_set	*s;
> +	struct ibuf ibuf;
> +	struct rde_filter_set *rfs = NULL, *nrfs;
> +	struct rde_filter_set_elm *rfse;
> +	int count, i;
>  
> -	TAILQ_FOREACH(s, set, entry)
> -		if (imsg_compose(imsgbuf, IMSG_FILTER_SET, 0, 0, -1, s,
> -		    sizeof(*s)) == -1)
> -			return (-1);
> -	return (0);
> -}
> +	if (imsg_get_ibuf(imsg, &ibuf) == -1)
> +		goto fail;
>  
> -void
> -filterset_recv(struct imsg *imsg, struct filter_set_head *set)
> -{
> -	struct filter_set	*s;
> +	count = ibuf_recv_filterset_count(&ibuf);

I'd align this with imsg_check_filterset().

> +	if (count == -1)
> +		goto fail;
>  
> -	if ((s = malloc(sizeof(*s))) == NULL)
> -		fatal(NULL);
> -	if (imsg_get_data(imsg, s, sizeof(*s)) == -1) {
> -		log_warnx("rde_dispatch: wrong imsg len");
> -		free(s);
> -		return;
> +	if ((rfs = calloc(1, sizeof(*rfs) + count * sizeof(*rfse))) == NULL)

Should this check for overflow?

	if (count > (SIZE_MAX - sizeof(*rfs)) / sizeof(*rfse))

> +		goto fail;
> +
> +	rdemem.filter_set_size += sizeof(*rfs) + count * sizeof(*rfse);
> +	rdemem.filter_set_cnt++;
> +
> +	rfs->len = count;
> +	rfse = rfs->set;
> +
> +	for (i = 0; i < count; i++, rfse++) {
> +		struct filter_set set;
> +		if (ibuf_recv_one_filterset(&ibuf, &set) == -1)
> +			goto fail;
> +		rde_filterset_conv(&set, rfse); 

trailing space after the semicolon

>  	}
> -	switch (s->type) {
> -	case ACTION_SET_NEXTHOP:
> -		s->action.nh_ref = nexthop_get(&s->action.nexthop);
> -		s->type = ACTION_SET_NEXTHOP_REF;
> -		break;
> -	case ACTION_RTLABEL:
> -		/* convert the route label to an id for faster access */
> -		s->action.id = rtlabel_name2id(s->action.rtlabel);
> -		s->type = ACTION_RTLABEL_ID;
> -		break;
> -	case ACTION_PFTABLE:
> -		/* convert pftable name to an id */
> -		s->action.id = pftable_name2id(s->action.pftable);
> -		s->type = ACTION_PFTABLE_ID;
> -		break;
> -	default:
> -		break;
> +
> +	if (ibuf_size(&ibuf) != 0) {
> +		errno = EBADMSG;
> +		goto fail;
>  	}
> -	TAILQ_INSERT_TAIL(set, s, entry);
> +
> +	rfs->hash = rde_filterset_calc_hash(rfs);
> +
> +	if ((nrfs = CH_FIND(rde_filterset, &filterset, rfs)) == NULL) {
> +		if (CH_INSERT(rde_filterset, &filterset, rfs, NULL) != 1)
> +			fatalx("%s: already present set", __func__);
> +	} else {
> +		rde_filterset_free(rfs);
> +		rfs = nrfs;
> +	}
> +	rde_filterset_ref(rfs);
> +	return rfs;
> +
> + fail:
> +	log_warn("filter set receive");
> +	rde_filterset_free(rfs);
> +	return NULL;
>  }
>  
> +CH_GENERATE(rde_filterset, rde_filter_set, rde_filterset_equal,
> +   rde_filterset_hash);
> +
>  /*
>   * Copyright (c) 2001 Daniel Hartmeier
>   * All rights reserved.
> @@ -921,7 +1047,8 @@ rde_filter(struct filter_head *rules, st
>  		     f->skip[RDE_FILTER_SKIP_REMOTE_AS]);
>  
>  		if (rde_filter_match(f, peer, from, state, prefix, plen)) {
> -			rde_apply_set(&f->set, peer, from, state, prefix->aid);
> +			rde_apply_set(f->rde_set, peer, from, state,
> +			    prefix->aid);
>  			if (f->action != ACTION_NONE)
>  				action = f->action;
>  			if (f->quick)
>