1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /* Configuration generator.
3 * Copyright (C) 2000 Kunihiro Ishiguro
14 #include "vtysh/vtysh.h"
15 #include "vtysh/vtysh_user.h"
17 DEFINE_MGROUP(MVTYSH
, "vtysh");
18 DEFINE_MTYPE_STATIC(MVTYSH
, VTYSH_CONFIG
, "Vtysh configuration");
19 DEFINE_MTYPE_STATIC(MVTYSH
, VTYSH_CONFIG_LINE
, "Vtysh configuration line");
23 PREDECL_LIST(config_master
);
24 PREDECL_HASH(config_master_hash
);
27 /* Configuration node name. */
30 /* Configuration string line. */
33 /* Configuration can be nested. */
34 struct config
*parent
;
40 /* Index of this config. */
43 /* Node entry for the typed Red-black tree */
44 struct config_master_item rbt_item
;
45 struct config_master_hash_item hash_item
;
48 struct list
*config_top
;
50 static int line_cmp(char *c1
, char *c2
)
52 return strcmp(c1
, c2
);
55 static void line_del(char *line
)
57 XFREE(MTYPE_VTYSH_CONFIG_LINE
, line
);
60 static struct config
*config_new(void)
62 struct config
*config
;
63 config
= XCALLOC(MTYPE_VTYSH_CONFIG
, sizeof(struct config
));
67 static void config_del(struct config
*config
)
69 vector_free(config
->nested
);
70 list_delete(&config
->line
);
72 XFREE(MTYPE_VTYSH_CONFIG_LINE
, config
->exit
);
73 XFREE(MTYPE_VTYSH_CONFIG_LINE
, config
->name
);
74 XFREE(MTYPE_VTYSH_CONFIG
, config
);
77 static int config_cmp(const struct config
*c1
, const struct config
*c2
)
79 return strcmp(c1
->name
, c2
->name
);
82 static uint32_t config_hash(const struct config
*c
)
84 return string_hash_make(c
->name
);
87 DECLARE_LIST(config_master
, struct config
, rbt_item
);
88 DECLARE_HASH(config_master_hash
, struct config
, hash_item
, config_cmp
,
92 * The config_master_head is a list for order of receipt
93 * The hash is for quick lookup under this NODE
95 struct configuration
{
96 struct config_master_head master
;
97 struct config_master_hash_head hash_master
;
100 static struct config
*config_get_vec(vector vec
, int index
, const char *line
)
102 struct config
*config
, *config_loop
;
103 struct configuration
*configuration
;
104 struct config lookup
;
106 config
= config_loop
= NULL
;
108 configuration
= vector_lookup_ensure(vec
, index
);
110 if (!configuration
) {
111 configuration
= XMALLOC(MTYPE_VTYSH_CONFIG
,
112 sizeof(struct configuration
));
113 config_master_init(&configuration
->master
);
114 config_master_hash_init(&configuration
->hash_master
);
115 vector_set_index(vec
, index
, configuration
);
118 lookup
.name
= (char *)line
;
119 config
= config_master_hash_find(&configuration
->hash_master
, &lookup
);
122 config
= config_new();
123 config
->line
= list_new();
124 config
->line
->del
= (void (*)(void *))line_del
;
125 config
->line
->cmp
= (int (*)(void *, void *))line_cmp
;
126 config
->name
= XSTRDUP(MTYPE_VTYSH_CONFIG_LINE
, line
);
128 config
->index
= index
;
129 config
->nested
= vector_init(1);
130 config_master_add_tail(&configuration
->master
, config
);
131 config_master_hash_add(&configuration
->hash_master
, config
);
136 static struct config
*config_get(int index
, const char *line
)
138 return config_get_vec(configvec
, index
, line
);
141 static struct config
*config_get_nested(struct config
*parent
, int index
,
144 struct config
*config
;
146 config
= config_get_vec(parent
->nested
, index
, line
);
147 config
->parent
= parent
;
152 void config_add_line(struct list
*config
, const char *line
)
154 listnode_add(config
, XSTRDUP(MTYPE_VTYSH_CONFIG_LINE
, line
));
157 static void config_add_line_uniq(struct list
*config
, const char *line
)
159 struct listnode
*node
, *nnode
;
162 for (ALL_LIST_ELEMENTS(config
, node
, nnode
, pnt
)) {
163 if (strcmp(pnt
, line
) == 0)
166 listnode_add_sort(config
, XSTRDUP(MTYPE_VTYSH_CONFIG_LINE
, line
));
170 * Add a line that should only be shown once, and always show at the end of the
173 * If the line already exists, it will be moved to the end of the block. If it
174 * does not exist, it will be added at the end of the block.
176 * Note that this only makes sense when there is just one such line that should
177 * show up at the very end of a config block. Furthermore, if the same block
178 * can show up from multiple daemons, all of them must make sure to print the
179 * line at the end of their config, otherwise the line will show at the end of
180 * the config for the last daemon that printed it.
182 * Here is a motivating example with the 'exit-vrf' command. Suppose we receive
183 * a config from Zebra like so:
190 * Then suppose we later receive this config from PIM:
193 * ip msdp mesh-group MyGroup member 1.2.3.4
196 * Then we will combine them into one config block like so:
201 * ip msdp mesh-group MyGroup member 1.2.3.4
204 * Because PIM also sent us an 'exit-vrf', we noticed that we already had one
205 * under the 'vrf BLUE' config block and so we moved it to the end of the
206 * config block again. If PIM had neglected to send us 'exit-vrf', the result
213 * ip msdp mesh-group MyGroup member 1.2.3.4
215 * Therefore, daemons that share config blocks must take care to consistently
216 * print the same block terminators.
218 * Ideally this would be solved by adding a string to struct config that is
219 * always printed at the end when dumping a config. However, this would only
220 * work when the user is using integrated config. In the non-integrated config
221 * case, daemons are responsible for writing their own config files, and so the
222 * must be able to print these blocks correctly independently of vtysh, which
223 * means they are the ones that need to handle printing the block terminators
224 * and VTYSH needs to be smart enough to combine them properly.
229 * The config to add the line to
232 * The line to add to the end of the config
234 static void config_add_line_uniq_end(struct list
*config
, const char *line
)
236 struct listnode
*node
;
239 for (ALL_LIST_ELEMENTS_RO(config
, node
, pnt
)) {
240 if (strcmp(pnt
, line
) == 0)
245 config_add_line(config
, line
);
247 listnode_move_to_tail(config
, node
);
250 static void config_add_line_head(struct list
*config
, const char *line
)
252 listnode_add_head(config
, XSTRDUP(MTYPE_VTYSH_CONFIG_LINE
, line
));
255 void vtysh_config_parse_line(void *arg
, const char *line
)
258 static struct config
*config
= NULL
;
269 /* Suppress exclamation points ! and commented lines. The !s are
271 * dynamically in vtysh_config_dump() */
276 /* Store line to current configuration. */
278 if (config
->index
== KEYCHAIN_NODE
279 && strncmp(line
, " key", strlen(" key")) == 0) {
280 config
= config_get_nested(
281 config
, KEYCHAIN_KEY_NODE
, line
);
282 } else if (config
->index
== KEYCHAIN_KEY_NODE
) {
283 if (strncmp(line
, " exit", strlen(" exit"))
285 config_add_line_uniq_end(config
->line
,
287 config
= config
->parent
;
289 config_add_line_uniq(config
->line
,
292 } else if (strncmp(line
, " link-params",
293 strlen(" link-params"))
295 config_add_line(config
->line
, line
);
296 config
->index
= LINK_PARAMS_NODE
;
297 } else if (strncmp(line
, " ip multicast boundary",
298 strlen(" ip multicast boundary"))
300 config_add_line_uniq_end(config
->line
, line
);
301 } else if (strncmp(line
, " ip igmp query-interval",
302 strlen(" ip igmp query-interval"))
304 config_add_line_uniq_end(config
->line
, line
);
305 } else if (config
->index
== LINK_PARAMS_NODE
306 && strncmp(line
, " exit-link-params",
309 config_add_line(config
->line
, line
);
310 config
->index
= INTERFACE_NODE
;
311 } else if (!strncmp(line
, " vrrp", strlen(" vrrp"))
312 || !strncmp(line
, " no vrrp",
313 strlen(" no vrrp"))) {
314 config_add_line(config
->line
, line
);
315 } else if (!strncmp(line
, " ip mroute",
316 strlen(" ip mroute"))) {
317 config_add_line_uniq_end(config
->line
, line
);
318 } else if (config
->index
== RMAP_NODE
||
319 config
->index
== INTERFACE_NODE
||
320 config
->index
== VTY_NODE
)
321 config_add_line_uniq(config
->line
, line
);
322 else if (config
->index
== NH_GROUP_NODE
) {
323 if (strncmp(line
, " resilient",
324 strlen(" resilient")) == 0)
325 config_add_line_head(config
->line
,
328 config_add_line_uniq_end(config
->line
,
331 config_add_line(config
->line
, line
);
333 config_add_line(config_top
, line
);
336 if (strncmp(line
, "exit", strlen("exit")) == 0) {
339 XFREE(MTYPE_VTYSH_CONFIG_LINE
,
342 XSTRDUP(MTYPE_VTYSH_CONFIG_LINE
, line
);
344 } else if (strncmp(line
, "interface", strlen("interface")) == 0)
345 config
= config_get(INTERFACE_NODE
, line
);
346 else if (strncmp(line
, "pseudowire", strlen("pseudowire")) == 0)
347 config
= config_get(PW_NODE
, line
);
348 else if (strncmp(line
, "vrf", strlen("vrf")) == 0)
349 config
= config_get(VRF_NODE
, line
);
350 else if (strncmp(line
, "nexthop-group", strlen("nexthop-group"))
352 config
= config_get(NH_GROUP_NODE
, line
);
353 else if (strncmp(line
, "router-id", strlen("router-id")) == 0)
354 config
= config_get(ZEBRA_NODE
, line
);
355 else if (strncmp(line
, "router rip", strlen("router rip")) == 0)
356 config
= config_get(RIP_NODE
, line
);
357 else if (strncmp(line
, "router ripng", strlen("router ripng"))
359 config
= config_get(RIPNG_NODE
, line
);
360 else if (strncmp(line
, "router eigrp", strlen("router eigrp"))
362 config
= config_get(EIGRP_NODE
, line
);
363 else if (strncmp(line
, "router babel", strlen("router babel"))
365 config
= config_get(BABEL_NODE
, line
);
366 else if (strncmp(line
, "router ospf", strlen("router ospf"))
368 config
= config_get(OSPF_NODE
, line
);
369 else if (strncmp(line
, "router ospf6", strlen("router ospf6"))
371 config
= config_get(OSPF6_NODE
, line
);
372 else if (strncmp(line
, "mpls ldp", strlen("mpls ldp")) == 0)
373 config
= config_get(LDP_NODE
, line
);
374 else if (strncmp(line
, "l2vpn", strlen("l2vpn")) == 0)
375 config
= config_get(LDP_L2VPN_NODE
, line
);
376 else if (strncmp(line
, "router bgp", strlen("router bgp")) == 0)
377 config
= config_get(BGP_NODE
, line
);
378 else if (strncmp(line
, "router isis", strlen("router isis"))
380 config
= config_get(ISIS_NODE
, line
);
381 else if (strncmp(line
, "router openfabric", strlen("router openfabric"))
383 config
= config_get(OPENFABRIC_NODE
, line
);
384 else if (strncmp(line
, "affinity-map",
385 strlen("affinity-map")) == 0)
386 config
= config_get(AFFMAP_NODE
, line
);
387 else if (strncmp(line
, "route-map", strlen("route-map")) == 0)
388 config
= config_get(RMAP_NODE
, line
);
389 else if (strncmp(line
, "no route-map", strlen("no route-map"))
391 config
= config_get(RMAP_NODE
, line
);
392 else if (strncmp(line
, "pbr-map", strlen("pbr-map")) == 0)
393 config
= config_get(PBRMAP_NODE
, line
);
394 else if (strncmp(line
, "access-list", strlen("access-list"))
396 config
= config_get(ACCESS_NODE
, line
);
397 else if (strncmp(line
, "ipv6 access-list",
398 strlen("ipv6 access-list"))
400 config
= config_get(ACCESS_IPV6_NODE
, line
);
401 else if (strncmp(line
, "mac access-list",
402 strlen("mac access-list"))
404 config
= config_get(ACCESS_MAC_NODE
, line
);
405 else if (strncmp(line
, "ip prefix-list",
406 strlen("ip prefix-list"))
408 config
= config_get(PREFIX_NODE
, line
);
409 else if (strncmp(line
, "ipv6 prefix-list",
410 strlen("ipv6 prefix-list"))
412 config
= config_get(PREFIX_IPV6_NODE
, line
);
413 else if (strncmp(line
, "bgp as-path access-list",
414 strlen("bgp as-path access-list"))
416 config
= config_get(AS_LIST_NODE
, line
);
417 else if (strncmp(line
, "bgp community-list",
418 strlen("bgp community-list"))
420 || strncmp(line
, "bgp extcommunity-list",
421 strlen("bgp extcommunity-list"))
423 || strncmp(line
, "bgp large-community-list",
424 strlen("bgp large-community-list"))
426 config
= config_get(COMMUNITY_LIST_NODE
, line
);
427 else if (strncmp(line
, "bgp community alias",
428 strlen("bgp community alias")) == 0)
429 config
= config_get(COMMUNITY_ALIAS_NODE
, line
);
430 else if (strncmp(line
, "ip route", strlen("ip route")) == 0)
431 config
= config_get(IP_NODE
, line
);
432 else if (strncmp(line
, "ipv6 route", strlen("ipv6 route")) == 0)
433 config
= config_get(IP_NODE
, line
);
434 else if (strncmp(line
, "key", strlen("key")) == 0)
435 config
= config_get(KEYCHAIN_NODE
, line
);
436 else if (strncmp(line
, "line", strlen("line")) == 0)
437 config
= config_get(VTY_NODE
, line
);
438 else if ((strncmp(line
, "ipv6 forwarding",
439 strlen("ipv6 forwarding"))
441 || (strncmp(line
, "ip forwarding",
442 strlen("ip forwarding"))
444 config
= config_get(FORWARDING_NODE
, line
);
445 else if (strncmp(line
, "debug vrf", strlen("debug vrf")) == 0)
446 config
= config_get(VRF_DEBUG_NODE
, line
);
447 else if (strncmp(line
, "debug northbound",
448 strlen("debug northbound"))
450 config
= config_get(NORTHBOUND_DEBUG_NODE
, line
);
451 else if (strncmp(line
, "debug route-map",
452 strlen("debug route-map"))
454 config
= config_get(RMAP_DEBUG_NODE
, line
);
455 else if (strncmp(line
, "debug resolver",
456 strlen("debug resolver")) == 0)
457 config
= config_get(RESOLVER_DEBUG_NODE
, line
);
458 else if (strncmp(line
, "debug", strlen("debug")) == 0)
459 config
= config_get(DEBUG_NODE
, line
);
460 else if (strncmp(line
, "password", strlen("password")) == 0
461 || strncmp(line
, "enable password",
462 strlen("enable password"))
464 config
= config_get(AAA_NODE
, line
);
465 else if (strncmp(line
, "ip protocol", strlen("ip protocol"))
467 config
= config_get(PROTOCOL_NODE
, line
);
468 else if (strncmp(line
, "ipv6 protocol", strlen("ipv6 protocol"))
470 config
= config_get(PROTOCOL_NODE
, line
);
471 else if (strncmp(line
, "ip nht", strlen("ip nht")) == 0)
472 config
= config_get(PROTOCOL_NODE
, line
);
473 else if (strncmp(line
, "ipv6 nht", strlen("ipv6 nht")) == 0)
474 config
= config_get(PROTOCOL_NODE
, line
);
475 else if (strncmp(line
, "mpls", strlen("mpls")) == 0)
476 config
= config_get(MPLS_NODE
, line
);
477 else if (strncmp(line
, "segment-routing",
478 strlen("segment-routing"))
480 config
= config_get(SEGMENT_ROUTING_NODE
, line
);
481 else if (strncmp(line
, "bfd", strlen("bfd")) == 0)
482 config
= config_get(BFD_NODE
, line
);
483 else if (strncmp(line
, "rpki", strlen("rpki")) == 0)
484 config
= config_get(RPKI_NODE
, line
);
486 if (strncmp(line
, "log", strlen("log")) == 0 ||
487 strncmp(line
, "hostname", strlen("hostname")) ==
489 strncmp(line
, "domainname", strlen("domainname")) ==
491 strncmp(line
, "allow-reserved-ranges",
492 strlen("allow-reserved-ranges")) == 0 ||
493 strncmp(line
, "frr", strlen("frr")) == 0 ||
494 strncmp(line
, "agentx", strlen("agentx")) == 0 ||
495 strncmp(line
, "no log", strlen("no log")) == 0 ||
496 strncmp(line
, "no ip prefix-list",
497 strlen("no ip prefix-list")) == 0 ||
498 strncmp(line
, "no ipv6 prefix-list",
499 strlen("no ipv6 prefix-list")) == 0 ||
500 strncmp(line
, "service ", strlen("service ")) ==
502 strncmp(line
, "no service cputime-stats",
503 strlen("no service cputime-stats")) == 0 ||
504 strncmp(line
, "service cputime-warning",
505 strlen("service cputime-warning")) == 0)
506 config_add_line_uniq(config_top
, line
);
508 config_add_line(config_top
, line
);
515 /* Macro to check delimiter is needed between each configuration line
517 #define NO_DELIMITER(I) \
518 ((I) == AFFMAP_NODE || (I) == ACCESS_NODE || (I) == PREFIX_NODE || \
519 (I) == IP_NODE || (I) == AS_LIST_NODE || \
520 (I) == COMMUNITY_LIST_NODE || (I) == COMMUNITY_ALIAS_NODE || \
521 (I) == ACCESS_IPV6_NODE || (I) == ACCESS_MAC_NODE || \
522 (I) == PREFIX_IPV6_NODE || (I) == FORWARDING_NODE || \
523 (I) == DEBUG_NODE || (I) == AAA_NODE || (I) == VRF_DEBUG_NODE || \
524 (I) == NORTHBOUND_DEBUG_NODE || (I) == RMAP_DEBUG_NODE || \
525 (I) == RESOLVER_DEBUG_NODE || (I) == MPLS_NODE || \
526 (I) == KEYCHAIN_KEY_NODE)
528 static void configvec_dump(vector vec
, bool nested
)
530 struct listnode
*mnode
, *mnnode
;
531 struct config
*config
;
532 struct configuration
*configuration
;
536 for (i
= 0; i
< vector_active(vec
); i
++)
537 if ((configuration
= vector_slot(vec
, i
)) != NULL
) {
538 while ((config
= config_master_pop(
539 &configuration
->master
))) {
540 config_master_hash_del(
541 &configuration
->hash_master
, config
);
542 /* Don't print empty sections for interface.
544 * other hand could have a legitimate empty
545 * section at the end.
546 * VRF is handled in the backend, we could have
547 * "configured" VRFs with static routes which
548 * are not under the VRF node.
550 if (config
->index
== INTERFACE_NODE
551 && (listcount(config
->line
) == 1)
552 && (line
= listnode_head(config
->line
))
553 && strmatch(line
, "exit")) {
558 vty_out(vty
, "%s\n", config
->name
);
560 for (ALL_LIST_ELEMENTS(config
->line
, mnode
,
562 vty_out(vty
, "%s\n", line
);
564 configvec_dump(config
->nested
, true);
567 vty_out(vty
, "%s\n", config
->exit
);
569 if (!NO_DELIMITER(i
))
574 config_master_fini(&configuration
->master
);
575 config_master_hash_fini(&configuration
->hash_master
);
576 XFREE(MTYPE_VTYSH_CONFIG
, configuration
);
577 vector_slot(vec
, i
) = NULL
;
578 if (!nested
&& NO_DELIMITER(i
))
583 void vtysh_config_dump(void)
585 struct listnode
*node
, *nnode
;
588 for (ALL_LIST_ELEMENTS(config_top
, node
, nnode
, line
))
589 vty_out(vty
, "%s\n", line
);
591 list_delete_all_node(config_top
);
595 configvec_dump(configvec
, false);
598 /* Read up configuration file from file_name. */
599 static int vtysh_read_file(FILE *confp
, bool dry_run
)
605 vty
->wfd
= STDERR_FILENO
;
606 vty
->type
= VTY_TERM
;
607 vty
->node
= CONFIG_NODE
;
609 vtysh_execute_no_pager("enable");
610 vtysh_execute_no_pager("configure terminal");
613 vtysh_execute_no_pager("XFRR_start_configuration");
615 /* Execute configuration file. */
616 ret
= vtysh_config_from_file(vty
, confp
);
619 vtysh_execute_no_pager("XFRR_end_configuration");
621 vtysh_execute_no_pager("end");
622 vtysh_execute_no_pager("disable");
630 * Read configuration file and send it to all connected daemons
632 static int vtysh_read_config(const char *config_file_path
, bool dry_run
)
638 confp
= fopen(config_file_path
, "r");
641 "%% Can't open configuration file %s due to '%s'.\n",
642 config_file_path
, safe_strerror(errno
));
643 return CMD_ERR_NO_FILE
;
646 save
= vtysh_add_timestamp
;
647 vtysh_add_timestamp
= false;
649 ret
= vtysh_read_file(confp
, dry_run
);
652 vtysh_add_timestamp
= save
;
657 int vtysh_apply_config(const char *config_file_path
, bool dry_run
, bool do_fork
)
660 * We need to apply the whole config file to all daemons. Instead of
661 * having one client talk to N daemons, we fork N times and let each
662 * child handle one daemon.
664 pid_t fork_pid
= getpid();
671 for (unsigned int i
= 0; i
< array_size(vtysh_client
); i
++) {
672 /* Store name of client this fork will handle */
673 strlcpy(my_client
, vtysh_client
[i
].name
,
675 my_client_type
= vtysh_client
[i
].flag
;
678 /* If child, break */
683 /* parent, wait for children */
688 "Waiting for children to finish applying config...\n");
689 while (wait(&status
) > 0) {
690 if (!keep_status
&& WEXITSTATUS(status
))
691 keep_status
= WEXITSTATUS(status
);
695 * This will return the first status received
696 * that failed( if that happens ). This is
697 * good enough for the moment
703 * children, grow up to be cowboys
705 for (unsigned int i
= 0; i
< array_size(vtysh_client
); i
++) {
706 if (my_client_type
!= vtysh_client
[i
].flag
) {
707 struct vtysh_client
*cl
;
710 * If this is a client we aren't responsible
713 for (cl
= &vtysh_client
[i
]; cl
; cl
= cl
->next
) {
718 } else if (vtysh_client
[i
].fd
== -1 &&
719 vtysh_client
[i
].next
== NULL
) {
721 * If this is the client we are responsible
722 * for, but we aren't already connected to that
723 * client, that means the client isn't up in
724 * the first place and we can exit early
730 fprintf(stdout
, "[%d|%s] sending configuration\n", getpid(),
734 ret
= vtysh_read_config(config_file_path
, dry_run
);
739 "[%d|%s] Configuration file[%s] processing failure: %d\n",
740 getpid(), my_client
, frr_config
, ret
);
743 "Configuration file[%s] processing failure: %d\n",
745 } else if (do_fork
) {
746 fprintf(stderr
, "[%d|%s] done\n", getpid(), my_client
);
753 /* We don't write vtysh specific into file from vtysh. vtysh.conf should
754 * be edited by hand. So, we handle only "write terminal" case here and
755 * integrate vtysh specific conf with conf from daemons.
757 void vtysh_config_write(void)
762 name
= cmd_hostname_get();
763 if (name
&& name
[0] != '\0') {
764 snprintf(line
, sizeof(line
), "hostname %s", name
);
765 vtysh_config_parse_line(NULL
, line
);
768 name
= cmd_domainname_get();
769 if (name
&& name
[0] != '\0') {
770 snprintf(line
, sizeof(line
), "domainname %s", name
);
771 vtysh_config_parse_line(NULL
, line
);
774 if (vtysh_write_integrated
== WRITE_INTEGRATED_NO
)
775 vtysh_config_parse_line(NULL
,
776 "no service integrated-vtysh-config");
777 if (vtysh_write_integrated
== WRITE_INTEGRATED_YES
)
778 vtysh_config_parse_line(NULL
,
779 "service integrated-vtysh-config");
784 void vtysh_config_init(void)
786 config_top
= list_new();
787 config_top
->del
= (void (*)(void *))line_del
;
788 configvec
= vector_init(1);