From b0475d5a52aedca05238fad4effcc426d276773f Mon Sep 17 00:00:00 2001 From: Sarita Patra Date: Fri, 23 Oct 2020 04:46:39 -0700 Subject: [PATCH] pimd: Northbound implementation for igmp commands. interface_ip_igmp interface_no_ip_igmp interface_ip_igmp_join interface_no_ip_igmp_join interface_ip_igmp_query_interval interface_no_ip_igmp_query_interval interface_ip_igmp_version interface_no_ip_igmp_version interface_ip_igmp_query_max_response_time interface_no_ip_igmp_query_max_response_time interface_ip_igmp_query_max_response_time_dsec interface_no_ip_igmp_query_max_response_time_dsec interface_ip_igmp_last_member_query_count interface_no_ip_igmp_last_member_query_count interface_ip_igmp_last_member_query_interval interface_no_ip_igmp_last_member_query_interval IGMP yang tree: module: frr-igmp augment /frr-interface:lib/frr-interface:interface: +--rw igmp! +--rw igmp-enable? boolean +--rw version? uint8 +--rw query-interval? uint16 <125> +--rw query-max-response-time? uint8 <100> +--rw last-member-query-interval? uint8 <10> +--rw robustness-variable? uint8 <2> +--rw address-family* [address-family] +--rw address-family identityref +--rw static-group* [group-addr source-addr] +--rw group-addr ietf-routing-types:ip-multicast-group-address +--rw source-addr ietf-inet-types:ip-address Signed-off-by: Sarita Patra --- pimd/pim_cmd.c | 630 +++++++++++-------------------------------- pimd/pim_nb_config.c | 414 ++++++++++++++++++++++++++-- 2 files changed, 559 insertions(+), 485 deletions(-) diff --git a/pimd/pim_cmd.c b/pimd/pim_cmd.c index 38123cc8f6..9065d30f14 100644 --- a/pimd/pim_cmd.c +++ b/pimd/pim_cmd.c @@ -64,6 +64,9 @@ #include "pim_mlag.h" #include "bfd.h" #include "pim_bsm.h" +#include "lib/northbound_cli.h" +#include "pim_errors.h" +#include "pim_nb.h" #ifndef VTYSH_EXTRACT_PL #include "pimd/pim_cmd_clippy.c" @@ -101,7 +104,7 @@ static struct vrf *pim_cmd_lookup_vrf(struct vty *vty, struct cmd_token *argv[], return vrf; } -static void pim_if_membership_clear(struct interface *ifp) +void pim_if_membership_clear(struct interface *ifp) { struct pim_interface *pim_ifp; @@ -125,7 +128,7 @@ static void pim_if_membership_clear(struct interface *ifp) whenever PIM is enabled on the interface in order to collect missed local membership information. */ -static void pim_if_membership_refresh(struct interface *ifp) +void pim_if_membership_refresh(struct interface *ifp) { struct pim_interface *pim_ifp; struct listnode *sock_node; @@ -7517,51 +7520,15 @@ DEFUN (no_ip_pim_ecmp_rebalance, return CMD_SUCCESS; } -static int pim_cmd_igmp_start(struct vty *vty, struct interface *ifp) -{ - struct pim_interface *pim_ifp; - struct pim_instance *pim; - uint8_t need_startup = 0; - - pim_ifp = ifp->info; - - if (!pim_ifp) { - pim = pim_get_pim_instance(ifp->vrf_id); - /* Limit mcast interfaces to number of vifs available */ - if (pim->mcast_if_count == MAXVIFS) { - vty_out(vty, - "Max multicast interfaces(%d) Reached. Could not enable IGMP on interface %s\n", - MAXVIFS, ifp->name); - return CMD_WARNING_CONFIG_FAILED; - } - (void)pim_if_new(ifp, true, false, false, false); - need_startup = 1; - } else { - if (!PIM_IF_TEST_IGMP(pim_ifp->options)) { - PIM_IF_DO_IGMP(pim_ifp->options); - need_startup = 1; - } - } - - /* 'ip igmp' executed multiple times, with need_startup - avoid multiple if add all and membership refresh */ - if (need_startup) { - pim_if_addr_add_all(ifp); - pim_if_membership_refresh(ifp); - } - - return CMD_SUCCESS; -} - DEFUN (interface_ip_igmp, interface_ip_igmp_cmd, "ip igmp", IP_STR IFACE_IGMP_STR) { - VTY_DECLVAR_CONTEXT(interface, ifp); + nb_cli_enqueue_change(vty, "./igmp-enable", NB_OP_MODIFY, "true"); - return pim_cmd_igmp_start(vty, ifp); + return nb_cli_apply_changes(vty, "./frr-igmp:igmp"); } DEFUN (interface_no_ip_igmp, @@ -7571,23 +7538,28 @@ DEFUN (interface_no_ip_igmp, IP_STR IFACE_IGMP_STR) { - VTY_DECLVAR_CONTEXT(interface, ifp); - struct pim_interface *pim_ifp = ifp->info; - - if (!pim_ifp) - return CMD_SUCCESS; + const struct lyd_node *pim_enable_dnode; + char pim_if_xpath[XPATH_MAXLEN]; - PIM_IF_DONT_IGMP(pim_ifp->options); + snprintf(pim_if_xpath, sizeof(pim_if_xpath), + "%s/frr-pim:pim", VTY_CURR_XPATH); - pim_if_membership_clear(ifp); - - pim_if_addr_del_all_igmp(ifp); - - if (!PIM_IF_TEST_PIM(pim_ifp->options)) { - pim_if_delete(ifp); + pim_enable_dnode = yang_dnode_get(vty->candidate_config->dnode, + "%s/pim-enable", pim_if_xpath); + if (!pim_enable_dnode) { + nb_cli_enqueue_change(vty, pim_if_xpath, NB_OP_DESTROY, NULL); + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + } else { + if (!yang_dnode_get_bool(pim_enable_dnode, ".")) { + nb_cli_enqueue_change(vty, pim_if_xpath, NB_OP_DESTROY, + NULL); + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + } else + nb_cli_enqueue_change(vty, "./igmp-enable", + NB_OP_MODIFY, "false"); } - return CMD_SUCCESS; + return nb_cli_apply_changes(vty, "./frr-igmp:igmp"); } DEFUN (interface_ip_igmp_join, @@ -7599,46 +7571,28 @@ DEFUN (interface_ip_igmp_join, "Multicast group address\n" "Source address\n") { - VTY_DECLVAR_CONTEXT(interface, ifp); - int idx_ipv4 = 3; - int idx_ipv4_2 = 4; - const char *group_str; + int idx_group = 3; + int idx_source = 4; const char *source_str; - struct in_addr group_addr; - struct in_addr source_addr; - int result; + char xpath[XPATH_MAXLEN]; - /* Group address */ - group_str = argv[idx_ipv4]->arg; - result = inet_pton(AF_INET, group_str, &group_addr); - if (result <= 0) { - vty_out(vty, "Bad group address %s: errno=%d: %s\n", group_str, - errno, safe_strerror(errno)); - return CMD_WARNING_CONFIG_FAILED; - } + if (argc == 5) { + source_str = argv[idx_source]->arg; - /* Source address */ - if (argc == (idx_ipv4_2 + 1)) { - source_str = argv[idx_ipv4_2]->arg; - result = inet_pton(AF_INET, source_str, &source_addr); - if (result <= 0) { - vty_out(vty, "Bad source address %s: errno=%d: %s\n", - source_str, errno, safe_strerror(errno)); + if (strcmp(source_str, "0.0.0.0") == 0) { + vty_out(vty, "Bad source address %s\n", + argv[idx_source]->arg); return CMD_WARNING_CONFIG_FAILED; } - /* Reject 0.0.0.0. Reserved for any source. */ - if (source_addr.s_addr == INADDR_ANY) { - vty_out(vty, "Bad source address %s\n", source_str); - return CMD_WARNING_CONFIG_FAILED; - } - } else { - source_addr.s_addr = INADDR_ANY; - } + } else + source_str = "0.0.0.0"; - CMD_FERR_RETURN(pim_if_igmp_join_add(ifp, group_addr, source_addr), - "Failure joining IGMP group: $ERR"); + snprintf(xpath, sizeof(xpath), FRR_IGMP_JOIN_XPATH, + "frr-routing:ipv4", argv[idx_group]->arg, source_str); - return CMD_SUCCESS; + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + return nb_cli_apply_changes(vty, NULL); } DEFUN (interface_no_ip_igmp_join, @@ -7651,190 +7605,30 @@ DEFUN (interface_no_ip_igmp_join, "Multicast group address\n" "Source address\n") { - VTY_DECLVAR_CONTEXT(interface, ifp); - int idx_ipv4 = 4; - int idx_ipv4_2 = 5; - const char *group_str; + int idx_group = 4; + int idx_source = 5; const char *source_str; - struct in_addr group_addr; - struct in_addr source_addr; - int result; + char xpath[XPATH_MAXLEN]; - /* Group address */ - group_str = argv[idx_ipv4]->arg; - result = inet_pton(AF_INET, group_str, &group_addr); - if (result <= 0) { - vty_out(vty, "Bad group address %s: errno=%d: %s\n", group_str, - errno, safe_strerror(errno)); - return CMD_WARNING_CONFIG_FAILED; - } + if (argc == 6) { + source_str = argv[idx_source]->arg; - /* Source address */ - if (argc == (idx_ipv4_2 + 1)) { - source_str = argv[idx_ipv4_2]->arg; - result = inet_pton(AF_INET, source_str, &source_addr); - if (result <= 0) { - vty_out(vty, "Bad source address %s: errno=%d: %s\n", - source_str, errno, safe_strerror(errno)); + if (strcmp(source_str, "0.0.0.0") == 0) { + vty_out(vty, "Bad source address %s\n", + argv[idx_source]->arg); return CMD_WARNING_CONFIG_FAILED; } - /* Reject 0.0.0.0. Reserved for any source. */ - if (source_addr.s_addr == INADDR_ANY) { - vty_out(vty, "Bad source address %s\n", source_str); - return CMD_WARNING_CONFIG_FAILED; - } - } else { - source_str = "*"; - source_addr.s_addr = INADDR_ANY; - } - - result = pim_if_igmp_join_del(ifp, group_addr, source_addr); - if (result) { - vty_out(vty, - "%% Failure leaving IGMP group %s source %s on interface %s: %d\n", - group_str, source_str, ifp->name, result); - return CMD_WARNING_CONFIG_FAILED; - } - - return CMD_SUCCESS; -} - -/* - CLI reconfiguration affects the interface level (struct pim_interface). - This function propagates the reconfiguration to every active socket - for that interface. - */ -static void igmp_sock_query_interval_reconfig(struct igmp_sock *igmp) -{ - struct interface *ifp; - struct pim_interface *pim_ifp; - - zassert(igmp); - - /* other querier present? */ - - if (igmp->t_other_querier_timer) - return; - - /* this is the querier */ - - zassert(igmp->interface); - zassert(igmp->interface->info); - - ifp = igmp->interface; - pim_ifp = ifp->info; - - if (PIM_DEBUG_IGMP_TRACE) { - char ifaddr_str[INET_ADDRSTRLEN]; - pim_inet4_dump("", igmp->ifaddr, ifaddr_str, - sizeof(ifaddr_str)); - zlog_debug("%s: Querier %s on %s reconfig query_interval=%d", - __func__, ifaddr_str, ifp->name, - pim_ifp->igmp_default_query_interval); - } - - /* - igmp_startup_mode_on() will reset QQI: - - igmp->querier_query_interval = pim_ifp->igmp_default_query_interval; - */ - igmp_startup_mode_on(igmp); -} - -static void igmp_sock_query_reschedule(struct igmp_sock *igmp) -{ - if (igmp->mtrace_only) - return; - - if (igmp->t_igmp_query_timer) { - /* other querier present */ - zassert(igmp->t_igmp_query_timer); - zassert(!igmp->t_other_querier_timer); - - pim_igmp_general_query_off(igmp); - pim_igmp_general_query_on(igmp); - - zassert(igmp->t_igmp_query_timer); - zassert(!igmp->t_other_querier_timer); - } else { - /* this is the querier */ - - zassert(!igmp->t_igmp_query_timer); - zassert(igmp->t_other_querier_timer); - - pim_igmp_other_querier_timer_off(igmp); - pim_igmp_other_querier_timer_on(igmp); - - zassert(!igmp->t_igmp_query_timer); - zassert(igmp->t_other_querier_timer); - } -} - -static void change_query_interval(struct pim_interface *pim_ifp, - int query_interval) -{ - struct listnode *sock_node; - struct igmp_sock *igmp; - - pim_ifp->igmp_default_query_interval = query_interval; - - for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) { - igmp_sock_query_interval_reconfig(igmp); - igmp_sock_query_reschedule(igmp); - } -} - -static void change_query_max_response_time(struct pim_interface *pim_ifp, - int query_max_response_time_dsec) -{ - struct listnode *sock_node; - struct igmp_sock *igmp; - - pim_ifp->igmp_query_max_response_time_dsec = - query_max_response_time_dsec; - - /* - Below we modify socket/group/source timers in order to quickly - reflect the change. Otherwise, those timers would eventually catch - up. - */ - - /* scan all sockets */ - for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) { - struct listnode *grp_node; - struct igmp_group *grp; - - /* reschedule socket general query */ - igmp_sock_query_reschedule(igmp); - - /* scan socket groups */ - for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, grp_node, - grp)) { - struct listnode *src_node; - struct igmp_source *src; + } else + source_str = "0.0.0.0"; - /* reset group timers for groups in EXCLUDE mode */ - if (grp->group_filtermode_isexcl) { - igmp_group_reset_gmi(grp); - } + snprintf(xpath, sizeof(xpath), FRR_IGMP_JOIN_XPATH, + "frr-routing:ipv4", argv[idx_group]->arg, source_str); - /* scan group sources */ - for (ALL_LIST_ELEMENTS_RO(grp->group_source_list, - src_node, src)) { + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); - /* reset source timers for sources with running - * timers */ - if (src->t_source_timer) { - igmp_source_reset_gmi(igmp, grp, src); - } - } - } - } + return nb_cli_apply_changes(vty, NULL); } -#define IGMP_QUERY_INTERVAL_MIN (1) -#define IGMP_QUERY_INTERVAL_MAX (1800) - DEFUN (interface_ip_igmp_query_interval, interface_ip_igmp_query_interval_cmd, "ip igmp query-interval (1-1800)", @@ -7843,50 +7637,24 @@ DEFUN (interface_ip_igmp_query_interval, IFACE_IGMP_QUERY_INTERVAL_STR "Query interval in seconds\n") { - VTY_DECLVAR_CONTEXT(interface, ifp); - struct pim_interface *pim_ifp = ifp->info; - int query_interval; - int query_interval_dsec; - int ret; - - if (!pim_ifp) { - ret = pim_cmd_igmp_start(vty, ifp); - if (ret != CMD_SUCCESS) - return ret; - pim_ifp = ifp->info; - } - - query_interval = atoi(argv[3]->arg); - query_interval_dsec = 10 * query_interval; - - /* - It seems we don't need to check bounds since command.c does it - already, but we verify them anyway for extra safety. - */ - if (query_interval < IGMP_QUERY_INTERVAL_MIN) { - vty_out(vty, - "General query interval %d lower than minimum %d\n", - query_interval, IGMP_QUERY_INTERVAL_MIN); - return CMD_WARNING_CONFIG_FAILED; - } - if (query_interval > IGMP_QUERY_INTERVAL_MAX) { - vty_out(vty, - "General query interval %d higher than maximum %d\n", - query_interval, IGMP_QUERY_INTERVAL_MAX); - return CMD_WARNING_CONFIG_FAILED; - } + const struct lyd_node *pim_enable_dnode; - if (query_interval_dsec <= pim_ifp->igmp_query_max_response_time_dsec) { - vty_out(vty, - "Can't set general query interval %d dsec <= query max response time %d dsec.\n", - query_interval_dsec, - pim_ifp->igmp_query_max_response_time_dsec); - return CMD_WARNING_CONFIG_FAILED; + pim_enable_dnode = yang_dnode_get(vty->candidate_config->dnode, + "%s/frr-pim:pim/pim-enable", + VTY_CURR_XPATH); + if (!pim_enable_dnode) { + nb_cli_enqueue_change(vty, "./igmp-enable", NB_OP_MODIFY, + "true"); + } else { + if (!yang_dnode_get_bool(pim_enable_dnode, ".")) + nb_cli_enqueue_change(vty, "./igmp-enable", + NB_OP_MODIFY, "true"); } - change_query_interval(pim_ifp, query_interval); + nb_cli_enqueue_change(vty, "./query-interval", NB_OP_MODIFY, + argv[3]->arg); - return CMD_SUCCESS; + return nb_cli_apply_changes(vty, "./frr-igmp:igmp"); } DEFUN (interface_no_ip_igmp_query_interval, @@ -7897,27 +7665,15 @@ DEFUN (interface_no_ip_igmp_query_interval, IFACE_IGMP_STR IFACE_IGMP_QUERY_INTERVAL_STR) { - VTY_DECLVAR_CONTEXT(interface, ifp); - struct pim_interface *pim_ifp = ifp->info; - int default_query_interval_dsec; - - if (!pim_ifp) - return CMD_SUCCESS; - - default_query_interval_dsec = IGMP_GENERAL_QUERY_INTERVAL * 10; + char default_query_interval[5]; - if (default_query_interval_dsec - <= pim_ifp->igmp_query_max_response_time_dsec) { - vty_out(vty, - "Can't set default general query interval %d dsec <= query max response time %d dsec.\n", - default_query_interval_dsec, - pim_ifp->igmp_query_max_response_time_dsec); - return CMD_WARNING_CONFIG_FAILED; - } + snprintf(default_query_interval, sizeof(default_query_interval), "%d", + IGMP_GENERAL_QUERY_INTERVAL); - change_query_interval(pim_ifp, IGMP_GENERAL_QUERY_INTERVAL); + nb_cli_enqueue_change(vty, "./query-interval", NB_OP_MODIFY, + default_query_interval); - return CMD_SUCCESS; + return nb_cli_apply_changes(vty, "./frr-igmp:igmp"); } DEFUN (interface_ip_igmp_version, @@ -7928,36 +7684,11 @@ DEFUN (interface_ip_igmp_version, "IGMP version\n" "IGMP version number\n") { - VTY_DECLVAR_CONTEXT(interface, ifp); - struct pim_interface *pim_ifp = ifp->info; - int igmp_version, old_version = 0; - int ret; + nb_cli_enqueue_change(vty, "./igmp-enable", NB_OP_MODIFY, + "true"); + nb_cli_enqueue_change(vty, "./version", NB_OP_MODIFY, argv[3]->arg); - if (!pim_ifp) { - ret = pim_cmd_igmp_start(vty, ifp); - if (ret != CMD_SUCCESS) - return ret; - pim_ifp = ifp->info; - } - - igmp_version = atoi(argv[3]->arg); - old_version = pim_ifp->igmp_version; - pim_ifp->igmp_version = igmp_version; - - // Check if IGMP is Enabled otherwise, enable on interface - if (!PIM_IF_TEST_IGMP(pim_ifp->options)) { - PIM_IF_DO_IGMP(pim_ifp->options); - pim_if_addr_add_all(ifp); - pim_if_membership_refresh(ifp); - old_version = igmp_version; - // avoid refreshing membership again. - } - /* Current and new version is different refresh existing - membership. Going from 3 -> 2 or 2 -> 3. */ - if (old_version != igmp_version) - pim_if_membership_refresh(ifp); - - return CMD_SUCCESS; + return nb_cli_apply_changes(vty, "./frr-igmp:igmp"); } DEFUN (interface_no_ip_igmp_version, @@ -7969,20 +7700,11 @@ DEFUN (interface_no_ip_igmp_version, "IGMP version\n" "IGMP version number\n") { - VTY_DECLVAR_CONTEXT(interface, ifp); - struct pim_interface *pim_ifp = ifp->info; - - if (!pim_ifp) - return CMD_SUCCESS; + nb_cli_enqueue_change(vty, "./version", NB_OP_DESTROY, NULL); - pim_ifp->igmp_version = IGMP_DEFAULT_VERSION; - - return CMD_SUCCESS; + return nb_cli_apply_changes(vty, "./frr-igmp:igmp"); } -#define IGMP_QUERY_MAX_RESPONSE_TIME_MIN_DSEC (10) -#define IGMP_QUERY_MAX_RESPONSE_TIME_MAX_DSEC (250) - DEFUN (interface_ip_igmp_query_max_response_time, interface_ip_igmp_query_max_response_time_cmd, "ip igmp query-max-response-time (10-250)", @@ -7991,32 +7713,25 @@ DEFUN (interface_ip_igmp_query_max_response_time, IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_STR "Query response value in deci-seconds\n") { - VTY_DECLVAR_CONTEXT(interface, ifp); - struct pim_interface *pim_ifp = ifp->info; - int query_max_response_time; - int ret; - - if (!pim_ifp) { - ret = pim_cmd_igmp_start(vty, ifp); - if (ret != CMD_SUCCESS) - return ret; - pim_ifp = ifp->info; - } + const struct lyd_node *pim_enable_dnode; - query_max_response_time = atoi(argv[3]->arg); + pim_enable_dnode = yang_dnode_get(vty->candidate_config->dnode, + "%s/frr-pim:pim/pim-enable", + VTY_CURR_XPATH); - if (query_max_response_time - >= pim_ifp->igmp_default_query_interval * 10) { - vty_out(vty, - "Can't set query max response time %d sec >= general query interval %d sec\n", - query_max_response_time, - pim_ifp->igmp_default_query_interval); - return CMD_WARNING_CONFIG_FAILED; + if (!pim_enable_dnode) { + nb_cli_enqueue_change(vty, "./igmp-enable", NB_OP_MODIFY, + "true"); + } else { + if (!yang_dnode_get_bool(pim_enable_dnode, ".")) + nb_cli_enqueue_change(vty, "./igmp-enable", + NB_OP_MODIFY, "true"); } - change_query_max_response_time(pim_ifp, query_max_response_time); + nb_cli_enqueue_change(vty, "./query-max-response-time", NB_OP_MODIFY, + argv[3]->arg); - return CMD_SUCCESS; + return nb_cli_apply_changes(vty, "./frr-igmp:igmp"); } DEFUN (interface_no_ip_igmp_query_max_response_time, @@ -8028,21 +7743,17 @@ DEFUN (interface_no_ip_igmp_query_max_response_time, IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_STR "Time for response in deci-seconds\n") { - VTY_DECLVAR_CONTEXT(interface, ifp); - struct pim_interface *pim_ifp = ifp->info; - - if (!pim_ifp) - return CMD_SUCCESS; + char default_query_max_response_time[4]; - change_query_max_response_time(pim_ifp, - IGMP_QUERY_MAX_RESPONSE_TIME_DSEC); + snprintf(default_query_max_response_time, + sizeof(default_query_max_response_time), + "%d", IGMP_QUERY_MAX_RESPONSE_TIME_DSEC); - return CMD_SUCCESS; + nb_cli_enqueue_change(vty, "./query-max-response-time", NB_OP_MODIFY, + default_query_max_response_time); + return nb_cli_apply_changes(vty, "./frr-igmp:igmp"); } -#define IGMP_QUERY_MAX_RESPONSE_TIME_MIN_DSEC (10) -#define IGMP_QUERY_MAX_RESPONSE_TIME_MAX_DSEC (250) - DEFUN_HIDDEN (interface_ip_igmp_query_max_response_time_dsec, interface_ip_igmp_query_max_response_time_dsec_cmd, "ip igmp query-max-response-time-dsec (10-250)", @@ -8051,34 +7762,24 @@ DEFUN_HIDDEN (interface_ip_igmp_query_max_response_time_dsec, IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_DSEC_STR "Query response value in deciseconds\n") { - VTY_DECLVAR_CONTEXT(interface, ifp); - struct pim_interface *pim_ifp = ifp->info; - int query_max_response_time_dsec; - int default_query_interval_dsec; - int ret; - - if (!pim_ifp) { - ret = pim_cmd_igmp_start(vty, ifp); - if (ret != CMD_SUCCESS) - return ret; - pim_ifp = ifp->info; - } - - query_max_response_time_dsec = atoi(argv[4]->arg); + const struct lyd_node *pim_enable_dnode; - default_query_interval_dsec = 10 * pim_ifp->igmp_default_query_interval; - - if (query_max_response_time_dsec >= default_query_interval_dsec) { - vty_out(vty, - "Can't set query max response time %d dsec >= general query interval %d dsec\n", - query_max_response_time_dsec, - default_query_interval_dsec); - return CMD_WARNING_CONFIG_FAILED; + pim_enable_dnode = yang_dnode_get(vty->candidate_config->dnode, + "%s/frr-pim:pim/pim-enable", + VTY_CURR_XPATH); + if (!pim_enable_dnode) { + nb_cli_enqueue_change(vty, "./igmp-enable", NB_OP_MODIFY, + "true"); + } else { + if (!yang_dnode_get_bool(pim_enable_dnode, ".")) + nb_cli_enqueue_change(vty, "./igmp-enable", + NB_OP_MODIFY, "true"); } - change_query_max_response_time(pim_ifp, query_max_response_time_dsec); + nb_cli_enqueue_change(vty, "./query-max-response-time", NB_OP_MODIFY, + argv[3]->arg); - return CMD_SUCCESS; + return nb_cli_apply_changes(vty, "./frr-igmp:igmp"); } DEFUN_HIDDEN (interface_no_ip_igmp_query_max_response_time_dsec, @@ -8089,21 +7790,18 @@ DEFUN_HIDDEN (interface_no_ip_igmp_query_max_response_time_dsec, IFACE_IGMP_STR IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_DSEC_STR) { - VTY_DECLVAR_CONTEXT(interface, ifp); - struct pim_interface *pim_ifp = ifp->info; + char default_query_max_response_time[4]; - if (!pim_ifp) - return CMD_SUCCESS; + snprintf(default_query_max_response_time, + sizeof(default_query_max_response_time), + "%d", IGMP_QUERY_MAX_RESPONSE_TIME_DSEC); - change_query_max_response_time(pim_ifp, - IGMP_QUERY_MAX_RESPONSE_TIME_DSEC); + nb_cli_enqueue_change(vty, "./query-max-response-time", NB_OP_MODIFY, + default_query_max_response_time); - return CMD_SUCCESS; + return nb_cli_apply_changes(vty, "./frr-igmp:igmp"); } -#define IGMP_LAST_MEMBER_QUERY_COUNT_MIN (1) -#define IGMP_LAST_MEMBER_QUERY_COUNT_MAX (7) - DEFUN (interface_ip_igmp_last_member_query_count, interface_ip_igmp_last_member_query_count_cmd, "ip igmp last-member-query-count (1-7)", @@ -8112,23 +7810,24 @@ DEFUN (interface_ip_igmp_last_member_query_count, IFACE_IGMP_LAST_MEMBER_QUERY_COUNT_STR "Last member query count\n") { - VTY_DECLVAR_CONTEXT(interface, ifp); - struct pim_interface *pim_ifp = ifp->info; - int last_member_query_count; - int ret; + const struct lyd_node *pim_enable_dnode; - if (!pim_ifp) { - ret = pim_cmd_igmp_start(vty, ifp); - if (ret != CMD_SUCCESS) - return ret; - pim_ifp = ifp->info; + pim_enable_dnode = yang_dnode_get(vty->candidate_config->dnode, + "%s/frr-pim:pim/pim-enable", + VTY_CURR_XPATH); + if (!pim_enable_dnode) { + nb_cli_enqueue_change(vty, "./igmp-enable", NB_OP_MODIFY, + "true"); + } else { + if (!yang_dnode_get_bool(pim_enable_dnode, ".")) + nb_cli_enqueue_change(vty, "./igmp-enable", + NB_OP_MODIFY, "true"); } - last_member_query_count = atoi(argv[3]->arg); - - pim_ifp->igmp_last_member_query_count = last_member_query_count; + nb_cli_enqueue_change(vty, "./robustness-variable", NB_OP_MODIFY, + argv[3]->arg); - return CMD_SUCCESS; + return nb_cli_apply_changes(vty, "./frr-igmp:igmp"); } DEFUN (interface_no_ip_igmp_last_member_query_count, @@ -8139,21 +7838,17 @@ DEFUN (interface_no_ip_igmp_last_member_query_count, IFACE_IGMP_STR IFACE_IGMP_LAST_MEMBER_QUERY_COUNT_STR) { - VTY_DECLVAR_CONTEXT(interface, ifp); - struct pim_interface *pim_ifp = ifp->info; + char default_robustness[2]; - if (!pim_ifp) - return CMD_SUCCESS; + snprintf(default_robustness, sizeof(default_robustness), "%d", + IGMP_DEFAULT_ROBUSTNESS_VARIABLE); - pim_ifp->igmp_last_member_query_count = - IGMP_DEFAULT_ROBUSTNESS_VARIABLE; + nb_cli_enqueue_change(vty, "./robustness-variable", NB_OP_MODIFY, + default_robustness); - return CMD_SUCCESS; + return nb_cli_apply_changes(vty, "./frr-igmp:igmp"); } -#define IGMP_LAST_MEMBER_QUERY_INTERVAL_MIN (1) -#define IGMP_LAST_MEMBER_QUERY_INTERVAL_MAX (255) - DEFUN (interface_ip_igmp_last_member_query_interval, interface_ip_igmp_last_member_query_interval_cmd, "ip igmp last-member-query-interval (1-255)", @@ -8162,23 +7857,24 @@ DEFUN (interface_ip_igmp_last_member_query_interval, IFACE_IGMP_LAST_MEMBER_QUERY_INTERVAL_STR "Last member query interval in deciseconds\n") { - VTY_DECLVAR_CONTEXT(interface, ifp); - struct pim_interface *pim_ifp = ifp->info; - int last_member_query_interval; - int ret; + const struct lyd_node *pim_enable_dnode; - if (!pim_ifp) { - ret = pim_cmd_igmp_start(vty, ifp); - if (ret != CMD_SUCCESS) - return ret; - pim_ifp = ifp->info; + pim_enable_dnode = yang_dnode_get(vty->candidate_config->dnode, + "%s/frr-pim:pim/pim-enable", + VTY_CURR_XPATH); + if (!pim_enable_dnode) { + nb_cli_enqueue_change(vty, "./igmp-enable", NB_OP_MODIFY, + "true"); + } else { + if (!yang_dnode_get_bool(pim_enable_dnode, ".")) + nb_cli_enqueue_change(vty, "./igmp-enable", + NB_OP_MODIFY, "true"); } - last_member_query_interval = atoi(argv[3]->arg); - pim_ifp->igmp_specific_query_max_response_time_dsec - = last_member_query_interval; + nb_cli_enqueue_change(vty, "./last-member-query-interval", NB_OP_MODIFY, + argv[3]->arg); - return CMD_SUCCESS; + return nb_cli_apply_changes(vty, "./frr-igmp:igmp"); } DEFUN (interface_no_ip_igmp_last_member_query_interval, @@ -8189,16 +7885,16 @@ DEFUN (interface_no_ip_igmp_last_member_query_interval, IFACE_IGMP_STR IFACE_IGMP_LAST_MEMBER_QUERY_INTERVAL_STR) { - VTY_DECLVAR_CONTEXT(interface, ifp); - struct pim_interface *pim_ifp = ifp->info; + char default_last_member_query_count[4]; - if (!pim_ifp) - return CMD_SUCCESS; + snprintf(default_last_member_query_count, + sizeof(default_last_member_query_count), + "%d", IGMP_SPECIFIC_QUERY_MAX_RESPONSE_TIME_DSEC); - pim_ifp->igmp_specific_query_max_response_time_dsec = - IGMP_SPECIFIC_QUERY_MAX_RESPONSE_TIME_DSEC; + nb_cli_enqueue_change(vty, "./last-member-query-interval", NB_OP_MODIFY, + default_last_member_query_count); - return CMD_SUCCESS; + return nb_cli_apply_changes(vty, "./frr-igmp:igmp"); } DEFUN (interface_ip_pim_drprio, diff --git a/pimd/pim_nb_config.c b/pimd/pim_nb_config.c index f7496b3a5d..366b00f4b6 100644 --- a/pimd/pim_nb_config.c +++ b/pimd/pim_nb_config.c @@ -20,6 +20,192 @@ #include "pimd.h" #include "pim_nb.h" #include "lib/northbound_cli.h" +#include "pim_igmpv3.h" + +void pim_if_membership_clear(struct interface *ifp); +void pim_if_membership_refresh(struct interface *ifp); + +static bool is_pim_interface(const struct lyd_node *dnode) +{ + char if_xpath[XPATH_MAXLEN]; + const struct lyd_node *pim_enable_dnode; + const struct lyd_node *igmp_enable_dnode; + + yang_dnode_get_path(dnode, if_xpath, sizeof(if_xpath)); + pim_enable_dnode = yang_dnode_get(dnode, "%s/frr-pim:pim/pim-enable", + if_xpath); + igmp_enable_dnode = yang_dnode_get(dnode, "%s/frr-igmp:igmp/igmp-enable", + if_xpath); + + if (((pim_enable_dnode) && + (yang_dnode_get_bool(pim_enable_dnode, "."))) || + ((igmp_enable_dnode) && + (yang_dnode_get_bool(igmp_enable_dnode, ".")))) + return true; + + return false; +} + +static int pim_cmd_igmp_start(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + uint8_t need_startup = 0; + + pim_ifp = ifp->info; + + if (!pim_ifp) { + (void)pim_if_new(ifp, true, false, false, false); + need_startup = 1; + } else { + if (!PIM_IF_TEST_IGMP(pim_ifp->options)) { + PIM_IF_DO_IGMP(pim_ifp->options); + need_startup = 1; + } + } + + /* 'ip igmp' executed multiple times, with need_startup + * avoid multiple if add all and membership refresh + */ + if (need_startup) { + pim_if_addr_add_all(ifp); + pim_if_membership_refresh(ifp); + } + + return NB_OK; +} + +/* + * CLI reconfiguration affects the interface level (struct pim_interface). + * This function propagates the reconfiguration to every active socket + * for that interface. + */ +static void igmp_sock_query_interval_reconfig(struct igmp_sock *igmp) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + + zassert(igmp); + + /* other querier present? */ + + if (igmp->t_other_querier_timer) + return; + + /* this is the querier */ + + zassert(igmp->interface); + zassert(igmp->interface->info); + + ifp = igmp->interface; + pim_ifp = ifp->info; + + if (PIM_DEBUG_IGMP_TRACE) { + char ifaddr_str[INET_ADDRSTRLEN]; + + pim_inet4_dump("", igmp->ifaddr, ifaddr_str, + sizeof(ifaddr_str)); + zlog_debug("%s: Querier %s on %s reconfig query_interval=%d", + __func__, ifaddr_str, ifp->name, + pim_ifp->igmp_default_query_interval); + } + + /* + * igmp_startup_mode_on() will reset QQI: + + * igmp->querier_query_interval = pim_ifp->igmp_default_query_interval; + */ + igmp_startup_mode_on(igmp); +} + +static void igmp_sock_query_reschedule(struct igmp_sock *igmp) +{ + if (igmp->mtrace_only) + return; + + if (igmp->t_igmp_query_timer) { + /* other querier present */ + zassert(igmp->t_igmp_query_timer); + zassert(!igmp->t_other_querier_timer); + + pim_igmp_general_query_off(igmp); + pim_igmp_general_query_on(igmp); + + zassert(igmp->t_igmp_query_timer); + zassert(!igmp->t_other_querier_timer); + } else { + /* this is the querier */ + + zassert(!igmp->t_igmp_query_timer); + zassert(igmp->t_other_querier_timer); + + pim_igmp_other_querier_timer_off(igmp); + pim_igmp_other_querier_timer_on(igmp); + + zassert(!igmp->t_igmp_query_timer); + zassert(igmp->t_other_querier_timer); + } +} + +static void change_query_interval(struct pim_interface *pim_ifp, + int query_interval) +{ + struct listnode *sock_node; + struct igmp_sock *igmp; + + pim_ifp->igmp_default_query_interval = query_interval; + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) { + igmp_sock_query_interval_reconfig(igmp); + igmp_sock_query_reschedule(igmp); + } +} + +static void change_query_max_response_time(struct pim_interface *pim_ifp, + int query_max_response_time_dsec) +{ + struct listnode *sock_node; + struct igmp_sock *igmp; + + pim_ifp->igmp_query_max_response_time_dsec = + query_max_response_time_dsec; + + /* + * Below we modify socket/group/source timers in order to quickly + * reflect the change. Otherwise, those timers would args->eventually + * catch up. + */ + + /* scan all sockets */ + for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) { + struct listnode *grp_node; + struct igmp_group *grp; + + /* reschedule socket general query */ + igmp_sock_query_reschedule(igmp); + + /* scan socket groups */ + for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, grp_node, + grp)) { + struct listnode *src_node; + struct igmp_source *src; + + /* reset group timers for groups in EXCLUDE mode */ + if (grp->group_filtermode_isexcl) + igmp_group_reset_gmi(grp); + + /* scan group sources */ + for (ALL_LIST_ELEMENTS_RO(grp->group_source_list, + src_node, src)) { + + /* reset source timers for sources with running + * timers + */ + if (src->t_source_timer) + igmp_source_reset_gmi(igmp, grp, src); + } + } + } +} int routing_control_plane_protocols_name_validate( struct nb_cb_create_args *args) @@ -1174,7 +1360,6 @@ int lib_interface_igmp_create(struct nb_cb_create_args *args) case NB_EV_PREPARE: case NB_EV_ABORT: case NB_EV_APPLY: - /* TODO: implement me. */ break; } @@ -1183,13 +1368,29 @@ int lib_interface_igmp_create(struct nb_cb_create_args *args) int lib_interface_igmp_destroy(struct nb_cb_destroy_args *args) { + struct interface *ifp; + struct pim_interface *pim_ifp; + switch (args->event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: - case NB_EV_APPLY: - /* TODO: implement me. */ break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + + if (!pim_ifp) + return NB_OK; + + PIM_IF_DONT_IGMP(pim_ifp->options); + + pim_if_membership_clear(ifp); + + pim_if_addr_del_all_igmp(ifp); + + if (!PIM_IF_TEST_PIM(pim_ifp->options)) + pim_if_delete(ifp); } return NB_OK; @@ -1200,13 +1401,52 @@ int lib_interface_igmp_destroy(struct nb_cb_destroy_args *args) */ int lib_interface_igmp_igmp_enable_modify(struct nb_cb_modify_args *args) { + struct interface *ifp; + bool igmp_enable; + struct pim_interface *pim_ifp; + int mcast_if_count; + const char *ifp_name; + const struct lyd_node *if_dnode; + switch (args->event) { case NB_EV_VALIDATE: + if_dnode = yang_dnode_get_parent(args->dnode, "interface"); + ifp_name = yang_dnode_get_string(if_dnode, "."); + mcast_if_count = + yang_get_list_elements_count(if_dnode); + /* Limiting mcast interfaces to number of VIFs */ + if (mcast_if_count == MAXVIFS) { + snprintf(args->errmsg, args->errmsg_len, + "Max multicast interfaces(%d) Reached. Could not enable IGMP on interface %s", + MAXVIFS, ifp_name); + return NB_ERR_VALIDATION; + } + break; case NB_EV_PREPARE: case NB_EV_ABORT: - case NB_EV_APPLY: - /* TODO: implement me. */ break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + igmp_enable = yang_dnode_get_bool(args->dnode, NULL); + + if (igmp_enable) + return pim_cmd_igmp_start(ifp); + + else { + pim_ifp = ifp->info; + + if (!pim_ifp) + return NB_ERR_INCONSISTENCY; + + PIM_IF_DONT_IGMP(pim_ifp->options); + + pim_if_membership_clear(ifp); + + pim_if_addr_del_all_igmp(ifp); + + if (!PIM_IF_TEST_PIM(pim_ifp->options)) + pim_if_delete(ifp); + } } return NB_OK; @@ -1217,12 +1457,34 @@ int lib_interface_igmp_igmp_enable_modify(struct nb_cb_modify_args *args) */ int lib_interface_igmp_version_modify(struct nb_cb_modify_args *args) { + struct interface *ifp; + struct pim_interface *pim_ifp; + int igmp_version, old_version = 0; + switch (args->event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: + break; case NB_EV_APPLY: - /* TODO: implement me. */ + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + + if (!pim_ifp) + return NB_ERR_INCONSISTENCY; + + igmp_version = yang_dnode_get_uint8(args->dnode, NULL); + old_version = pim_ifp->igmp_version; + pim_ifp->igmp_version = igmp_version; + + old_version = igmp_version; + + /* Current and new version is different refresh existing + * membership. Going from 3 -> 2 or 2 -> 3. + */ + if (old_version != igmp_version) + pim_if_membership_refresh(ifp); + break; } @@ -1231,12 +1493,18 @@ int lib_interface_igmp_version_modify(struct nb_cb_modify_args *args) int lib_interface_igmp_version_destroy(struct nb_cb_destroy_args *args) { + struct interface *ifp; + struct pim_interface *pim_ifp; + switch (args->event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: + break; case NB_EV_APPLY: - /* TODO: implement me. */ + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + pim_ifp->igmp_version = IGMP_DEFAULT_VERSION; break; } @@ -1248,13 +1516,30 @@ int lib_interface_igmp_version_destroy(struct nb_cb_destroy_args *args) */ int lib_interface_igmp_query_interval_modify(struct nb_cb_modify_args *args) { + struct interface *ifp; + struct pim_interface *pim_ifp; + int query_interval; + int query_interval_dsec; + switch (args->event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: - case NB_EV_APPLY: - /* TODO: implement me. */ break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + query_interval = yang_dnode_get_uint16(args->dnode, NULL); + query_interval_dsec = 10 * query_interval; + if (query_interval_dsec <= + pim_ifp->igmp_query_max_response_time_dsec) { + snprintf(args->errmsg, args->errmsg_len, + "Can't set general query interval %d dsec <= query max response time %d dsec.", + query_interval_dsec, + pim_ifp->igmp_query_max_response_time_dsec); + return NB_ERR_INCONSISTENCY; + } + change_query_interval(pim_ifp, query_interval); } return NB_OK; @@ -1265,13 +1550,35 @@ int lib_interface_igmp_query_interval_modify(struct nb_cb_modify_args *args) */ int lib_interface_igmp_query_max_response_time_modify(struct nb_cb_modify_args *args) { + struct interface *ifp; + struct pim_interface *pim_ifp; + int query_max_response_time_dsec; + int default_query_interval_dsec; + switch (args->event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: - case NB_EV_APPLY: - /* TODO: implement me. */ break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + query_max_response_time_dsec = + yang_dnode_get_uint8(args->dnode, NULL); + default_query_interval_dsec = + 10 * pim_ifp->igmp_default_query_interval; + + if (query_max_response_time_dsec + >= default_query_interval_dsec) { + snprintf(args->errmsg, args->errmsg_len, + "Can't set query max response time %d sec >= general query interval %d sec", + query_max_response_time_dsec, + pim_ifp->igmp_default_query_interval); + return NB_ERR_INCONSISTENCY; + } + + change_query_max_response_time(pim_ifp, + query_max_response_time_dsec); } return NB_OK; @@ -1282,12 +1589,23 @@ int lib_interface_igmp_query_max_response_time_modify(struct nb_cb_modify_args * */ int lib_interface_igmp_last_member_query_interval_modify(struct nb_cb_modify_args *args) { + struct interface *ifp; + struct pim_interface *pim_ifp; + int last_member_query_interval; + switch (args->event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: + break; case NB_EV_APPLY: - /* TODO: implement me. */ + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + last_member_query_interval = yang_dnode_get_uint8(args->dnode, + NULL); + pim_ifp->igmp_specific_query_max_response_time_dsec = + last_member_query_interval; + break; } @@ -1299,12 +1617,22 @@ int lib_interface_igmp_last_member_query_interval_modify(struct nb_cb_modify_arg */ int lib_interface_igmp_robustness_variable_modify(struct nb_cb_modify_args *args) { + struct interface *ifp; + struct pim_interface *pim_ifp; + int last_member_query_count; + switch (args->event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: + break; case NB_EV_APPLY: - /* TODO: implement me. */ + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + last_member_query_count = yang_dnode_get_uint8(args->dnode, + NULL); + pim_ifp->igmp_last_member_query_count = last_member_query_count; + break; } @@ -1321,7 +1649,6 @@ int lib_interface_igmp_address_family_create(struct nb_cb_create_args *args) case NB_EV_PREPARE: case NB_EV_ABORT: case NB_EV_APPLY: - /* TODO: implement me. */ break; } @@ -1335,7 +1662,6 @@ int lib_interface_igmp_address_family_destroy(struct nb_cb_destroy_args *args) case NB_EV_PREPARE: case NB_EV_ABORT: case NB_EV_APPLY: - /* TODO: implement me. */ break; } @@ -1347,13 +1673,39 @@ int lib_interface_igmp_address_family_destroy(struct nb_cb_destroy_args *args) */ int lib_interface_igmp_address_family_static_group_create(struct nb_cb_create_args *args) { + struct interface *ifp; + struct ipaddr source_addr; + struct ipaddr group_addr; + int result; + const char *ifp_name; + const struct lyd_node *if_dnode; + switch (args->event) { case NB_EV_VALIDATE: + if_dnode = yang_dnode_get_parent(args->dnode, "interface"); + if (!is_pim_interface(if_dnode)) { + ifp_name = yang_dnode_get_string(if_dnode, "."); + snprintf(args->errmsg, args->errmsg_len, + "multicast not enabled on interface %s", + ifp_name); + return NB_ERR_VALIDATION; + } + break; case NB_EV_PREPARE: case NB_EV_ABORT: - case NB_EV_APPLY: - /* TODO: implement me. */ break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + yang_dnode_get_ip(&source_addr, args->dnode, "./source-addr"); + yang_dnode_get_ip(&group_addr, args->dnode, "./group-addr"); + + result = pim_if_igmp_join_add(ifp, group_addr.ip._v4_addr, + source_addr.ip._v4_addr); + if (result) { + snprintf(args->errmsg, args->errmsg_len, + "Failure joining IGMP group"); + return NB_ERR_INCONSISTENCY; + } } return NB_OK; @@ -1361,12 +1713,38 @@ int lib_interface_igmp_address_family_static_group_create(struct nb_cb_create_ar int lib_interface_igmp_address_family_static_group_destroy(struct nb_cb_destroy_args *args) { + struct interface *ifp; + struct ipaddr source_addr; + struct ipaddr group_addr; + int result; + switch (args->event) { case NB_EV_VALIDATE: case NB_EV_PREPARE: case NB_EV_ABORT: + break; case NB_EV_APPLY: - /* TODO: implement me. */ + ifp = nb_running_get_entry(args->dnode, NULL, true); + yang_dnode_get_ip(&source_addr, args->dnode, "./source-addr"); + yang_dnode_get_ip(&group_addr, args->dnode, "./group-addr"); + + result = pim_if_igmp_join_del(ifp, group_addr.ip._v4_addr, + source_addr.ip._v4_addr); + + if (result) { + char src_str[INET_ADDRSTRLEN]; + char grp_str[INET_ADDRSTRLEN]; + + ipaddr2str(&source_addr, src_str, sizeof(src_str)); + ipaddr2str(&group_addr, grp_str, sizeof(grp_str)); + + snprintf(args->errmsg, args->errmsg_len, + "%% Failure leaving IGMP group %s %s on interface %s: %d", + src_str, grp_str, ifp->name, result); + + return NB_ERR_INCONSISTENCY; + } + break; } -- 2.39.5