Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions frr/rt_grout.c
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,11 @@ static int grout_gr_nexthop_to_frr_nexthop(
SET_SRV6_FLV_OP(ctx.flv.flv_ops, ZEBRA_SEG6_LOCAL_FLV_OP_PSP);
if (sr6->flags & GR_SR_FL_FLAVOR_USD)
SET_SRV6_FLV_OP(ctx.flv.flv_ops, ZEBRA_SEG6_LOCAL_FLV_OP_USD);
if (sr6->flags & GR_SR_FL_FLAVOR_NEXT_CSID) {
SET_SRV6_FLV_OP(ctx.flv.flv_ops, ZEBRA_SEG6_LOCAL_FLV_OP_NEXT_CSID);
ctx.flv.lcblock_len = sr6->block_bits;
ctx.flv.lcnode_func_len = sr6->csid_bits;
}

switch (sr6->behavior) {
case SR_BEHAVIOR_END:
Expand Down Expand Up @@ -723,8 +728,11 @@ grout_add_nexthop(uint32_t nh_id, gr_nh_origin_t origin, const struct nexthop *n
gr_log_debug("USP always enabled, ignoring flag");
if (CHECK_SRV6_FLV_OP(flv, ZEBRA_SEG6_LOCAL_FLV_OP_USD))
sr6_local->flags |= GR_SR_FL_FLAVOR_USD;
if (CHECK_SRV6_FLV_OP(flv, ZEBRA_SEG6_LOCAL_FLV_OP_NEXT_CSID))
gr_log_debug("next-c-sid not supported, ignoring flag");
if (CHECK_SRV6_FLV_OP(flv, ZEBRA_SEG6_LOCAL_FLV_OP_NEXT_CSID)) {
sr6_local->flags |= GR_SR_FL_FLAVOR_NEXT_CSID;
sr6_local->block_bits = nh->nh_srv6->seg6local_ctx.flv.lcblock_len;
sr6_local->csid_bits = nh->nh_srv6->seg6local_ctx.flv.lcnode_func_len;
}

break;
case GR_NH_T_SR6_OUTPUT:
Expand Down
3 changes: 3 additions & 0 deletions modules/srv6/api/gr_srv6.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ static inline const char *gr_srv6_behavior_name(gr_srv6_behavior_t b) {
typedef enum : uint8_t {
GR_SR_FL_FLAVOR_PSP = GR_BIT8(0), // Penultimate Segment Popping.
GR_SR_FL_FLAVOR_USD = GR_BIT8(1), // Ultimate Segment Decapsulation.
GR_SR_FL_FLAVOR_NEXT_CSID = GR_BIT8(2), // Compressed SID (RFC 9800).
} gr_srv6_flags_t;

// SRv6 local nexthop information for Local SID processing.
Expand All @@ -93,4 +94,6 @@ struct gr_nexthop_info_srv6_local {
uint16_t out_vrf_id;
gr_srv6_behavior_t behavior;
gr_srv6_flags_t flags;
uint8_t block_bits; // Locator-block length in bits (default 32).
uint8_t csid_bits; // Compressed SID length in bits (default 16).
};
80 changes: 68 additions & 12 deletions modules/srv6/cli/localsid.c
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,18 @@ static cmd_status_t srv6_localsid_add(struct gr_api_client *c, const struct ec_p
sr6->flags |= GR_SR_FL_FLAVOR_PSP;
if (!strcmp(str, "usd"))
sr6->flags |= GR_SR_FL_FLAVOR_USD;
if (!strcmp(str, "next-csid"))
sr6->flags |= GR_SR_FL_FLAVOR_NEXT_CSID;
}
}

if (sr6->flags & GR_SR_FL_FLAVOR_NEXT_CSID) {
if (arg_u8(p, "BLOCK_BITS", &sr6->block_bits) < 0 && errno != ENOENT)
goto out;
if (arg_u8(p, "CSID_BITS", &sr6->csid_bits) < 0 && errno != ENOENT)
goto out;
}

n = ec_pnode_find(p, "BEHAVIOR");
if (n == NULL || ec_pnode_len(n) < 1)
goto out;
Expand All @@ -97,25 +106,54 @@ static cmd_status_t srv6_localsid_add(struct gr_api_client *c, const struct ec_p

static ssize_t format_nexthop_info_srv6_local(char *buf, size_t len, const void *info) {
const struct gr_nexthop_info_srv6_local *sr6 = info;
ssize_t n = 0;
char vrf[64];
char flavors[64], vrf[64];
ssize_t n = 0, f = 0;

SAFE_BUF(snprintf, len, "behavior=%s", gr_srv6_behavior_name(sr6->behavior));
vrf[0] = 0;
if (sr6->out_vrf_id != GR_VRF_ID_UNDEF)
snprintf(vrf, sizeof(vrf), "out_vrf=%d", sr6->out_vrf_id);

flavors[0] = 0;
if (sr6->flags & GR_SR_FL_FLAVOR_PSP)
f += snprintf(flavors + f, sizeof(flavors) - f, "psp,");
if (sr6->flags & GR_SR_FL_FLAVOR_USD)
f += snprintf(flavors + f, sizeof(flavors) - f, "usd,");
if (sr6->flags & GR_SR_FL_FLAVOR_NEXT_CSID)
f += snprintf(flavors + f, sizeof(flavors) - f, "next-csid,");
if (f > 0)
flavors[f - 1] = 0; // trim trailing comma

switch (sr6->behavior) {
case SR_BEHAVIOR_END:
SAFE_BUF(snprintf, len, " flavor=%#02x", sr6->flags);
break;
case SR_BEHAVIOR_END_T:
SAFE_BUF(snprintf, len, " flavor=%#02x %s", sr6->flags, vrf);
SAFE_BUF(snprintf, len, " flavor=%s", flavors[0] ? flavors : "none");
if (sr6->flags & GR_SR_FL_FLAVOR_NEXT_CSID)
SAFE_BUF(
snprintf,
len,
" block-bits=%u csid-bits=%u",
sr6->block_bits,
sr6->csid_bits
);
if (vrf[0])
SAFE_BUF(snprintf, len, " %s", vrf);
break;
case SR_BEHAVIOR_END_DT6:
case SR_BEHAVIOR_END_DT4:
case SR_BEHAVIOR_END_DT46:
SAFE_BUF(snprintf, len, " %s", vrf);
if (flavors[0])
SAFE_BUF(snprintf, len, " flavor=%s", flavors);
if (sr6->flags & GR_SR_FL_FLAVOR_NEXT_CSID)
SAFE_BUF(
snprintf,
len,
" block-bits=%u csid-bits=%u",
sr6->block_bits,
sr6->csid_bits
);
if (vrf[0])
SAFE_BUF(snprintf, len, " %s", vrf);
break;
}
return n;
Expand All @@ -135,23 +173,33 @@ static int ctx_init(struct ec_node *root) {

flavor_node = EC_NODE_CMD(
"FLAVORS",
"(psp,usd)",
"(psp,usd,next-csid)",
with_help("Penultimate Segment Pop of the SRH", ec_node_str("psp", "psp")),
with_help("Ultimate Segment Decapsulation of the SRH", ec_node_str("usd", "usd"))
with_help("Ultimate Segment Decapsulation of the SRH", ec_node_str("usd", "usd")),
with_help("NEXT-CSID uSID flavor (RFC 9800)", ec_node_str("next-csid", "next-csid"))
);
if (flavor_node == NULL)
return -1;
beh_node = EC_NODE_OR(
"BEHAVIOR",
EC_NODE_CMD(
EC_NO_ID,
"end [flavor FLAVORS]",
"end [(flavor FLAVORS),(block-bits BLOCK_BITS),(csid-bits CSID_BITS)]",
with_help("Transit endpoint.", ec_node_str("end", "end")),
with_help("Endpoint flavor(s).", ec_node_clone(flavor_node))
with_help("Endpoint flavor(s).", ec_node_clone(flavor_node)),
with_help(
"Locator-block length in bits.",
ec_node_uint("BLOCK_BITS", 8, 120, 10)
),
with_help(
"Compressed SID length in bits.",
ec_node_uint("CSID_BITS", 8, 64, 10)
)
),
EC_NODE_CMD(
EC_NO_ID,
"end.t [flavor FLAVORS] table TABLE",
"end.t [(flavor FLAVORS),(block-bits BLOCK_BITS),(csid-bits CSID_BITS)]"
" table TABLE",
with_help(
"L3 routing domain name.",
ec_node_dyn("TABLE", complete_vrf_names, NULL)
Expand All @@ -160,7 +208,15 @@ static int ctx_init(struct ec_node *root) {
"Transit endpoint with specific IPv6 table lookup.",
ec_node_str("end.t", "end.t")
),
with_help("Endpoint flavor(s).", flavor_node)
with_help("Endpoint flavor(s).", flavor_node),
with_help(
"Locator-block length in bits.",
ec_node_uint("BLOCK_BITS", 8, 120, 10)
),
with_help(
"Compressed SID length in bits.",
ec_node_uint("CSID_BITS", 8, 64, 10)
)
),
EC_NODE_CMD(
EC_NO_ID,
Expand Down
18 changes: 17 additions & 1 deletion modules/srv6/control/localsid.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,30 @@ static bool srv6_local_nh_equal(const struct nexthop *a, const struct nexthop *b
struct nexthop_info_srv6_local *bd = nexthop_info_srv6_local(b);

return ad->behavior == bd->behavior && ad->out_vrf_id == bd->out_vrf_id
&& ad->flags == bd->flags;
&& ad->flags == bd->flags && ad->block_bits == bd->block_bits
&& ad->csid_bits == bd->csid_bits;
}

static int srv6_local_nh_import_info(struct nexthop *nh, const void *info) {
struct nexthop_info_srv6_local *priv = nexthop_info_srv6_local(nh);
const struct gr_nexthop_info_srv6_local *pub = info;
uint8_t block_bits = pub->block_bits;
uint8_t csid_bits = pub->csid_bits;

if (pub->flags & GR_SR_FL_FLAVOR_NEXT_CSID) {
if (block_bits == 0)
block_bits = 32;
if (priv->csid_bits == 0)
csid_bits = 16;
if (block_bits % CHAR_BIT != 0 || csid_bits % CHAR_BIT != 0)
return errno_set(EDOM);
if (block_bits + csid_bits > RTE_IPV6_MAX_DEPTH)
return errno_set(ERANGE);
Comment on lines +24 to +35
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix NEXT‑CSID defaulting check to avoid zero CSID length.

The defaulting uses priv->csid_bits, so a request with CSID_BITS=0 on an existing nexthop can leave csid_bits at 0. That passes validation and makes csid_shift() return true without changing the DA, which can trap packets in a local re-lookup loop. Use the incoming value for the defaulting check (or explicitly reject 0).

🐛 Proposed fix
-		if (priv->csid_bits == 0)
-			csid_bits = 16;
+		if (csid_bits == 0)
+			csid_bits = 16;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/srv6/control/localsid.c` around lines 24 - 35, The NEXT‑CSID
defaulting uses priv->csid_bits which allows an incoming CSID_BITS=0 to remain
zero and bypass validation; update the logic in the GR_SR_FL_FLAVOR_NEXT_CSID
branch to check the incoming pub->csid_bits (not priv->csid_bits) when deciding
to apply the default (e.g., if (pub->csid_bits == 0) csid_bits = 16), and ensure
after defaulting that csid_bits != 0 (or explicitly return errno_set(EDOM)) so
csid_shift() cannot get a zero CSID length.

}

priv->base = *pub;
priv->block_bits = block_bits;
priv->csid_bits = csid_bits;

return 0;
}
Expand Down
60 changes: 60 additions & 0 deletions modules/srv6/datapath/srv6_local.c
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,49 @@ static int process_behav_decap(
return edge;
}

//
// Compressed SID shift-and-lookup (RFC 9800, Section 4.1.1).
//
// A CSID container packs multiple compressed SIDs after the locator block:
//
// |<-- block -->|<-- csid -->|<--- argument (remaining csids) --->|
// 0 ^ ^ 15
// block_end arg_off
//
// If the argument portion is non-zero, shift it left into the active CSID
// position and zero the vacated tail. This exposes the next CSID in the
// container as the new destination for FIB lookup. E.g.:
//
// Before: fd00:0202 : 0300 : 0100 : 0000 : 0000 : 0000 : 0000
// block csid ~~~~ argument (next csid) ~~~~~~~
//
// After: fd00:0202 : 0100 : 0000 : 0000 : 0000 : 0000 : 0000
// block csid ~~~~~~~~ zeroed tail ~~~~~~~~~~~~
//
// Returns true if the shift was performed, false if the container is exhausted
// (argument is all zeros, fall through to standard End).
//
static inline bool csid_shift(struct rte_ipv6_addr *da, uint8_t block_bits, uint8_t csid_bits) {
uint8_t block_end = block_bits / CHAR_BIT;
uint8_t csid_len = csid_bits / CHAR_BIT;
uint8_t arg_off = block_end + csid_len;
uint8_t arg = 0;

assert(arg_off <= ARRAY_DIM(da->a));

for (uint8_t i = arg_off; i < ARRAY_DIM(da->a); i++)
arg |= da->a[i];
if (arg == 0)
return false; // argument portion is all zeros

// shift argument left into the active CSID position
memmove(&da->a[block_end], &da->a[arg_off], ARRAY_DIM(da->a) - arg_off);
// zero the vacated tail
memset(&da->a[ARRAY_DIM(da->a) - csid_len], 0, csid_len);

return true;
}

//
// End behavior
//
Expand All @@ -290,6 +333,23 @@ static int process_behav_end(
struct rte_ipv6_routing_ext *sr = ip6_info->sr;
const struct iface *iface;

// NEXT-CSID processing (RFC 9800)
if (sr_d->flags & GR_SR_FL_FLAVOR_NEXT_CSID) {
struct rte_ipv6_hdr *ip6 = ip6_info->ip6_hdr;

if (csid_shift(&ip6->dst_addr, sr_d->block_bits, sr_d->csid_bits)) {
if (sr_d->out_vrf_id != GR_VRF_ID_UNDEF) {
iface = get_vrf_iface(sr_d->out_vrf_id);
if (iface == NULL)
return DEST_UNREACH;
mbuf_data(m)->iface = iface;
}
eth_input_mbuf_data(m)->domain = ETH_DOMAIN_LOCAL;
return IP6_INPUT;
}
// container exhausted, fall through to standard End
}

// at the end of the tunnel
if (sr == NULL || sr->segments_left == 0) {
// 4.16.3 USD
Expand Down
11 changes: 8 additions & 3 deletions smoke/_init_frr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -127,16 +127,21 @@ set_srv6_localsid() {
local count=0

# ---- translate behaviour aliases --------------------------------------
# map: end.dt4 -> uDT4, end.dt6 -> uDT6, end.dt46 -> uDT46
local frr_behavior
case "${grout_behavior,,}" in # ,, = lower-case
end) frr_behavior="uN" ;;
end.dt4) frr_behavior="uDT4" ;;
end.dt6) frr_behavior="uDT6" ;;
end.dt46) frr_behavior="uDT46" ;;
*) echo "Unsupported behavior '${grout_behavior}'. Use end.dt4, end.dt6, end.dt46."; exit 1 ;;
*) echo "Unsupported behavior '${grout_behavior}'."; exit 1 ;;
esac

# --- push the config into FRR ------------------------------------------
local vrf_clause="vrf default"
case "${frr_behavior}" in
uN) vrf_clause="" ;;
esac

vtysh <<-EOF
configure terminal
segment-routing
Expand All @@ -147,7 +152,7 @@ set_srv6_localsid() {
exit
exit
static-sids
sid ${sid_local}/48 locator ${locator} behavior ${frr_behavior} vrf default
sid ${sid_local}/48 locator ${locator} behavior ${frr_behavior} ${vrf_clause}
exit
exit
EOF
Expand Down
20 changes: 20 additions & 0 deletions smoke/srv6_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,23 @@ grcli route add fd00:202:100::/48 via id 666
ip netns exec n0 ping -i0.01 -c3 -n 192.168.60.1
# check that sid is reachable
ip netns exec n1 ping6 -i0.01 -c3 -n fd00:202:100::

#
# NEXT-CSID transit test
#
# A CSID container fd00:202:0300:0100:: packs two CSIDs (0300 and 0100)
# with block-bits=32, csid-bits=16. Grout matches fd00:202:0300::/48 as
# a NEXT-CSID End transit node, shifts the DA to fd00:202:0100::
# (= fd00:202:100::), and the second lookup hits the existing End.DT4
# endpoint.
#

# NEXT-CSID End transit node
grcli nexthop add srv6-local behavior end flavor next-csid id 700
grcli route add fd00:202:300::/48 via id 700

# linux sends to the CSID container instead of the single SID
ip -n n1 route replace 192.168.61.0/24 encap seg6 mode encap segs fd00:202:0300:0100:: dev x-p1

# test: ping goes through the NEXT-CSID transit node
ip netns exec n0 ping -i0.01 -c3 -n 192.168.60.1