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: