diff --git a/frr/rt_grout.c b/frr/rt_grout.c index 1f2e83ec2..017ded3a3 100644 --- a/frr/rt_grout.c +++ b/frr/rt_grout.c @@ -5,6 +5,9 @@ #include "log_grout.h" #include "rt_grout.h" +#include + +#include #include #include #include @@ -195,6 +198,10 @@ static int grout_gr_nexthop_to_frr_nexthop( gr_log_debug("no vrf support for nexthop, nexthop not sync"); return -1; } + if (gr_nh->type != GR_NH_T_L3) { + gr_log_err("sync nexthop not L3 from grout is not supported"); + return -1; + } if (gr_nh->iface_id) nh->ifindex = gr_nh->iface_id + GROUT_INDEX_OFFSET; else @@ -382,6 +389,234 @@ void grout_route6_change(bool new, struct gr_ip6_route *gr_r6) { ); } +static_assert(SRV6_MAX_SEGS <= GR_SRV6_ROUTE_SEGLIST_COUNT_MAX); + +static enum zebra_dplane_result grout_add_del_srv6_route( + struct zebra_dplane_ctx *ctx, + const struct prefix *p, + gr_nh_origin_t origin, + struct nexthop *nh, + vrf_id_t vrf_id, + bool new +) { + union { + struct { + struct gr_srv6_route_add_req rsrv6_add; + struct rte_ipv6_addr seglist[SRV6_MAX_SEGS]; + }; + struct gr_srv6_route_del_req rsrv6_del; + } req; + struct seg6_seg_stack *segs = nh->nh_srv6->seg6_segs; + struct gr_srv6_route *srv6_route; + struct gr_srv6_route_key *key; + uint32_t req_type; + size_t req_len; + int i; + + if (new) { + req.rsrv6_add = (struct gr_srv6_route_add_req) { + .r.key.vrf_id = vrf_id, + .exist_ok = true, + .origin = origin, + }; + srv6_route = &req.rsrv6_add.r; + key = &srv6_route->key; + req_type = GR_SRV6_ROUTE_ADD; + req_len = sizeof(struct gr_srv6_route_add_req); + } else { + req.rsrv6_del = (struct gr_srv6_route_del_req) { + .key.vrf_id = vrf_id, + .missing_ok = false, + }; + srv6_route = NULL; + key = &req.rsrv6_del.key; + req_type = GR_SRV6_ROUTE_DEL; + req_len = sizeof(struct gr_srv6_route_del_req); + } + *key = (struct gr_srv6_route_key) {.vrf_id = vrf_id, .is_dest6 = (p->family == AF_INET6)}; + + if (key->is_dest6) { + memcpy(key->dest6.ip.a, p->u.prefix6.s6_addr, sizeof(key->dest6.ip.a)); + key->dest6.prefixlen = p->prefixlen; + + gr_log_debug( + "%s srv6 route %pI6/%u (origin %s)", + new ? "add" : "del", + &key->dest6.ip, + key->dest6.prefixlen, + gr_nh_origin_name(origin) + ); + + } else { + key->dest4.ip = p->u.prefix4.s_addr; + key->dest4.prefixlen = p->prefixlen; + + gr_log_debug( + "%s srv6 route %pI4/%u (origin %s)", + new ? "add" : "del", + &key->dest4.ip, + key->dest4.prefixlen, + gr_nh_origin_name(origin) + ); + } + + // just need key to delete + if (!new) + goto end; + + switch (segs->encap_behavior) { + case SRV6_HEADEND_BEHAVIOR_H_ENCAPS: + srv6_route->encap_behavior = SR_H_ENCAPS; + break; + case SRV6_HEADEND_BEHAVIOR_H_ENCAPS_RED: + srv6_route->encap_behavior = SR_H_ENCAPS_RED; + break; + default: + zlog_err( + "%s: encap behavior '%s' not supported by grout", + __func__, + srv6_headend_behavior2str(segs->encap_behavior, true) + ); + return ZEBRA_DPLANE_REQUEST_FAILURE; + } + + if (segs->num_segs > SRV6_MAX_SEGS) { + zlog_err( + "%s: too many segments %u (max zebra %u, max grout %u)", + __func__, + segs->num_segs, + SRV6_MAX_SEGS, + GR_SRV6_ROUTE_SEGLIST_COUNT_MAX + ); + return ZEBRA_DPLANE_REQUEST_FAILURE; + } + + srv6_route->n_seglist = segs->num_segs; + for (i = 0; i < segs->num_segs; i++) { + memcpy(&srv6_route->seglist[i], &segs->seg[i], sizeof(srv6_route->seglist[i])); + req_len += sizeof(srv6_route->seglist[i]); + } + +end: + + if (!is_selfroute(origin)) { + gr_log_debug("no frr route, skip it"); + return ZEBRA_DPLANE_REQUEST_SUCCESS; + } + + if (grout_client_send_recv(req_type, req_len, &req, NULL) < 0) + return ZEBRA_DPLANE_REQUEST_FAILURE; + + return ZEBRA_DPLANE_REQUEST_SUCCESS; +} + +static enum zebra_dplane_result grout_add_del_srv6_local( + struct zebra_dplane_ctx *ctx, + const struct prefix *p, + gr_nh_origin_t origin, + struct nexthop *nh, + vrf_id_t vrf_id, + bool new +) { + union { + struct gr_srv6_localsid_add_req localsid_add; + struct gr_srv6_localsid_del_req localsid_del; + } req; + const struct seg6local_flavors_info *flv; + const struct seg6local_context *ctx6; + struct gr_srv6_localsid *gr_l; + uint32_t action, req_type; + size_t req_len; + + if (p->family != AF_INET6) { + gr_log_err("impossible to add/del local srv6 with family %u", p->family); + return ZEBRA_DPLANE_REQUEST_FAILURE; + } + + if (p->prefixlen != 128) { + gr_log_err( + "impossible to add/del local srv6 with prefix len %u (should be 128)", + p->prefixlen + ); + return ZEBRA_DPLANE_REQUEST_FAILURE; + } + + if (!new) { + req.localsid_del = (struct gr_srv6_localsid_del_req) { + .vrf_id = vrf_id, + .missing_ok = true, + }; + memcpy(&req.localsid_del.lsid, p->u.prefix6.s6_addr, sizeof(req.localsid_del.lsid)); + + req_type = GR_SRV6_LOCALSID_DEL; + req_len = sizeof(struct gr_srv6_localsid_del_req); + goto end; + } + + req.localsid_add = (struct gr_srv6_localsid_add_req) { + .l.vrf_id = vrf_id, + .origin = origin, + .exist_ok = true, + }; + req_type = GR_SRV6_LOCALSID_ADD; + req_len = sizeof(struct gr_srv6_localsid_add_req); + + gr_l = &req.localsid_add.l; + memcpy(&gr_l->lsid, p->u.prefix6.s6_addr, sizeof(gr_l->lsid)); + action = nh->nh_srv6->seg6local_action; + ctx6 = &nh->nh_srv6->seg6local_ctx; + + switch (action) { + case ZEBRA_SEG6_LOCAL_ACTION_END: + gr_l->behavior = SR_BEHAVIOR_END; + break; + case ZEBRA_SEG6_LOCAL_ACTION_END_T: + gr_l->behavior = SR_BEHAVIOR_END_T; + gr_l->out_vrf_id = zebra_vrf_lookup_by_table(ctx6->table, NS_DEFAULT); + break; + case ZEBRA_SEG6_LOCAL_ACTION_END_DT6: + gr_l->behavior = SR_BEHAVIOR_END_DT6; + gr_l->out_vrf_id = zebra_vrf_lookup_by_table(ctx6->table, NS_DEFAULT); + break; + case ZEBRA_SEG6_LOCAL_ACTION_END_DT4: + gr_l->behavior = SR_BEHAVIOR_END_DT4; + gr_l->out_vrf_id = zebra_vrf_lookup_by_table(ctx6->table, NS_DEFAULT); + break; + case ZEBRA_SEG6_LOCAL_ACTION_END_DT46: + gr_l->behavior = SR_BEHAVIOR_END_DT4; + gr_l->out_vrf_id = zebra_vrf_lookup_by_table(ctx6->table, NS_DEFAULT); + break; + default: + zlog_err("%s: not supported srv6 local behaviour action=%u", __func__, action); + return ZEBRA_DPLANE_REQUEST_FAILURE; + } + + flv = &ctx6->flv; + if (flv->flv_ops == ZEBRA_SEG6_LOCAL_FLV_OP_NEXT_CSID) { + zlog_err("%s: not supported next-c-sid for srv6 local", __func__); + return ZEBRA_DPLANE_REQUEST_FAILURE; + } + if (flv->flv_ops == ZEBRA_SEG6_LOCAL_FLV_OP_PSP) + gr_l->flags |= GR_SR_FL_FLAVOR_PSP; + if (flv->flv_ops == ZEBRA_SEG6_LOCAL_FLV_OP_USD) + gr_l->flags |= GR_SR_FL_FLAVOR_USD; + // XXX: if (flv->flv_ops == ZEBRA_SEG6_LOCAL_FLV_OP_USP) + if (flv->flv_ops == ZEBRA_SEG6_LOCAL_FLV_OP_UNSPEC) + zlog_debug("%s: USP is always configured", __func__); + +end: + + if (!is_selfroute(origin)) { + gr_log_debug("no frr route, skip it"); + return ZEBRA_DPLANE_REQUEST_SUCCESS; + } + + if (grout_client_send_recv(req_type, req_len, &req, NULL) < 0) + return ZEBRA_DPLANE_REQUEST_FAILURE; + + return ZEBRA_DPLANE_REQUEST_SUCCESS; +} + enum zebra_dplane_result grout_add_del_route(struct zebra_dplane_ctx *ctx) { union { struct gr_ip4_route_add_req r4_add; @@ -390,8 +625,10 @@ enum zebra_dplane_result grout_add_del_route(struct zebra_dplane_ctx *ctx) { struct gr_ip6_route_del_req r6_del; } req; uint32_t nh_id = dplane_ctx_get_nhe_id(ctx); + const struct nexthop_group *ng; const struct prefix *p; gr_nh_origin_t origin; + struct nexthop *nh; uint32_t req_type; size_t req_len; bool new; @@ -418,6 +655,27 @@ enum zebra_dplane_result grout_add_del_route(struct zebra_dplane_ctx *ctx) { origin = zebra2origin(dplane_ctx_get_type(ctx)); new = dplane_ctx_get_op(ctx) != DPLANE_OP_ROUTE_DELETE; + ng = dplane_ctx_get_ng(ctx); + nh = ng->nexthop; + if (nh && nh->nh_srv6) { + if (nexthop_group_nexthop_num(ng) > 1) { + gr_log_err( + "impossible to add/del srv6 route with several nexthop (not " + "supported)" + ); + return ZEBRA_DPLANE_REQUEST_FAILURE; + } + + if (nh->nh_srv6->seg6local_action != ZEBRA_SEG6_LOCAL_ACTION_UNSPEC) + return grout_add_del_srv6_local(ctx, p, origin, nh, VRF_DEFAULT, new); + if (nh->nh_srv6->seg6_segs && nh->nh_srv6->seg6_segs->num_segs + && !sid_zero(nh->nh_srv6->seg6_segs)) + return grout_add_del_srv6_route(ctx, p, origin, nh, VRF_DEFAULT, new); + + gr_log_err("impossible to add/del srv6 route (invalid format)"); + return ZEBRA_DPLANE_REQUEST_FAILURE; + } + if (new && nh_id == 0) { gr_log_err("impossible to add route with no nexthop id"); return ZEBRA_DPLANE_REQUEST_FAILURE; @@ -474,8 +732,8 @@ enum zebra_dplane_result grout_add_del_route(struct zebra_dplane_ctx *ctx) { .missing_ok = true, .vrf_id = 0 }; - req_type = GR_IP6_ROUTE_ADD; - req_len = sizeof(struct gr_ip6_route_add_req); + req_type = GR_IP6_ROUTE_DEL; + req_len = sizeof(struct gr_ip6_route_del_req); dest = &req.r6_del.dest; new = false; @@ -542,6 +800,10 @@ enum zebra_dplane_result grout_add_del_nexthop(struct zebra_dplane_ctx *ctx) { gr_log_err("impossible to add/del blackhole nexthop (not supported)"); return ZEBRA_DPLANE_REQUEST_FAILURE; } + if (nh->nh_srv6) { + gr_log_err("impossible to add/del srv6 nexthop (not supported)"); + return ZEBRA_DPLANE_REQUEST_SUCCESS; + } new = dplane_ctx_get_op(ctx) != DPLANE_OP_NH_DELETE; if (new) { @@ -597,7 +859,7 @@ enum zebra_dplane_result grout_add_del_nexthop(struct zebra_dplane_ctx *ctx) { break; case NEXTHOP_TYPE_IFINDEX: gr_log_debug("add nexthop id %u ifindex %u", nh_id, gr_nh->iface_id); - break; + return ZEBRA_DPLANE_REQUEST_FAILURE; default: gr_log_err("impossible to add nexthop %u (type %u not supported)", nh_id, nh->type); return ZEBRA_DPLANE_REQUEST_FAILURE; diff --git a/frr/zebra_dplane_grout.c b/frr/zebra_dplane_grout.c index 348d9e385..01bc1f385 100644 --- a/frr/zebra_dplane_grout.c +++ b/frr/zebra_dplane_grout.c @@ -7,6 +7,7 @@ #include "rt_grout.h" #include +#include #include #include @@ -161,6 +162,14 @@ static const char *gr_req_type_to_str(uint32_t e) { return TOSTRING(GR_NH_ADD); case GR_NH_DEL: return TOSTRING(GR_NH_DEL); + case GR_SRV6_LOCALSID_ADD: + return TOSTRING(GR_SRV6_LOCALSID_ADD); + case GR_SRV6_LOCALSID_DEL: + return TOSTRING(GR_SRV6_LOCALSID_DEL); + case GR_SRV6_ROUTE_ADD: + return TOSTRING(GR_SRV6_ROUTE_ADD); + case GR_SRV6_ROUTE_DEL: + return TOSTRING(GR_SRV6_ROUTE_DEL); default: return "unknown"; diff --git a/modules/infra/control/gr_nh_control.h b/modules/infra/control/gr_nh_control.h index d0532e610..5f4a94a5e 100644 --- a/modules/infra/control/gr_nh_control.h +++ b/modules/infra/control/gr_nh_control.h @@ -89,6 +89,7 @@ const struct nexthop_af_ops *nexthop_af_ops_get(addr_family_t af); struct nexthop_type_ops { // Callback that will be invoked the nexthop refcount reaches zero. void (*free)(struct nexthop *); + bool (*equal)(const struct nexthop *, const struct nexthop *); }; void nexthop_type_ops_register(gr_nh_type_t type, const struct nexthop_type_ops *); diff --git a/modules/infra/control/nexthop.c b/modules/infra/control/nexthop.c index ddf97ead5..7c337f994 100644 --- a/modules/infra/control/nexthop.c +++ b/modules/infra/control/nexthop.c @@ -116,7 +116,7 @@ void nexthop_type_ops_register(gr_nh_type_t type, const struct nexthop_type_ops case GR_NH_T_SR6_OUTPUT: case GR_NH_T_SR6_LOCAL: case GR_NH_T_DNAT: - if (ops == NULL || ops->free == NULL) + if (ops == NULL || (ops->free == NULL && ops->equal == NULL)) ABORT("invalid type ops"); if (type_ops[type] != NULL) ABORT("duplicate type ops %hhu", type); @@ -168,19 +168,30 @@ struct nexthop *nexthop_new(const struct gr_nexthop *base) { } bool nexthop_equal(const struct nexthop *a, const struct nexthop *b) { - if (a->vrf_id != b->vrf_id || a->iface_id != b->iface_id || a->af != b->af) + const struct nexthop_type_ops *ops = type_ops[a->type]; + + if (a->vrf_id != b->vrf_id || a->iface_id != b->iface_id || a->af != b->af + || a->type != b->type) return false; switch (a->af) { case GR_AF_IP4: - return memcmp(&a->ipv4, &b->ipv4, sizeof(a->ipv4)) == 0; + if (memcmp(&a->ipv4, &b->ipv4, sizeof(a->ipv4))) + return false; + break; case GR_AF_IP6: - return memcmp(&a->ipv6, &b->ipv6, sizeof(a->ipv6)) == 0; + if (memcmp(&a->ipv6, &b->ipv6, sizeof(a->ipv6))) + return false; + break; default: ABORT("invalid nexthop family %hhu", a->af); } - return false; + if (ops != NULL && ops->equal != NULL) + if (!ops->equal(a, b)) + return false; + + return true; } struct pool_iterator { diff --git a/modules/ip/api/dnat44.c b/modules/ip/api/dnat44.c index c0e70d0d9..cb8486d3b 100644 --- a/modules/ip/api/dnat44.c +++ b/modules/ip/api/dnat44.c @@ -7,9 +7,46 @@ #include #include +static bool dnat44_data_priv_equal(const struct nexthop *a, const struct nexthop *b) { + struct dnat44_nh_data *ad, *bd; + + assert(a->type == GR_NH_T_DNAT); + assert(b->type == GR_NH_T_DNAT); + + ad = dnat44_nh_data(a); + bd = dnat44_nh_data(b); + + return (ad->replace == bd->replace); +} + +static int dnat44_data_priv_add( + struct nexthop *nh, + struct iface *iface, + ip4_addr_t match, + ip4_addr_t replace +) { + struct dnat44_nh_data *data; + + data = dnat44_nh_data(nh); + data->replace = replace; + + return snat44_static_rule_add(iface, replace, match); +} + +static void dnat44_data_priv_del(struct nexthop *nh) { + struct iface *iface; + + nh->type = GR_NH_T_L3; + + iface = iface_from_id(nh->iface_id); + if (iface == NULL) + return; + + snat44_static_rule_del(iface, nh->ipv4); +} + static struct api_out dnat44_add(const void *request, void ** /*response*/) { const struct gr_dnat44_add_req *req = request; - struct dnat44_nh_data *data; struct iface *iface; struct nexthop *nh; int ret; @@ -18,16 +55,6 @@ static struct api_out dnat44_add(const void *request, void ** /*response*/) { if (iface == NULL) return api_out(ENODEV, 0); - nh = nh4_lookup(iface->vrf_id, req->rule.match); - if (nh != NULL) { - data = dnat44_nh_data(nh); - if (nh->type != GR_NH_T_DNAT || data->replace != req->rule.replace) - return api_out(EADDRINUSE, 0); - if (req->exist_ok) - return api_out(0, 0); - return api_out(EEXIST, 0); - } - nh = nexthop_new(&(struct gr_nexthop) { .type = GR_NH_T_DNAT, .af = GR_AF_IP4, @@ -41,47 +68,36 @@ static struct api_out dnat44_add(const void *request, void ** /*response*/) { if (nh == NULL) return api_out(ENOMEM, 0); - data = dnat44_nh_data(nh); - data->replace = req->rule.replace; - ret = rib4_insert(iface->vrf_id, req->rule.match, 32, GR_NH_ORIGIN_INTERNAL, nh); - if (ret < 0) + ret = dnat44_data_priv_add(nh, iface, req->rule.match, req->rule.replace); + if (ret < 0) { + if (ret == -EEXIST && req->exist_ok) + ret = 0; + + nexthop_decref(nh); return api_out(-ret, 0); + } - ret = snat44_static_rule_add(iface, req->rule.replace, req->rule.match); - if (ret < 0) - goto fail; + ret = rib4_insert(iface->vrf_id, req->rule.match, 32, GR_NH_ORIGIN_INTERNAL, nh); + if (ret == -EEXIST && req->exist_ok) + ret = 0; - return api_out(0, 0); -fail: - rib4_delete(iface->vrf_id, req->rule.match, 32); - snat44_static_rule_del(iface, req->rule.replace); return api_out(-ret, 0); } static struct api_out dnat44_del(const void *request, void ** /*response*/) { const struct gr_dnat44_del_req *req = request; - struct dnat44_nh_data *data; struct iface *iface; - struct nexthop *nh; + int ret; - iface = iface_from_id(req->rule.iface_id); + iface = iface_from_id(req->iface_id); if (iface == NULL) return api_out(ENODEV, 0); - nh = nh4_lookup(iface->vrf_id, req->rule.match); - if (nh == NULL) { - if (req->missing_ok) - return api_out(0, 0); - return api_out(ENOENT, 0); - } - data = dnat44_nh_data(nh); - if (nh->type != GR_NH_T_DNAT || data->replace != req->rule.replace) - return api_out(EADDRINUSE, 0); + ret = rib4_delete(iface->vrf_id, req->match, 32, GR_NH_T_DNAT); + if (ret == -ENOENT && req->missing_ok) + ret = 0; - rib4_delete(iface->vrf_id, req->rule.match, 32); - snat44_static_rule_del(iface, req->rule.replace); - - return api_out(0, 0); + return api_out(-ret, 0); } struct dnat44_list_iterator { @@ -152,8 +168,14 @@ static struct gr_api_handler list_handler = { .callback = dnat44_list, }; +static struct nexthop_type_ops nh_ops = { + .equal = dnat44_data_priv_equal, + .free = dnat44_data_priv_del, +}; + RTE_INIT(_init) { gr_register_api_handler(&add_handler); gr_register_api_handler(&del_handler); gr_register_api_handler(&list_handler); + nexthop_type_ops_register(GR_NH_T_DNAT, &nh_ops); } diff --git a/modules/ip/api/gr_ip4.h b/modules/ip/api/gr_ip4.h index 20a3801ce..c644e743f 100644 --- a/modules/ip/api/gr_ip4.h +++ b/modules/ip/api/gr_ip4.h @@ -160,7 +160,8 @@ struct gr_dnat44_add_req { #define GR_DNAT44_DEL REQUEST_TYPE(GR_IP4_MODULE, 0x0032) struct gr_dnat44_del_req { - struct gr_dnat44_rule rule; + uint16_t iface_id; + ip4_addr_t match; bool missing_ok; }; diff --git a/modules/ip/cli/dnat44.c b/modules/ip/cli/dnat44.c index ffb8f56b1..5764349f7 100644 --- a/modules/ip/cli/dnat44.c +++ b/modules/ip/cli/dnat44.c @@ -39,10 +39,8 @@ static cmd_status_t dnat44_del(const struct gr_api_client *c, const struct ec_pn if (iface_from_name(c, arg_str(p, "IFACE"), &iface) < 0) return CMD_ERROR; - req.rule.iface_id = iface.id; - if (arg_ip4(p, "DEST", &req.rule.match) < 0) - return CMD_ERROR; - if (arg_ip4(p, "REPLACE", &req.rule.replace) < 0) + req.iface_id = iface.id; + if (arg_ip4(p, "DEST", &req.match) < 0) return CMD_ERROR; if (gr_api_client_send_recv(c, GR_DNAT44_DEL, sizeof(req), &req, NULL) < 0) @@ -114,12 +112,11 @@ static int ctx_init(struct ec_node *root) { return ret; ret = CLI_COMMAND( IP_DEL_CTX(root), - "dnat44 interface IFACE destination DEST replace REPLACE", + "dnat44 interface IFACE destination DEST", dnat44_del, "Delete a DNAT44 rule.", with_help("Input interface.", ec_node_dyn("IFACE", complete_iface_names, NULL)), - with_help("Destination IPv4 address to match.", ec_node_re("DEST", IPV4_RE)), - with_help("Replace match with this IPv4 address.", ec_node_re("REPLACE", IPV4_RE)) + with_help("Destination IPv4 address to match.", ec_node_re("DEST", IPV4_RE)) ); if (ret < 0) return ret; diff --git a/modules/ip/control/gr_ip4_control.h b/modules/ip/control/gr_ip4_control.h index 5b166591f..e1cca03ee 100644 --- a/modules/ip/control/gr_ip4_control.h +++ b/modules/ip/control/gr_ip4_control.h @@ -35,7 +35,7 @@ int rib4_insert( gr_nh_origin_t origin, struct nexthop *nh ); -int rib4_delete(uint16_t vrf_id, ip4_addr_t ip, uint8_t prefixlen); +int rib4_delete(uint16_t vrf_id, ip4_addr_t ip, uint8_t prefixlen, gr_nh_type_t nh_type); void rib4_cleanup(struct nexthop *); // get the default address for a given interface diff --git a/modules/ip/control/nexthop.c b/modules/ip/control/nexthop.c index 28fe91b2f..1c4b197a0 100644 --- a/modules/ip/control/nexthop.c +++ b/modules/ip/control/nexthop.c @@ -192,7 +192,7 @@ static int nh4_add(struct nexthop *nh) { } static void nh4_del(struct nexthop *nh) { - rib4_delete(nh->vrf_id, nh->ipv4, 32); + rib4_delete(nh->vrf_id, nh->ipv4, 32, nh->type); if (nh->ref_count > 0) { nh->state = GR_NH_S_NEW; memset(&nh->mac, 0, sizeof(nh->mac)); diff --git a/modules/ip/control/route.c b/modules/ip/control/route.c index 51a165f18..02cae8e9d 100644 --- a/modules/ip/control/route.c +++ b/modules/ip/control/route.c @@ -173,7 +173,7 @@ int rib4_insert( return errno_set(-ret); } -int rib4_delete(uint16_t vrf_id, ip4_addr_t ip, uint8_t prefixlen) { +int rib4_delete(uint16_t vrf_id, ip4_addr_t ip, uint8_t prefixlen, gr_nh_type_t nh_type) { struct rte_rib *rib = get_rib(vrf_id); gr_nh_origin_t *o, origin; struct rte_rib_node *rn; @@ -191,6 +191,8 @@ int rib4_delete(uint16_t vrf_id, ip4_addr_t ip, uint8_t prefixlen) { origin = *o; rte_rib_get_nh(rn, &nh_id); nh = nh_id_to_ptr(nh_id); + if (nh->type != nh_type) + return errno_set(EINVAL); rte_rib_remove(rib, rte_be_to_cpu_32(ip), prefixlen); fib4_remove(vrf_id, ip, prefixlen); @@ -254,18 +256,13 @@ static struct api_out route4_add(const void *request, void ** /*response*/) { static struct api_out route4_del(const void *request, void ** /*response*/) { const struct gr_ip4_route_del_req *req = request; - struct nexthop *nh; - - if ((nh = rib4_lookup_exact(req->vrf_id, req->dest.ip, req->dest.prefixlen)) == NULL) { - if (req->missing_ok) - return api_out(0, 0); - return api_out(ENOENT, 0); - } + int ret; - if (rib4_delete(req->vrf_id, req->dest.ip, req->dest.prefixlen) < 0) - return api_out(errno, 0); + ret = rib4_delete(req->vrf_id, req->dest.ip, req->dest.prefixlen, GR_NH_T_L3); + if (ret == -ENOENT && req->missing_ok) + ret = 0; - return api_out(0, 0); + return api_out(-ret, 0); } static struct api_out route4_get(const void *request, void **response) { @@ -416,9 +413,9 @@ void rib4_cleanup(struct nexthop *nh) { local_prefixlen = nh->prefixlen; if (nh->flags & (GR_NH_F_LOCAL | GR_NH_F_LINK)) - rib4_delete(nh->vrf_id, nh->ipv4, nh->prefixlen); + rib4_delete(nh->vrf_id, nh->ipv4, nh->prefixlen, nh->type); else - rib4_delete(nh->vrf_id, nh->ipv4, 32); + rib4_delete(nh->vrf_id, nh->ipv4, 32, nh->type); rib = get_rib(nh->vrf_id); while ((rn = rte_rib_get_nxt(rib, 0, 0, rn, RTE_RIB_GET_NXT_ALL)) != NULL) { @@ -430,10 +427,15 @@ void rib4_cleanup(struct nexthop *nh) { rte_rib_get_depth(rn, &prefixlen); ip = rte_cpu_to_be_32(ip); - LOG(DEBUG, "delete " IP4_F "/%hhu via " IP4_F, &ip, prefixlen, &nh->ipv4); + LOG(DEBUG, + "delete %s " IP4_F "/%hhu via " IP4_F, + gr_nh_type_name(&nh->base), + &ip, + prefixlen, + &nh->ipv4); - rib4_delete(nh->vrf_id, ip, prefixlen); - rib4_delete(nh->vrf_id, ip, 32); + rib4_delete(nh->vrf_id, ip, prefixlen, nh->type); + rib4_delete(nh->vrf_id, ip, 32, nh->type); } } @@ -446,10 +448,15 @@ void rib4_cleanup(struct nexthop *nh) { rte_rib_get_depth(rn, &prefixlen); ip = rte_cpu_to_be_32(ip); - LOG(DEBUG, "delete " IP4_F "/%hhu via " IP4_F, &ip, prefixlen, &nh->ipv4); + LOG(DEBUG, + "delete %s " IP4_F "/%hhu via " IP4_F, + gr_nh_type_name(&nh->base), + &ip, + prefixlen, + &nh->ipv4); - rib4_delete(nh->vrf_id, nh->ipv4, nh->prefixlen); - rib4_delete(nh->vrf_id, nh->ipv4, 32); + rib4_delete(nh->vrf_id, nh->ipv4, nh->prefixlen, nh->type); + rib4_delete(nh->vrf_id, nh->ipv4, 32, nh->type); } } } diff --git a/modules/ip6/control/gr_ip6_control.h b/modules/ip6/control/gr_ip6_control.h index ff305955e..213d82067 100644 --- a/modules/ip6/control/gr_ip6_control.h +++ b/modules/ip6/control/gr_ip6_control.h @@ -36,7 +36,8 @@ int rib6_delete( uint16_t vrf_id, uint16_t iface_id, const struct rte_ipv6_addr *, - uint8_t prefixlen + uint8_t prefixlen, + gr_nh_type_t nh_type ); void rib6_cleanup(struct nexthop *); struct nexthop *rib6_lookup(uint16_t vrf_id, uint16_t iface_id, const struct rte_ipv6_addr *); diff --git a/modules/ip6/control/nexthop.c b/modules/ip6/control/nexthop.c index c307712ff..a6a8d313d 100644 --- a/modules/ip6/control/nexthop.c +++ b/modules/ip6/control/nexthop.c @@ -236,7 +236,7 @@ static int nh6_add(struct nexthop *nh) { } static void nh6_del(struct nexthop *nh) { - rib6_delete(nh->vrf_id, nh->iface_id, &nh->ipv6, RTE_IPV6_MAX_DEPTH); + rib6_delete(nh->vrf_id, nh->iface_id, &nh->ipv6, RTE_IPV6_MAX_DEPTH, nh->type); if (nh->ref_count > 0) { nh->state = GR_NH_S_NEW; memset(&nh->mac, 0, sizeof(nh->mac)); diff --git a/modules/ip6/control/route.c b/modules/ip6/control/route.c index 8e1e3b1bf..b1feb56b1 100644 --- a/modules/ip6/control/route.c +++ b/modules/ip6/control/route.c @@ -191,7 +191,8 @@ int rib6_delete( uint16_t vrf_id, uint16_t iface_id, const struct rte_ipv6_addr *ip, - uint8_t prefixlen + uint8_t prefixlen, + gr_nh_type_t nh_type ) { struct rte_rib6 *rib = get_rib6(vrf_id); const struct rte_ipv6_addr *scoped_ip; @@ -213,6 +214,8 @@ int rib6_delete( origin = *o; rte_rib6_get_nh(rn, &nh_id); nh = nh_id_to_ptr(nh_id); + if (nh->type != nh_type) + return errno_set(EINVAL); rte_rib6_remove(rib, scoped_ip, prefixlen); @@ -277,21 +280,15 @@ static struct api_out route6_add(const void *request, void ** /*response*/) { static struct api_out route6_del(const void *request, void ** /*response*/) { const struct gr_ip6_route_del_req *req = request; - struct nexthop *nh; - - if ((nh = rib6_lookup_exact( - req->vrf_id, GR_IFACE_ID_UNDEF, &req->dest.ip, req->dest.prefixlen - )) - == NULL) { - if (req->missing_ok) - return api_out(0, 0); - return api_out(ENOENT, 0); - } + int ret; - if (rib6_delete(req->vrf_id, nh->iface_id, &req->dest.ip, req->dest.prefixlen) < 0) - return api_out(errno, 0); + ret = rib6_delete( + req->vrf_id, GR_IFACE_ID_UNDEF, &req->dest.ip, req->dest.prefixlen, GR_NH_T_L3 + ); + if (ret == -ENOENT && req->missing_ok) + ret = 0; - return api_out(0, 0); + return api_out(-ret, 0); } static struct api_out route6_get(const void *request, void **response) { @@ -446,9 +443,9 @@ void rib6_cleanup(struct nexthop *nh) { local_depth = nh->prefixlen; if (nh->flags & (GR_NH_F_LOCAL | GR_NH_F_LINK)) - rib6_delete(nh->vrf_id, nh->iface_id, &nh->ipv6, nh->prefixlen); + rib6_delete(nh->vrf_id, nh->iface_id, &nh->ipv6, nh->prefixlen, nh->type); else - rib6_delete(nh->vrf_id, nh->iface_id, &nh->ipv6, RTE_IPV6_MAX_DEPTH); + rib6_delete(nh->vrf_id, nh->iface_id, &nh->ipv6, RTE_IPV6_MAX_DEPTH, nh->type); rib = get_rib6(nh->vrf_id); while ((rn = rte_rib6_get_nxt(rib, 0, 0, rn, RTE_RIB6_GET_NXT_ALL)) != NULL) { @@ -459,10 +456,15 @@ void rib6_cleanup(struct nexthop *nh) { rte_rib6_get_ip(rn, &ip); rte_rib6_get_depth(rn, &depth); - LOG(DEBUG, "delete " IP6_F "/%hhu via " IP6_F, &ip, depth, &nh->ipv6); + LOG(DEBUG, + "delete %s " IP6_F "/%hhu via " IP6_F, + gr_nh_type_name(&nh->base), + &ip, + depth, + &nh->ipv6); - rib6_delete(nh->vrf_id, nh->iface_id, &ip, depth); - rib6_delete(nh->vrf_id, nh->iface_id, &ip, RTE_IPV6_MAX_DEPTH); + rib6_delete(nh->vrf_id, nh->iface_id, &ip, depth, nh->type); + rib6_delete(nh->vrf_id, nh->iface_id, &ip, RTE_IPV6_MAX_DEPTH, nh->type); } } @@ -474,10 +476,17 @@ void rib6_cleanup(struct nexthop *nh) { rte_rib6_get_ip(rn, &ip); rte_rib6_get_depth(rn, &depth); - LOG(DEBUG, "delete " IP6_F "/%hhu via " IP6_F, &ip, depth, &nh->ipv6); - - rib6_delete(nh->vrf_id, nh->iface_id, &nh->ipv6, nh->prefixlen); - rib6_delete(nh->vrf_id, nh->iface_id, &nh->ipv6, RTE_IPV6_MAX_DEPTH); + LOG(DEBUG, + "delete %s " IP6_F "/%hhu via " IP6_F, + gr_nh_type_name(&nh->base), + &ip, + depth, + &nh->ipv6); + + rib6_delete(nh->vrf_id, nh->iface_id, &nh->ipv6, nh->prefixlen, nh->type); + rib6_delete( + nh->vrf_id, nh->iface_id, &nh->ipv6, RTE_IPV6_MAX_DEPTH, nh->type + ); } } } diff --git a/modules/srv6/api/gr_srv6.h b/modules/srv6/api/gr_srv6.h index f67a44cd2..ba70efb48 100644 --- a/modules/srv6/api/gr_srv6.h +++ b/modules/srv6/api/gr_srv6.h @@ -5,6 +5,7 @@ #include #include +#include #define GR_SRV6_MODULE 0xfeef @@ -39,6 +40,8 @@ struct gr_srv6_route { #define GR_SRV6_ROUTE_ADD REQUEST_TYPE(GR_SRV6_MODULE, 0x0001) struct gr_srv6_route_add_req { + uint8_t exist_ok; + gr_nh_origin_t origin; struct gr_srv6_route r; }; @@ -46,6 +49,7 @@ struct gr_srv6_route_add_req { struct gr_srv6_route_del_req { struct gr_srv6_route_key key; + uint8_t missing_ok; }; #define GR_SRV6_ROUTE_LIST REQUEST_TYPE(GR_SRV6_MODULE, 0x0004) @@ -92,6 +96,8 @@ struct gr_srv6_localsid { struct gr_srv6_localsid_add_req { struct gr_srv6_localsid l; + gr_nh_origin_t origin; + uint8_t exist_ok; }; #define GR_SRV6_LOCALSID_DEL REQUEST_TYPE(GR_SRV6_MODULE, 0x0022) @@ -99,6 +105,7 @@ struct gr_srv6_localsid_add_req { struct gr_srv6_localsid_del_req { struct rte_ipv6_addr lsid; uint16_t vrf_id; + uint8_t missing_ok; }; #define GR_SRV6_LOCALSID_LIST REQUEST_TYPE(GR_SRV6_MODULE, 0x0023) diff --git a/modules/srv6/cli/localsid.c b/modules/srv6/cli/localsid.c index 2da1af7ef..317b6eaf3 100644 --- a/modules/srv6/cli/localsid.c +++ b/modules/srv6/cli/localsid.c @@ -37,7 +37,9 @@ static gr_srv6_behavior_t str_to_behavior(const char *str) { } static cmd_status_t srv6_localsid_add(const struct gr_api_client *c, const struct ec_pnode *p) { - struct gr_srv6_localsid_add_req req = {.l.out_vrf_id = UINT16_MAX}; + struct gr_srv6_localsid_add_req req = { + .l.out_vrf_id = UINT16_MAX, .exist_ok = true, .origin = GR_NH_ORIGIN_USER + }; const struct ec_pnode *n; const struct ec_strvec *v; const char *str; @@ -78,7 +80,7 @@ static cmd_status_t srv6_localsid_add(const struct gr_api_client *c, const struc } static cmd_status_t srv6_localsid_del(const struct gr_api_client *c, const struct ec_pnode *p) { - struct gr_srv6_localsid_del_req req = {.vrf_id = 0}; + struct gr_srv6_localsid_del_req req = {.vrf_id = 0, .missing_ok = true}; if (arg_ip6(p, "SID", &req.lsid) < 0) return CMD_ERROR; diff --git a/modules/srv6/cli/route.c b/modules/srv6/cli/route.c index 693d211ae..f72558428 100644 --- a/modules/srv6/cli/route.c +++ b/modules/srv6/cli/route.c @@ -30,6 +30,8 @@ static cmd_status_t srv6_route_add(const struct gr_api_client *c, const struct e if ((req = calloc(1, len)) == NULL) return CMD_ERROR; req->r.n_seglist = ec_pnode_len(n); + req->exist_ok = true; + req->origin = GR_NH_ORIGIN_USER; // parse SEGLIST list. for (n = ec_pnode_get_first_child(n), i = 0; n != NULL; n = ec_pnode_next(n), i++) { @@ -62,7 +64,7 @@ static cmd_status_t srv6_route_add(const struct gr_api_client *c, const struct e } static cmd_status_t srv6_route_del(const struct gr_api_client *c, const struct ec_pnode *p) { - struct gr_srv6_route_del_req req = {}; + struct gr_srv6_route_del_req req = {.missing_ok = true}; if (arg_ip6_net(p, "DEST6", &req.key.dest6, true) >= 0) req.key.is_dest6 = true; diff --git a/modules/srv6/control/localsid.c b/modules/srv6/control/localsid.c index 75f5847c8..b8fe0cc32 100644 --- a/modules/srv6/control/localsid.c +++ b/modules/srv6/control/localsid.c @@ -11,6 +11,19 @@ #include // localsid ///////////////////////////////////////////////////////////// +static bool srv6_localsid_priv_equal(const struct nexthop *a, const struct nexthop *b) { + struct srv6_localsid_nh_priv *ad, *bd; + + assert(a->type == GR_NH_T_SR6_LOCAL); + assert(b->type == GR_NH_T_SR6_LOCAL); + + ad = srv6_localsid_nh_priv(a); + bd = srv6_localsid_nh_priv(b); + + return (ad->behavior == bd->behavior && ad->out_vrf_id == bd->out_vrf_id + && ad->flags == bd->flags); +} + static struct api_out srv6_localsid_add(const void *request, void ** /*response*/) { const struct gr_srv6_localsid_add_req *req = request; struct srv6_localsid_nh_priv *data; @@ -22,7 +35,7 @@ static struct api_out srv6_localsid_add(const void *request, void ** /*response* .vrf_id = req->l.vrf_id, .iface_id = GR_IFACE_ID_UNDEF, .ipv6 = req->l.lsid, - .origin = GR_NH_ORIGIN_LINK, + .origin = req->origin, }; struct nexthop *nh; int r; @@ -35,24 +48,22 @@ static struct api_out srv6_localsid_add(const void *request, void ** /*response* data->behavior = req->l.behavior; data->out_vrf_id = req->l.out_vrf_id; data->flags = req->l.flags; - r = rib6_insert(req->l.vrf_id, GR_IFACE_ID_UNDEF, &req->l.lsid, 128, GR_NH_ORIGIN_LINK, nh); - if (r < 0) - return api_out(-r, 0); + r = rib6_insert(req->l.vrf_id, GR_IFACE_ID_UNDEF, &req->l.lsid, 128, req->origin, nh); + if (r == -EEXIST && req->exist_ok) + r = 0; - return api_out(0, 0); + return api_out(-r, 0); } static struct api_out srv6_localsid_del(const void *request, void ** /*response*/) { const struct gr_srv6_localsid_del_req *req = request; - struct nexthop *nh; - - if ((nh = rib6_lookup_exact(req->vrf_id, GR_IFACE_ID_UNDEF, &req->lsid, 128)) == NULL) - return api_out(ENOENT, 0); + int ret; - if (rib6_delete(req->vrf_id, GR_IFACE_ID_UNDEF, &req->lsid, 128) < 0) - return api_out(errno, 0); + ret = rib6_delete(req->vrf_id, GR_IFACE_ID_UNDEF, &req->lsid, 128, GR_NH_T_SR6_LOCAL); + if (ret == -ENOENT && req->missing_ok) + ret = 0; - return api_out(0, 0); + return api_out(-ret, 0); } struct list_context { @@ -121,8 +132,13 @@ static struct gr_api_handler srv6_localsid_list_handler = { .callback = srv6_localsid_list, }; +static struct nexthop_type_ops nh_ops = { + .equal = srv6_localsid_priv_equal, +}; + RTE_INIT(srv6_constructor) { gr_register_api_handler(&srv6_localsid_add_handler); gr_register_api_handler(&srv6_localsid_del_handler); gr_register_api_handler(&srv6_localsid_list_handler); + nexthop_type_ops_register(GR_NH_T_SR6_LOCAL, &nh_ops); } diff --git a/modules/srv6/control/route.c b/modules/srv6/control/route.c index 5b3d4362e..77271f00e 100644 --- a/modules/srv6/control/route.c +++ b/modules/srv6/control/route.c @@ -13,6 +13,32 @@ #include // routes //////////////////////////////////////////////////////////////// +static bool srv6_encap_data_equal(const struct nexthop *a, const struct nexthop *b) { + struct srv6_encap_data *ad, *bd; + uint8_t i; + + assert(a->type == GR_NH_T_SR6_OUTPUT); + assert(b->type == GR_NH_T_SR6_OUTPUT); + + ad = srv6_encap_nh_priv(a)->d; + bd = srv6_encap_nh_priv(b)->d; + + assert(ad != NULL); + assert(bd != NULL); + + if (ad->encap != bd->encap) + return false; + + if (ad->n_seglist != bd->n_seglist) + return false; + + for (i = 0; i < ad->n_seglist; i++) { + if (memcmp(&ad->seglist[i], &bd->seglist[i], sizeof(struct rte_ipv6_addr))) + return false; + } + + return true; +} static int srv6_encap_data_add( struct nexthop *nh, @@ -51,39 +77,24 @@ static struct api_out srv6_route_add(const void *request, void ** /*response*/) const struct gr_srv6_route_add_req *req = request; struct gr_nexthop base = { .type = GR_NH_T_SR6_OUTPUT, - .af = GR_AF_IP6, .state = GR_NH_S_REACHABLE, .flags = GR_NH_F_GATEWAY | GR_NH_F_STATIC, .vrf_id = req->r.key.vrf_id, .iface_id = GR_IFACE_ID_UNDEF, - .origin = GR_NH_ORIGIN_LINK, + .origin = req->origin, }; struct nexthop *nh; int ret; // retrieve or create nexthop into rib4/rib6 if (req->r.key.is_dest6) { - nh = rib6_lookup_exact( - req->r.key.vrf_id, - GR_IFACE_ID_UNDEF, - &req->r.key.dest6.ip, - req->r.key.dest6.prefixlen - ); - - if (nh && srv6_encap_nh_priv(nh)->d != NULL) - return api_out(EEXIST, 0); - base.ipv6 = req->r.key.dest6.ip; base.prefixlen = req->r.key.dest6.prefixlen; + base.af = GR_AF_IP6; } else { - nh = rib4_lookup_exact( - req->r.key.vrf_id, req->r.key.dest4.ip, req->r.key.dest4.prefixlen - ); - if (nh && srv6_encap_nh_priv(nh)->d != NULL) - return api_out(EEXIST, 0); - base.ipv4 = req->r.key.dest4.ip; base.prefixlen = req->r.key.dest4.prefixlen; + base.af = GR_AF_IP4; } nh = nexthop_new(&base); @@ -102,7 +113,7 @@ static struct api_out srv6_route_add(const void *request, void ** /*response*/) GR_IFACE_ID_UNDEF, &req->r.key.dest6.ip, req->r.key.dest6.prefixlen, - GR_NH_ORIGIN_LINK, + req->origin, nh ); else @@ -110,47 +121,41 @@ static struct api_out srv6_route_add(const void *request, void ** /*response*/) req->r.key.vrf_id, req->r.key.dest4.ip, req->r.key.dest4.prefixlen, - GR_NH_ORIGIN_LINK, + req->origin, nh ); - if (ret < 0) - return api_out(-ret, 0); + if (ret == -EEXIST && req->exist_ok) + ret = 0; - return api_out(0, 0); + return api_out(-ret, 0); } static struct api_out srv6_route_del(const void *request, void ** /*response*/) { const struct gr_srv6_route_del_req *req = request; - struct nexthop *nh; + int ret; if (req->key.is_dest6) - nh = rib6_lookup_exact( + ret = rib6_delete( req->key.vrf_id, GR_IFACE_ID_UNDEF, &req->key.dest6.ip, - req->key.dest6.prefixlen - ); - else - nh = rib4_lookup_exact( - req->key.vrf_id, req->key.dest4.ip, req->key.dest4.prefixlen + req->key.dest6.prefixlen, + GR_NH_T_SR6_OUTPUT ); - if (nh == NULL || nh->type != GR_NH_T_SR6_OUTPUT) - return api_out(ENOENT, 0); - - if (req->key.is_dest6) - rib6_delete( + else + ret = rib4_delete( req->key.vrf_id, - GR_IFACE_ID_UNDEF, - &req->key.dest6.ip, - req->key.dest6.prefixlen + req->key.dest4.ip, + req->key.dest4.prefixlen, + GR_NH_T_SR6_OUTPUT ); - else - rib4_delete(req->key.vrf_id, req->key.dest4.ip, req->key.dest4.prefixlen); + if (ret == -ENOENT && req->missing_ok) + ret = 0; - return api_out(0, 0); + return api_out(-ret, 0); } struct list_context { @@ -247,6 +252,7 @@ static struct gr_api_handler srv6_route_list_handler = { static struct nexthop_type_ops nh_ops = { .free = srv6_encap_data_del, + .equal = srv6_encap_data_equal, }; RTE_INIT(srv6_constructor) { diff --git a/smoke/_init_frr.sh b/smoke/_init_frr.sh index 0d432945a..9e42d20d3 100644 --- a/smoke/_init_frr.sh +++ b/smoke/_init_frr.sh @@ -95,3 +95,117 @@ EOF count=$((count + 1)) done } + +# set_srv6_localsid [behavior] +# +# Example: +# set_srv6_localsid myloc fd00:202 fc00:100:64:10::666 end.dt4 +# +set_srv6_localsid() { + local locator="$1" + local sid_prefix="$2" + local sid_local="$3" + local grout_behavior="${4:-end.dt4}" # default behaviour + local max_tries=5 + 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.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 ;; + esac + + # --- push the config into FRR ------------------------------------------ + vtysh <<-EOF + configure terminal + segment-routing + srv6 + locators + locator ${locator} + prefix ${sid_prefix}::/32 block-len 16 node-len 16 func-bits 0 + exit + exit + static-sids + sid ${sid_local}/128 locator ${locator} behavior ${frr_behavior} vrf default + exit + exit +EOF + + # --- wait until grout has the localsid --------------------------------- + # Expected "grcli show sr localsid" output pattern: + # vrf lsid behavior args + # 0 fc00:100:64:10::666 end.dt4 out_vrf=0 + local grep_pattern="^[[:space:]]*0[[:space:]]+${sid_local}[[:space:]]+${grout_behavior}" + + while ! grcli show sr localsid | grep -qE "${grep_pattern}"; do + if [ "$count" -ge "$max_tries" ]; then + grcli show sr localsid + echo "SRv6 localsid ${sid_local} (${grout_behavior}) not found after ${max_tries} attempts." + exit 1 + fi + sleep 1 + count=$((count + 1)) + done +} + +# set_srv6_route [sid2 sid3 …] +# +# EXAMPLE +# # Three SIDs provided as separate arguments +# set_srv6_route 192.168.0.0/16 gmydsn1 \ +# fd00:202::2 fd00:202::3 fd00:202::4 +# +set_srv6_route() { + local prefix="$1" + local nhop="$2" + local max_tries=5 + local count=0 + shift 2 # all remaining words are SIDs (and maybe max_tries) + + # ----- collect SIDs ---------------------------------------------------- + local sids=() + while [[ "$1" =~ : ]]; do # anything with a ":" is assumed to be a SID + sids+=("$1") + shift + done + [[ ${#sids[@]} -eq 0 ]] && { echo "set_srv6_route: need at least one SID" >&2; return 1; } + + # ----- choose FRR keyword --------------------------------------------- + local frr_ip gr_ip + if [[ "$prefix" == *:* ]]; then + frr_ip="ipv6"; gr_ip="ip6" + else + frr_ip="ip"; gr_ip="ip" + fi + + # ----- build CLI & Grout forms ---------------------------------------- + local seg_frr ; IFS=/ ; seg_frr="${sids[*]}" # SID/SID/… + local seg_space ; IFS=' ' ; seg_space="${sids[*]}" # SID SID … + + # ----- push route into FRR -------------------------------------------- + vtysh <<-EOF + configure terminal + ${frr_ip} route ${prefix} ${nhop} segments ${seg_frr} + exit +EOF + + # ----- make BRE pattern for Grout ------------------------------------- + local sid_regex="${sids[0]}" + for ((i=1; i<${#sids[@]}; i++)); do + sid_regex+="[[:space:]]\\+${sids[i]}" + done + local grep_pattern="^[[:space:]]*0[[:space:]]\\+${prefix}[[:space:]]\\+h\\.encap[[:space:]]\\+${sid_regex}" + + # ----- wait until Grout shows it -------------------------------------- + while ! grcli show sr route | grep -q "${grep_pattern}"; do + if (( count++ >= max_tries )); then + echo "SRv6 route ${prefix} via ${seg_space} not visible in Grout after ${max_tries}s." >&2 + exit 1 + fi + sleep 1 + done +} diff --git a/smoke/srv6_frr_test.sh b/smoke/srv6_frr_test.sh new file mode 100755 index 000000000..ef2fae0b5 --- /dev/null +++ b/smoke/srv6_frr_test.sh @@ -0,0 +1,69 @@ +#!/bin/bash +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2025 Maxime Leroy, Free Mobile + +. $(dirname $0)/_init_frr.sh + +p0=${run_id}0 +p1=${run_id}1 + +# setup ports and connected +create_interface $p0 d2:f0:0c:ba:a5:10 +create_interface $p1 d2:f0:0c:ba:a5:11 + +for n in 0 1; do + p=$run_id$n + netns_add n-$p + ip link set v1-$p netns n-$p + ip -n n-$p link set v1-$p address d2:ad:ca:fe:b4:10 + ip -n n-$p link set lo up + ip -n n-$p link set v1-$p up +done +ip -n n-$p0 addr add 192.168.61.2/24 dev v1-$p0 +ip -n n-$p1 addr add fd00:102::2/64 dev v1-$p1 + +set_ip_address $p0 192.168.61.1/24 +set_ip_address $p1 fd00:102::1/64 + +sleep 3 + +# +# network layout: +# (client) p0(netns) <--> p0 p1 <---> p1(netns) (public: 192.168.60.1/24 on p0) +# ipv4 ---------------| srv6 |-- ipv4 +# +# test case: +# - (1) send ipv4 ping from p0 +# - (2) grout encap in srv6, send to sid fd00:202::2 +# - (3) linux p1 decap it +# - (4) reply to ping +# - (5) linux p1 reencap in srv6, send to grout sid fd00:202::100, +# - (6) grout decap it, reply back in ipv4 to p0 +# + +# only linux's p1 will see srv6 +ip netns exec n-$p1 sysctl -w net.ipv6.conf.v1-$p1.seg6_enabled=1 +ip netns exec n-$p1 sysctl -w net.ipv6.conf.v1-$p1.forwarding=1 + +# (1) send ipv4 to grout +ip -n n-$p0 route add default via 192.168.61.1 dev v1-$p0 + +# (2) +set_srv6_route 192.168.0.0/16 $p1 fd00:202::2 +set_ip_route fd00:202::/64 fd00:102::2 + +# (3) +ip -n n-$p1 -6 route add fd00:202::2 encap seg6local action End.DX4 nh4 192.168.60.1 count dev v1-$p1 + +# (4) 192.168.60.0/24 is our 'public' network +ip -n n-$p1 addr add 192.168.60.1/24 dev v1-$p1 + +# (5) +ip -n n-$p1 route add 192.168.61.0/24 encap seg6 mode encap segs fd00:202::100 dev v1-$p1 +ip -n n-$p1 -6 route add fd00:202::/64 via fd00:102::1 dev v1-$p1 + +# (6) +set_srv6_localsid locator_grout fd00:202 fd00:202::100 + +# test +ip netns exec n-$p0 ping -c 3 192.168.60.1