diff --git a/dockers/docker-fpm-frr/frr/supervisord/supervisord.conf.common.j2 b/dockers/docker-fpm-frr/frr/supervisord/supervisord.conf.common.j2 index 09512adec43..124d354b965 100644 --- a/dockers/docker-fpm-frr/frr/supervisord/supervisord.conf.common.j2 +++ b/dockers/docker-fpm-frr/frr/supervisord/supervisord.conf.common.j2 @@ -24,7 +24,11 @@ dependent_startup_wait_for={{ _wait }} {% endif %} [program:zebra] +{% if DEVICE_METADATA is defined and DEVICE_METADATA.localhost.nhg_fib is defined and DEVICE_METADATA.localhost.nhg_fib == 'enabled' %} +command=/usr/lib/frr/zebra -A 127.0.0.1 -s 90000000 -M dplane_fpm_sonic{{ ' -M snmp' if _snmp else '' }} --asic-offload=notify_on_offload --nhg-fib +{% else %} command=/usr/lib/frr/zebra -A 127.0.0.1 -s 90000000 -M dplane_fpm_sonic{{ ' -M snmp' if _snmp else '' }} --asic-offload=notify_on_offload +{% endif %} priority={{ _pbase }} autostart=false autorestart=false diff --git a/src/sonic-frr/dplane_fpm_sonic/dplane_fpm_sonic.c b/src/sonic-frr/dplane_fpm_sonic/dplane_fpm_sonic.c index c1c0e88aeb6..f60a9a2b20e 100644 --- a/src/sonic-frr/dplane_fpm_sonic/dplane_fpm_sonic.c +++ b/src/sonic-frr/dplane_fpm_sonic/dplane_fpm_sonic.c @@ -59,8 +59,20 @@ #include "fpm/fpm.h" #include "lib/srv6.h" #include "lib/vrf.h" +#include #include +/* Global flag set by zebra --nhg-fib command-line option. */ +extern bool zebra_nhg_fib_enabled; + +/* FIB log level constants, aligned with fib::LogLevel in nexthopgroup_debug.h */ +enum fib_log_level { + FIB_LOG_LEVEL_DEBUG = 0, + FIB_LOG_LEVEL_INFO = 1, + FIB_LOG_LEVEL_WARN = 2, + FIB_LOG_LEVEL_ERROR = 3, +}; + #define SOUTHBOUND_DEFAULT_ADDR INADDR_LOOPBACK #define SOUTHBOUND_DEFAULT_PORT 2620 #define SEG6_SEGMENT_NAME_LEN 64 @@ -103,6 +115,8 @@ enum custom_nlmsg_types { RTM_DELSRV6VPNROUTE = 3001, RTM_NEWSIDLIST = 4000, RTM_DELSIDLIST = 4001, + RTM_NEWNHGFIB = 5000, + RTM_DELNHGFIB = 5001, }; /* Custom Netlink attribute types */ @@ -130,7 +144,7 @@ enum custom_rtattr_encap_srv6 { FPM_ROUTE_ENCAP_SRV6_ENCAP_UNSPEC = 0, FPM_ROUTE_ENCAP_SRV6_VPN_SID = 1, FPM_ROUTE_ENCAP_SRV6_ENCAP_SRC_ADDR = 2, - FPM_ROUTE_ENCAP_SRV6_PIC_ID = 3, + FPM_ROUTE_ENCAP_SRV6_NH_RECEIVED_ID = 3, FPM_ROUTE_ENCAP_SRV6_NH_ID = 4, FPM_ROUTE_ENCAP_SRV6_ENCAP_SIDLIST_NAME = 5, FPM_ROUTE_ENCAP_SRV6_ENCAP_SIDLIST_LEN = 6, @@ -170,6 +184,10 @@ enum custom_rtattr_srv6_localsid_action { FPM_SRV6_LOCALSID_ACTION_UDT46 = 21, }; +enum custoem_rtattr_nexthop_group { + FPM_NHA_JSON_STR = 2, +}; + static const char *prov_name = "dplane_fpm_sonic"; static atomic_bool fpm_cleaning_up; @@ -181,6 +199,8 @@ struct fpm_nl_ctx { bool connecting; bool use_nhg; bool use_route_replace; + bool use_nhg_fib; + enum fib_log_level fib_log_level; struct sockaddr_storage addr; /* data plane buffers. */ @@ -422,6 +442,50 @@ DEFUN(no_fpm_use_nhg, no_fpm_use_nhg_cmd, return CMD_SUCCESS; } +DEFUN(fpm_set_fib_log_level, fpm_set_fib_log_level_cmd, + "fpm fib-log-level ", + FPM_STR + "Set FIB library log level\n" + "Debug level (most verbose)\n" + "Info level\n" + "Warn level\n" + "Error level (least verbose)\n") +{ + enum fib_log_level level; + const char *level_str = argv[2]->text; + + if (strcmp(level_str, "debug") == 0) + level = FIB_LOG_LEVEL_DEBUG; + else if (strcmp(level_str, "info") == 0) + level = FIB_LOG_LEVEL_INFO; + else if (strcmp(level_str, "warn") == 0) + level = FIB_LOG_LEVEL_WARN; + else if (strcmp(level_str, "error") == 0) + level = FIB_LOG_LEVEL_ERROR; + else { + vty_out(vty, "%% Invalid log level: %s\n", level_str); + return CMD_WARNING; + } + + gfnc->fib_log_level = level; + fib_frr_set_log_level((int)level); + zlog_info("%s: FIB log level set to %s (%d)", __func__, level_str, (int)level); + return CMD_SUCCESS; +} + +DEFUN(no_fpm_set_fib_log_level, no_fpm_set_fib_log_level_cmd, + "no fpm fib-log-level []", + NO_STR + FPM_STR + "Set FIB library log level\n" + "Log level value\n") +{ + gfnc->fib_log_level = FIB_LOG_LEVEL_INFO; /* restore to default: INFO */ + fib_frr_set_log_level(gfnc->fib_log_level); + zlog_info("%s: FIB log level reset to default (INFO)", __func__); + return CMD_SUCCESS; +} + DEFUN(fpm_reset_counters, fpm_reset_counters_cmd, "clear fpm counters", CLEAR_STR @@ -580,6 +644,29 @@ DEFUN(fpm_show_counters_json, fpm_show_counters_json_cmd, return CMD_SUCCESS; } +static const char *fib_log_level_str(enum fib_log_level level) +{ + switch (level) { + case FIB_LOG_LEVEL_DEBUG: return "debug"; + case FIB_LOG_LEVEL_INFO: return "info"; + case FIB_LOG_LEVEL_WARN: return "warn"; + case FIB_LOG_LEVEL_ERROR: return "error"; + default: return "unknown"; + } +} + +DEFUN(fpm_show_fib_log_level, fpm_show_fib_log_level_cmd, + "show fpm fib-log-level", + SHOW_STR + FPM_STR + "Show current FIB library log level\n") +{ + vty_out(vty, "FIB log level: %d (%s)\n", + gfnc->fib_log_level, + fib_log_level_str(gfnc->fib_log_level)); + return CMD_SUCCESS; +} + static int fpm_write_config(struct vty *vty) { struct sockaddr_in *sin; @@ -623,6 +710,12 @@ static int fpm_write_config(struct vty *vty) written = 1; } + if (gfnc->fib_log_level != FIB_LOG_LEVEL_INFO) { + vty_out(vty, "fpm fib-log-level %s\n", + fib_log_level_str(gfnc->fib_log_level)); + written = 1; + } + return written; } @@ -1106,6 +1199,192 @@ static bool has_srv6_localsid_nexthop(struct zebra_dplane_ctx *ctx) return false; } +/** + * Construct C_NextHopGroupFull Object for nexthop group + * with multiple nexthops based on zebra information. + * + * @param c_nhg pointer of the object we're going to construct + * @param ctx pointer of zebra_dplane_ctx, we get zebra information from it + */ +static bool build_c_nexthopgroupfull_multi(struct C_NextHopGroupFull *c_nhg, + const struct zebra_dplane_ctx *ctx) +{ + memset(c_nhg, 0, sizeof(struct C_NextHopGroupFull)); + const struct nexthop_group *nhg; + + /* set id */ + c_nhg->id = dplane_ctx_get_nhe_id(ctx); + + /* set hash value */ + nhg = dplane_ctx_get_nhe_ng(ctx); + uint32_t key = nexthop_group_hash_no_recurse(nhg); + c_nhg->key = key; + + /* set nhg_flags */ + c_nhg->nhg_flags = dplane_ctx_get_nhe_nhg_flags(ctx); + bool is_recurisve = false; + /* + * For recursive NH, we keep the nexthop information for convergence handling + */ + if (CHECK_FLAG(c_nhg->nhg_flags, NEXTHOP_GROUP_RECURSIVE)) { + const struct nexthop *nh = nhg->nexthop; + memcpy(&c_nhg->gate, &nh->gate, sizeof(union g_addr)); + + /* set nexthop type */ + c_nhg->type = nh->type; + + is_recurisve = true; + } + + /* set nh_grp_full_list */ + const struct nh_grp_full *nh_grp_full_list = dplane_ctx_get_nhe_nh_grp_full(ctx); + for (uint32_t i = 0; i < dplane_ctx_get_nhe_nh_grp_full_count(ctx); i++) { + c_nhg->nh_grp_full_list[i].id = nh_grp_full_list[i].id; + c_nhg->nh_grp_full_list[i].weight = nh_grp_full_list[i].weight; + c_nhg->nh_grp_full_list[i].num_direct = nh_grp_full_list[i].num_direct; + } + + /* set depends list */ + const uint32_t *depends = dplane_ctx_get_nhe_depends(ctx); + for (uint32_t i = 0; i < dplane_ctx_get_nhe_depends_count(ctx); i++) { + c_nhg->depends[i] = depends[i]; + } + + /* set dependents list */ + const uint32_t *dependents = dplane_ctx_get_nhe_dependents(ctx); + for (uint32_t i = 0; i < dplane_ctx_get_nhe_dependents_count(ctx); i++) { + c_nhg->dependents[i] = dependents[i]; + } + + return is_recurisve; +} + +/** + * Construct C_NextHopGroupFull Object for nexthop group + * with singleton based on zebra information. + * + * @param c_nhg pointer of the object we're going to construct + * @param ctx pointer of zebra_dplane_ctx, we get zebra information from it + */ +static void build_c_nexthopgroupfull_singleton(struct C_NextHopGroupFull *c_nhg, + const struct zebra_dplane_ctx *ctx) +{ + memset(c_nhg, 0, sizeof(struct C_NextHopGroupFull)); + + /* set id */ + c_nhg->id = dplane_ctx_get_nhe_id(ctx); + + /* set hash value */ + const struct nexthop_group *nhg = dplane_ctx_get_nhe_ng(ctx); + uint32_t key = nexthop_group_hash_no_recurse(nhg); + c_nhg->key = key; + + /* set nhg_flags */ + c_nhg->nhg_flags = dplane_ctx_get_nhe_nhg_flags(ctx); + + const struct nexthop *nh = nhg->nexthop; + /* set nexthop type */ + c_nhg->type = nh->type; + + /* set nexthop vrf_id */ + c_nhg->vrf_id = nh->vrf_id; + + /* set nexthop interface index */ + c_nhg->ifindex = nh->ifindex; + + /* set nexthop label type, if any */ + c_nhg->nh_label_type = nh->nh_label_type; + + /* set nexthop bh_type or gateway address based on type */ + if (c_nhg->type == NEXTHOP_TYPE_BLACKHOLE) { + c_nhg->bh_type = nh->bh_type; + } else { + memcpy(&c_nhg->gate, &nh->gate, sizeof(union g_addr)); + } + + /* set nexthop src */ + memcpy(&c_nhg->src, &nh->src, sizeof(union g_addr)); + + /* set nexthop rmap src */ + memcpy(&c_nhg->rmap_src, &nh->rmap_src, sizeof(union g_addr)); + + /* set nexthop weight */ + c_nhg->weight = nh->weight; + + /* set nexthop flags */ + c_nhg->flags = nh->flags; + + /* set depends list */ + const uint32_t *depends = dplane_ctx_get_nhe_depends(ctx); + for (uint32_t i = 0; i < dplane_ctx_get_nhe_depends_count(ctx); i++) { + c_nhg->depends[i] = depends[i]; + } + + /* set dependents list */ + const uint32_t *dependents = dplane_ctx_get_nhe_dependents(ctx); + for (uint32_t i = 0; i < dplane_ctx_get_nhe_dependents_count(ctx); i++) { + c_nhg->dependents[i] = dependents[i]; + } + + /* set nexthop srv6 information if present */ + if (nh->nh_srv6 != NULL) { + /* Get the default SRv6 context which contains seg6's src IP */ + struct zebra_srv6 *srv6 = zebra_srv6_get_default(); + + c_nhg->nh_srv6 = malloc(sizeof(struct C_nexthop_srv6)); + if (c_nhg->nh_srv6) { + /* field copy from nexthop_srv6 to C_nexthop_srv6 */ + /* seg6local_action */ + c_nhg->nh_srv6->seg6local_action = + (enum C_seg6local_action_t)nh->nh_srv6->seg6local_action; + + /* seg6local_ctx */ + memcpy(&c_nhg->nh_srv6->seg6local_ctx, &nh->nh_srv6->seg6local_ctx, + sizeof(struct C_seg6local_context)); + + /* seg6_src */ + c_nhg->nh_srv6->seg6_src = srv6->encap_src_addr; + + /* seg6_segs */ + c_nhg->nh_srv6->seg6_segs = NULL; // clear the pointer to avoid pointing to old address + /* set nexthop_srv6 seg6_segs if present */ + if (nh->nh_srv6->seg6_segs != NULL) { + size_t total_size = sizeof(struct C_seg6_seg_stack) + + nh->nh_srv6->seg6_segs->num_segs * sizeof(struct in6_addr); + c_nhg->nh_srv6->seg6_segs = malloc(total_size); + if (c_nhg->nh_srv6->seg6_segs) { + memcpy(c_nhg->nh_srv6->seg6_segs, nh->nh_srv6->seg6_segs, total_size); + } + } + } + } +} + +/** + * Free C_NextHopGroupFull Object. + * + * \param[in] c_nhg pointer of C_NextHopGroupFull Object. + */ +static void free_c_nexthopgroupfull(struct C_NextHopGroupFull *c_nhg) +{ + if (!c_nhg) { + return; + } + + /* Free srv6 related memory if present */ + if (c_nhg->nh_srv6) { + /* Free seg6_segs if present */ + if (c_nhg->nh_srv6->seg6_segs) { + free(c_nhg->nh_srv6->seg6_segs); + c_nhg->nh_srv6->seg6_segs = NULL; + } + + /* Free nh_srv6 itself */ + free(c_nhg->nh_srv6); + c_nhg->nh_srv6 = NULL; + } +} + /** * Resets the SRv6 routes FPM flags so we send all SRv6 routes again. */ @@ -1437,8 +1716,8 @@ static ssize_t netlink_vpn_route_msg_encode(int cmd, struct nlsock *nl; int bytelen; vrf_id_t vrf_id; - uint32_t pic_id = dplane_ctx_get_nhe_id(ctx); - uint32_t nhg_id = dplane_ctx_get_pic_nhe_id(ctx); + uint32_t nhg_id = dplane_ctx_get_nhe_id(ctx); + uint32_t nhg_received_id = dplane_ctx_get_nhe_received_id(ctx); uint32_t table_id; struct { @@ -1517,7 +1796,7 @@ static ssize_t netlink_vpn_route_msg_encode(int cmd, if (!nest) return false; - if (!nl_attr_put32(&req->n, datalen, FPM_ROUTE_ENCAP_SRV6_PIC_ID, pic_id)){ + if (!nl_attr_put32(&req->n, datalen, FPM_ROUTE_ENCAP_SRV6_NH_RECEIVED_ID, nhg_received_id)){ return 0; } @@ -2394,6 +2673,175 @@ static ssize_t netlink_pic_context_msg_encode(uint16_t cmd, return NLMSG_ALIGN(req->n.nlmsg_len); } +/** + * Next hop packet (full message) encoding helper function. + * This function is modified from function netlink_nexthop_msg_encode to + * encode the nexthopgroupfull JSON string to fpmsyncd. + * + * \param[in] cmd netlink command. + * \param[in] ctx dataplane context (information snapshot). + * \param[out] buf buffer to hold the packet. + * \param[in] buflen amount of buffer bytes. + * \param[in] fpm whether the message is for fpmsyncd. + * + * \returns -1 on failure, 0 when the msg doesn't fit entirely in the buffer + * otherwise the number of bytes written to buf. + */ +static ssize_t netlink_nexthopgroupfull_msg_encode(uint16_t cmd, + const struct zebra_dplane_ctx *ctx, + void *buf, size_t buflen, bool fpm) +{ + struct { + struct nlmsghdr n; + struct nhmsg nhm; + char buf[]; + } *req = buf; + + uint32_t id = dplane_ctx_get_nhe_id(ctx); + int type = dplane_ctx_get_nhe_type(ctx); + struct nlsock *nl = + kernel_netlink_nlsock_lookup(dplane_ctx_get_ns_sock(ctx)); + char* json_str = NULL; + ssize_t ret = -1; + + if (!id) { + zlog_err( + "%s: Failed trying to update a nexthop group in the kernel that does not have an ID", + __func__); + return -1; + } + + /* + * Nothing to do if the kernel doesn't support nexthop objects or + * we dont want to install this type of NHG, but FPM may possible to + * handle this. + */ + if (!fpm && !kernel_nexthops_supported()) { + if (IS_ZEBRA_DEBUG_KERNEL || IS_ZEBRA_DEBUG_NHG) + zlog_debug( + "%s: nhg_id %u (%s): kernel nexthops not supported, ignoring", + __func__, id, zebra_route_string(type)); + return 0; + } + + if (buflen < sizeof(*req)) + return 0; + + memset(req, 0, sizeof(*req)); + + req->n.nlmsg_len = NLMSG_LENGTH(sizeof(struct nhmsg)); + req->n.nlmsg_flags = NLM_F_CREATE | NLM_F_REQUEST; + + if (cmd == RTM_NEWNHGFIB) + req->n.nlmsg_flags |= NLM_F_REPLACE; + + req->n.nlmsg_type = cmd; + req->n.nlmsg_pid = nl->snl.nl_pid; + + req->nhm.nh_family = AF_UNSPEC; + + /* Put the nhe ID */ + if (!nl_attr_put32(&req->n, buflen, NHA_ID, id)) + return 0; + + if (cmd == RTM_NEWNHGFIB) { + /* Build C_NextHopGroupFull Object */ + struct C_NextHopGroupFull c_nhg; + const struct C_NextHopGroupFull *c_nhg_ptr = &c_nhg; + + memset(&c_nhg, 0, sizeof(c_nhg)); + + /* Defensive bounds check on array count */ + if (dplane_ctx_get_nhe_nh_grp_full_count(ctx) > + (MULTIPATH_NUM * MAX_NHG_RECURSION) + 1 || + dplane_ctx_get_nhe_depends_count(ctx) > MULTIPATH_NUM + 1 || + dplane_ctx_get_nhe_dependents_count(ctx) > MULTIPATH_NUM + 1) { + zlog_err( + "%s: C_NextHopGroupFull array count exceeds bounds: nh_grp_full_count=%u, depends_count=%u, dependents_count=%u", + __func__, dplane_ctx_get_nhe_nh_grp_full_count(ctx), + dplane_ctx_get_nhe_depends_count(ctx), + dplane_ctx_get_nhe_dependents_count(ctx)); + free_c_nexthopgroupfull(&c_nhg); + return -1; + } + + /* + * We also distinguish between a "group" and a singleton similar + * as what is done in netlink_nexthop_msg_encode. + * For each case, we create C_NextHopGroupFull Object, + * then convert it to C++ NextHopGroupFull Object and return its JSON string. + * multi case would be the following cases + * 1. ctx has multiple paths + * 2. single recursive path + */ + if (dplane_ctx_get_nhe_nh_grp_full_count(ctx) || + CHECK_FLAG(dplane_ctx_get_nhe_nhg_flags(ctx), NEXTHOP_GROUP_RECURSIVE)) { + /* multi nexthops case */ + bool is_recursive = build_c_nexthopgroupfull_multi(&c_nhg, ctx); + json_str = nexthopgroupfull_json_from_c_nhg_multi(c_nhg_ptr, dplane_ctx_get_nhe_nh_grp_full_count(ctx), + dplane_ctx_get_nhe_depends_count(ctx), dplane_ctx_get_nhe_dependents_count(ctx), + is_recursive); + + if (!json_str) { + zlog_err( + "%s:Failed to convert C_NextHopGroupFull to JSON string in multi-nexthop case", + __func__); + free_c_nexthopgroupfull(&c_nhg); + return -1; + } + } else { + /* singleton case */ + afi_t afi = dplane_ctx_get_nhe_afi(ctx); + if (afi == AFI_IP) + req->nhm.nh_family = AF_INET; + else if (afi == AFI_IP6) + req->nhm.nh_family = AF_INET6; + + build_c_nexthopgroupfull_singleton(&c_nhg, ctx); + json_str = nexthopgroupfull_json_from_c_nhg_singleton(c_nhg_ptr, dplane_ctx_get_nhe_depends_count(ctx), + dplane_ctx_get_nhe_dependents_count(ctx)); + + if (!json_str) { + zlog_err( + "%s: Failed to convert C_NextHopGroupFull to JSON string in singleton case", + __func__); + free_c_nexthopgroupfull(&c_nhg); + return -1; + } + } + + /* Encode JSON string as attribute in message */ + if (!nl_attr_put(&req->n, buflen, FPM_NHA_JSON_STR, + json_str, strlen(json_str) + 1)) { + zlog_err( + "%s: Failed to put nexthop group JSON into netlink message", + __func__); + goto cleanup; + } + + ret = NLMSG_ALIGN(req->n.nlmsg_len); + +cleanup: + free(json_str); + free_c_nexthopgroupfull(&c_nhg); + + } else if (cmd == RTM_DELNHGFIB) { + /* Delete only needs NHA_ID, already encoded above */ + ret = NLMSG_ALIGN(req->n.nlmsg_len); + } else { + zlog_err( + "%s: Nexthop group kernel update command (%d) does not exist", + __func__, cmd); + return -1; + } + + if (IS_ZEBRA_DEBUG_FPM) + zlog_debug("%s: %s, id=%u", __func__, + (cmd == RTM_NEWNHGFIB) ? "RTM_NEWNHGFIB" : "RTM_DELNHGFIB", id); + + return ret; +} + static ssize_t netlink_sidlist_msg_encode(int cmd, struct zebra_dplane_ctx *ctx, uint8_t *data, size_t datalen) @@ -2755,26 +3203,48 @@ static int fpm_nl_enqueue(struct fpm_nl_ctx *fnc, struct zebra_dplane_ctx *ctx) break; case DPLANE_OP_NH_DELETE: - rv = netlink_nexthop_msg_encode(RTM_DELNEXTHOP, ctx, nl_buf, - sizeof(nl_buf), true); - if (rv <= 0) { - zlog_err("%s: netlink_nexthop_msg_encode failed", - __func__); - dplane_ctx_set_status(ctx, ZEBRA_DPLANE_REQUEST_FAILURE); - return 0; + if (fnc->use_nhg_fib) { + rv = netlink_nexthopgroupfull_msg_encode(RTM_DELNHGFIB, ctx, nl_buf, + sizeof(nl_buf), true); + if (rv <= 0) { + zlog_err("%s: netlink_nexthopgroupfull_msg_encode failed", + __func__); + dplane_ctx_set_status(ctx, ZEBRA_DPLANE_REQUEST_FAILURE); + return 0; + } + } else { + rv = netlink_nexthop_msg_encode(RTM_DELNEXTHOP, ctx, nl_buf, + sizeof(nl_buf), true); + if (rv <= 0) { + zlog_err("%s: netlink_nexthop_msg_encode failed", + __func__); + dplane_ctx_set_status(ctx, ZEBRA_DPLANE_REQUEST_FAILURE); + return 0; + } } nl_buf_len = (size_t)rv; break; case DPLANE_OP_NH_INSTALL: case DPLANE_OP_NH_UPDATE: - rv = netlink_nexthop_msg_encode(RTM_NEWNEXTHOP, ctx, nl_buf, - sizeof(nl_buf), true); - if (rv <= 0) { - zlog_err("%s: netlink_nexthop_msg_encode failed", - __func__); - dplane_ctx_set_status(ctx, ZEBRA_DPLANE_REQUEST_FAILURE); - return 0; + if (fnc->use_nhg_fib) { + rv = netlink_nexthopgroupfull_msg_encode(RTM_NEWNHGFIB, ctx, nl_buf, + sizeof(nl_buf), true); + if (rv <= 0) { + zlog_err("%s: netlink_nexthopgroupfull_msg_encode failed", + __func__); + dplane_ctx_set_status(ctx, ZEBRA_DPLANE_REQUEST_FAILURE); + return 0; + } + } else { + rv = netlink_nexthop_msg_encode(RTM_NEWNEXTHOP, ctx, nl_buf, + sizeof(nl_buf), true); + if (rv <= 0) { + zlog_err("%s: netlink_nexthop_msg_encode failed", + __func__); + dplane_ctx_set_status(ctx, ZEBRA_DPLANE_REQUEST_FAILURE); + return 0; + } } nl_buf_len = (size_t)rv; @@ -3633,6 +4103,12 @@ static int fpm_nl_new(struct event_loop *tm) int rv; gfnc = calloc(1, sizeof(*gfnc)); + gfnc->fib_log_level = FIB_LOG_LEVEL_INFO; /* Default: INFO */ + gfnc->use_nhg_fib = zebra_nhg_fib_enabled; + if (gfnc->use_nhg_fib) + zlog_info("%s: NHG Full encoding enabled via --nhg-fib", __func__); + fib_frr_set_log_level(gfnc->fib_log_level); + zlog_info("%s: FIB log level initialized to %d (INFO)", __func__, gfnc->fib_log_level); rv = dplane_provider_register(prov_name, DPLANE_PRIO_POSTPROCESS, DPLANE_PROV_FLAG_THREADED, fpm_nl_start, fpm_nl_process, fpm_nl_finish, gfnc, @@ -3645,6 +4121,7 @@ static int fpm_nl_new(struct event_loop *tm) install_element(ENABLE_NODE, &fpm_show_status_cmd); install_element(ENABLE_NODE, &fpm_show_counters_cmd); install_element(ENABLE_NODE, &fpm_show_counters_json_cmd); + install_element(ENABLE_NODE, &fpm_show_fib_log_level_cmd); install_element(ENABLE_NODE, &fpm_reset_counters_cmd); install_element(CONFIG_NODE, &fpm_set_address_cmd); install_element(CONFIG_NODE, &no_fpm_set_address_cmd); @@ -3652,13 +4129,59 @@ static int fpm_nl_new(struct event_loop *tm) install_element(CONFIG_NODE, &no_fpm_use_nhg_cmd); install_element(CONFIG_NODE, &fpm_use_route_replace_cmd); install_element(CONFIG_NODE, &no_fpm_use_route_replace_cmd); + install_element(CONFIG_NODE, &fpm_set_fib_log_level_cmd); + install_element(CONFIG_NODE, &no_fpm_set_fib_log_level_cmd); return 0; } +/* + * FRR-compatible callback to forward logs from FIB to FRR's logging system. + */ +static void frr_log_forwarder(int level, + const char *file, + int line, + const char *func, + const char *fmt, + va_list args) +{ + int current_log_level = fib_frr_get_log_level(); + + /* + * We would skip logging message if + * level is below current log level and debug zebra fpm is not enabled + */ + if (level < current_log_level && !IS_ZEBRA_DEBUG_FPM) { + return; + } + + /* + * Direct stderr — FRR convention for FIB path debugging + * We can't use FRR's zlog here because no API for forwarding va_list, and + * we want to preserve the original log level and formatting as much as possible. + */ + fprintf(stderr, "[ZEBRA:FIB] %s:%d (%s) ", file, line, func); + va_list args_copy; + va_copy(args_copy, args); + vfprintf(stderr, fmt, args_copy); + va_end(args_copy); + fprintf(stderr, "\n"); + fflush(stderr); +} + +/* Called during FRR daemon initialization */ +static void fib_init_logging(void) { + /* Register callback BEFORE any fib_LOG() calls */ + fib_frr_register_callback(frr_log_forwarder); + + zlog_info("%s : FIB logging callback registered, log level will be applied after configuration load", + __func__); +} + static int fpm_nl_init(void) { hook_register(frr_late_init, fpm_nl_new); + fib_init_logging(); return 0; } diff --git a/src/sonic-frr/patch/0117-Add-Dplane-definitions-and-functions-for-ribfib.patch b/src/sonic-frr/patch/0117-Add-Dplane-definitions-and-functions-for-ribfib.patch new file mode 100644 index 00000000000..1846b435cf2 --- /dev/null +++ b/src/sonic-frr/patch/0117-Add-Dplane-definitions-and-functions-for-ribfib.patch @@ -0,0 +1,229 @@ +From 46c51df7756fedfdb4c24401df3bce4ec1eaf730 Mon Sep 17 00:00:00 2001 +From: Yuqing Zhao +Date: Fri, 22 May 2026 14:03:47 +0800 +Subject: [PATCH] 0113-Add-Dplane-definitions-and-functions-for-ribfib + +Signed-off-by: Yuqing Zhao +--- + zebra/main.c | 11 +++++++- + zebra/zebra_dplane.c | 67 ++++++++++++++++++++++++++++++++++++++++++++ + zebra/zebra_dplane.h | 8 ++++++ + zebra/zebra_nhg.h | 6 ++++ + 4 files changed, 91 insertions(+), 1 deletion(-) + +diff --git a/zebra/main.c b/zebra/main.c +index 67638afbb2..880ba6dfe4 100644 +--- a/zebra/main.c ++++ b/zebra/main.c +@@ -66,6 +66,9 @@ struct mgmt_be_client *mgmt_be_client; + /* Route retain mode flag. */ + int retain_mode = 0; + ++/* Enable NHG Full encoding via sonic-fib (set by --nhg-fib flag). */ ++bool zebra_nhg_fib_enabled; ++ + /* Receive buffer size for kernel control sockets */ + #define RCVBUFSIZE_MIN 4194304 + #ifdef HAVE_NETLINK +@@ -80,7 +83,8 @@ uint32_t rt_table_main_id = RT_TABLE_MAIN; + #define OPTION_ASIC_OFFLOAD 2001 + #define OPTION_V6_WITH_V4_NEXTHOP 2002 + #define OPTION_NEXTHOP_WEIGHT_16_BIT 2003 + #define OPTION_KERNEL_MAC_EXT_LEARN 2004 ++#define OPTION_NHG_FIB 2005 + + /* Command line options. */ + const struct option longopts[] = { +@@ -91,7 +95,8 @@ const struct option longopts[] = { + { "asic-offload", optional_argument, NULL, OPTION_ASIC_OFFLOAD }, + { "v6-with-v4-nexthops", no_argument, NULL, OPTION_V6_WITH_V4_NEXTHOP }, + { "nexthop-weight-16-bit", no_argument, NULL, OPTION_NEXTHOP_WEIGHT_16_BIT }, + { "kernel-mac-ext-learn", no_argument, NULL, OPTION_KERNEL_MAC_EXT_LEARN }, ++ { "nhg-fib", no_argument, NULL, OPTION_NHG_FIB }, + #ifdef HAVE_NETLINK + { "vrfwnetns", no_argument, NULL, 'n' }, + { "nl-bufsize", required_argument, NULL, 's' }, +@@ -388,7 +393,8 @@ int main(int argc, char **argv) + #else + " -s, Set kernel socket receive buffer size\n" + #endif /* HAVE_NETLINK */ +- " -R, --routing-table Set kernel routing table\n"); ++ " -R, --routing-table Set kernel routing table\n" ++ " --nhg-fib Enable NHG Full encoding via sonic-fib\n"); + + while (1) { + int opt = frr_getopt(argc, argv, NULL); +@@ -461,6 +467,9 @@ int main(int argc, char **argv) + case OPTION_V6_WITH_V4_NEXTHOP: + v6_with_v4_nexthop = true; + break; ++ case OPTION_NHG_FIB: ++ zebra_nhg_fib_enabled = true; ++ break; + #endif /* HAVE_NETLINK */ + case OPTION_NEXTHOP_WEIGHT_16_BIT: + nexthop_weight_16_bit = true; +diff --git a/zebra/zebra_dplane.c b/zebra/zebra_dplane.c +index 06d5d04d7f..3dd22bcea5 100644 +--- a/zebra/zebra_dplane.c ++++ b/zebra/zebra_dplane.c +@@ -76,6 +76,14 @@ const uint32_t DPLANE_DEFAULT_NEW_WORK = 100; + + #endif /* DPLANE_DEBUG */ + ++/* ++ * Maximum size of nh_grp_full array. We use 2x MULTIPATH_NUM because ++ * in practice it is unlikely to have more than two levels of fully ++ * expanded ECMP recursion. This may be increased if deeper recursion ++ * is needed in the future. ++ */ ++#define NH_GRP_FULL_NUM (MULTIPATH_NUM * 2) ++ + /* + * Nexthop information captured for nexthop/nexthop group updates + */ +@@ -85,10 +93,20 @@ struct dplane_nexthop_info { + afi_t afi; + vrf_id_t vrf_id; + int type; ++ uint32_t nhg_flags; + + struct nexthop_group ng; + struct nh_grp nh_grp[MULTIPATH_NUM]; + uint16_t nh_grp_count; ++ ++ struct nh_grp_full nh_grp_full[NH_GRP_FULL_NUM + 1]; ++ uint32_t nh_grp_full_count; ++ ++ uint32_t depends[MULTIPATH_NUM + 1]; ++ uint32_t depends_count; ++ ++ uint32_t dependents[MULTIPATH_NUM + 1]; ++ uint32_t dependents_count; + }; + + /* +@@ -136,6 +154,7 @@ struct dplane_route_info { + + /* Nexthop hash entry info */ + struct dplane_nexthop_info nhe; ++ struct dplane_nexthop_info nhe_received; + + /* Nexthops */ + uint32_t zd_nhg_id; +@@ -2330,6 +2349,12 @@ uint32_t dplane_ctx_get_old_nhe_id(const struct zebra_dplane_ctx *ctx) + return ctx->u.rinfo.nhe.old_id; + } + ++uint32_t dplane_ctx_get_nhe_received_id(const struct zebra_dplane_ctx *ctx) ++{ ++ DPLANE_CTX_VALID(ctx); ++ return ctx->u.rinfo.nhe_received.id; ++} ++ + afi_t dplane_ctx_get_nhe_afi(const struct zebra_dplane_ctx *ctx) + { + DPLANE_CTX_VALID(ctx); +@@ -2348,6 +2373,12 @@ int dplane_ctx_get_nhe_type(const struct zebra_dplane_ctx *ctx) + return ctx->u.rinfo.nhe.type; + } + ++uint32_t dplane_ctx_get_nhe_nhg_flags(const struct zebra_dplane_ctx *ctx) ++{ ++ DPLANE_CTX_VALID(ctx); ++ return ctx->u.rinfo.nhe.nhg_flags; ++} ++ + const struct nexthop_group * + dplane_ctx_get_nhe_ng(const struct zebra_dplane_ctx *ctx) + { +@@ -2368,6 +2399,42 @@ uint16_t dplane_ctx_get_nhe_nh_grp_count(const struct zebra_dplane_ctx *ctx) + return ctx->u.rinfo.nhe.nh_grp_count; + } + ++const struct nh_grp_full *dplane_ctx_get_nhe_nh_grp_full(const struct zebra_dplane_ctx *ctx) ++{ ++ DPLANE_CTX_VALID(ctx); ++ return ctx->u.rinfo.nhe.nh_grp_full; ++} ++ ++uint32_t dplane_ctx_get_nhe_nh_grp_full_count(const struct zebra_dplane_ctx *ctx) ++{ ++ DPLANE_CTX_VALID(ctx); ++ return ctx->u.rinfo.nhe.nh_grp_full_count; ++} ++ ++const uint32_t *dplane_ctx_get_nhe_depends(const struct zebra_dplane_ctx *ctx) ++{ ++ DPLANE_CTX_VALID(ctx); ++ return ctx->u.rinfo.nhe.depends; ++} ++ ++uint32_t dplane_ctx_get_nhe_depends_count(const struct zebra_dplane_ctx *ctx) ++{ ++ DPLANE_CTX_VALID(ctx); ++ return ctx->u.rinfo.nhe.depends_count; ++} ++ ++const uint32_t *dplane_ctx_get_nhe_dependents(const struct zebra_dplane_ctx *ctx) ++{ ++ DPLANE_CTX_VALID(ctx); ++ return ctx->u.rinfo.nhe.dependents; ++} ++ ++uint32_t dplane_ctx_get_nhe_dependents_count(const struct zebra_dplane_ctx *ctx) ++{ ++ DPLANE_CTX_VALID(ctx); ++ return ctx->u.rinfo.nhe.dependents_count; ++} ++ + /* Accessors for LSP information */ + + mpls_label_t dplane_ctx_get_in_label(const struct zebra_dplane_ctx *ctx) +diff --git a/zebra/zebra_dplane.h b/zebra/zebra_dplane.h +index dd8a69e7db..009bc70859 100644 +--- a/zebra/zebra_dplane.h ++++ b/zebra/zebra_dplane.h +@@ -618,14 +618,22 @@ dplane_ctx_get_old_backup_ng(const struct zebra_dplane_ctx *ctx); + /* Accessors for nexthop information */ + uint32_t dplane_ctx_get_nhe_id(const struct zebra_dplane_ctx *ctx); + uint32_t dplane_ctx_get_old_nhe_id(const struct zebra_dplane_ctx *ctx); ++uint32_t dplane_ctx_get_nhe_received_id(const struct zebra_dplane_ctx *ctx); + afi_t dplane_ctx_get_nhe_afi(const struct zebra_dplane_ctx *ctx); + vrf_id_t dplane_ctx_get_nhe_vrf_id(const struct zebra_dplane_ctx *ctx); + int dplane_ctx_get_nhe_type(const struct zebra_dplane_ctx *ctx); ++uint32_t dplane_ctx_get_nhe_nhg_flags(const struct zebra_dplane_ctx *ctx); + const struct nexthop_group * + dplane_ctx_get_nhe_ng(const struct zebra_dplane_ctx *ctx); + const struct nh_grp * + dplane_ctx_get_nhe_nh_grp(const struct zebra_dplane_ctx *ctx); + uint16_t dplane_ctx_get_nhe_nh_grp_count(const struct zebra_dplane_ctx *ctx); ++const struct nh_grp_full *dplane_ctx_get_nhe_nh_grp_full(const struct zebra_dplane_ctx *ctx); ++uint32_t dplane_ctx_get_nhe_nh_grp_full_count(const struct zebra_dplane_ctx *ctx); ++const uint32_t *dplane_ctx_get_nhe_depends(const struct zebra_dplane_ctx *ctx); ++uint32_t dplane_ctx_get_nhe_depends_count(const struct zebra_dplane_ctx *ctx); ++const uint32_t *dplane_ctx_get_nhe_dependents(const struct zebra_dplane_ctx *ctx); ++uint32_t dplane_ctx_get_nhe_dependents_count(const struct zebra_dplane_ctx *ctx); + + /* Accessors for LSP information */ + +diff --git a/zebra/zebra_nhg.h b/zebra/zebra_nhg.h +index b1fb4c68ce..bcff39a9d6 100644 +--- a/zebra/zebra_nhg.h ++++ b/zebra/zebra_nhg.h +@@ -25,6 +25,12 @@ struct nh_grp { + uint16_t weight; + }; + ++struct nh_grp_full { ++ uint32_t id; ++ uint16_t weight; ++ uint32_t num_direct; ++}; ++ + PREDECL_RBTREE_UNIQ(nhg_connected_tree); + + /* +-- +2.54.0 + diff --git a/src/sonic-frr/patch/series b/src/sonic-frr/patch/series index 64dc174ba5e..8ffc5b744c4 100644 --- a/src/sonic-frr/patch/series +++ b/src/sonic-frr/patch/series @@ -85,3 +85,4 @@ 0114-Provide-interface-when-installing-uninstalling-SRv6-uA-SIDs.patch 0115-zebra-Update-promiscuity-flag-silently-without-route.patch 0116-zebra-keep-route_node-lock-in-NB-RIB-route-lookup_next.patch +0117-Add-Dplane-definitions-and-functions-for-ribfib.patch