From c562ec9b32360c64183fa3aa8e2eefeef5bbbf2c Mon Sep 17 00:00:00 2001 From: ali-aqrabawi Date: Sat, 13 Jul 2024 18:06:24 +0300 Subject: [PATCH] [link,vlan]: add support for vlans config pull, fix link vlan update. - added iproute2 patch for /bridge/vlan.c - add oper_cmd_inner extension for inner list show command. - for onupdate_replace: duplicate the start_cmd after adding the operations to the original startcmd Signed-off-by: ali-aqrabawi --- Makefile | 14 +-- .../{json_print.patch => ipr2_src.patch} | 10 ++ src/iproute2_sysrepo.c | 4 +- src/lib/cmdgen.c | 85 +++++++------- src/lib/cmdgen.h | 4 +- src/lib/oper_data.c | 111 ++++++++++++++++-- yang/iproute2-cmdgen-extensions.yang | 5 + yang/iproute2-ip-link.yang | 17 ++- 8 files changed, 185 insertions(+), 65 deletions(-) rename ipr2_patches/{json_print.patch => ipr2_src.patch} (68%) diff --git a/Makefile b/Makefile index b1037f1..bb5c490 100644 --- a/Makefile +++ b/Makefile @@ -28,9 +28,9 @@ BINDIR = $(PREFIX)/bin all: $(IPR2_SR_OBJ) $(IPR2_SR_LIB_OBJ) iproute2/config.mk @echo "Checking if json_print patch is already applied..." - @if ! patch --dry-run --reverse --force -d iproute2 -p1 < ipr2_patches/json_print.patch; then \ + @if ! patch --dry-run --reverse --force -d iproute2 -p1 < ipr2_patches/ipr2_src.patch; then \ echo "Applying json_print patch..."; \ - patch -d iproute2 -p1 < ipr2_patches/json_print.patch || { echo "Patch failed"; true; }; \ + patch -d iproute2 -p1 < ipr2_patches/ipr2_src.patch || { echo "Patch failed"; true; }; \ else \ echo "json_print patch is already applied, skipping..."; \ fi @@ -65,11 +65,11 @@ uninstall: clean: @echo "Checking if json_print patch is already applied..." - @if ! patch --dry-run --reverse --force -d iproute2 -p1 < ipr2_patches/json_print.patch; then \ + @if ! patch --dry-run --reverse --force -d iproute2 -p1 < ipr2_patches/ipr2_src.patch; then \ echo "Patch wasn't applying, nothing to reverse..."; \ else \ echo "Reversing json_print patch..."; \ - patch -R -d iproute2 -p1 < ipr2_patches/json_print.patch || { echo "Patch failed"; true; }; \ + patch -R -d iproute2 -p1 < ipr2_patches/ipr2_src.patch || { echo "Patch failed"; true; }; \ fi @set -e; \ for i in $(SUBDIRS); do \ @@ -97,7 +97,5 @@ check: src/lib/%.o: src/lib/%.c $(CC) -c $< -o $@ -Iiproute2/ip -Iiproute2/bridge -Iiproute2/tc -Iiproute2/include - -src/%.o: $(IPR2_SR_SRC) $(IPR2_SR_LIB_OBJ) - $(CC) -c $< -o $@ -Iiproute2/ip -Iiproute2/bridge -Iiproute2/tc -Iiproute2/include - +src/%.o: src/%.c + $(CC) -c $< -o $@ -Iiproute2/ip -Iiproute2/bridge -Iiproute2/tc -Iiproute2/include \ No newline at end of file diff --git a/ipr2_patches/json_print.patch b/ipr2_patches/ipr2_src.patch similarity index 68% rename from ipr2_patches/json_print.patch rename to ipr2_patches/ipr2_src.patch index aa40dae..88a966e 100644 --- a/ipr2_patches/json_print.patch +++ b/ipr2_patches/ipr2_src.patch @@ -24,3 +24,13 @@ + fclose(j_stream); } } + +--- /bridge/vlan.c 2024-03-01 19:01:32.645610624 -0500 ++++ ipr2_patches/bridge_vlan_modified.c 2024-03-01 18:41:21.869658736 -0500 +@@ -1119,7 +1119,8 @@ + static int vlan_show(int argc, char **argv, int subject) + { + char *filter_dev = NULL; +- int ret = 0; ++ int ret = 0; ++ vlan_rtm_cur_ifidx = -1; diff --git a/src/iproute2_sysrepo.c b/src/iproute2_sysrepo.c index 0f1ddbc..64669b0 100644 --- a/src/iproute2_sysrepo.c +++ b/src/iproute2_sysrepo.c @@ -64,7 +64,7 @@ int force; int max_flush_loops = 10; int batch_mode; bool do_all; - +int vlan_rtm_cur_ifidx = -1; /* bridge definitions */ int compress_vlans; @@ -650,7 +650,6 @@ int apply_ipr2_cmd(char *ipr2_show_cmd) int ret; char **argv; int argc; - parse_command(ipr2_show_cmd, &argc, &argv); jump_set = 1; if (setjmp(jbuf)) { @@ -921,7 +920,6 @@ int sysrepo_start(int do_monitor) ++json; /* set iproute2 to format its print outputs in json */ ++show_details; /* set iproute2 to include details in its print outputs */ ++show_stats; /* set iproute2 to include stats in its print outputs */ - ret = sr_connect(SR_CONN_DEFAULT, &sr_connection); if (ret != SR_ERR_OK) { diff --git a/src/lib/cmdgen.c b/src/lib/cmdgen.c index b0107e9..70691c5 100644 --- a/src/lib/cmdgen.c +++ b/src/lib/cmdgen.c @@ -710,7 +710,6 @@ int create_cmd_arg_value(struct lyd_node *dnode, oper_t startcmd_op_val, char ** if (xpath_arg != NULL) { struct ly_set *match_set = NULL; int ret = lyd_find_xpath(dnode, xpath_arg, &match_set); - // lyd_find_xpath(dnode, "../../name", &match_set); if (match_set != NULL) { free(xpath_arg); xpath_arg = strdup(lyd_get_value(match_set->dnodes[0])); @@ -968,13 +967,13 @@ char *lyd2cmdline_args(const struct lyd_node *startcmd_node, oper_t op_val) /** * @brief this will fetch the startcmd_node from sysrepo, and merge it with the change node - * this will result on a replace on update change node. + * this is used by include_all_by_update extension. * @param dnode the change lyd_node. * @return EXIST_SUCCESS * @return EXIST_FAILURE */ -int ext_onupdate_replace_hdlr(struct lyd_node **dnode) +int ext_onupdate_include_all_hdlr(struct lyd_node **dnode) { int ret; // get the original node from sysrepo @@ -1029,7 +1028,7 @@ char *lyd2cmd_line(struct lyd_node *startcmd_node, char *oper2cmd_prefix[3]) if (op_val == UPDATE_OPR && (get_extension(INCLUDE_ALL_ON_UPDATE_EXT, startcmd_node, NULL) == EXIT_SUCCESS)) { - ext_onupdate_replace_hdlr(&startcmd_node); + ext_onupdate_include_all_hdlr(&startcmd_node); } // add cmd prefix to the cmd_line strlcpy(cmd_line, oper2cmd_prefix[op_val], sizeof(cmd_line)); @@ -1233,44 +1232,6 @@ int add_cmd_info_core(struct cmd_info **cmds, int *cmd_idx, struct lyd_node *sta goto cleanup; } - // if this node is replace-on-update (where we need to delete and recreate the node in iproute2) - // first create a del_node with del operation then generate and add the command to the cmds before - // the original update cmd. - if (get_extension(REPLACE_ON_UPDATE_EXT, startcmd_node, NULL) == EXIT_SUCCESS && - get_operation(startcmd_node) == UPDATE_OPR) { - // - ret = lyd_dup_single(startcmd_node, NULL, LYD_DUP_RECURSIVE, &del_startcmd_node); - if (ret != LY_SUCCESS) { - fprintf( - stderr, - "%s: ipr2cgen:failed to create del_startcmd_node: failed to duplicate node %s: %s\n", - __func__, startcmd_node->schema->name, ly_strerrcode(ret)); - ret = EXIT_FAILURE; - goto cleanup; - } - // set the operation of the del_startcmd to delete. - struct lyd_meta *del_meta = lyd_find_meta(del_startcmd_node->meta, NULL, "yang:operation"); - ret = lyd_change_meta(del_meta, "delete"); - if (ret != LY_SUCCESS) { - fprintf( - stderr, - "%s: ipr2cgen:failed to add delete operation to del_startcmd_node for node %s: %s \n", - __func__, startcmd_node->schema->name, ly_strerrcode(ret)); - ret = EXIT_FAILURE; - goto cleanup; - } - char *del_cmd_line = NULL; - initialize_startcmdinfo(del_startcmd_node); - del_cmd_line = lyd2cmd_line(del_startcmd_node, oper2cmd_prefix); - if (del_cmd_line == NULL) { - fprintf(stderr, "%s: failed to generate cmd for del_startcmd node \"%s\" \n", __func__, - startcmd_node->schema->name); - ret = EXIT_FAILURE; - goto cleanup; - } - add_command(cmds, cmd_idx, del_cmd_line, del_cmd_line); - } - // special case: for inner start_cmd, where the operation need to be taken from parent node. oper_t op_val = get_operation(startcmd_node); if (op_val == UNKNOWN_OPR) { @@ -1308,6 +1269,46 @@ int add_cmd_info_core(struct cmd_info **cmds, int *cmd_idx, struct lyd_node *sta } } + // if this node is replace-on-update (where we need to delete and recreate the node in iproute2) + // first create a del_node with del operation then generate and add the command to the cmds before + // the original update cmd. + if (get_extension(REPLACE_ON_UPDATE_EXT, startcmd_node, NULL) == EXIT_SUCCESS && + get_operation(startcmd_node) == UPDATE_OPR) { + // + ret = lyd_dup_single(startcmd_node, NULL, LYD_DUP_RECURSIVE | LYD_DUP_WITH_PARENTS, + &del_startcmd_node); + if (ret != LY_SUCCESS) { + fprintf( + stderr, + "%s: ipr2cgen:failed to create del_startcmd_node: failed to duplicate node %s: %s\n", + __func__, startcmd_node->schema->name, ly_strerrcode(ret)); + ret = EXIT_FAILURE; + goto cleanup; + } + // set the operation of the del_startcmd to delete. + struct lyd_meta *del_meta = lyd_find_meta(del_startcmd_node->meta, NULL, "yang:operation"); + ret = lyd_change_meta(del_meta, "delete"); + if (ret != LY_SUCCESS) { + fprintf( + stderr, + "%s: ipr2cgen:failed to add delete operation to del_startcmd_node for node %s: %s \n", + __func__, startcmd_node->schema->name, ly_strerrcode(ret)); + ret = EXIT_FAILURE; + goto cleanup; + } + char *del_cmd_line = NULL; + initialize_startcmdinfo(del_startcmd_node); + + del_cmd_line = lyd2cmd_line(del_startcmd_node, oper2cmd_prefix); + if (del_cmd_line == NULL) { + fprintf(stderr, "%s: failed to generate cmd for del_startcmd node \"%s\" \n", __func__, + startcmd_node->schema->name); + ret = EXIT_FAILURE; + goto cleanup; + } + add_command(cmds, cmd_idx, del_cmd_line, del_cmd_line); + } + // before calling diff_reserve we need to do dup_single, otherwise all sibling startcmds, // will be reversed struct lyd_node *tmp_startcmd; diff --git a/src/lib/cmdgen.h b/src/lib/cmdgen.h index 62c0132..a09a11e 100644 --- a/src/lib/cmdgen.h +++ b/src/lib/cmdgen.h @@ -26,7 +26,7 @@ struct cmd_info { void free_cmds_info(struct cmd_info **cmds_info); /** - * parse the command line and convert it to argc, argv + * FOR_OPER: parse the command line and convert it to argc, argv * @param [in] command command line string "ip link add ..." * @param [out] argc parsed argv count * @param [out] argv parsed argv @@ -34,7 +34,7 @@ void free_cmds_info(struct cmd_info **cmds_info); void parse_command(const char *command, int *argc, char ***argv); /** - * this func insert the netns name in the cmd, for example if cmd = ip link, and netns = "red" + * FOR_OPER: this func insert the netns name in the cmd, for example if cmd = ip link, and netns = "red" * the cmd will become "ip -n red link", * @param cmd * @param netns diff --git a/src/lib/oper_data.c b/src/lib/oper_data.c index 81172f0..3b73256 100644 --- a/src/lib/oper_data.c +++ b/src/lib/oper_data.c @@ -24,6 +24,7 @@ char *net_namespace; typedef enum { // leaf extensions OPER_CMD_EXT, + OPER_INNER_CMD_EXT, OPER_ARG_NAME_EXT, OPER_VALUE_MAP_EXT, OPER_FLAG_MAP_EXT, @@ -36,6 +37,7 @@ typedef enum { /* to be merged with cmdgen */ char *oper_yang_ext_map[] = { [OPER_CMD_EXT] = "oper-cmd", + [OPER_INNER_CMD_EXT] = "oper-inner-cmd", [OPER_ARG_NAME_EXT] = "oper-arg-name", [OPER_VALUE_MAP_EXT] = "oper-value-map", [OPER_FLAG_MAP_EXT] = "oper-flag-map", @@ -73,8 +75,8 @@ void free_list_params(const char ***key_values, uint32_t **values_lengths, int k * @param [out] found_obj: JSON object to store the found key object. * @return json_bool, returns 1 if key is found, 0 if not. */ -JSON_EXPORT json_bool find_json_value_by_key(struct json_object *jobj, const char *key, - struct json_object **found_obj) +json_bool find_json_value_by_key(struct json_object *jobj, const char *key, + struct json_object **found_obj) { enum json_type jtype = json_object_get_type(jobj); @@ -891,6 +893,15 @@ void jdata_to_list(struct json_object *json_obj, const char *arg_name, const struct lysc_node *s_node, uint16_t lys_flags, struct lyd_node **parent_data_node) { + // json_obj itself is an array of objects + if (json_object_get_type(json_obj) == json_type_array) { + size_t n_arrays = json_object_array_length(json_obj); + for (size_t i = 0; i < n_arrays; i++) { + struct json_object *arr_obj = json_object_array_get_idx(json_obj, i); + jdata_to_list(arr_obj, arg_name, s_node, lys_flags, parent_data_node); + } + } + // json_obj itself is single object but its content is an array struct json_object *lists_arrays; if (find_json_value_by_key(json_obj, arg_name, &lists_arrays) && json_object_get_type(lists_arrays) == json_type_array) { @@ -900,7 +911,7 @@ void jdata_to_list(struct json_object *json_obj, const char *arg_name, single_jobj_to_list2(list_obj, parent_data_node, s_node, lys_flags); } } else { - // Handle case where json_array_obj itself is the list to process + // json_obj itself is single object and the object content is the list leafs single_jobj_to_list2(json_obj, parent_data_node, s_node, lys_flags); } } @@ -987,7 +998,24 @@ int process_node(const struct lysc_node *s_node, json_object *json_obj, uint16_t break; } case LYS_LIST: { - jdata_to_list(json_obj, arg_name, s_node, lys_flags, parent_data_node); + char *sub_jobj_name = NULL; + json_object *list_jobj = NULL; + if (get_lys_extension(OPER_SUB_JOBJ_EXT, s_node, &sub_jobj_name) == EXIT_SUCCESS) { + if (sub_jobj_name == NULL) { + fprintf(stderr, + "%s: ipr2cgen:oper-sub-job extension found but failed to " + "get the sub json object name value for node \"%s\"\n", + __func__, s_node->name); + return EXIT_FAILURE; + } + } + if (sub_jobj_name != NULL) { + find_json_value_by_key(json_obj, sub_jobj_name, &list_jobj); + } else { + list_jobj = json_obj; + } + free(sub_jobj_name); + jdata_to_list(list_jobj, arg_name, s_node, lys_flags, parent_data_node); break; } case LYS_CHOICE: @@ -1024,8 +1052,46 @@ int process_node(const struct lysc_node *s_node, json_object *json_obj, uint16_t return EXIT_SUCCESS; } +int merge_json_by_key(struct json_object *dest_jobj, struct json_object *src_array, char *key, + char *include_key) +{ + // Get the key value from the outer JSON object + const char *outter_jkey = json_object_get_string(json_object_object_get(dest_jobj, key)); + if (!outter_jkey) { + return -1; // Handle the case where the key is not found + } + + // Iterate through the inner array + size_t inner_array_length = json_object_array_length(src_array); + for (size_t i = 0; i < inner_array_length; i++) { + struct json_object *inner_obj = json_object_array_get_idx(src_array, i); + const char *inner_jkey = json_object_get_string(json_object_object_get(inner_obj, key)); + + // If keys match, merge the objects + if (strcmp(outter_jkey, inner_jkey) == 0) { + struct json_object *include_obj = json_object_object_get(inner_obj, include_key); + if (!include_obj) { + return -1; // Handle the case where the include_key is not found + } + + // Deep copy the include_obj + struct json_object *include_obj_copy = NULL; + json_object_deep_copy(include_obj, &include_obj_copy, NULL); + if (!include_obj_copy) { + return -1; // Handle deep copy failure + } + + // Add the copied object to the outer JSON object + json_object_object_add(dest_jobj, include_key, include_obj_copy); + break; + } + } + + return 0; +} + /** - * Starts the porcessing of module schema, it processes every node in the schema to lyd_node if the node name + * Starts the processing of module schema, it processes every node in the schema to lyd_node if the node name * is found in the input json_obj. * First it looks for show commands to apply as it iterates through the schema node, then it stores the outputs * into json_obj, and starts processing the schema nodes based on the content of that json_obj. @@ -1039,8 +1105,10 @@ int process_node(const struct lysc_node *s_node, json_object *json_obj, uint16_t int process_schema(const struct lysc_node *s_node, uint16_t lys_flags, struct lyd_node **parent_data_node) { - char *show_cmd = "NULL"; + char *show_cmd = NULL; + char *json_buffer_cpy = NULL; struct json_object *cmd_output = NULL; + /* Create top-level lyd_node */ if (*parent_data_node == NULL) { // Top-level node lyd_new_inner(NULL, s_node->module, s_node->name, 0, parent_data_node); @@ -1074,16 +1142,43 @@ int process_schema(const struct lysc_node *s_node, uint16_t lys_flags, } free(show_cmd); - cmd_output = json_tokener_parse(json_buffer); + json_buffer_cpy = strdup(json_buffer); + cmd_output = json_tokener_parse(json_buffer_cpy); + + struct json_object *inner_cmd_output = NULL; + char *inner_show_cmd, *json_buffer_inner_cpy = NULL; + if (get_lys_extension(OPER_INNER_CMD_EXT, s_node, &inner_show_cmd) == EXIT_SUCCESS) { + if (inner_show_cmd == NULL) { + fprintf(stderr, "%s: failed to get inner_show_cmd ext argument for node = %s\n", + __func__, s_node->name); + return EXIT_FAILURE; + } + if (apply_ipr2_cmd(inner_show_cmd) != EXIT_SUCCESS) { + fprintf(stderr, "%s: command execution failed\n", __func__); + return EXIT_FAILURE; + } + free(inner_show_cmd); + json_buffer_inner_cpy = strdup(json_buffer); + inner_cmd_output = json_tokener_parse(json_buffer_inner_cpy); + } if (json_object_get_type(cmd_output) == json_type_array) { // Iterate over json_obj arrays: size_t n_arrays = json_object_array_length(cmd_output); for (size_t i = 0; i < n_arrays; i++) { struct json_object *array_obj = json_object_array_get_idx(cmd_output, i); + + if (inner_cmd_output) { + merge_json_by_key(array_obj, inner_cmd_output, "ifname", "vlans"); + } process_node(s_node, array_obj, lys_flags, parent_data_node); } } + if (inner_cmd_output) + json_object_put(inner_cmd_output); + if (json_buffer_inner_cpy) + free(json_buffer_inner_cpy); + } else { const struct lysc_node *s_child; LY_LIST_FOR(lysc_node_child(s_node), s_child) @@ -1093,6 +1188,8 @@ int process_schema(const struct lysc_node *s_node, uint16_t lys_flags, } if (cmd_output) json_object_put(cmd_output); + if (json_buffer_cpy) + free(json_buffer_cpy); return EXIT_SUCCESS; } diff --git a/yang/iproute2-cmdgen-extensions.yang b/yang/iproute2-cmdgen-extensions.yang index b8d367b..c89bee0 100644 --- a/yang/iproute2-cmdgen-extensions.yang +++ b/yang/iproute2-cmdgen-extensions.yang @@ -114,6 +114,11 @@ module iproute2-cmdgen-extensions { ir2cmdgen:oper-cmd \"ip link show type vrf\""; argument "show_cmd"; } + extension oper-inner-cmd { + description "define node which a new show cmd is needed, typically used in the first list in the schema, example + ir2cmdgen:oper-cmd \"ip link show type vrf\""; + argument "show_cmd"; + } extension flag { description diff --git a/yang/iproute2-ip-link.yang b/yang/iproute2-ip-link.yang index 081c920..6f80c55 100644 --- a/yang/iproute2-ip-link.yang +++ b/yang/iproute2-ip-link.yang @@ -537,10 +537,11 @@ module iproute2-ip-link { list vlan { ipr2cgen:cmd-start; ipr2cgen:cmd-add "bridge vlan add"; - ipr2cgen:cmd-update "bridge vlan set"; + ipr2cgen:cmd-update "bridge vlan add"; ipr2cgen:cmd-delete "bridge vlan delete"; - ipr2cgen:oper-cmd "bridge vlan show dev (../../name)"; // still need to be supported in oper_data.c - ipr2cgen:include-all-on-delete; + ipr2cgen:replace-on-update; + ipr2cgen:include-all-on-delete; + ipr2cgen:oper-sub-jobj "vlans"; must "(../../master and /links/bridge[name=current()/../../master]) or (/links/bridge[name=current()/../../name])" { error-message "please set the link 'master' to a bridge, before configuring bridge vlan"; } @@ -555,6 +556,7 @@ module iproute2-ip-link { } leaf pvid { ipr2cgen:flag; + ipr2cgen:oper-arg-name "flags"; ipr2cgen:oper-flag-map "PVID:true;FLAG-UNSET:false"; type boolean; description @@ -563,6 +565,7 @@ module iproute2-ip-link { } leaf untagged { ipr2cgen:flag; + ipr2cgen:oper-arg-name "flags"; ipr2cgen:oper-flag-map "Egress Untagged:true;FLAG-UNSET:false"; type boolean; description @@ -570,6 +573,7 @@ module iproute2-ip-link { } leaf self { ipr2cgen:flag; + ipr2cgen:oper-arg-name "flags"; ipr2cgen:oper-flag-map "SELF:true;FLAG-UNSET:false"; type boolean; description @@ -578,6 +582,7 @@ module iproute2-ip-link { } leaf master { ipr2cgen:flag; + ipr2cgen:oper-arg-name "flags"; ipr2cgen:oper-flag-map "MASTER:true;FLAG-UNSET:false"; type boolean; description @@ -917,6 +922,7 @@ module iproute2-ip-link { ipr2cgen:cmd-delete "ip link delete"; ipr2cgen:cmd-start; ipr2cgen:oper-cmd "ip address show"; + ipr2cgen:oper-inner-cmd "bridge vlan show"; ipr2cgen:oper-stop-if "{\"info_kind\": [\"vti\", \"vlan\", \"vrf\", \"bond\", \"bridge\", \"vxlan\", \"gre\", \"sit\"], \"link_type\": [\"tunnel6\",\"loopback\"]}"; key "name"; @@ -1005,6 +1011,7 @@ module iproute2-ip-link { ipr2cgen:cmd-delete "ip link delete"; ipr2cgen:cmd-start; ipr2cgen:oper-cmd "ip address show type vlan"; + ipr2cgen:oper-inner-cmd "bridge vlan show"; key "name"; leaf name { ipr2cgen:oper-arg-name "ifname"; @@ -1109,6 +1116,7 @@ module iproute2-ip-link { ipr2cgen:cmd-delete "ip link delete"; ipr2cgen:cmd-start; ipr2cgen:oper-cmd "ip address show type vxlan"; + ipr2cgen:oper-inner-cmd "bridge vlan show"; key "name"; leaf name { ipr2cgen:oper-arg-name "ifname"; @@ -1310,6 +1318,7 @@ module iproute2-ip-link { ipr2cgen:cmd-update "ip link set"; ipr2cgen:cmd-delete "ip link delete"; ipr2cgen:oper-cmd "ip address show type bridge"; + ipr2cgen:oper-inner-cmd "bridge vlan show"; key "name"; leaf name { ipr2cgen:oper-arg-name "ifname"; @@ -1451,6 +1460,7 @@ module iproute2-ip-link { ipr2cgen:cmd-delete "ip link delete"; ipr2cgen:cmd-start; ipr2cgen:oper-cmd "ip address show type gre"; + ipr2cgen:oper-inner-cmd "bridge vlan show"; key "name"; leaf name { ipr2cgen:oper-arg-name "ifname"; @@ -1556,6 +1566,7 @@ module iproute2-ip-link { ipr2cgen:cmd-update "ip link set"; ipr2cgen:cmd-delete "ip link delete"; ipr2cgen:oper-cmd "ip address show type bond"; + ipr2cgen:oper-inner-cmd "bridge vlan show"; key "name"; leaf name { ipr2cgen:oper-arg-name "ifname";