diff --git a/frr/rt_grout.c b/frr/rt_grout.c index 6ae2b637e..1aa628275 100644 --- a/frr/rt_grout.c +++ b/frr/rt_grout.c @@ -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: @@ -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: diff --git a/modules/srv6/api/gr_srv6.h b/modules/srv6/api/gr_srv6.h index 86e86b1d5..224bfccfb 100644 --- a/modules/srv6/api/gr_srv6.h +++ b/modules/srv6/api/gr_srv6.h @@ -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. @@ -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). }; diff --git a/modules/srv6/cli/localsid.c b/modules/srv6/cli/localsid.c index 20867d67e..cec6ab626 100644 --- a/modules/srv6/cli/localsid.c +++ b/modules/srv6/cli/localsid.c @@ -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; @@ -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; @@ -135,9 +173,10 @@ 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; @@ -145,13 +184,22 @@ static int ctx_init(struct ec_node *root) { "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) @@ -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, diff --git a/modules/srv6/control/localsid.c b/modules/srv6/control/localsid.c index a7d36729d..0e26abcdb 100644 --- a/modules/srv6/control/localsid.c +++ b/modules/srv6/control/localsid.c @@ -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); + } priv->base = *pub; + priv->block_bits = block_bits; + priv->csid_bits = csid_bits; return 0; } diff --git a/modules/srv6/datapath/srv6_local.c b/modules/srv6/datapath/srv6_local.c index 86060af6e..94476cfe3 100644 --- a/modules/srv6/datapath/srv6_local.c +++ b/modules/srv6/datapath/srv6_local.c @@ -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 // @@ -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 diff --git a/smoke/_init_frr.sh b/smoke/_init_frr.sh index a399bb928..573dec7ea 100644 --- a/smoke/_init_frr.sh +++ b/smoke/_init_frr.sh @@ -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 @@ -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 diff --git a/smoke/srv6_test.sh b/smoke/srv6_test.sh index c3226cf60..24b1b3696 100755 --- a/smoke/srv6_test.sh +++ b/smoke/srv6_test.sh @@ -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