On Wed, Nov 16, 2022 at 02:52:59PM +0100, Theo Buehler wrote: > On Wed, Nov 16, 2022 at 12:47:46PM +0100, Claudio Jeker wrote: > > ASPA sets are the 2nd thing that will be generated out of rpki (and > > rpki-client). ASPA is used to further prevent leaks by providing customer > > provider attestation (and blocking paths which violate these attestations). > > > > I started to implement this and while this code does no checks yet it is > > enough plumbing to parse and "merge" the ASPA sets. Similar to the roa-set > > an aspa-set in the config will be parsed and passed to the rtr process > > where it will be merged with the data from RTR sessions. > > What is missing is all the code needed in the RDE to do the lookups. > > > > A aspa-set is defined like this: > > aspa-set { > > source-as 1 transit-as { 5 } > > source-as 2 expires 1668181648 transit-as { 3 4 } > > source-as 5 transit-as { 1 2 allow inet 7 allow inet6 } > > } > > Adding this to the end of the default bgpd.conf I get > > /tmp/bgpd.conf:134: syntax error, expires not allowed > /tmp/bgpd.conf:136: syntax error > > Deleting the expire time, it parses fine. I suspect the duplicate > aspa_set rules in parse.y don't work as intended.
No this is actually the roa-set code that triggers this. It changes noexpires to 1 and then after that aspa-set fails. I changed to code to set noexpires in the origin-set case (where expires is not allowed) and then set it back to 0 when leaving that node. > > > > As usual more than one aspa-set can be used and these sets will all be > > merged into one table. > > > > Since this is already a lot of code to fiddle with various rb trees and > > doing various realloc games I would like to get this reviewed and > > committed before adding a lot more code. > > > > Once this is in rpki-client can be adjusted to produce an aspa-set in the > > openbgpd output. > > Generally looks good. The bitmask is a bit of a hack, but I guess we > can live with it. My plan is to compress the bitmask before sending it over but because of the memmove()s it is easier to work with bytes initially. > A few comments and questions below. See below > > @@ -586,6 +597,55 @@ roa_set_l : prefixset_item SOURCEAS as4n > > } > > ; > > > > +aspa_set : ASPASET '{' optnl aspa_set_l optnl '}' > > + | ASPASET '{' optnl '}' > > + ; > > + > > +aspa_set_l : aspa_set > > + | aspa_set_l comma aspa_set > > + ; > > + > > +aspa_set : SOURCEAS as4number expires TRANSITAS '{' optnl > > aspa_set was already defined a few lines up. Is this legal yacc? As > mentioned above, it doesn't seem to work. Magic. I guess it works because the one look-ahead is able to distinguish the two. Need to fix this though. > > +static int > > +merge_aspa_set(uint32_t as, struct aspa_tas_l *tas, time_t expires) > > +{ > > + struct aspa_set *aspa, needle = { .as = as }; > > + uint32_t i, num, *newtas; > > + uint8_t *newtasaid = NULL; > > + > > + aspa = RB_FIND(aspa_tree, &conf->aspa, &needle); > > + if (aspa == NULL) { > > + if ((aspa = calloc(1, sizeof(*aspa))) == NULL) { > > + yyerror("out of memory"); > > + return -1; > > + } > > + aspa->as = as; > > + RB_INSERT(aspa_tree, &conf->aspa, aspa); > > + } > > + > > + num = aspa->num + tas->num; > > Are these numbers guaranteed to be small enough that we don't overflow > the 32-bit num? Also, it's not clear to me that num > 0 is guaranteed > (which seems to be assumed in the for loop below). ASnumers are 32-bit only. So without duplicates no overflow should happen. Now if one builds a dumb config it would be possible to overflow this. I think the avage num here will be around 3-4 and the max will be < 1000. Adding an extra check here may be appropriate. > > + newtas = recallocarray(aspa->tas, aspa->num, num, sizeof(uint32_t)); > > + if (newtas == NULL) { > > + yyerror("out of memory"); > > + return -1; > > + } > > + if (aspa->tas_aid) { > > I don't really understand the reason for this conditional. Are aspa > sets with all tas->aid == AFI_UNSPEC common enough to warrant such extra > dances? Doing this recallocarray() unconditionally would allow us to > remove the calloc() in the somewhat tricky loop below. I may drop this here and do the compression in the RTR code. I expect that 99% of all ASPA entries to be AFI_UNSPEC (as in both IPv4 and IPv6). But since this is both done here and in the RTR code it may be sensible to just compress at the end. > > + newtasaid = recallocarray(aspa->tas_aid, aspa->num, num, 1); > > extra space after = fixed > > + if (newtasaid == NULL) { > > + free(newtas); > > + yyerror("out of memory"); > > + return -1; > > + } > > + } > > + > > + /* fill starting at the end since the tas list is reversed */ > > + for (i = num - 1; tas; tas = tas->next) { > > I'd move the i-- here: > > for (i = num - 1; tas; tas = tas->next, i--) { Sure. > > + newtas[i] = tas->as; > > + if (tas->aid != AFI_UNSPEC) { > > typo: AID_UNSPEC? Yes, result is the same. > > @@ -274,6 +296,41 @@ rtr_dispatch_imsg_parent(struct imsgbuf > > fatalx("IMSG_RECONF_ROA_ITEM bad len"); > > roa_insert(&nconf->roa, imsg.data); > > break; > > + case IMSG_RECONF_ASPA: > > + if (imsg.hdr.len - IMSG_HEADER_SIZE != > > + offsetof(struct aspa_set, tas)) > > + fatalx("IMSG_RECONF_ASPA bad len"); > > + if (aspa != NULL) > > + fatalx("unexpected IMSG_RECONF_ASPA"); > > + if ((aspa = calloc(1, sizeof(*aspa))) == NULL) > > + fatal("aspa alloc"); > > Worth switching to reallocarray(NULL, ...) to avoid zeroing and immediately > overwriting all of it? It is not overwriting all. Only the bytes up to the tas. memcpy uses offsetof(struct aspa_set, tas) not sizeof(*aspa). > > + memcpy(aspa, imsg.data, offsetof(struct aspa_set, tas)); > > + break; > > + case IMSG_RECONF_ASPA_TAS: > > + if (imsg.hdr.len - IMSG_HEADER_SIZE != > > + aspa->num * sizeof(*aspa->tas)) > > + fatalx("IMSG_RECONF_ASPA_TAS bad len"); > > + aspa->tas = calloc(aspa->num, sizeof(*aspa->tas)); > > ditto Here reallocarray(NULL, ...) makes sense. > > + if (aspa->tas == NULL) > > + fatal("aspa tas alloc"); > > + memcpy(aspa->tas, imsg.data, > > + aspa->num * sizeof(*aspa->tas)); > > + break; > > + case IMSG_RECONF_ASPA_TAS_AID: > > + if (imsg.hdr.len - IMSG_HEADER_SIZE != aspa->num) > > + fatalx("IMSG_RECONF_ASPA_TAS_AID bad len"); > > + aspa->tas_aid = malloc(aspa->num); > > here you use malloc. > > > + if (aspa->tas_aid == NULL) > > + fatal("aspa tas aid alloc"); > > + memcpy(aspa->tas_aid, imsg.data, aspa->num); > > + break; > > +static void > > +asap_set_entry(struct aspa_set *aspa, uint32_t asnum, uint8_t aid) > > typo: aspa_set_entry() Grrrr. I constanly type asap instead of aspa. > > +{ > > + uint32_t i, num, *newtas; > > + uint8_t *newtasaid; > > + > > + switch (aid) { > > + case AID_INET: > > + aid = 0x1; > > + break; > > + case AID_INET6: > > + aid = 0x2; > > + break; > > + case AID_UNSPEC: > > + aid = 0x3; > > This is not yet untangled, right? Or did I miss it? Yeah, up to here the table could have two entries (one with AID_INET and one with AID_INET6) but we want to combine them into one entry allowing both. I do this here so I can use a simple |= to combine equal entries. > > + break; > > + default: > > + fatalx("aspa_set bad AID"); > > + } > > + > > + for (i = 0; i < aspa->num; i++) { > > + if (asnum < aspa->tas[i] || aspa->tas[i] == 0) > > + break; > > + if (asnum == aspa->tas[i]) { > > + aspa->tas_aid[i] |= aid; > > + return; > > + } > > + } > > + > > + num = aspa->num + 1; > > + newtas = recallocarray(aspa->tas, aspa->num, num, sizeof(uint32_t)); > > + newtasaid = recallocarray(aspa->tas_aid, aspa->num, num, 1); > > + if (newtas == NULL || newtasaid == NULL) > > + fatal("aspa_set merge"); > > + > > + if (i < aspa->num) { > > + memmove(newtas + i + 1, newtas + i, > > + (aspa->num - i) * sizeof(uint32_t)); > > + memmove(newtasaid + i + 1, newtasaid + i, (aspa->num - i)); > > + } > > Phew. looks correct to me. yeah, not happy. The ASPA spec is overengineered to make the implementors lives hard. > > + newtas[i] = asnum; > > + newtasaid[i] = aid; > > + > > + aspa->num = num; > > + aspa->tas = newtas; > > + aspa->tas_aid = newtasaid; > > +} > > + > > +static void > > +rtr_aspa_merge_set(struct aspa_tree *a, struct aspa_set *mergeset) > > +{ > > + struct aspa_set *aspa, needle = { .as = mergeset->as }; > > + uint32_t i; > > + > > + aspa = RB_FIND(aspa_tree, a, &needle); > > + if (aspa == NULL) { > > + if ((aspa = calloc(1, sizeof(*aspa))) == NULL) > > + fatal("aspa insert"); > > + aspa->as = mergeset->as; > > + RB_INSERT(aspa_tree, a, aspa); > > + } > > + > > + for (i = 0; i < mergeset->num; i++) { > > + uint8_t aid = AID_UNSPEC; > > + if (mergeset->tas_aid != NULL) > > + aid = mergeset->tas_aid[i]; > > Again, I'm not quite sure what allowing mergeset->tas_aid == NULL buys us. > We rely on the fact that AFI_UNSPEC == AID_UNSPEC == 0 when not setting > them in parse.y. As mentioned above I will do the reduction in the last step. -- :wq Claudio