This diff changes the way communites are stored and modified in bgpd.
The current implementation was showing that community_*_delete() consumed
a lot of CPU time because of the way rde_attr was used.
Communities are extensivly used by filters (e.g. arouteserver) and
therefor they are stored in an own special datastructure that allows fast
changes especially during filtering. The communities are now a special
part of the filterstate and because of this set and delete of a community
is much simpler.

This seems to make my test setup a fair bit snappier and quicker at
processing UPDATEs.
-- 
:wq Claudio


Index: usr.sbin/bgpctl/bgpctl.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpctl/bgpctl.c,v
retrieving revision 1.236
diff -u -p -r1.236 bgpctl.c
--- usr.sbin/bgpctl/bgpctl.c    3 May 2019 01:48:42 -0000       1.236
+++ usr.sbin/bgpctl/bgpctl.c    12 May 2019 15:25:13 -0000
@@ -81,6 +81,7 @@ int            show_rib_detail_msg(struct imsg *,
 void            show_rib_brief(struct ctl_show_rib *, u_char *);
 void            show_rib_detail(struct ctl_show_rib *, u_char *, int, int);
 void            show_attr(void *, u_int16_t, int);
+void            show_communities(u_char *, size_t, int);
 void            show_community(u_char *, u_int16_t);
 void            show_large_community(u_char *, u_int16_t);
 void            show_ext_community(u_char *, u_int16_t);
@@ -1248,6 +1249,12 @@ show_rib_detail_msg(struct imsg *imsg, i
                asdata += sizeof(struct ctl_show_rib);
                show_rib_detail(&rib, asdata, nodescr, flag0);
                break;
+       case IMSG_CTL_SHOW_RIB_COMMUNITIES:
+               ilen = imsg->hdr.len - IMSG_HEADER_SIZE;
+               if (ilen % sizeof(struct community))
+                       errx(1, "bad IMSG_CTL_SHOW_RIB_COMMUNITIES received");
+               show_communities(imsg->data, ilen, flag0);
+               break;
        case IMSG_CTL_SHOW_RIB_ATTR:
                ilen = imsg->hdr.len - IMSG_HEADER_SIZE;
                if (ilen < 3)
@@ -1628,6 +1635,138 @@ show_attr(void *b, u_int16_t len, int fl
        printf("%c", EOL0(flag0));
 }
 
+static void
+print_community(u_int16_t a, u_int16_t v)
+{
+       if (a == COMMUNITY_WELLKNOWN)
+               switch (v) {
+               case COMMUNITY_GRACEFUL_SHUTDOWN:
+                       printf("GRACEFUL_SHUTDOWN");
+                       break;
+               case COMMUNITY_NO_EXPORT:
+                       printf("NO_EXPORT");
+                       break;
+               case COMMUNITY_NO_ADVERTISE:
+                       printf("NO_ADVERTISE");
+                       break;
+               case COMMUNITY_NO_EXPSUBCONFED:
+                       printf("NO_EXPORT_SUBCONFED");
+                       break;
+               case COMMUNITY_NO_PEER:
+                       printf("NO_PEER");
+                       break;
+               case COMMUNITY_BLACKHOLE:
+                       printf("BLACKHOLE");
+                       break;
+               default:
+                       printf("%hu:%hu", a, v);
+                       break;
+               }
+       else
+               printf("%hu:%hu", a, v);
+}
+
+static void
+print_ext_community(u_int8_t *data)
+{
+       u_int64_t       ext;
+       struct in_addr  ip;
+       u_int32_t       as4, u32;
+       u_int16_t       as2, u16;
+       u_int8_t        type, subtype;
+
+       type = data[0];
+       subtype = data[1];
+
+       printf("%s ", log_ext_subtype(type, subtype));
+
+       switch (type) {
+       case EXT_COMMUNITY_TRANS_TWO_AS:
+               memcpy(&as2, data + 2, sizeof(as2));
+               memcpy(&u32, data + 4, sizeof(u32));
+               printf("%s:%u", log_as(ntohs(as2)), ntohl(u32));
+               break;
+       case EXT_COMMUNITY_TRANS_IPV4:
+               memcpy(&ip, data + 2, sizeof(ip));
+               memcpy(&u16, data + 6, sizeof(u16));
+               printf("%s:%hu", inet_ntoa(ip), ntohs(u16));
+               break;
+       case EXT_COMMUNITY_TRANS_FOUR_AS:
+               memcpy(&as4, data + 2, sizeof(as4));
+               memcpy(&u16, data + 6, sizeof(u16));
+               printf("%s:%hu", log_as(ntohl(as4)), ntohs(u16));
+               break;
+       case EXT_COMMUNITY_TRANS_OPAQUE:
+       case EXT_COMMUNITY_TRANS_EVPN:
+               memcpy(&ext, data, sizeof(ext));
+               ext = be64toh(ext) & 0xffffffffffffLL;
+               printf("0x%llx", (unsigned long long)ext);
+               break;
+       case EXT_COMMUNITY_NON_TRANS_OPAQUE:
+               memcpy(&ext, data, sizeof(ext));
+               ext = be64toh(ext) & 0xffffffffffffLL;
+               switch (ext) {
+               case EXT_COMMUNITY_OVS_VALID:
+                       printf("valid ");
+                       break;
+               case EXT_COMMUNITY_OVS_NOTFOUND:
+                       printf("not-found ");
+                       break;
+               case EXT_COMMUNITY_OVS_INVALID:
+                       printf("invalid ");
+                       break;
+               default:
+                       printf("0x%llx ", (unsigned long long)ext);
+                       break;
+               }
+               break;
+       default:
+               memcpy(&ext, data, sizeof(ext));
+               printf("0x%llx", (unsigned long long)be64toh(ext));
+       }
+}
+
+void
+show_communities(u_char *data, size_t len, int flag0)
+{
+       struct community c;
+       size_t  i;
+       u_int32_t value;
+       int type = 0;
+
+       if (len % sizeof(c))
+               return;
+
+       for (i = 0; i < len; i += sizeof(c)) {
+               memcpy(&c, data + i, sizeof(c));
+
+               if (type != c.type) {
+                       if (type != 0)
+                               printf("%c", EOL0(flag0));
+                       printf("    %s:", print_attr(c.type,
+                           ATTR_OPTIONAL | ATTR_TRANSITIVE));
+                       type = c.type;
+               }
+               printf(" ");
+
+               switch (c.type) {
+               case COMMUNITY_TYPE_BASIC:
+                       value = ntohl(c.data1);
+                       print_community(value >> 16, value & USHRT_MAX);
+                       break;
+               case COMMUNITY_TYPE_LARGE:
+                       printf("%u:%u:%u", ntohl(c.data1), ntohl(c.data2),
+                           ntohl(c.data3));
+                       break;
+               case COMMUNITY_TYPE_EXT:
+                       print_ext_community((void *)&c.data1);
+                       break;
+               }
+       }
+
+       printf("%c", EOL0(flag0));
+}
+
 void
 show_community(u_char *data, u_int16_t len)
 {
@@ -1642,33 +1781,7 @@ show_community(u_char *data, u_int16_t l
                memcpy(&v, data + i + 2, sizeof(v));
                a = ntohs(a);
                v = ntohs(v);
-               if (a == COMMUNITY_WELLKNOWN)
-                       switch (v) {
-                       case COMMUNITY_GRACEFUL_SHUTDOWN:
-                               printf("GRACEFUL_SHUTDOWN");
-                               break;
-                       case COMMUNITY_NO_EXPORT:
-                               printf("NO_EXPORT");
-                               break;
-                       case COMMUNITY_NO_ADVERTISE:
-                               printf("NO_ADVERTISE");
-                               break;
-                       case COMMUNITY_NO_EXPSUBCONFED:
-                               printf("NO_EXPORT_SUBCONFED");
-                               break;
-                       case COMMUNITY_NO_PEER:
-                               printf("NO_PEER");
-                               break;
-                       case COMMUNITY_BLACKHOLE:
-                               printf("BLACKHOLE");
-                               break;
-                       default:
-                               printf("%hu:%hu", a, v);
-                               break;
-                       }
-               else
-                       printf("%hu:%hu", a, v);
-
+               print_community(a, v);
                if (i + 4 < len)
                        printf(" ");
        }
@@ -1700,67 +1813,16 @@ show_large_community(u_char *data, u_int
 void
 show_ext_community(u_char *data, u_int16_t len)
 {
-       u_int64_t       ext;
-       struct in_addr  ip;
-       u_int32_t       as4, u32;
-       u_int16_t       i, as2, u16;
-       u_int8_t        type, subtype;
+       u_int16_t       i;
 
        if (len & 0x7)
                return;
 
        for (i = 0; i < len; i += 8) {
-               type = data[i];
-               subtype = data[i + 1];
+               print_ext_community(data + i);
 
-               printf("%s ", log_ext_subtype(type, subtype));
-
-               switch (type) {
-               case EXT_COMMUNITY_TRANS_TWO_AS:
-                       memcpy(&as2, data + i + 2, sizeof(as2));
-                       memcpy(&u32, data + i + 4, sizeof(u32));
-                       printf("%s:%u", log_as(ntohs(as2)), ntohl(u32));
-                       break;
-               case EXT_COMMUNITY_TRANS_IPV4:
-                       memcpy(&ip, data + i + 2, sizeof(ip));
-                       memcpy(&u16, data + i + 6, sizeof(u16));
-                       printf("%s:%hu", inet_ntoa(ip), ntohs(u16));
-                       break;
-               case EXT_COMMUNITY_TRANS_FOUR_AS:
-                       memcpy(&as4, data + i + 2, sizeof(as4));
-                       memcpy(&u16, data + i + 6, sizeof(u16));
-                       printf("%s:%hu", log_as(ntohl(as4)), ntohs(u16));
-                       break;
-               case EXT_COMMUNITY_TRANS_OPAQUE:
-               case EXT_COMMUNITY_TRANS_EVPN:
-                       memcpy(&ext, data + i, sizeof(ext));
-                       ext = be64toh(ext) & 0xffffffffffffLL;
-                       printf("0x%llx", (unsigned long long)ext);
-                       break;
-               case EXT_COMMUNITY_NON_TRANS_OPAQUE:
-                       memcpy(&ext, data + i, sizeof(ext));
-                       ext = be64toh(ext) & 0xffffffffffffLL;
-                       switch (ext) {
-                       case EXT_COMMUNITY_OVS_VALID:
-                               printf("valid ");
-                               break;
-                       case EXT_COMMUNITY_OVS_NOTFOUND:
-                               printf("not-found ");
-                               break;
-                       case EXT_COMMUNITY_OVS_INVALID:
-                               printf("invalid ");
-                               break;
-                       default:
-                               printf("0x%llx ", (unsigned long long)ext);
-                               break;
-                       }
-                       break;
-               default:
-                       memcpy(&ext, data + i, sizeof(ext));
-                       printf("0x%llx", (unsigned long long)be64toh(ext));
-               }
                if (i + 8 < len)
-                       printf(", ");
+                       printf(" ");
        }
 }
 
@@ -1813,6 +1875,12 @@ show_rib_memory_msg(struct imsg *imsg)
                    "%s of memory\n\t   and holding %lld references\n",
                    stats.aspath_cnt, fmt_mem(stats.aspath_size),
                    stats.aspath_refs);
+               printf("%10lld entries for %lld BGP communities "
+                   "using %s of memory\n", stats.comm_cnt, stats.comm_nmemb,
+                   fmt_mem(stats.comm_cnt * sizeof(struct rde_community) +
+                   stats.comm_size * sizeof(struct community)));
+               printf("\t   and holding %lld references\n",
+                   stats.comm_refs);
                printf("%10lld BGP attributes entries using %s of memory\n",
                    stats.attr_cnt, fmt_mem(stats.attr_cnt *
                    sizeof(struct attr)));
Index: usr.sbin/bgpd/Makefile
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/Makefile,v
retrieving revision 1.34
diff -u -p -r1.34 Makefile
--- usr.sbin/bgpd/Makefile      7 Sep 2018 10:49:22 -0000       1.34
+++ usr.sbin/bgpd/Makefile      12 May 2019 15:25:13 -0000
@@ -2,8 +2,8 @@
 
 PROG=  bgpd
 SRCS=  bgpd.c session.c log.c logmsg.c parse.y config.c \
-       rde.c rde_rib.c rde_decide.c rde_prefix.c mrt.c kroute.c \
-       control.c pfkey.c rde_update.c rde_attr.c printconf.c \
+       rde.c rde_rib.c rde_decide.c rde_prefix.c mrt.c kroute.c control.c \
+       pfkey.c rde_update.c rde_attr.c rde_community.c printconf.c \
        rde_filter.c rde_sets.c rde_trie.c pftable.c name2id.c \
        util.c carp.c timer.c
 CFLAGS+= -Wall -I${.CURDIR}
Index: usr.sbin/bgpd/bgpd.h
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/bgpd.h,v
retrieving revision 1.382
diff -u -p -r1.382 bgpd.h
--- usr.sbin/bgpd/bgpd.h        8 May 2019 12:41:55 -0000       1.382
+++ usr.sbin/bgpd/bgpd.h        12 May 2019 15:25:13 -0000
@@ -412,7 +412,6 @@ enum network_type {
 struct network_config {
        struct bgpd_addr         prefix;
        struct filter_set_head   attrset;
-       struct rde_aspath       *asp;
        char                     psname[SET_NAME_LEN];
        u_int64_t                rd;
        u_int16_t                rtlabel;
@@ -446,6 +445,7 @@ enum imsg_type {
        IMSG_CTL_SHOW_INTERFACE,
        IMSG_CTL_SHOW_RIB,
        IMSG_CTL_SHOW_RIB_PREFIX,
+       IMSG_CTL_SHOW_RIB_COMMUNITIES,
        IMSG_CTL_SHOW_RIB_ATTR,
        IMSG_CTL_SHOW_NETWORK,
        IMSG_CTL_SHOW_RIB_MEM,
@@ -787,9 +787,9 @@ struct filter_community {
                } l;
                struct ext {
                        u_int32_t       data1;
-                       u_int64_t       data2;
+                       u_int32_t       data2;
                        short           type;
-                       u_int8_t        subtype;        /* if extended type */
+                       short           subtype;
                } e;
        }               c;
 };
@@ -847,11 +847,11 @@ struct filter_peers {
        u_int8_t        ibgp;
 };
 
-/* special community type */
+/* special community type, keep in sync with the attribute type */
 #define        COMMUNITY_TYPE_NONE             0
-#define        COMMUNITY_TYPE_BASIC            1
-#define        COMMUNITY_TYPE_EXT              2
-#define        COMMUNITY_TYPE_LARGE            3
+#define        COMMUNITY_TYPE_BASIC            8
+#define        COMMUNITY_TYPE_EXT              16
+#define        COMMUNITY_TYPE_LARGE            32
 
 #define        COMMUNITY_ANY                   1
 #define        COMMUNITY_NEIGHBOR_AS           2
@@ -868,7 +868,7 @@ struct filter_peers {
 
 /* extended community definitions */
 #define EXT_COMMUNITY_IANA             0x80
-#define EXT_COMMUNITY_TRANSITIVE       0x40
+#define EXT_COMMUNITY_NON_TRANSITIVE   0x40
 #define EXT_COMMUNITY_VALUE            0x3f
 /* extended types transitive */
 #define EXT_COMMUNITY_TRANS_TWO_AS     0x00    /* 2 octet AS specific */
@@ -1089,6 +1089,10 @@ struct rde_memstats {
        long long       aspath_cnt;
        long long       aspath_size;
        long long       aspath_refs;
+       long long       comm_cnt;
+       long long       comm_nmemb;
+       long long       comm_size;
+       long long       comm_refs;
        long long       attr_cnt;
        long long       attr_refs;
        long long       attr_data;
Index: usr.sbin/bgpd/parse.y
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/parse.y,v
retrieving revision 1.387
diff -u -p -r1.387 parse.y
--- usr.sbin/bgpd/parse.y       3 May 2019 15:08:47 -0000       1.387
+++ usr.sbin/bgpd/parse.y       12 May 2019 15:25:13 -0000
@@ -1115,7 +1115,7 @@ l3vpnopts : RD STRING {
                         * RD is almost encode like an ext-community,
                         * but only almost so convert here.
                         */
-                       if (community_ext_conv(&ext, NULL, &rd, NULL)) {
+                       if (community_ext_conv(&ext, &rd) == -1) {
                                yyerror("bad encoding of rd");
                                YYERROR;
                        }
@@ -3728,6 +3728,7 @@ parseextcommunity(struct filter_communit
                        yyerror("Bad ext-community value too big");
                        return (-1);
                }
+               c->c.e.data1 = ullval >> 32;
                c->c.e.data2 = ullval;
                break;
        case EXT_COMMUNITY_NON_TRANS_OPAQUE:
Index: usr.sbin/bgpd/rde.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/rde.c,v
retrieving revision 1.468
diff -u -p -r1.468 rde.c
--- usr.sbin/bgpd/rde.c 31 Mar 2019 17:02:47 -0000      1.468
+++ usr.sbin/bgpd/rde.c 12 May 2019 15:25:13 -0000
@@ -57,7 +57,7 @@ void           rde_update_withdraw(struct rde_pe
                     u_int8_t);
 int             rde_attr_parse(u_char *, u_int16_t, struct rde_peer *,
                     struct filterstate *, struct mpattr *);
-int             rde_attr_add(struct rde_aspath *, u_char *, u_int16_t);
+int             rde_attr_add(struct filterstate *, u_char *, u_int16_t);
 u_int8_t        rde_attr_missing(struct rde_aspath *, int, u_int16_t);
 int             rde_get_mp_nexthop(u_char *, u_int16_t, u_int8_t,
                     struct filterstate *);
@@ -75,7 +75,7 @@ void           rde_dump_ctx_throttle(pid_t, int)
 void            rde_dump_ctx_terminate(pid_t);
 void            rde_dump_mrt_new(struct mrt *, pid_t, int);
 
-int             rde_l3vpn_import(struct rde_aspath *, struct l3vpn *);
+int             rde_l3vpn_import(struct rde_community *, struct l3vpn *);
 void            rde_reload_done(void);
 static void     rde_softreconfig_in_done(void *, u_int8_t);
 static void     rde_softreconfig_out_done(void *, u_int8_t);
@@ -104,7 +104,7 @@ void                 peer_dump(u_int32_t, u_int8_t);
 static void     peer_recv_eor(struct rde_peer *, u_int8_t);
 static void     peer_send_eor(struct rde_peer *, u_int8_t);
 
-void            network_add(struct network_config *, int);
+void            network_add(struct network_config *, struct filterstate *);
 void            network_delete(struct network_config *);
 static void     network_dump_upcall(struct rib_entry *, void *);
 static void     network_flush_upcall(struct rib_entry *, void *);
@@ -158,10 +158,10 @@ rde_sighdlr(int sig)
        }
 }
 
-u_int32_t      peerhashsize = 64;
+u_int32_t      peerhashsize = 1024;
 u_int32_t      pathhashsize = 128 * 1024;
 u_int32_t      attrhashsize = 16 * 1024;
-u_int32_t      nexthophashsize = 64;
+u_int32_t      nexthophashsize = 1024;
 
 void
 rde_main(int debug, int verbose)
@@ -213,6 +213,7 @@ rde_main(int debug, int verbose)
        pt_init();
        path_init(pathhashsize);
        aspath_init(pathhashsize);
+       communities_init(attrhashsize);
        attr_init(attrhashsize);
        nexthop_init(nexthophashsize);
        peer_init(peerhashsize);
@@ -352,6 +353,7 @@ rde_main(int debug, int verbose)
 }
 
 struct network_config   netconf_s, netconf_p;
+struct filterstate      netconf_state;
 struct filter_set_head *session_set, *parent_set;
 
 void
@@ -458,6 +460,14 @@ rde_dispatch_imsg_session(struct imsgbuf
                        memcpy(&netconf_s, imsg.data, sizeof(netconf_s));
                        TAILQ_INIT(&netconf_s.attrset);
                        session_set = &netconf_s.attrset;
+                       rde_filterstate_prep(&netconf_state, NULL, NULL, NULL,
+                           0);
+                       asp = &netconf_state.aspath;
+                       asp->aspath = aspath_get(NULL, 0);
+                       asp->origin = ORIGIN_IGP;
+                       asp->flags = F_ATTR_ORIGIN | F_ATTR_ASPATH |
+                           F_ATTR_LOCALPREF | F_PREFIX_ANNOUNCED |
+                           F_ANN_DYNAMIC;
                        break;
                case IMSG_NETWORK_ASPATH:
                        if (imsg.hdr.len - IMSG_HEADER_SIZE <
@@ -475,28 +485,28 @@ rde_dispatch_imsg_session(struct imsgbuf
                                bzero(&netconf_s, sizeof(netconf_s));
                                break;
                        }
-                       asp = path_get();
+                       asp = &netconf_state.aspath;
                        asp->lpref = csr.local_pref;
                        asp->med = csr.med;
                        asp->weight = csr.weight;
                        asp->flags = csr.flags;
                        asp->origin = csr.origin;
                        asp->flags |= F_PREFIX_ANNOUNCED | F_ANN_DYNAMIC;
+                       aspath_put(asp->aspath);
                        asp->aspath = aspath_get(asdata, csr.aspath_len);
-                       netconf_s.asp = asp;
                        break;
                case IMSG_NETWORK_ATTR:
                        if (imsg.hdr.len <= IMSG_HEADER_SIZE) {
                                log_warnx("rde_dispatch: wrong imsg len");
                                break;
                        }
-                       /* parse path attributes */
+                       /* parse optional path attributes */
                        len = imsg.hdr.len - IMSG_HEADER_SIZE;
-                       asp = netconf_s.asp;
-                       if (rde_attr_add(asp, imsg.data, len) == -1) {
+                       if (rde_attr_add(&netconf_state, imsg.data,
+                           len) == -1) {
                                log_warnx("rde_dispatch: bad network "
                                    "attribute");
-                               path_put(asp);
+                               rde_filterstate_clean(&netconf_state);
                                bzero(&netconf_s, sizeof(netconf_s));
                                break;
                        }
@@ -511,12 +521,12 @@ rde_dispatch_imsg_session(struct imsgbuf
                        case AID_INET:
                                if (netconf_s.prefixlen > 32)
                                        goto badnet;
-                               network_add(&netconf_s, 0);
+                               network_add(&netconf_s, &netconf_state);
                                break;
                        case AID_INET6:
                                if (netconf_s.prefixlen > 128)
                                        goto badnet;
-                               network_add(&netconf_s, 0);
+                               network_add(&netconf_s, &netconf_state);
                                break;
                        case 0:
                                /* something failed beforehands */
@@ -526,6 +536,7 @@ badnet:
                                log_warnx("request to insert invalid network");
                                break;
                        }
+                       rde_filterstate_clean(&netconf_state);
                        break;
                case IMSG_NETWORK_REMOVE:
                        if (imsg.hdr.len - IMSG_HEADER_SIZE !=
@@ -630,6 +641,9 @@ badnetdel:
                        aspath_hash_stats(&rdehash);
                        imsg_compose(ibuf_se_ctl, IMSG_CTL_SHOW_RIB_HASH, 0,
                            imsg.hdr.pid, -1, &rdehash, sizeof(rdehash));
+                       communities_hash_stats(&rdehash);
+                       imsg_compose(ibuf_se_ctl, IMSG_CTL_SHOW_RIB_HASH, 0,
+                           imsg.hdr.pid, -1, &rdehash, sizeof(rdehash));
                        attr_hash_stats(&rdehash);
                        imsg_compose(ibuf_se_ctl, IMSG_CTL_SHOW_RIB_HASH, 0,
                            imsg.hdr.pid, -1, &rdehash, sizeof(rdehash));
@@ -679,12 +693,14 @@ rde_dispatch_imsg_parent(struct imsgbuf 
        struct imsg              imsg;
        struct mrt               xmrt;
        struct rde_rib           rn;
+       struct filterstate       state;
        struct imsgbuf          *i;
        struct filter_head      *nr;
        struct filter_rule      *r;
        struct filter_set       *s;
        struct rib              *rib;
        struct rde_prefixset    *ps;
+       struct rde_aspath       *asp;
        struct prefixset_item    psi;
        char                    *name;
        size_t                   nmemb;
@@ -738,7 +754,16 @@ rde_dispatch_imsg_parent(struct imsgbuf 
                        break;
                case IMSG_NETWORK_DONE:
                        parent_set = NULL;
-                       network_add(&netconf_p, 1);
+
+                       rde_filterstate_prep(&state, NULL, NULL, NULL, 0);
+                       asp = &state.aspath;
+                       asp->aspath = aspath_get(NULL, 0);
+                       asp->origin = ORIGIN_IGP;
+                       asp->flags = F_ATTR_ORIGIN | F_ATTR_ASPATH |
+                           F_ATTR_LOCALPREF | F_PREFIX_ANNOUNCED;
+
+                       network_add(&netconf_p, &state);
+                       rde_filterstate_clean(&state);
                        break;
                case IMSG_NETWORK_REMOVE:
                        if (imsg.hdr.len - IMSG_HEADER_SIZE !=
@@ -1085,7 +1110,7 @@ rde_update_dispatch(struct imsg *imsg)
            imsg->hdr.len - IMSG_HEADER_SIZE - 4 - withdrawn_len - attrpath_len;
        bzero(&mpa, sizeof(mpa));
 
-       rde_filterstate_prep(&state, NULL, NULL, 0);
+       rde_filterstate_prep(&state, NULL, NULL, NULL, 0);
        if (attrpath_len != 0) { /* 0 = no NLRI information in this message */
                /* parse path attributes */
                while (len > 0) {
@@ -1445,8 +1470,8 @@ rde_update_update(struct rde_peer *peer,
        for (i = RIB_LOC_START; i < rib_size; i++) {
                if (!rib_valid(i))
                        continue;
-               rde_filterstate_prep(&state, &in->aspath, in->nexthop,
-                   in->nhflags);
+               rde_filterstate_prep(&state, &in->aspath, &in->communities,
+                   in->nexthop, in->nhflags);
                /* input filter */
                action = rde_filter(ribs[i].in_rules, peer, p, &state);
 
@@ -1696,7 +1721,8 @@ bad_flags:
                if (!CHECK_FLAGS(flags, ATTR_OPTIONAL|ATTR_TRANSITIVE,
                    ATTR_PARTIAL))
                        goto bad_flags;
-               if (attr_len == 0 || attr_len % 4 != 0) {
+               if (community_add(&state->communities, flags, p,
+                   attr_len) == -1) {
                        /*
                         * mark update as bad and withdraw all routes as per
                         * RFC 7606
@@ -1706,12 +1732,14 @@ bad_flags:
                            "path invalidated and prefix withdrawn");
                        break;
                }
-               goto optattr;
+               plen += attr_len;
+               break;
        case ATTR_LARGE_COMMUNITIES:
                if (!CHECK_FLAGS(flags, ATTR_OPTIONAL|ATTR_TRANSITIVE,
                    ATTR_PARTIAL))
                        goto bad_flags;
-               if (attr_len == 0 || attr_len % 12 != 0) {
+               if (community_large_add(&state->communities, flags, p,
+                   attr_len) == -1) {
                        /*
                         * mark update as bad and withdraw all routes as per
                         * RFC 7606
@@ -1721,12 +1749,14 @@ bad_flags:
                            "path invalidated and prefix withdrawn");
                        break;
                }
-               goto optattr;
+               plen += attr_len;
+               break;
        case ATTR_EXT_COMMUNITIES:
                if (!CHECK_FLAGS(flags, ATTR_OPTIONAL|ATTR_TRANSITIVE,
                    ATTR_PARTIAL))
                        goto bad_flags;
-               if (attr_len == 0 || attr_len % 8 != 0) {
+               if (community_ext_add(&state->communities, flags, p,
+                   attr_len) == -1) {
                        /*
                         * mark update as bad and withdraw all routes as per
                         * RFC 7606
@@ -1736,7 +1766,8 @@ bad_flags:
                            "path invalidated and prefix withdrawn");
                        break;
                }
-               goto optattr;
+               plen += attr_len;
+               break;
        case ATTR_ORIGINATOR_ID:
                if (attr_len != 4)
                        goto bad_len;
@@ -1843,7 +1874,7 @@ bad_list:
 }
 
 int
-rde_attr_add(struct rde_aspath *a, u_char *p, u_int16_t len)
+rde_attr_add(struct filterstate *state, u_char *p, u_int16_t len)
 {
        u_int16_t        attr_len;
        u_int16_t        plen = 0;
@@ -1851,8 +1882,6 @@ rde_attr_add(struct rde_aspath *a, u_cha
        u_int8_t         type;
        u_int8_t         tmp8;
 
-       if (a == NULL)          /* no aspath, nothing to do */
-               return (0);
        if (len < 3)
                return (-1);
 
@@ -1872,7 +1901,18 @@ rde_attr_add(struct rde_aspath *a, u_cha
        if (len - plen < attr_len)
                return (-1);
 
-       if (attr_optadd(a, flags, type, p, attr_len) == -1)
+       switch (type) {
+       case ATTR_COMMUNITIES:
+               return community_add(&state->communities, flags, p, attr_len);
+       case ATTR_LARGE_COMMUNITIES:
+               return community_large_add(&state->communities, flags, p,
+                   attr_len);
+       case ATTR_EXT_COMMUNITIES:
+               return community_ext_add(&state->communities, flags, p,
+                   attr_len);
+       }
+
+       if (attr_optadd(&state->aspath, flags, type, p, attr_len) == -1)
                return (-1);
        return (0);
 }
@@ -2226,7 +2266,21 @@ rde_dump_rib_as(struct prefix *p, struct
                return;
        imsg_close(ibuf_se_ctl, wbuf);
 
-       if (flags & F_CTL_DETAIL)
+       if (flags & F_CTL_DETAIL) {
+               struct rde_community *comm = prefix_communities(p);
+               size_t len = comm->nentries * sizeof(struct community);
+               if (comm->nentries > 0) {
+                       if ((wbuf = imsg_create(ibuf_se_ctl,
+                           IMSG_CTL_SHOW_RIB_COMMUNITIES, 0, pid,
+                           len)) == NULL)
+                               return;
+                       if ((bp = ibuf_reserve(wbuf, len)) == NULL) {
+                               ibuf_free(wbuf);
+                               return;
+                       }
+                       memcpy(bp, comm->communities, len);
+                       imsg_close(ibuf_se_ctl, wbuf);
+               }
                for (l = 0; l < asp->others_len; l++) {
                        if ((a = asp->others[l]) == NULL)
                                break;
@@ -2245,6 +2299,7 @@ rde_dump_rib_as(struct prefix *p, struct
                        }
                        imsg_close(ibuf_se_ctl, wbuf);
                }
+       }
 }
 
 static int
@@ -2283,21 +2338,10 @@ rde_dump_filter(struct prefix *p, struct
        if (req->as.type != AS_UNDEF &&
            !aspath_match(asp->aspath, &req->as, 0))
                return;
-       switch (req->community.type) {
-       case COMMUNITY_TYPE_NONE:
-               break;
-       case COMMUNITY_TYPE_BASIC:
-               if (!community_match(asp, &req->community, NULL))
-                       return;
-               break;
-       case COMMUNITY_TYPE_LARGE:
-               if (!community_large_match(asp, &req->community, NULL))
-                       return;
-               break;
-       case COMMUNITY_TYPE_EXT:
-               if (!community_ext_match(asp, &req->community, 0))
+       if (req->community.type != COMMUNITY_TYPE_NONE) {
+               if (!community_match(prefix_communities(p), &req->community,
+                   NULL))
                        return;
-               break;
        }
        if (!ovs_match(p, req->flags))
                return;
@@ -2526,12 +2570,12 @@ rde_dump_mrt_new(struct mrt *mrt, pid_t 
  * kroute specific functions
  */
 int
-rde_l3vpn_import(struct rde_aspath *asp, struct l3vpn *rd)
+rde_l3vpn_import(struct rde_community *comm, struct l3vpn *rd)
 {
        struct filter_set       *s;
 
        TAILQ_FOREACH(s, &rd->import, entry) {
-               if (community_ext_match(asp, &s->action.community, 0))
+               if (community_match(comm, &s->action.community, 0))
                        return (1);
        }
        return (0);
@@ -2585,7 +2629,7 @@ rde_send_kroute(struct rib *rib, struct 
                        break;
 
                SIMPLEQ_FOREACH(vpn, l3vpns_l, entry) {
-                       if (!rde_l3vpn_import(asp, vpn))
+                       if (!rde_l3vpn_import(prefix_communities(p), vpn))
                                continue;
                        /* must send exit_nexthop so that correct MPLS tunnel
                         * is chosen
@@ -3178,8 +3222,8 @@ rde_softreconfig_in(struct rib_entry *re
                        if (rib->state != RECONF_RELOAD && !force_eval)
                                continue;
 
-                       rde_filterstate_prep(&state, asp, prefix_nexthop(p),
-                           prefix_nhflags(p));
+                       rde_filterstate_prep(&state, asp, prefix_communities(p),
+                           prefix_nexthop(p), prefix_nhflags(p));
                        action = rde_filter(rib->in_rules, peer, p, &state);
 
                        if (action == ACTION_ALLOW) {
@@ -3681,11 +3725,9 @@ peer_send_eor(struct rde_peer *peer, u_i
  * network announcement stuff
  */
 void
-network_add(struct network_config *nc, int flagstatic)
+network_add(struct network_config *nc, struct filterstate *state)
 {
-       struct filterstate       state;
        struct l3vpn            *vpn;
-       struct rde_aspath       *asp;
        struct filter_set_head  *vpnset = NULL;
        in_addr_t                prefix4;
        struct in6_addr          prefix6;
@@ -3747,40 +3789,25 @@ network_add(struct network_config *nc, i
                }
        }
 
-       if (nc->type == NETWORK_MRTCLONE) {
-               asp = nc->asp;
-       } else {
-               asp = path_get();
-               asp->aspath = aspath_get(NULL, 0);
-               asp->origin = ORIGIN_IGP;
-               asp->flags = F_ATTR_ORIGIN | F_ATTR_ASPATH |
-                   F_ATTR_LOCALPREF | F_PREFIX_ANNOUNCED;
-               /* the nexthop is unset unless a default set overrides it */
-       }
-       if (!flagstatic)
-               asp->flags |= F_ANN_DYNAMIC;
-       rde_filterstate_prep(&state, asp, NULL, 0); /* nexthop is not set */
-       rde_apply_set(&nc->attrset, &state, nc->prefix.aid, peerself, peerself);
+       rde_apply_set(&nc->attrset, state, nc->prefix.aid, peerself, peerself);
        if (vpnset)
-               rde_apply_set(vpnset, &state, nc->prefix.aid, peerself,
+               rde_apply_set(vpnset, state, nc->prefix.aid, peerself,
                    peerself);
 
        vstate = rde_roa_validity(&conf->rde_roa, &nc->prefix,
-           nc->prefixlen, aspath_origin(asp->aspath));
-       if (path_update(&ribs[RIB_ADJ_IN].rib, peerself, &state, &nc->prefix,
+           nc->prefixlen, aspath_origin(state->aspath.aspath));
+       if (path_update(&ribs[RIB_ADJ_IN].rib, peerself, state, &nc->prefix,
            nc->prefixlen, vstate) == 1)
                peerself->prefix_cnt++;
        for (i = RIB_LOC_START; i < rib_size; i++) {
                if (!rib_valid(i))
                        continue;
                rde_update_log("announce", i, peerself,
-                   state.nexthop ? &state.nexthop->exit_nexthop : NULL,
+                   state->nexthop ? &state->nexthop->exit_nexthop : NULL,
                    &nc->prefix, nc->prefixlen);
-               path_update(&ribs[i].rib, peerself, &state, &nc->prefix,
+               path_update(&ribs[i].rib, peerself, state, &nc->prefix,
                    nc->prefixlen, vstate);
        }
-       rde_filterstate_clean(&state);
-       path_put(asp);
        filterset_free(&nc->attrset);
 }
 
Index: usr.sbin/bgpd/rde.h
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/rde.h,v
retrieving revision 1.211
diff -u -p -r1.211 rde.h
--- usr.sbin/bgpd/rde.h 7 Mar 2019 07:42:36 -0000       1.211
+++ usr.sbin/bgpd/rde.h 12 May 2019 15:25:13 -0000
@@ -173,6 +173,30 @@ struct mpattr {
        u_int16_t        unreach_len;
 };
 
+
+struct community {
+       u_int8_t        type;
+       u_int8_t        p1;
+       u_int8_t        p2;
+       u_int8_t        p3;
+       u_int32_t       data1;
+       u_int32_t       data2;
+       u_int32_t       data3;
+};
+
+struct rde_community {
+       LIST_ENTRY(rde_community)       entry;
+       size_t                          size;
+       size_t                          nentries;
+       int                             flags;
+       int                             refcnt;
+       struct community                *communities;
+};
+
+#define        PARTIAL_COMMUNITIES             0x01
+#define        PARTIAL_LARGE_COMMUNITIES       0x02
+#define        PARTIAL_EXT_COMMUNITIES         0x04
+
 #define        F_ATTR_ORIGIN           0x00001
 #define        F_ATTR_ASPATH           0x00002
 #define        F_ATTR_NEXTHOP          0x00004
@@ -292,6 +316,7 @@ struct prefix {
        RB_ENTRY(prefix)                 entry;
        struct rib_entry                *re;
        struct rde_aspath               *aspath;
+       struct rde_community            *communities;
        struct rde_peer                 *peer;
        struct nexthop                  *nexthop;       /* may be NULL */
        time_t                           lastchange;
@@ -310,6 +335,7 @@ struct prefix {
 
 struct filterstate {
        struct rde_aspath        aspath;
+       struct rde_community     communities;
        struct nexthop          *nexthop;
        u_int8_t                 nhflags;
 };
@@ -374,34 +400,60 @@ u_char            *aspath_override(struct aspath *
                    u_int16_t *);
 int             aspath_lenmatch(struct aspath *, enum aslen_spec, u_int);
 
-int     community_match(struct rde_aspath *, struct filter_community *,
+int     community_match(struct rde_community *, struct filter_community *,
            struct rde_peer *);
-int     community_set(struct rde_aspath *, struct filter_community *,
+int     community_set(struct rde_community *, struct filter_community *,
            struct rde_peer *);
-void    community_delete(struct rde_aspath *, struct filter_community *,
+void    community_delete(struct rde_community *, struct filter_community *,
            struct rde_peer *);
-int     community_large_match(struct rde_aspath *, struct filter_community *,
-           struct rde_peer *);
-int     community_large_set(struct rde_aspath *, struct filter_community *,
-           struct rde_peer *);
-void    community_large_delete(struct rde_aspath *, struct filter_community *,
-           struct rde_peer *);
-int     community_ext_match(struct rde_aspath *,
-           struct filter_community *, struct rde_peer *);
-int     community_ext_set(struct rde_aspath *,
-           struct filter_community *, struct rde_peer *);
-void    community_ext_delete(struct rde_aspath *,
-           struct filter_community *, struct rde_peer *);
-int     community_ext_conv(struct filter_community *, struct rde_peer *,
-           u_int64_t *, u_int64_t *);
-u_char *community_ext_delete_non_trans(u_char *, u_int16_t, u_int16_t *);
+
+int     community_add(struct rde_community *, int, void *, size_t);
+int     community_large_add(struct rde_community *, int, void *, size_t);
+int     community_ext_add(struct rde_community *, int, void *, size_t);
+
+int     community_write(struct rde_community *, void *, u_int16_t);
+int     community_large_write(struct rde_community *, void *, u_int16_t);
+int     community_ext_write(struct rde_community *, int, void *, u_int16_t);
+
+void                    communities_init(u_int32_t);
+void                    communities_shutdown(void);
+void                    communities_hash_stats(struct rde_hashstats *);
+struct rde_community   *communities_lookup(struct rde_community *);
+struct rde_community   *communities_link(struct rde_community *);
+void                    communities_unlink(struct rde_community *);
+
+int     communities_compare(struct rde_community *, struct rde_community *);
+void    communities_copy(struct rde_community *, struct rde_community *);
+void    communities_clean(struct rde_community *);
+
+static inline struct rde_community *
+communities_get(struct rde_community *comm)
+{
+       if (comm->refcnt == 0)
+               fatalx("%s: not-referenced community", __func__);
+       comm->refcnt++;
+       rdemem.comm_refs++;
+       return comm;
+}
+
+static inline void
+communities_put(struct rde_community *comm)
+{
+       if (comm == NULL)
+               return;
+       rdemem.comm_refs--;
+       if (--comm->refcnt == 1)
+               communities_unlink(comm);
+}
+
+int     community_ext_conv(struct filter_community *, u_int64_t *);
 
 /* rde_decide.c */
 void            prefix_evaluate(struct prefix *, struct rib_entry *);
 
 /* rde_filter.c */
 void            rde_filterstate_prep(struct filterstate *, struct rde_aspath *,
-                    struct nexthop *, u_int8_t);
+                    struct rde_community *, struct nexthop *, u_int8_t);
 void            rde_filterstate_clean(struct filterstate *);
 enum filter_actions rde_filter(struct filter_head *, struct rde_peer *,
                     struct prefix *, struct filterstate *);
@@ -523,6 +575,12 @@ static inline struct rde_aspath *
 prefix_aspath(struct prefix *p)
 {
        return (p->aspath);
+}
+
+static inline struct rde_community *
+prefix_communities(struct prefix *p)
+{
+       return (p->communities);
 }
 
 static inline struct nexthop *
Index: usr.sbin/bgpd/rde_attr.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/rde_attr.c,v
retrieving revision 1.121
diff -u -p -r1.121 rde_attr.c
--- usr.sbin/bgpd/rde_attr.c    26 Feb 2019 12:46:08 -0000      1.121
+++ usr.sbin/bgpd/rde_attr.c    12 May 2019 15:25:13 -0000
@@ -18,15 +18,11 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <sys/types.h>
 #include <sys/queue.h>
 
-#include <netinet/in.h>
-
 #include <endian.h>
 #include <limits.h>
 #include <stdlib.h>
-#include <stdio.h>
 #include <string.h>
 #include <siphash.h>
 
@@ -61,6 +57,9 @@ attr_write(void *p, u_int16_t p_len, u_i
        } else
                *b++ = (u_char)data_len;
 
+       if (data == NULL)
+               return (tot_len - data_len);
+
        if (data_len != 0)
                memcpy(b, data, data_len);
 
@@ -129,11 +128,11 @@ attr_init(u_int32_t hashsize)
 void
 attr_shutdown(void)
 {
-       u_int32_t       i;
+       u_int64_t       i;
 
        for (i = 0; i <= attrtable.hashmask; i++)
                if (!LIST_EMPTY(&attrtable.hashtbl[i]))
-                       log_warnx("attr_shutdown: free non-free table");
+                       log_warnx("%s: free non-free table", __func__);
 
        free(attrtable.hashtbl);
 }
@@ -142,7 +141,7 @@ void
 attr_hash_stats(struct rde_hashstats *hs)
 {
        struct attr             *a;
-       u_int32_t               i;
+       u_int64_t               i;
        int64_t                 n;
 
        memset(hs, 0, sizeof(*hs));
@@ -1147,764 +1146,4 @@ aspath_lenmatch(struct aspath *a, enum a
                        fatalx("%s: would overflow", __func__);
        }
        return (0);
-}
-
-/*
- * Functions handling communities and extended communities.
- */
-
-static int
-community_extract(struct filter_community *fc, struct rde_peer *peer,
-     int field, u_int32_t *value)
-{
-       u_int32_t data;
-       u_int8_t flag;
-       switch (field) {
-       case 1:
-               flag = fc->dflag1;
-               switch (fc->type) {
-               case COMMUNITY_TYPE_BASIC:
-                       data = fc->c.b.data1;
-                       break;
-               case COMMUNITY_TYPE_LARGE:
-                       data = fc->c.l.data1;
-                       break;
-               case COMMUNITY_TYPE_EXT:
-                       data = fc->c.e.data1;
-                       break;
-               default:
-                       fatalx("%s: unknown type %d", __func__, fc->type);
-               }
-               break;
-       case 2:
-               flag = fc->dflag2;
-               switch (fc->type) {
-               case COMMUNITY_TYPE_BASIC:
-                       data = fc->c.b.data2;
-                       break;
-               case COMMUNITY_TYPE_LARGE:
-                       data = fc->c.l.data2;
-                       break;
-               case COMMUNITY_TYPE_EXT:
-                       data = fc->c.e.data2;
-                       break;
-               default:
-                       fatalx("%s: unknown type %d", __func__, fc->type);
-               }
-               break;
-       case 3:
-               if (fc->type != COMMUNITY_TYPE_LARGE)
-                       fatalx("%s: bad field %d", __func__, field);
-               flag = fc->dflag3;
-               data = fc->c.l.data3;
-               break;
-       default:
-               fatalx("%s: unknown field %d", __func__, field);
-       }
-
-       switch (flag) {
-       case COMMUNITY_NEIGHBOR_AS:
-               if (peer == NULL)
-                       return -1;
-               *value = peer->conf.remote_as;
-               break;
-       case COMMUNITY_LOCAL_AS:
-               if (peer == NULL)
-                       return -1;
-               *value = peer->conf.local_as;
-               break;
-       default:
-               *value = data;
-               break;
-       }
-       if (fc->type == COMMUNITY_TYPE_BASIC && *value > USHRT_MAX)
-               return -1;
-       return 0;
-}
-
-static int
-community_ext_matchone(struct filter_community *c, struct rde_peer *peer,
-    u_int64_t community)
-{
-       u_int32_t       val;
-       u_int64_t       com, mask;
-
-       community = be64toh(community);
-
-       if (c->dflag3 == COMMUNITY_ANY)
-               /* handle 'ext-community *', etc */
-               return (1);
-
-       /* special handling of ext-community rt * since type is not known */
-       if (c->dflag1 == COMMUNITY_ANY && c->c.e.type == -1) {
-               u_int8_t type = community >> 56;
-               if (type == EXT_COMMUNITY_TRANS_TWO_AS ||
-                   type == EXT_COMMUNITY_TRANS_FOUR_AS ||
-                   type == EXT_COMMUNITY_TRANS_IPV4)
-                       goto subtype;
-       }
-
-       com = (u_int64_t)c->c.e.type << 56;
-       mask = 0xffULL << 56;
-       if ((com & mask) != (community & mask))
-               return (0);
-
-       switch (c->c.e.type & EXT_COMMUNITY_VALUE) {
-       case EXT_COMMUNITY_TRANS_TWO_AS:
-       case EXT_COMMUNITY_TRANS_FOUR_AS:
-       case EXT_COMMUNITY_TRANS_IPV4:
-       case EXT_COMMUNITY_TRANS_OPAQUE:
-       case EXT_COMMUNITY_NON_TRANS_OPAQUE:
-subtype:
-               com = (u_int64_t)c->c.e.subtype << 48;
-               mask = 0xffULL << 48;
-               if ((com & mask) != (community & mask))
-                       return (0);
-               break;
-       default:
-               com = c->c.e.data2 & 0xffffffffffffffULL;
-               mask = 0xffffffffffffffULL;
-               if ((com & mask) == (community & mask))
-                       return (1);
-               return (0);
-       }
-
-       if (c->dflag1 == COMMUNITY_ANY)
-               /* handle 'ext-community rt *', etc */
-               return (1);
-
-       switch (c->c.e.type & EXT_COMMUNITY_VALUE) {
-       case EXT_COMMUNITY_TRANS_TWO_AS:
-               if (community_extract(c, peer, 1, &val) == -1)
-                       return (0);
-               com = (u_int64_t)val << 32;
-               mask = 0xffffULL << 32;
-               if ((com & mask) != (community & mask))
-                       return (0);
-
-               if (community_extract(c, peer, 2, &val) == -1)
-                       return (0);
-               com = val;
-               mask = 0xffffffffULL;
-               break;
-       case EXT_COMMUNITY_TRANS_IPV4:
-       case EXT_COMMUNITY_TRANS_FOUR_AS:
-               if (community_extract(c, peer, 1, &val) == -1)
-                       return (0);
-               com = (u_int64_t)val << 16;
-               mask = 0xffffffffULL << 16;
-               if ((com & mask) != (community & mask))
-                       return (0);
-
-               if (community_extract(c, peer, 2, &val) == -1)
-                       return (0);
-               com = val;
-               mask = 0xffff;
-               break;
-       case EXT_COMMUNITY_TRANS_OPAQUE:
-       case EXT_COMMUNITY_NON_TRANS_OPAQUE:
-               com = c->c.e.data2;
-               mask = EXT_COMMUNITY_OPAQUE_MAX;
-               break;
-       }
-
-       if (c->dflag2 == COMMUNITY_ANY ||
-           (com & mask) == (community & mask))
-               return (1);
-       return (0);
-}
-
-int
-community_match(struct rde_aspath *asp, struct filter_community *fc,
-    struct rde_peer *peer)
-{
-       struct attr     *a;
-       u_int8_t        *p;
-       u_int32_t        as, type, eas, etype;
-       u_int16_t        len;
-
-       a = attr_optget(asp, ATTR_COMMUNITIES);
-       if (a == NULL)
-               /* no communities, no match */
-               return (0);
-
-       if (community_extract(fc, peer, 1, &as) == -1 ||
-           community_extract(fc, peer, 2, &type) == -1)
-               /* can't match community */
-               return (0);
-
-       p = a->data;
-       for (len = a->len / 4; len > 0; len--) {
-               eas = *p++;
-               eas <<= 8;
-               eas |= *p++;
-               etype = *p++;
-               etype <<= 8;
-               etype |= *p++;
-               if ((fc->dflag1 == COMMUNITY_ANY || as == eas) &&
-                   (fc->dflag2 == COMMUNITY_ANY || type == etype))
-                       return (1);
-       }
-       return (0);
-}
-
-int
-community_set(struct rde_aspath *asp, struct filter_community *fc,
-    struct rde_peer *peer)
-{
-       struct attr     *attr;
-       u_int8_t        *p = NULL;
-       unsigned int     i, ncommunities = 0;
-       u_int32_t        as, type;
-       u_int8_t         f = ATTR_OPTIONAL|ATTR_TRANSITIVE;
-
-       if (fc->dflag1 == COMMUNITY_ANY || fc->dflag2 == COMMUNITY_ANY ||
-           community_extract(fc, peer, 1, &as) == -1 ||
-           community_extract(fc, peer, 2, &type) == -1)
-               /* bad community */
-               return (0);
-
-       attr = attr_optget(asp, ATTR_COMMUNITIES);
-       if (attr != NULL) {
-               p = attr->data;
-               ncommunities = attr->len / 4;
-       }
-
-       /* first check if the community is not already set */
-       for (i = 0; i < ncommunities; i++) {
-               if (as >> 8 == p[0] && (as & 0xff) == p[1] &&
-                   type >> 8 == p[2] && (type & 0xff) == p[3])
-                       /* already present, nothing todo */
-                       return (1);
-               p += 4;
-       }
-
-       if (ncommunities++ >= USHRT_MAX / 4)
-               /* overflow */
-               return (0);
-
-       if ((p = reallocarray(NULL, ncommunities, 4)) == NULL)
-               fatal("community_set");
-
-       p[0] = as >> 8;
-       p[1] = as & 0xff;
-       p[2] = type >> 8;
-       p[3] = type & 0xff;
-
-       if (attr != NULL) {
-               memcpy(p + 4, attr->data, attr->len);
-               f = attr->flags;
-               attr_free(asp, attr);
-       }
-
-       attr_optadd(asp, f, ATTR_COMMUNITIES, p, ncommunities * 4);
-
-       free(p);
-       return (1);
-}
-
-void
-community_delete(struct rde_aspath *asp, struct filter_community *fc,
-    struct rde_peer *peer)
-{
-       struct attr     *attr;
-       u_int8_t        *p, *n;
-       u_int16_t        l, len = 0;
-       u_int32_t        as, type, eas, etype;
-       u_int8_t         f;
-
-       attr = attr_optget(asp, ATTR_COMMUNITIES);
-       if (attr == NULL)
-               /* no attr nothing to do */
-               return;
-
-       if (community_extract(fc, peer, 1, &as) == -1 ||
-           community_extract(fc, peer, 2, &type) == -1)
-               /* bad community, nothing to do */
-               return;
-
-       p = attr->data;
-       for (l = 0; l < attr->len; l += 4) {
-               eas = *p++;
-               eas <<= 8;
-               eas |= *p++;
-               etype = *p++;
-               etype <<= 8;
-               etype |= *p++;
-
-               if ((fc->dflag1 == COMMUNITY_ANY || as == eas) &&
-                   (fc->dflag2 == COMMUNITY_ANY || type == etype))
-                       /* match */
-                       continue;
-               len += 4;
-       }
-
-       if (len == 0) {
-               attr_free(asp, attr);
-               return;
-       }
-
-       if ((n = malloc(len)) == NULL)
-               fatal("community_delete");
-
-       p = attr->data;
-       for (l = 0; l < len && p < attr->data + attr->len; ) {
-               eas = *p++;
-               eas <<= 8;
-               eas |= *p++;
-               etype = *p++;
-               etype <<= 8;
-               etype |= *p++;
-
-               if ((fc->dflag1 == COMMUNITY_ANY || as == eas) &&
-                   (fc->dflag2 == COMMUNITY_ANY || type == etype))
-                       /* match */
-                       continue;
-               n[l++] = eas >> 8;
-               n[l++] = eas & 0xff;
-               n[l++] = etype >> 8;
-               n[l++] = etype & 0xff;
-       }
-
-       f = attr->flags;
-
-       attr_free(asp, attr);
-       attr_optadd(asp, f, ATTR_COMMUNITIES, n, len);
-       free(n);
-}
-
-int
-community_ext_match(struct rde_aspath *asp, struct filter_community *c,
-    struct rde_peer *peer)
-{
-       struct attr     *attr;
-       u_int8_t        *p;
-       u_int64_t        ec;
-       u_int16_t        len;
-
-       attr = attr_optget(asp, ATTR_EXT_COMMUNITIES);
-       if (attr == NULL)
-               /* no communities, no match */
-               return (0);
-
-       p = attr->data;
-       for (len = attr->len / sizeof(ec); len > 0; len--) {
-               memcpy(&ec, p, sizeof(ec));
-               if (community_ext_matchone(c, peer, ec))
-                       return (1);
-               p += sizeof(ec);
-       }
-
-       return (0);
-}
-
-int
-community_ext_set(struct rde_aspath *asp, struct filter_community *c,
-    struct rde_peer *peer)
-{
-       struct attr     *attr;
-       u_int8_t        *p = NULL;
-       u_int64_t        community;
-       unsigned int     i, ncommunities = 0;
-       u_int8_t         f = ATTR_OPTIONAL|ATTR_TRANSITIVE;
-
-       if (community_ext_conv(c, peer, &community, NULL))
-               return (0);
-
-       attr = attr_optget(asp, ATTR_EXT_COMMUNITIES);
-       if (attr != NULL) {
-               p = attr->data;
-               ncommunities = attr->len / sizeof(community);
-       }
-
-       /* first check if the community is not already set */
-       for (i = 0; i < ncommunities; i++) {
-               if (memcmp(&community, p, sizeof(community)) == 0)
-                       /* already present, nothing todo */
-                       return (1);
-               p += sizeof(community);
-       }
-
-       if (ncommunities++ >= USHRT_MAX / sizeof(community))
-               /* overflow */
-               return (0);
-
-       if ((p = reallocarray(NULL, ncommunities, sizeof(community))) == NULL)
-               fatal("community_ext_set");
-
-       memcpy(p, &community, sizeof(community));
-       if (attr != NULL) {
-               memcpy(p + sizeof(community), attr->data, attr->len);
-               f = attr->flags;
-               attr_free(asp, attr);
-       }
-
-       attr_optadd(asp, f, ATTR_EXT_COMMUNITIES, p,
-           ncommunities * sizeof(community));
-
-       free(p);
-       return (1);
-}
-
-void
-community_ext_delete(struct rde_aspath *asp, struct filter_community *c,
-    struct rde_peer *peer)
-{
-       struct attr     *attr;
-       u_int8_t        *p, *n;
-       u_int64_t        community, mask, test;
-       u_int16_t        l, len = 0;
-       u_int8_t         f;
-       int              check_type = 0;
-
-       if (community_ext_conv(c, peer, &community, &mask))
-               return;
-       if (mask != 0 && be64toh(mask) >> 56 == 0)
-               check_type = 1;
-
-       attr = attr_optget(asp, ATTR_EXT_COMMUNITIES);
-       if (attr == NULL)
-               /* no attr nothing to do */
-               return;
-
-       p = attr->data;
-       for (l = 0; l < attr->len; l += sizeof(community)) {
-               memcpy(&test, p + l, sizeof(community));
-               /* special handling of ext-community rt *, type is not known */
-               if (check_type) {
-                       u_int8_t type = be64toh(test) >> 56;
-                       if (type != EXT_COMMUNITY_TRANS_TWO_AS &&
-                           type != EXT_COMMUNITY_TRANS_FOUR_AS &&
-                           type != EXT_COMMUNITY_TRANS_IPV4) {
-                               /* no match */
-                               len += sizeof(community);
-                               continue;
-                       }
-               }
-               if ((test & mask) == (community & mask))
-                       /* match */
-                       continue;
-               len += sizeof(community);
-       }
-
-       if (len == 0) {
-               attr_free(asp, attr);
-               return;
-       }
-
-       if ((n = malloc(len)) == NULL)
-               fatal("community_delete");
-
-       p = attr->data;
-       for (l = 0; l < len && p < attr->data + attr->len;
-           p += sizeof(community)) {
-               memcpy(&test, p, sizeof(community));
-               /* special handling of ext-community rt *, type is not known */
-               if (check_type) {
-                       u_int8_t type = be64toh(test) >> 56;
-                       if (type != EXT_COMMUNITY_TRANS_TWO_AS &&
-                           type != EXT_COMMUNITY_TRANS_FOUR_AS &&
-                           type != EXT_COMMUNITY_TRANS_IPV4) {
-                               /* no match */
-                               memcpy(n + l, p, sizeof(community));
-                               l += sizeof(community);
-                               continue;
-                       }
-               }
-               if ((test & mask) == (community & mask)) {
-                       /* match */
-                       continue;
-               }
-               memcpy(n + l, p, sizeof(community));
-               l += sizeof(community);
-       }
-
-       f = attr->flags;
-
-       attr_free(asp, attr);
-       attr_optadd(asp, f, ATTR_EXT_COMMUNITIES, n, len);
-       free(n);
-}
-
-int
-community_ext_conv(struct filter_community *c, struct rde_peer *peer,
-    u_int64_t *community, u_int64_t *mask)
-{
-       u_int64_t       com = 0, m = 0;
-       u_int32_t       val;
-
-       if (c->dflag3 == COMMUNITY_ANY) {
-               m = ~m;
-               goto done;
-       }
-
-       /* special handling of ext-community rt * since type is not known */
-       if (c->dflag1 == COMMUNITY_ANY && c->c.e.type == -1) {
-               m |= 0xffULL << 56;
-               goto subtype;
-       }
-       com = (u_int64_t)c->c.e.type << 56;
-       switch (c->c.e.type & EXT_COMMUNITY_VALUE) {
-       case EXT_COMMUNITY_TRANS_TWO_AS:
-       case EXT_COMMUNITY_TRANS_FOUR_AS:
-       case EXT_COMMUNITY_TRANS_IPV4:
-       case EXT_COMMUNITY_TRANS_OPAQUE:
-subtype:
-               com |= (u_int64_t)c->c.e.subtype << 48;
-               if (c->dflag1 == COMMUNITY_ANY) {
-                       m |= 0xffffffffffffULL;
-                       goto done;
-               }
-               break;
-       }
-
-       switch (c->c.e.type & EXT_COMMUNITY_VALUE) {
-       case EXT_COMMUNITY_TRANS_TWO_AS:
-               if (community_extract(c, peer, 1, &val) == -1)
-                       return (-1);
-               com |= (u_int64_t)val << 32;
-
-               if (c->dflag2 == COMMUNITY_ANY) {
-                       m |= 0xffffffffULL;
-                       goto done;
-               }
-               if (community_extract(c, peer, 2, &val) == -1)
-                       return (-1);
-               com |= (u_int64_t)val & 0xffffffffULL;
-               break;
-       case EXT_COMMUNITY_TRANS_IPV4:
-       case EXT_COMMUNITY_TRANS_FOUR_AS:
-               if (community_extract(c, peer, 1, &val) == -1)
-                       return (-1);
-               com |= (u_int64_t)val << 16;
-
-               if (c->dflag2 == COMMUNITY_ANY) {
-                       m |= 0xffffULL;
-                       goto done;
-               }
-               if (community_extract(c, peer, 2, &val) == -1)
-                       return (-1);
-               com |= (u_int64_t)val & 0xffffULL;
-               break;
-       case EXT_COMMUNITY_TRANS_OPAQUE:
-               com |= c->c.e.data2 & EXT_COMMUNITY_OPAQUE_MAX;
-               break;
-       default:
-               com |= c->c.e.data2 & 0xffffffffffffffULL;
-               break;
-       }
-
-done:
-       *community = htobe64(com);
-       if (mask)
-               *mask = htobe64(~m);
-       else if (m != 0)
-               return (-1);
-
-       return (0);
-}
-
-u_char *
-community_ext_delete_non_trans(u_char *data, u_int16_t len, u_int16_t *newlen)
-{
-       u_int8_t        *ext = data, *newdata;
-       u_int16_t       l, nlen = 0;
-
-       for (l = 0; l < len; l += sizeof(u_int64_t)) {
-               if (!(ext[l] & EXT_COMMUNITY_TRANSITIVE))
-                       nlen += sizeof(u_int64_t);
-       }
-
-       if (nlen == 0) {
-               *newlen = 0;
-               return NULL;
-       }
-
-       newdata = malloc(nlen);
-       if (newdata == NULL)
-               fatal("%s", __func__);
-
-       for (l = 0, nlen = 0; l < len; l += sizeof(u_int64_t)) {
-               if (!(ext[l] & EXT_COMMUNITY_TRANSITIVE)) {
-                       memcpy(newdata + nlen, ext + l, sizeof(u_int64_t));
-                       nlen += sizeof(u_int64_t);
-               }
-       }
-
-       *newlen = nlen;
-       return newdata;
-}
-
-struct wire_largecommunity {
-       uint32_t        as;
-       uint32_t        ld1;
-       uint32_t        ld2;
-};
-
-int
-community_large_match(struct rde_aspath *asp, struct filter_community *fc,
-    struct rde_peer *peer)
-{
-       struct wire_largecommunity      *wlc;
-       struct attr     *a;
-       u_int8_t        *p;
-       u_int16_t        len;
-       u_int32_t        as, ld1, ld2;
-
-       a = attr_optget(asp, ATTR_LARGE_COMMUNITIES);
-       if (a == NULL)
-               /* no communities, no match */
-               return (0);
-
-       if (community_extract(fc, peer, 1, &as) == -1 ||
-           community_extract(fc, peer, 2, &ld1) == -1 ||
-           community_extract(fc, peer, 3, &ld2) == -1)
-               /* can't match community */
-               return (0);
-       
-       as = htonl(as);
-       ld1 = htonl(ld1);
-       ld2 = htonl(ld2);
-
-       p = a->data;
-       for (len = a->len / 12; len > 0; len--) {
-               wlc = (struct wire_largecommunity *)p;
-               p += 12;
-
-               if ((fc->dflag1 == COMMUNITY_ANY || as == wlc->as) &&
-                   (fc->dflag2 == COMMUNITY_ANY || ld1 == wlc->ld1) &&
-                   (fc->dflag3 == COMMUNITY_ANY || ld2 == wlc->ld2))
-                       return (1);
-       }
-       return (0);
-}
-
-int
-community_large_set(struct rde_aspath *asp, struct filter_community *fc,
-    struct rde_peer *peer)
-{
-       struct wire_largecommunity      *wlc;
-       struct attr     *attr;
-       u_int8_t        *p = NULL;
-       unsigned int     i, ncommunities = 0;
-       u_int32_t        as, ld1, ld2;
-       u_int8_t         f = ATTR_OPTIONAL|ATTR_TRANSITIVE;
-
-       if (fc->dflag1 == COMMUNITY_ANY || fc->dflag2 == COMMUNITY_ANY ||
-           fc->dflag3 == COMMUNITY_ANY ||
-           community_extract(fc, peer, 1, &as) == -1 ||
-           community_extract(fc, peer, 2, &ld1) == -1 ||
-           community_extract(fc, peer, 3, &ld2) == -1)
-               /* can't match community */
-               return (0);
-
-       as = htonl(as);
-       ld1 = htonl(ld1);
-       ld2 = htonl(ld2);
-
-       attr = attr_optget(asp, ATTR_LARGE_COMMUNITIES);
-       if (attr != NULL) {
-               p = attr->data;
-               ncommunities = attr->len / 12;
-       }
-
-       /* first check if the community is not already set */
-       for (i = 0; i < ncommunities; i++) {
-               wlc = (struct wire_largecommunity *)p;
-               if (wlc->as == as && wlc->ld1 == ld1 && wlc->ld2 == ld2)
-                       /* already present, nothing todo */
-                       return (1);
-               p += 12;
-       }
-
-       if (ncommunities++ >= USHRT_MAX / 12)
-               /* overflow */
-               return (0);
-
-       if ((p = reallocarray(NULL, ncommunities, 12)) == NULL)
-               fatal("community_set");
-
-       wlc = (struct wire_largecommunity *)p;
-       wlc->as = as;
-       wlc->ld1 = ld1;
-       wlc->ld2 = ld2;
-
-       if (attr != NULL) {
-               memcpy(p + 12, attr->data, attr->len);
-               f = attr->flags;
-               attr_free(asp, attr);
-       }
-
-       attr_optadd(asp, f, ATTR_LARGE_COMMUNITIES, p, ncommunities * 12);
-
-       free(p);
-       return (1);
-}
-
-void
-community_large_delete(struct rde_aspath *asp, struct filter_community *fc,
-    struct rde_peer *peer)
-{
-       struct wire_largecommunity      *wlc;
-       struct attr     *attr;
-       u_int8_t        *p, *n;
-       u_int16_t        l = 0, len = 0;
-       u_int32_t        as, ld1, ld2;
-       u_int8_t         f;
-
-       attr = attr_optget(asp, ATTR_LARGE_COMMUNITIES);
-       if (attr == NULL)
-               /* no attr nothing to do */
-               return;
-
-       if (community_extract(fc, peer, 1, &as) == -1 ||
-           community_extract(fc, peer, 2, &ld1) == -1 ||
-           community_extract(fc, peer, 3, &ld2) == -1)
-               /* can't match community */
-               return;
-
-       as = htonl(as);
-       ld1 = htonl(ld1);
-       ld2 = htonl(ld2);
-
-       p = attr->data;
-       for (len = 0; l < attr->len; l += 12) {
-               wlc = (struct wire_largecommunity *)p;
-               p += 12;
-
-               if ((fc->dflag1 == COMMUNITY_ANY || as == wlc->as) &&
-                   (fc->dflag2 == COMMUNITY_ANY || ld1 == wlc->ld1) &&
-                   (fc->dflag3 == COMMUNITY_ANY || ld2 == wlc->ld2))
-                       /* match */
-                       continue;
-               len += 12;
-       }
-
-       if (len == 0) {
-               attr_free(asp, attr);
-               return;
-       }
-
-       if ((n = malloc(len)) == NULL)
-               fatal("community_delete");
-
-       p = attr->data;
-       for (l = 0; l < len && p < attr->data + attr->len; ) {
-               wlc = (struct wire_largecommunity *)p;
-               p += 12;
-
-               if ((fc->dflag1 == COMMUNITY_ANY || as == wlc->as) &&
-                   (fc->dflag2 == COMMUNITY_ANY || ld1 == wlc->ld1) &&
-                   (fc->dflag3 == COMMUNITY_ANY || ld2 == wlc->ld2))
-                       /* match */
-                       continue;
-               memcpy(n + l, wlc, sizeof(*wlc));
-               l += 12;
-       }
-
-       f = attr->flags;
-
-       attr_free(asp, attr);
-       attr_optadd(asp, f, ATTR_LARGE_COMMUNITIES, n, len);
-       free(n);
 }
Index: usr.sbin/bgpd/rde_community.c
===================================================================
RCS file: usr.sbin/bgpd/rde_community.c
diff -N usr.sbin/bgpd/rde_community.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ usr.sbin/bgpd/rde_community.c       13 May 2019 21:16:55 -0000
@@ -0,0 +1,757 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2019 Claudio Jeker <clau...@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/queue.h>
+
+#include <endian.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <siphash.h>
+
+#include "bgpd.h"
+#include "rde.h"
+#include "log.h"
+
+struct community nomask = {
+       .data1 = UINT32_MAX,
+       .data2 = UINT32_MAX,
+       .data3 = UINT32_MAX
+};
+
+static int
+apply_flag(u_int32_t in, int flag, struct rde_peer *peer, void *has_mask,
+    u_int32_t *out, u_int32_t *mask)
+{
+       switch (flag) {
+       case COMMUNITY_NEIGHBOR_AS:
+               if (peer == NULL)
+                       return -1;
+               *out = peer->conf.remote_as;
+               break;
+       case COMMUNITY_LOCAL_AS:
+               if (peer == NULL)
+                       return -1;
+               *out = peer->conf.local_as;
+               break;
+       case COMMUNITY_ANY:
+               if (has_mask == NULL)
+                       return -1;
+               *out = 0;
+               *mask = 0;
+               return 0;
+       default:
+               *out = in;
+               break;
+       }
+       *mask = UINT32_MAX;
+       return 0;
+}
+
+static int
+fc2c(struct filter_community *fc, struct rde_peer *peer, struct community *c,
+    struct community *m)
+{
+       u_int32_t d1, d2, d3, m1, m2, m3;
+       u_int8_t type, subtype;
+
+       memset(c, 0, sizeof(*c));
+       if (m)
+               memset(m, 0, sizeof(*m));
+       c->type = fc->type;
+
+       switch (c->type) {
+       case COMMUNITY_TYPE_BASIC:
+               if (apply_flag(fc->c.b.data1, fc->dflag1, peer, m, &d1, &m1))
+                       return -1;
+               if (apply_flag(fc->c.b.data2, fc->dflag2, peer, m, &d2, &m2))
+                       return -1;
+
+               /* check that values fit */
+               if (d1 > USHRT_MAX || d2 > USHRT_MAX)
+                       return -1;
+               /* adjust mask to reduced size */
+               m1 &= USHRT_MAX;
+               m2 &= USHRT_MAX;
+
+               c->data1 = htonl(d1 << 16 | d2);
+               if (m)
+                       m->data1 = htonl(m1 << 16 | m2);
+               return 0;
+       case COMMUNITY_TYPE_LARGE:
+               if (apply_flag(fc->c.l.data1, fc->dflag1, peer, m, &d1, &m1))
+                       return -1;
+               if (apply_flag(fc->c.l.data2, fc->dflag2, peer, m, &d2, &m2))
+                       return -1;
+               if (apply_flag(fc->c.l.data3, fc->dflag3, peer, m, &d3, &m3))
+                       return -1;
+
+               c->data1 = htonl(d1);
+               c->data2 = htonl(d2);
+               c->data3 = htonl(d3);
+               if (m) {
+                       m->data1 = htonl(m1);
+                       m->data2 = htonl(m2);
+                       m->data3 = htonl(m3);
+               }
+               return 0;
+       case COMMUNITY_TYPE_EXT:
+               if (fc->dflag1 == COMMUNITY_ANY ||
+                   fc->dflag2 == COMMUNITY_ANY ||
+                   fc->dflag3 == COMMUNITY_ANY)
+                       if (m == NULL)
+                               return -1;
+
+               type = fc->c.e.type & 0xff;
+               subtype = fc->c.e.subtype;
+
+               d1 = (u_int32_t)type << 8 | subtype;
+               c->data1 = htonl(d1 << 16);
+               if (m)
+                       m->data1 = htonl(0xffff0000);
+
+               m1 = UINT32_MAX;
+               m2 = UINT32_MAX;
+
+               switch (fc->c.e.type) {
+               case EXT_COMMUNITY_TRANS_TWO_AS:
+                       if (fc->dflag1 == COMMUNITY_ANY)
+                               return 0;
+                       if (apply_flag(fc->c.e.data1, fc->dflag1, peer, NULL,
+                           &d1, &m1))
+                               return -1;
+                       if (apply_flag(fc->c.e.data2, fc->dflag2, peer, m,
+                           &d2, &m2))
+                               return -1;
+                       /* check that values fit */
+                       if (d1 > USHRT_MAX)
+                               return -1;
+                       c->data1 |= htonl(d1);
+                       if (m2 == 0)
+                               m2 = 0;
+                       else
+                               c->data2 = htonl(fc->c.e.data2);
+                       break;
+               case EXT_COMMUNITY_TRANS_FOUR_AS:
+               case EXT_COMMUNITY_TRANS_IPV4:
+                       if (fc->dflag1 == COMMUNITY_ANY)
+                               return 0;
+                       if (apply_flag(fc->c.e.data1, fc->dflag1, peer, NULL,
+                           &d1, &m1))
+                               return -1;
+                       if (apply_flag(fc->c.e.data2, fc->dflag2, peer, m,
+                           &d2, &m2))
+                               return -1;
+                       /* check that values fit */
+                       if (d2 > USHRT_MAX)
+                               return -1;
+                       if (m2 == 0)
+                               m2 = htonl(0xffff0000);
+                       d2 |= (d1 & 0xffff) << 16;
+                       c->data1 |= htonl(d1 >> 16);
+                       c->data2 = htonl(d2);
+                       break;
+               case -1:
+                       if (fc->dflag1 != COMMUNITY_ANY || m == NULL)
+                               return -1;
+                       c->data1 = htonl(subtype << 16);
+                       m->data1 = htonl(0x00ff0000);
+                       return 0;
+               case EXT_COMMUNITY_TRANS_OPAQUE:
+               case EXT_COMMUNITY_TRANS_EVPN:
+                       if (fc->dflag1 == COMMUNITY_ANY)
+                               return 0;
+                       c->data1 |= htonl(fc->c.e.data1);
+                       c->data2 = htonl(fc->c.e.data2);
+                       break;
+               case EXT_COMMUNITY_NON_TRANS_OPAQUE:
+                       if (fc->dflag1 == COMMUNITY_ANY)
+                               return 0;
+                       c->data2 = htonl(fc->c.e.data2);
+                       break;
+               }
+
+               if (m) {
+                       m->data1 = m1;
+                       m->data2 = m2;
+               }
+               return 0;
+       default:
+               fatalx("%s: unknown type %d", __func__, c->type);
+       }
+}
+
+static int
+match_community(struct community *a, struct community *b, struct community *m)
+{
+       if (a->type != b->type)
+               return a->type > b->type ? 1 : -1;
+
+       switch (a->type) {
+       case COMMUNITY_TYPE_BASIC:
+               if ((a->data1 & m->data1) != (b->data1 & m->data1)) {
+                       if (ntohl(a->data1 & m->data1) >
+                           ntohl(b->data1 & m->data1))
+                               return 1;
+                       return -1;
+               }
+               return 0;
+       case COMMUNITY_TYPE_LARGE:
+               if ((a->data1 & m->data1) != (b->data1 & m->data1)) {
+                       if (ntohl(a->data1 & m->data1) >
+                           ntohl(b->data1 & m->data1))
+                               return 1;
+                       return -1;
+               }
+               if ((a->data2 & m->data2) != (b->data2 & m->data2)) {
+                       if (ntohl(a->data2 & m->data2) >
+                           ntohl(b->data2 & m->data2))
+                               return 1;
+                       return -1;
+               }
+               if ((a->data3 & m->data3) != (b->data3 & m->data3)) {
+                       if (ntohl(a->data3 & m->data3) >
+                           ntohl(b->data3 & m->data3))
+                               return 1;
+                       return -1;
+               }
+               return 0;
+       case COMMUNITY_TYPE_EXT:
+               if ((a->data1 & m->data1) != (b->data1 & m->data1)) {
+                       if (ntohl(a->data1 & m->data1) >
+                           ntohl(b->data1 & m->data1))
+                               return 1;
+                       return -1;
+               }
+               if ((a->data2 & m->data2) != (b->data2 & m->data2)) {
+                       if (ntohl(a->data2 & m->data2) >
+                           ntohl(b->data2 & m->data2))
+                               return 1;
+                       return -1;
+               }
+               return 0;
+       default:
+               fatalx("%s: unknown type %d", __func__, a->type);
+       }
+}
+
+/*
+ * Insert a community keeping the list sorted. Don't add if already present.
+ */
+static void
+insert_community(struct rde_community *comm, struct community *c)
+{
+       size_t l;
+       int r;
+
+       if (comm->nentries + 1 > comm->size) {
+               struct community *new;
+               size_t newsize = comm->size + 8;
+
+               if ((new = recallocarray(comm->communities, comm->size, newsize,
+                   sizeof(struct community))) == NULL)
+                       fatal(__func__);
+               comm->communities = new;
+               comm->size = newsize;
+       }
+
+       for (l = 0; l < comm->nentries; l++) {
+               r = match_community(comm->communities + l, c, &nomask);
+               if (r == 0) {
+                       /* already present, nothing to do */
+                       return;
+               } else if (r > 0) {
+                       /* shift reminder by one slot */
+                       memmove(comm->communities + l + 1,
+                           comm->communities + l,
+                           (comm->nentries - l) * sizeof(*c));
+                       break;
+               }
+       }
+
+       /* insert community at slot l */
+       comm->communities[l] = *c;
+       comm->nentries++;
+}
+
+static int
+non_transitive_community(struct community *c)
+{
+       if (c->type == COMMUNITY_TYPE_EXT &&
+           !((ntohl(c->data1) >> 24) & EXT_COMMUNITY_NON_TRANSITIVE))
+               return 1;
+       return 0;
+}
+
+/*
+ * Check if a community is present. This function will expand local-as and
+ * neighbor-as and also mask of bits to support partial matches.
+ */
+int
+community_match(struct rde_community *comm, struct filter_community *fc,
+struct rde_peer *peer)
+{
+       struct community test, mask;
+       size_t l;
+
+       if (fc2c(fc, peer, &test, &mask) == -1)
+               return 0;
+
+       for (l = 0; l < comm->nentries; l++) {
+               if (match_community(&comm->communities[l], &test, &mask) == 0)
+                       return 1;
+       }
+       return 0;
+}
+
+/*
+ * Insert a community, expanding local-as and neighbor-as if needed.
+ */
+int
+community_set(struct rde_community *comm, struct filter_community *fc,
+struct rde_peer *peer)
+{
+       struct community set;
+
+       if (fc2c(fc, peer, &set, NULL) == -1)
+               return 0;
+
+       insert_community(comm, &set);
+       return 1;
+}
+
+/*
+ * Remove a community if present, This function will expand local-as and
+ * neighbor-as and also mask of bits to support partial matches.
+ */
+void
+community_delete(struct rde_community *comm, struct filter_community *fc,
+struct rde_peer *peer)
+{
+       struct community test, mask;
+       size_t l = 0;
+
+       if (fc2c(fc, peer, &test, &mask) == -1)
+               return;
+
+       while (l < comm->nentries) {
+               if (match_community(&comm->communities[l], &test, &mask) == 0) {
+                       memmove(comm->communities + l,
+                           comm->communities + l + 1,
+                           (comm->nentries - l - 1) * sizeof(test));
+                       comm->nentries--;
+                       continue;
+               }
+               l++;
+       }
+}
+
+/*
+ * Internalize communities from the wireformat.
+ * Store the partial flag in struct rde_community so it is not lost.
+ * - community_add for ATTR_COMMUNITUES
+ * - community_large_add for ATTR_LARGE_COMMUNITIES
+ * - community_ext_add for ATTR_EXT_COMMUNITIES
+ */
+int
+community_add(struct rde_community *comm, int flags, void *buf, size_t len)
+{
+       struct community set = { .type = COMMUNITY_TYPE_BASIC };
+       u_int8_t *b = buf;
+       size_t l;
+
+       if (len == 0 || len % 4 != 0)
+               return -1;
+
+       if (flags & ATTR_PARTIAL)
+               comm->flags |= PARTIAL_COMMUNITIES;
+
+       for (l = 0; l < len; l += 4, b += 4) {
+               memcpy(&set.data1, b, 4);
+               insert_community(comm, &set);
+       }
+
+       return 0;
+}
+
+int
+community_large_add(struct rde_community *comm, int flags, void *buf,
+    size_t len)
+{
+       struct community set = { .type = COMMUNITY_TYPE_LARGE };
+       u_int8_t *b = buf;
+       size_t l;
+
+       if (len == 0 || len % 12 != 0)
+               return -1;
+
+       if (flags & ATTR_PARTIAL)
+               comm->flags |= PARTIAL_LARGE_COMMUNITIES;
+
+       for (l = 0; l < len; l += 12, b += 12) {
+               memcpy(&set.data1, b, 12);
+               insert_community(comm, &set);
+       }
+
+       return 0;
+}
+
+int
+community_ext_add(struct rde_community *comm, int flags, void *buf, size_t len)
+{
+       struct community set = { .type = COMMUNITY_TYPE_EXT };
+       u_int8_t *b = buf;
+       size_t l;
+
+       if (len == 0 || len % 8 != 0)
+               return -1;
+
+       if (flags & ATTR_PARTIAL)
+               comm->flags |= PARTIAL_EXT_COMMUNITIES;
+
+       for (l = 0; l < len; l += 8, b += 8) {
+               memcpy(&set.data1, b, 8);
+               insert_community(comm, &set);
+       }
+
+       return 0;
+}
+
+/*
+ * Convert communities back to the wireformat.
+ * This function needs to make sure that the attribute buffer is overflowed
+ * while writing out the communities.
+ * - community_write for ATTR_COMMUNITUES
+ * - community_large_write for ATTR_LARGE_COMMUNITIES
+ * - community_ext_write for ATTR_EXT_COMMUNITIES
+ *   When writing ATTR_EXT_COMMUNITIES non-transitive communities need to
+ *   be skipped if it is sent to an ebgp peer.
+ */
+int
+community_write(struct rde_community *comm, void *buf, u_int16_t len)
+{
+       u_int8_t *b = buf;
+       size_t l, n = 0;
+       int r, flags = ATTR_OPTIONAL | ATTR_TRANSITIVE;
+
+       if (comm->flags & PARTIAL_COMMUNITIES)
+               flags |= ATTR_PARTIAL;
+
+       /* first count how many communities will be written */
+       for (l = 0; l < comm->nentries; l++)
+               if (comm->communities[l].type == COMMUNITY_TYPE_BASIC)
+                       n++;
+
+       if (n == 0)
+               return 0;
+
+       /* write attribute header */
+       r = attr_write(b, len, flags, ATTR_COMMUNITIES, NULL, n * 4);
+       if (r == -1)
+               return -1;
+       b += r; 
+
+       /* write out the communities */
+       for (l = 0; l < comm->nentries; l++)
+               if (comm->communities[l].type == COMMUNITY_TYPE_BASIC) {
+                       memcpy(b, &comm->communities[l].data1, 4);
+                       b += 4;
+                       r += 4;
+               }
+
+       return r;
+}
+
+int
+community_large_write(struct rde_community *comm, void *buf, u_int16_t len)
+{
+       u_int8_t *b = buf;
+       size_t l, n = 0;
+       int r, flags = ATTR_OPTIONAL | ATTR_TRANSITIVE;
+
+       if (comm->flags & PARTIAL_LARGE_COMMUNITIES)
+               flags |= ATTR_PARTIAL;
+
+       /* first count how many communities will be written */
+       for (l = 0; l < comm->nentries; l++)
+               if (comm->communities[l].type == COMMUNITY_TYPE_LARGE)
+                       n++;
+
+       if (n == 0)
+               return 0;
+
+       /* write attribute header */
+       r = attr_write(b, len, flags, ATTR_LARGE_COMMUNITIES, NULL, n * 12);
+       if (r == -1)
+               return -1;
+       b += r; 
+
+       /* write out the communities */
+       for (l = 0; l < comm->nentries; l++)
+               if (comm->communities[l].type == COMMUNITY_TYPE_LARGE) {
+                       memcpy(b, &comm->communities[l].data1, 12);
+                       b += 12;
+                       r += 12;
+               }
+
+       return r;
+}
+
+int
+community_ext_write(struct rde_community *comm, int ebgp, void *buf,
+    u_int16_t len)
+{
+       u_int8_t *b = buf;
+       size_t l, n = 0;
+       int r, flags = ATTR_OPTIONAL | ATTR_TRANSITIVE;
+
+       if (comm->flags & PARTIAL_EXT_COMMUNITIES)
+               flags |= ATTR_PARTIAL;
+
+       /* first count how many communities will be written */
+       for (l = 0; l < comm->nentries; l++)
+               if (comm->communities[l].type == COMMUNITY_TYPE_EXT &&
+                  !(ebgp && non_transitive_community(&comm->communities[l])))
+                       n++;
+
+       if (n == 0)
+               return 0;
+
+       /* write attribute header */
+       r = attr_write(b, len, flags, ATTR_EXT_COMMUNITIES, NULL, n * 8);
+       if (r == -1)
+               return -1;
+       b += r; 
+
+       /* write out the communities */
+       for (l = 0; l < comm->nentries; l++)
+               if (comm->communities[l].type == COMMUNITY_TYPE_EXT &&
+                  !(ebgp && non_transitive_community(&comm->communities[l]))) {
+                       memcpy(b, &comm->communities[l].data1, 8);
+                       b += 8;
+                       r += 8;
+               }
+
+       return r;
+}
+
+/*
+ * Global RIB cache for communities
+ */
+LIST_HEAD(commhead, rde_community);
+
+static struct comm_table {
+       struct commhead         *hashtbl;
+       u_int64_t                hashmask;
+} commtable;
+
+static SIPHASH_KEY commtablekey;
+
+static inline struct commhead *
+communities_hash(struct rde_community *comm)
+{
+       SIPHASH_CTX     ctx;
+       u_int64_t       hash;
+
+        SipHash24_Init(&ctx, &commtablekey);
+       SipHash24_Update(&ctx, &comm->nentries, sizeof(comm->nentries));
+       SipHash24_Update(&ctx, &comm->flags, sizeof(comm->flags));
+       if (comm->nentries > 0)
+               SipHash24_Update(&ctx, comm->communities,
+                   comm->nentries * sizeof(*comm->communities));
+       hash = SipHash24_End(&ctx);
+
+       return &commtable.hashtbl[hash & commtable.hashmask];
+}
+
+void
+communities_init(u_int32_t hashsize)
+{
+       u_int32_t       hs, i;
+
+       arc4random_buf(&commtablekey, sizeof(commtablekey));
+       for (hs = 1; hs < hashsize; hs <<= 1)
+               ;
+       commtable.hashtbl = calloc(hs, sizeof(*commtable.hashtbl));
+       if (commtable.hashtbl == NULL)
+               fatal(__func__);
+
+       for (i = 0; i < hs; i++)
+               LIST_INIT(&commtable.hashtbl[i]);
+       commtable.hashmask = hs - 1;
+}
+
+void
+communities_shutdown(void)
+{
+       u_int64_t       i;
+
+       for (i = 0; i <= commtable.hashmask; i++)
+               if (!LIST_EMPTY(&commtable.hashtbl[i]))
+                       log_warnx("%s: free non-free table", __func__);
+
+       free(commtable.hashtbl);
+}
+
+void
+communities_hash_stats(struct rde_hashstats *hs)
+{
+       struct rde_community *c;
+       u_int64_t i;
+       int64_t n;
+
+       memset(hs, 0, sizeof(*hs));
+       strlcpy(hs->name, "comm hash", sizeof(hs->name));
+       hs->min = LLONG_MAX;
+       hs->num = commtable.hashmask + 1;
+
+       for (i = 0; i <= commtable.hashmask; i++) {
+               n = 0;
+               LIST_FOREACH(c, &commtable.hashtbl[i], entry)
+                       n++;
+               if (n < hs->min)
+                       hs->min = n;
+               if (n > hs->max)
+                       hs->max = n;
+               hs->sum += n;
+               hs->sumq += n * n;
+       }
+}
+
+struct rde_community *
+communities_lookup(struct rde_community *comm)
+{
+       struct rde_community *c;
+       struct commhead *head;
+
+       head = communities_hash(comm);
+       LIST_FOREACH(c, head, entry) {
+               if (communities_compare(comm, c) == 0)
+                       return c;
+       }
+       return NULL;
+}
+
+struct rde_community *
+communities_link(struct rde_community *comm)
+{
+       struct rde_community *n;
+       struct commhead *head;
+
+       if ((n = malloc(sizeof(*n))) == NULL)
+               fatal(__func__);
+
+       communities_copy(n, comm);
+
+       head = communities_hash(n);
+       LIST_INSERT_HEAD(head, n, entry);
+       n->refcnt = 1;  /* initial reference by the cache */
+
+       rdemem.comm_size += n->size;
+       rdemem.comm_nmemb += n->nentries;
+       rdemem.comm_cnt++;
+
+       return n;
+}
+
+void
+communities_unlink(struct rde_community *comm)
+{
+       if (comm->refcnt != 1)
+               fatalx("%s: unlinking still referenced communities", __func__);
+
+       LIST_REMOVE(comm, entry);
+
+       rdemem.comm_size -= comm->size;
+       rdemem.comm_nmemb -= comm->nentries;
+       rdemem.comm_cnt--;
+
+       free(comm->communities);
+       free(comm);
+}
+
+/*
+ * Return zero if the two communities collections are identical,
+ * otherwise returns the positive if a is considered bigger than b or
+ * negative in the other case.
+ */
+int
+communities_compare(struct rde_community *a, struct rde_community *b)
+{
+       size_t l;
+       int r;
+
+       if (a->nentries != b->nentries)
+               return a->nentries > b->nentries ? 1 : -1;
+       if (a->flags != b->flags)
+               return a->flags > b->flags ? 1 : -1;
+
+       for (l = 0; l < a->nentries; l++) {
+               r = match_community(a->communities + l, b->communities + l,
+                   &nomask);
+               if (r != 0)
+                       return r;
+       }
+       return 0;
+}
+
+/*
+ * Copy communities to a new unreferenced struct. Needs to call
+ * communities_clean() when done. to can be statically allocated,
+ * it will be cleaned first.
+ */
+void
+communities_copy(struct rde_community *to, struct rde_community *from)
+{
+       memset(to, 0, sizeof(*to));
+
+       /* ingore from->size and allocate the perfect amount */
+       to->size = to->nentries = from->nentries;
+       to->flags = from->flags;
+
+       if ((to->communities = recallocarray(NULL, 0, to->size,
+           sizeof(struct community))) == NULL)
+               fatal(__func__);
+       
+       memcpy(to->communities, from->communities,
+           to->nentries * sizeof(struct community));
+}
+
+/*
+ * Clean up the communities by freeing any dynamically allocated memory.
+ */
+void
+communities_clean(struct rde_community *comm)
+{
+       if (comm->refcnt != 0)
+               fatalx("%s: cleaning still referenced communities", __func__);
+
+       free(comm->communities);
+       memset(comm, 0, sizeof(*comm));
+}
+
+int
+community_ext_conv(struct filter_community *fc, u_int64_t *community)
+{
+       struct community        c;
+
+       if (fc2c(fc, NULL, &c, NULL) == -1)
+               return -1;
+
+       memcpy(community, &c.data1, sizeof(*community));
+       return 0;
+}
Index: usr.sbin/bgpd/rde_filter.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/rde_filter.c,v
retrieving revision 1.118
diff -u -p -r1.118 rde_filter.c
--- usr.sbin/bgpd/rde_filter.c  9 May 2019 22:27:33 -0000       1.118
+++ usr.sbin/bgpd/rde_filter.c  12 May 2019 15:25:13 -0000
@@ -146,35 +146,12 @@ rde_apply_set(struct filter_set_head *sh
                            &state->nexthop, &state->nhflags);
                        break;
                case ACTION_SET_COMMUNITY:
-                       switch (set->action.community.type) {
-                       case COMMUNITY_TYPE_BASIC:
-                               community_set(&state->aspath,
-                                   &set->action.community, peer);
-                               break;
-                       case COMMUNITY_TYPE_LARGE:
-                               community_large_set(&state->aspath,
-                                   &set->action.community, peer);
-                               break;
-                       case COMMUNITY_TYPE_EXT:
-                               community_ext_set(&state->aspath,
-                                   &set->action.community, peer);
-                               break;
-                       }
+                       community_set(&state->communities,
+                           &set->action.community, peer);
                        break;
                case ACTION_DEL_COMMUNITY:
-                       switch (set->action.community.type) {
-                       case COMMUNITY_TYPE_BASIC:
-                               community_delete(&state->aspath,
-                                   &set->action.community, peer);
-                               break;
-                       case COMMUNITY_TYPE_LARGE:
-                               community_large_delete(&state->aspath,
-                                   &set->action.community, peer);
-                               break;
-                       case COMMUNITY_TYPE_EXT:
-                               community_ext_delete(&state->aspath,
-                                   &set->action.community, peer);
-                       }
+                       community_delete(&state->communities,
+                           &set->action.community, peer);
                        break;
                case ACTION_PFTABLE:
                        /* convert pftable name to an id */
@@ -229,26 +206,12 @@ rde_filter_match(struct filter_rule *f, 
                    f->match.aslen.aslen) == 0)
                        return (0);
 
-       for (i = 0; asp != NULL && i < MAX_COMM_MATCH; i++) {
-               switch (f->match.community[i].type) {
-               case COMMUNITY_TYPE_NONE:
-                       i = MAX_COMM_MATCH;
+       for (i = 0; i < MAX_COMM_MATCH; i++) {
+               if (f->match.community[i].type == COMMUNITY_TYPE_NONE)
                        break;
-               case COMMUNITY_TYPE_BASIC:
-                       if (community_match(asp, &f->match.community[i],
-                           peer) == 0)
-                               return (0);
-                       break;
-               case COMMUNITY_TYPE_LARGE:
-                       if (community_large_match(asp, &f->match.community[i],
-                           peer) == 0)
-                               return (0);
-                       break;
-               case COMMUNITY_TYPE_EXT:
-                       if (community_ext_match(asp, &f->match.community[i],
-                           peer) == 0)
-                               return (0);
-               }
+               if (community_match(&state->communities,
+                   &f->match.community[i], peer) == 0)
+                       return (0);
        }
 
        if (f->match.nexthop.flags != 0) {
@@ -463,13 +426,15 @@ rde_filter_equal(struct filter_head *a, 
 
 void
 rde_filterstate_prep(struct filterstate *state, struct rde_aspath *asp,
-    struct nexthop *nh, u_int8_t nhflags)
+    struct rde_community *communities, struct nexthop *nh, u_int8_t nhflags)
 {
        memset(state, 0, sizeof(*state));
 
        path_prep(&state->aspath);
        if (asp)
                path_copy(&state->aspath, asp);
+       if (communities)
+               communities_copy(&state->communities, communities);
        state->nexthop = nexthop_ref(nh);
        state->nhflags = nhflags;
 }
@@ -478,6 +443,7 @@ void
 rde_filterstate_clean(struct filterstate *state)
 {
        path_clean(&state->aspath);
+       communities_clean(&state->communities);
        nexthop_put(state->nexthop);
        state->nexthop = NULL;
 }
Index: usr.sbin/bgpd/rde_rib.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/rde_rib.c,v
retrieving revision 1.190
diff -u -p -r1.190 rde_rib.c
--- usr.sbin/bgpd/rde_rib.c     7 Mar 2019 07:42:36 -0000       1.190
+++ usr.sbin/bgpd/rde_rib.c     13 May 2019 21:59:40 -0000
@@ -64,9 +64,11 @@ LIST_HEAD(, rib_context) rib_dumps = LIS
 
 static int      prefix_add(struct bgpd_addr *, int, struct rib *,
                    struct rde_peer *, struct rde_aspath *,
-                   struct filterstate *, u_int8_t);
+                   struct rde_community *, struct nexthop *,
+                   u_int8_t, u_int8_t);
 static int      prefix_move(struct prefix *, struct rde_peer *,
-                   struct rde_aspath *, struct filterstate *, u_int8_t);
+                   struct rde_aspath *, struct rde_community *,
+                   struct nexthop *, u_int8_t, u_int8_t);
 
 static inline struct rib_entry *
 re_lock(struct rib_entry *re)
@@ -598,6 +600,7 @@ path_update(struct rib *rib, struct rde_
     struct bgpd_addr *prefix, int prefixlen, u_int8_t vstate)
 {
        struct rde_aspath       *asp, *nasp = &state->aspath;
+       struct rde_community    *comm, *ncomm = &state->communities;
        struct prefix           *p;
 
        if (nasp->pftableid) {
@@ -610,6 +613,7 @@ path_update(struct rib *rib, struct rde_
         */
        if ((p = prefix_get(rib, peer, prefix, prefixlen)) != NULL) {
                if (path_compare(nasp, prefix_aspath(p)) == 0 &&
+                   communities_compare(ncomm, prefix_communities(p)) == 0 &&
                    prefix_nexthop(p) == state->nexthop &&
                    prefix_nhflags(p) == state->nhflags) {
                        /* no change, update last change */
@@ -639,12 +643,18 @@ path_update(struct rib *rib, struct rde_
                path_link(asp);
        }
 
+       if ((comm = communities_lookup(ncomm)) == NULL) {
+               /* Communities not available, create and link a new one. */
+               comm = communities_link(ncomm);
+       }
+
        /* If the prefix was found move it else add it to the aspath. */
        if (p != NULL)
-               return (prefix_move(p, peer, asp, state, vstate));
+               return (prefix_move(p, peer, asp, comm, state->nexthop,
+                   state->nhflags, vstate));
        else
-               return (prefix_add(prefix, prefixlen, rib, peer, asp,
-                   state, vstate));
+               return (prefix_add(prefix, prefixlen, rib, peer, asp, comm,
+                   state->nexthop, state->nhflags, vstate));
 }
 
 int
@@ -855,7 +865,8 @@ path_put(struct rde_aspath *asp)
 
 static void             prefix_link(struct prefix *, struct rib_entry *,
                             struct rde_peer *, struct rde_aspath *,
-                            struct filterstate *, u_int8_t);
+                            struct rde_community *, struct nexthop *,
+                            u_int8_t, u_int8_t);
 static void             prefix_unlink(struct prefix *);
 static struct prefix   *prefix_alloc(void);
 static void             prefix_free(struct prefix *);
@@ -868,6 +879,8 @@ prefix_cmp(struct prefix *a, struct pref
                return a->eor - b->eor;
        if (a->aspath != b->aspath)
                return (a->aspath > b->aspath ? 1 : -1);
+       if (a->communities != b->communities)
+               return (a->communities > b->communities ? 1 : -1);
        if (a->nexthop != b->nexthop)
                return (a->nexthop > b->nexthop ? 1 : -1);
        if (a->nhflags != b->nhflags)
@@ -897,8 +910,8 @@ prefix_get(struct rib *rib, struct rde_p
  */
 static int
 prefix_add(struct bgpd_addr *prefix, int prefixlen, struct rib *rib,
-    struct rde_peer *peer, struct rde_aspath *asp, struct filterstate *state,
-    u_int8_t vstate)
+    struct rde_peer *peer, struct rde_aspath *asp, struct rde_community *comm,
+    struct nexthop *nexthop, u_int8_t nhflags, u_int8_t vstate)
 {
        struct prefix           *p;
        struct rib_entry        *re;
@@ -910,14 +923,16 @@ prefix_add(struct bgpd_addr *prefix, int
        p = prefix_bypeer(re, peer);
        if (p == NULL) {
                p = prefix_alloc();
-               prefix_link(p, re, peer, asp, state, vstate);
+               prefix_link(p, re, peer, asp, comm, nexthop, nhflags, vstate);
                return (1);
        } else {
                if (prefix_aspath(p) != asp ||
-                   prefix_nexthop(p) != state->nexthop ||
-                   prefix_nhflags(p) != state->nhflags) {
+                   prefix_communities(p) != comm ||
+                   prefix_nexthop(p) != nexthop ||
+                   prefix_nhflags(p) != nhflags) {
                        /* prefix metadata changed therefor move */
-                       return (prefix_move(p, peer, asp, state, vstate));
+                       return (prefix_move(p, peer, asp, comm, nexthop,
+                           nhflags, vstate));
                }
                p->lastchange = time(NULL);
                p->validation_state = vstate;
@@ -930,7 +945,8 @@ prefix_add(struct bgpd_addr *prefix, int
  */
 static int
 prefix_move(struct prefix *p, struct rde_peer *peer,
-    struct rde_aspath *asp, struct filterstate *state, u_int8_t vstate)
+    struct rde_aspath *asp, struct rde_community *comm,
+    struct nexthop *nexthop, u_int8_t nhflags, u_int8_t vstate)
 {
        struct prefix           *np;
        struct rde_aspath       *oasp;
@@ -940,16 +956,18 @@ prefix_move(struct prefix *p, struct rde
 
        np = prefix_alloc();
        np->aspath = asp;
-       np->nexthop = nexthop_ref(state->nexthop);
+       np->communities = comm;
+       np->nexthop = nexthop_ref(nexthop);
        nexthop_link(np);
        np->peer = peer;
        np->re = p->re;
        np->lastchange = time(NULL);
-       np->nhflags = state->nhflags;
+       np->nhflags = nhflags;
        np->validation_state = vstate;
 
-       /* add reference to new as path */
+       /* add reference to new AS path and communities */
        path_ref(asp);
+       communities_get(comm);
 
        /*
         * no need to update the peer prefix count because we are only moving
@@ -975,6 +993,8 @@ prefix_move(struct prefix *p, struct rde
 
        /* destroy all references to other objects and free the old prefix */
        p->aspath = NULL;
+       communities_put(p->communities);
+       p->communities = NULL;
        p->peer = NULL;
        p->re = NULL;
        nexthop_unlink(p);
@@ -1076,6 +1096,10 @@ prefix_withdraw(struct rib *rib, struct 
                        path_unlink(asp);
        }
 
+       /* ... communities ... */
+       communities_put(p->communities);
+       p->communities = NULL;
+
        /* ... and nexthop but keep the re link */
        nexthop_unlink(p);
        nexthop_put(p->nexthop);
@@ -1278,17 +1302,19 @@ prefix_destroy(struct prefix *p)
  */
 static void
 prefix_link(struct prefix *p, struct rib_entry *re, struct rde_peer *peer,
-    struct rde_aspath *asp, struct filterstate *state, u_int8_t vstate)
+    struct rde_aspath *asp, struct rde_community *comm,
+    struct nexthop *nexthop, u_int8_t nhflags, u_int8_t vstate)
 {
        path_ref(asp);
 
        p->aspath = asp;
        p->peer = peer;
-       p->nexthop = nexthop_ref(state->nexthop);
+       p->communities = communities_get(comm);
+       p->nexthop = nexthop_ref(nexthop);
        nexthop_link(p);
        p->re = re;
        p->lastchange = time(NULL);
-       p->nhflags = state->nhflags;
+       p->nhflags = nhflags;
        p->validation_state = vstate;
 
        /* make route decision */
@@ -1317,8 +1343,10 @@ prefix_unlink(struct prefix *p)
                rib_remove(re);
 
        /* destroy all references to other objects */
+       communities_put(p->communities);
        nexthop_unlink(p);
        nexthop_put(p->nexthop);
+       p->communities = NULL;
        p->nexthop = NULL;
        p->aspath = NULL;
        p->peer = NULL;
Index: usr.sbin/bgpd/rde_update.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/rde_update.c,v
retrieving revision 1.111
diff -u -p -r1.111 rde_update.c
--- usr.sbin/bgpd/rde_update.c  13 May 2019 21:13:04 -0000      1.111
+++ usr.sbin/bgpd/rde_update.c  13 May 2019 21:56:31 -0000
@@ -49,6 +49,7 @@ up_test_update(struct rde_peer *peer, st
 {
        struct bgpd_addr         addr;
        struct rde_aspath       *asp;
+       struct rde_community    *comm;
        struct rde_peer         *prefp;
        struct attr             *attr;
 
@@ -58,6 +59,7 @@ up_test_update(struct rde_peer *peer, st
 
        prefp = prefix_peer(p);
        asp = prefix_aspath(p);
+       comm = prefix_communities(p);
 
        if (peer == prefp)
                /* Do not send routes back to sender */
@@ -99,12 +101,12 @@ up_test_update(struct rde_peer *peer, st
        }
 
        /* well known communities */
-       if (community_match(asp, &comm_no_advertise, NULL))
+       if (community_match(comm, &comm_no_advertise, NULL))
                return (0);
        if (peer->conf.ebgp) {
-               if (community_match(asp, &comm_no_export, NULL))
+               if (community_match(comm, &comm_no_export, NULL))
                        return (0);
-               if (community_match(asp, &comm_no_expsubconfed, NULL))
+               if (community_match(comm, &comm_no_expsubconfed, NULL))
                        return (0);
        }
 
@@ -155,7 +157,8 @@ withdraw:
                }
 
                rde_filterstate_prep(&state, prefix_aspath(new),
-                   prefix_nexthop(new), prefix_nhflags(new));
+                   prefix_communities(new), prefix_nexthop(new),
+                   prefix_nhflags(new));
                if (rde_filter(rules, peer, new, &state) == ACTION_DENY) {
                        rde_filterstate_clean(&state);
                        goto withdraw;
@@ -192,7 +195,7 @@ up_generate_default(struct filter_head *
        if (peer->capa.mp[aid] == 0)
                return;
 
-       rde_filterstate_prep(&state, NULL, NULL, 0);
+       rde_filterstate_prep(&state, NULL, NULL, NULL, 0);
        asp = &state.aspath;
        asp->aspath = aspath_get(NULL, 0);
        asp->origin = ORIGIN_IGP;
@@ -298,6 +301,7 @@ up_generate_attr(u_char *buf, int len, s
     struct filterstate *state, u_int8_t aid)
 {
        struct rde_aspath *asp = &state->aspath;
+       struct rde_community *comm = &state->communities;
        struct attr     *oa = NULL, *newaggr = NULL;
        u_char          *pdata;
        u_int32_t        tmp32;
@@ -387,10 +391,21 @@ up_generate_attr(u_char *buf, int len, s
                        }
                        break;
                /*
-                * multiprotocol attributes are handled elsewhere
+                * Communities are stored in struct rde_community
                 */
-               case ATTR_MP_REACH_NLRI:
-               case ATTR_MP_UNREACH_NLRI:
+               case ATTR_COMMUNITIES:
+                       if ((r = community_write(comm, buf + wlen, len)) == -1)
+                               return (-1);
+                       break;
+               case ATTR_EXT_COMMUNITIES:
+                       if ((r = community_ext_write(comm, peer->conf.ebgp,
+                           buf + wlen, len)) == -1)
+                               return (-1);
+                       break;
+               case ATTR_LARGE_COMMUNITIES:
+                       if ((r = community_large_write(comm, buf + wlen,
+                           len)) == -1)
+                               return (-1);
                        break;
                /*
                 * NEW to OLD conversion when sending stuff to a 2byte AS peer
@@ -426,6 +441,10 @@ up_generate_attr(u_char *buf, int len, s
                                        return (-1);
                        }
                        break;
+               case ATTR_MP_REACH_NLRI:
+               case ATTR_MP_UNREACH_NLRI:
+                       /* specially handled later one */
+                       break;
                /*
                 * dump all other path attributes. Following rules apply:
                 *  1. well-known attrs: ATTR_ATOMIC_AGGREGATE and
@@ -475,10 +494,8 @@ up_generate_attr(u_char *buf, int len, s
                                break;
                        }
                        /* FALLTHROUGH */
-               case ATTR_COMMUNITIES:
                case ATTR_ORIGINATOR_ID:
                case ATTR_CLUSTER_LIST:
-               case ATTR_LARGE_COMMUNITIES:
                        if (oa == NULL || oa->type != type)
                                break;
                        if ((!(oa->flags & ATTR_TRANSITIVE)) &&
@@ -490,37 +507,9 @@ up_generate_attr(u_char *buf, int len, s
                            oa->flags, oa->type, oa->data, oa->len)) == -1)
                                return (-1);
                        break;
-               case ATTR_EXT_COMMUNITIES:
-                       if (oa == NULL || oa->type != type)
-                               break;
-                       /* handle (non-)transitive extended communities */
-                       if (peer->conf.ebgp) {
-                               u_char  *ndata;
-                               u_int16_t nlen;
-
-                               ndata = community_ext_delete_non_trans(oa->data,
-                                   oa->len, &nlen);
-
-                               if (nlen > 0) {
-                                       if ((r = attr_write(buf + wlen, len,
-                                           oa->flags, oa->type, ndata,
-                                           nlen)) == -1) {
-                                               free(ndata);
-                                               return (-1);
-                                       }
-                                       free(ndata);
-                               }
-                               /* else everything got removed */
-                       } else {
-                               if ((r = attr_write(buf + wlen, len, oa->flags,
-                                   oa->type, oa->data, oa->len)) == -1)
-                                       return (-1);
-                       }
-                       break;
                default:
                        if (oa == NULL || oa->type != type)
                                break;
-
                        /* unknown attribute */
                        if (!(oa->flags & ATTR_TRANSITIVE)) {
                                /*
@@ -535,7 +524,6 @@ up_generate_attr(u_char *buf, int len, s
                            oa->flags | ATTR_PARTIAL, oa->type,
                            oa->data, oa->len)) == -1)
                                return (-1);
-                       break;
                }
                wlen += r; len -= r;
        }
@@ -690,8 +678,8 @@ up_dump_attrnlri(u_char *buf, int len, s
        if (p == NULL)
                goto done;
 
-       rde_filterstate_prep(&state, prefix_aspath(p), prefix_nexthop(p),
-           prefix_nhflags(p));
+       rde_filterstate_prep(&state, prefix_aspath(p), prefix_communities(p),
+           prefix_nexthop(p), prefix_nhflags(p));
 
        r = up_generate_attr(buf + 2, len - 2, peer, &state, AID_INET);
        rde_filterstate_clean(&state);
@@ -962,8 +950,8 @@ up_dump_mp_reach(u_char *buf, int len, s
 
        wpos = 4;       /* reserve space for length fields */
 
-       rde_filterstate_prep(&state, prefix_aspath(p), prefix_nexthop(p),
-           prefix_nhflags(p));
+       rde_filterstate_prep(&state, prefix_aspath(p), prefix_communities(p),
+           prefix_nexthop(p), prefix_nhflags(p));
 
        /* write regular path attributes */
        r = up_generate_attr(buf + wpos, len - wpos, peer, &state, aid);
Index: usr.sbin/bgpd/session.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/session.c,v
retrieving revision 1.380
diff -u -p -r1.380 session.c
--- usr.sbin/bgpd/session.c     8 May 2019 12:41:55 -0000       1.380
+++ usr.sbin/bgpd/session.c     12 May 2019 15:25:13 -0000
@@ -2734,6 +2734,7 @@ session_dispatch_imsg(struct imsgbuf *ib
                        break;
                case IMSG_CTL_SHOW_RIB:
                case IMSG_CTL_SHOW_RIB_PREFIX:
+               case IMSG_CTL_SHOW_RIB_COMMUNITIES:
                case IMSG_CTL_SHOW_RIB_ATTR:
                case IMSG_CTL_SHOW_RIB_MEM:
                case IMSG_CTL_SHOW_RIB_HASH:

Reply via email to