Commit 1a2ec0d051 for frr
commit 1a2ec0d051611ab3acdc3c43d5bce95cc7760b29
Author: Donatas Abraitis <donatas@opensourcerouting.org>
Date: Fri Sep 18 11:27:10 2026 +0300
tests: Check if 4 lower bits are removed from unknown attrs outbound
Signed-off-by: Donatas Abraitis <donatas@opensourcerouting.org>
diff --git a/tests/bgpd/test_attr_parse.c b/tests/bgpd/test_attr_parse.c
index bd9ce9eaf6..5d90d9cd49 100644
--- a/tests/bgpd/test_attr_parse.c
+++ b/tests/bgpd/test_attr_parse.c
@@ -59,6 +59,10 @@ struct attr_test {
size_t len;
/* expected bgp_attr_parse() return */
enum bgp_attr_parse_ret expect;
+ /* expected attr->transit octets after parse; NULL to skip the check */
+ const uint8_t *transit;
+ /* octet count of .transit -- set by TRANSIT_DATA(), never by hand */
+ size_t transit_len;
};
/*
@@ -77,6 +81,11 @@ struct attr_test {
#define ATTR_DATA(...) \
.data = (const uint8_t[]){ __VA_ARGS__ }, .len = sizeof((const uint8_t[]){ __VA_ARGS__ })
+/* As ATTR_DATA(), for the attribute octets kept for re-advertisement. */
+#define TRANSIT_DATA(...) \
+ .transit = (const uint8_t[]){ __VA_ARGS__ }, \
+ .transit_len = sizeof((const uint8_t[]){ __VA_ARGS__ })
+
/*
* Well-formed baseline: ORIGIN(igp) + AS_PATH(65001) + NEXT_HOP(10.0.0.2).
* Used to prove the harness itself accepts a valid attribute block.
@@ -253,6 +262,33 @@ static const struct attr_test attr_tests[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a), /* metric 10 */
.expect = BGP_ATTR_PARSE_PROCEED,
},
+ /*
+ * RFC 4271 4.3: the lower-order four bits of the Attribute Flags
+ * octet are unused, and MUST be zero when sent. bgp_attr_unknown()
+ * keeps an unrecognized transitive attribute's octets verbatim for
+ * re-advertisement, so the flags octet it stores is the one that goes
+ * back on the wire and has to be normalized: Partial set, because the
+ * attribute is passed along unrecognized, and the unused nibble
+ * cleared, whatever the sender happened to leave in it.
+ *
+ * 0xc5 is optional + transitive with 0x05 in the unused nibble, so a
+ * missing normalization stores 0xe5 rather than 0xe0. Type 0xfa is
+ * unassigned, which is what makes the attribute unrecognized.
+ */
+ {
+ .name = "unknown-transitive-unused-bits-ebgp",
+ .desc = "unknown transitive attribute with a nonzero unused nibble, eBGP",
+ .sort = BGP_PEER_EBGP,
+ .sub_sort = 0,
+ .has_nlri = true,
+ ATTR_DATA(0x40, 0x01, 0x01, 0x00, /* ORIGIN igp */
+ 0x40, 0x02, 0x06, /* AS_PATH */
+ 0x02, 0x01, 0x00, 0x00, 0xfd, 0xe9, /* AS_SEQ 65001 */
+ 0x40, 0x03, 0x04, 0x0a, 0x00, 0x00, 0x02, /* NEXT_HOP */
+ 0xc5, 0xfa, 0x01, 0x99), /* unknown opt-trans */
+ .expect = BGP_ATTR_PARSE_PROCEED,
+ TRANSIT_DATA(0xe0, 0xfa, 0x01, 0x99),
+ },
/*
* NEXT_HOP. ExaBGP cannot put any of these on the wire: a raw
* attribute [0x03 ...] after the next-hop keyword is dropped by the
@@ -382,13 +418,11 @@ static const char *parse_ret_str(enum bgp_attr_parse_ret ret)
return "UNKNOWN";
}
-/* Print the octets bgp_attr_parse() is about to be handed, and how many. */
-static void print_data(const uint8_t *data, size_t len)
+static void print_hex(const char *label, const uint8_t *data, size_t len)
{
size_t i;
- printf(" len: %zu\n", len);
- printf(" data: ");
+ printf(" %-9s", label);
for (i = 0; i < len; i++) {
if (i && !(i % 16))
printf("\n ");
@@ -397,6 +431,13 @@ static void print_data(const uint8_t *data, size_t len)
printf("\n");
}
+/* Print the octets bgp_attr_parse() is about to be handed, and how many. */
+static void print_data(const uint8_t *data, size_t len)
+{
+ printf(" len: %zu\n", len);
+ print_hex("data:", data, len);
+}
+
static void parse_test(struct peer *peer, const struct attr_test *t)
{
struct attr attr = {};
@@ -437,6 +478,18 @@ static void parse_test(struct peer *peer, const struct attr_test *t)
if (ret != t->expect)
failed++;
+ if (t->transit) {
+ struct transit *transit = bgp_attr_get_transit(&attr);
+
+ print_hex("transit:", transit ? transit->val : NULL,
+ transit ? (size_t)transit->length : 0);
+ print_hex("expected:", t->transit, t->transit_len);
+
+ if (!transit || (size_t)transit->length != t->transit_len ||
+ memcmp(transit->val, t->transit, t->transit_len))
+ failed++;
+ }
+
if (tty)
printf("%s", (failed > oldfailed) ? VT100_RED "failed!" VT100_RESET
: VT100_GREEN "OK" VT100_RESET);