diff options
43 files changed, 1094 insertions, 137 deletions
diff --git a/bgpd/bgp_debug.c b/bgpd/bgp_debug.c index bd5d94908e..3ecdc0d3cf 100644 --- a/bgpd/bgp_debug.c +++ b/bgpd/bgp_debug.c @@ -16,6 +16,7 @@ #include "memory.h" #include "queue.h" #include "filter.h" +#include "hook.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_aspath.h" @@ -37,6 +38,9 @@ #include "bgpd/bgp_debug_clippy.c" +DEFINE_HOOK(bgp_hook_config_write_debug, (struct vty *vty, bool running), + (vty, running)); + unsigned long conf_bgp_debug_as4; unsigned long conf_bgp_debug_neighbor_events; unsigned long conf_bgp_debug_events; @@ -2272,6 +2276,8 @@ DEFUN_NOSH (show_debugging_bgp, cmd_show_lib_debugs(vty); + hook_call(bgp_hook_config_write_debug, vty, false); + return CMD_SUCCESS; } @@ -2411,6 +2417,9 @@ static int bgp_config_write_debug(struct vty *vty) write++; } + if (hook_call(bgp_hook_config_write_debug, vty, true)) + write++; + return write; } diff --git a/bgpd/bgp_debug.h b/bgpd/bgp_debug.h index bb4ddea81e..5b09501852 100644 --- a/bgpd/bgp_debug.h +++ b/bgpd/bgp_debug.h @@ -6,9 +6,15 @@ #ifndef _QUAGGA_BGP_DEBUG_H #define _QUAGGA_BGP_DEBUG_H +#include "hook.h" +#include "vty.h" + #include "bgp_attr.h" #include "bgp_updgrp.h" +DECLARE_HOOK(bgp_hook_config_write_debug, (struct vty *vty, bool running), + (vty, running)); + /* sort of packet direction */ #define DUMP_ON 1 #define DUMP_SEND 2 diff --git a/bgpd/bgp_rpki.c b/bgpd/bgp_rpki.c index f0b2ffdee5..78083a83a0 100644 --- a/bgpd/bgp_rpki.c +++ b/bgpd/bgp_rpki.c @@ -33,6 +33,7 @@ #include "bgpd/bgp_aspath.h" #include "bgpd/bgp_route.h" #include "bgpd/bgp_rpki.h" +#include "bgpd/bgp_debug.h" #include "northbound_cli.h" #include "lib/network.h" @@ -56,14 +57,19 @@ DEFINE_MTYPE_STATIC(BGPD, BGP_RPKI_REVALIDATE, "BGP RPKI Revalidation"); static struct event *t_rpki_sync; #define RPKI_DEBUG(...) \ - if (rpki_debug) { \ + if (rpki_debug_conf || rpki_debug_term) { \ zlog_debug("RPKI: " __VA_ARGS__); \ } #define RPKI_OUTPUT_STRING "Control rpki specific settings\n" struct cache { - enum { TCP, SSH } type; + enum { + TCP, +#if defined(FOUND_SSH) + SSH +#endif + } type; struct tr_socket *tr_socket; union { struct tr_tcp_config *tcp_config; @@ -83,6 +89,7 @@ struct rpki_for_each_record_arg { enum asnotation_mode asnotation; }; +static int bgp_rpki_write_debug(struct vty *vty, bool running); static int start(void); static void stop(void); static int reset(bool force); @@ -124,7 +131,7 @@ static bool rtr_is_running; static bool rtr_is_stopping; static bool rtr_is_synced; static _Atomic int rtr_update_overflow; -static bool rpki_debug; +static bool rpki_debug_conf, rpki_debug_term; static unsigned int polling_period; static unsigned int expire_interval; static unsigned int retry_interval; @@ -593,7 +600,8 @@ err: static int bgp_rpki_init(struct event_loop *master) { - rpki_debug = false; + rpki_debug_conf = false; + rpki_debug_term = false; rtr_is_running = false; rtr_is_stopping = false; rtr_is_synced = false; @@ -627,6 +635,7 @@ static int bgp_rpki_module_init(void) hook_register(bgp_rpki_prefix_status, rpki_validate_prefix); hook_register(frr_late_init, bgp_rpki_init); hook_register(frr_early_fini, bgp_rpki_fini); + hook_register(bgp_hook_config_write_debug, &bgp_rpki_write_debug); return 0; } @@ -733,7 +742,10 @@ static void print_prefix_table_by_asn(struct vty *vty, as_t as, arg.asnotation = bgp_get_asnotation(bgp_lookup_by_vrf_id(VRF_DEFAULT)); if (!group) { - if (!json) + if (json) { + json_object_string_add(json, "error", "Cannot find a connected group."); + vty_json(vty, json); + } else vty_out(vty, "Cannot find a connected group.\n"); return; } @@ -786,7 +798,10 @@ static void print_prefix_table(struct vty *vty, json_object *json) arg.asnotation = bgp_get_asnotation(bgp_lookup_by_vrf_id(VRF_DEFAULT)); if (!group) { - if (!json) + if (json) { + json_object_string_add(json, "error", "Cannot find a connected group."); + vty_json(vty, json); + } else vty_out(vty, "Cannot find a connected group.\n"); return; } @@ -1036,13 +1051,30 @@ static void free_cache(struct cache *cache) XFREE(MTYPE_BGP_RPKI_CACHE, cache); } +static int bgp_rpki_write_debug(struct vty *vty, bool running) +{ + if (rpki_debug_conf && running) { + vty_out(vty, "debug rpki\n"); + return 1; + } + if ((rpki_debug_conf || rpki_debug_term) && !running) { + vty_out(vty, " BGP RPKI debugging is on\n"); + return 1; + } + return 0; +} + static int config_write(struct vty *vty) { struct listnode *cache_node; struct cache *cache; - if (rpki_debug) - vty_out(vty, "debug rpki\n"); + if (list_isempty(cache_list) && + polling_period == POLLING_PERIOD_DEFAULT && + retry_interval == RETRY_INTERVAL_DEFAULT && + expire_interval == EXPIRE_INTERVAL_DEFAULT) + /* do not display the default config values */ + return 0; vty_out(vty, "!\n"); vty_out(vty, "rpki\n"); @@ -1077,7 +1109,7 @@ static int config_write(struct vty *vty) ssh_config->client_privkey_path, ssh_config->server_hostkey_path != NULL ? ssh_config->server_hostkey_path - : " "); + : ""); if (ssh_config->bindaddr) vty_out(vty, "source %s ", ssh_config->bindaddr); @@ -1111,6 +1143,10 @@ DEFPY (no_rpki, { rpki_delete_all_cache_nodes(); stop(); + polling_period = POLLING_PERIOD_DEFAULT; + expire_interval = EXPIRE_INTERVAL_DEFAULT; + retry_interval = RETRY_INTERVAL_DEFAULT; + return CMD_SUCCESS; } @@ -1238,6 +1274,7 @@ DEFPY(rpki_cache, rpki_cache_cmd, int return_value; struct listnode *cache_node; struct cache *current_cache; + bool init = !!list_isempty(cache_list); for (ALL_LIST_ELEMENTS_RO(cache_list, cache_node, current_cache)) { if (current_cache->preference == preference) { @@ -1270,6 +1307,9 @@ DEFPY(rpki_cache, rpki_cache_cmd, return CMD_WARNING; } + if (init) + start(); + return CMD_SUCCESS; } @@ -1326,15 +1366,18 @@ DEFPY (show_rpki_prefix_table, { struct json_object *json = NULL; + if (uj) + json = json_object_new_object(); + if (!is_synchronized()) { - if (!uj) + if (json) { + json_object_string_add(json, "error", "No Connection to RPKI cache server."); + vty_json(vty, json); + } else vty_out(vty, "No connection to RPKI cache server.\n"); return CMD_WARNING; } - if (uj) - json = json_object_new_object(); - print_prefix_table(vty, json); return CMD_SUCCESS; } @@ -1350,15 +1393,18 @@ DEFPY (show_rpki_as_number, { struct json_object *json = NULL; + if (uj) + json = json_object_new_object(); + if (!is_synchronized()) { - if (!uj) + if (json) { + json_object_string_add(json, "error", "No Connection to RPKI cache server."); + vty_json(vty, json); + } else vty_out(vty, "No Connection to RPKI cache server.\n"); return CMD_WARNING; } - if (uj) - json = json_object_new_object(); - print_prefix_table_by_asn(vty, by_asn, json); return CMD_SUCCESS; } @@ -1378,8 +1424,14 @@ DEFPY (show_rpki_prefix, json_object *json_records = NULL; enum asnotation_mode asnotation; + if (uj) + json = json_object_new_object(); + if (!is_synchronized()) { - if (!uj) + if (json) { + json_object_string_add(json, "error", "No Connection to RPKI cache server."); + vty_json(vty, json); + } else vty_out(vty, "No Connection to RPKI cache server.\n"); return CMD_WARNING; } @@ -1392,7 +1444,10 @@ DEFPY (show_rpki_prefix, memcpy(addr_str, prefix_str, addr_len); if (lrtr_ip_str_to_addr(addr_str, &addr) != 0) { - if (!json) + if (json) { + json_object_string_add(json, "error", "Invalid IP prefix."); + vty_json(vty, json); + } else vty_out(vty, "Invalid IP prefix\n"); return CMD_WARNING; } @@ -1404,13 +1459,14 @@ DEFPY (show_rpki_prefix, if (pfx_table_validate_r(rtr_config->pfx_table, &matches, &match_count, asn, &addr, prefix->prefixlen, &result) != PFX_SUCCESS) { - if (!json) + if (json) { + json_object_string_add(json, "error", "Prefix lookup failed."); + vty_json(vty, json); + } else vty_out(vty, "Prefix lookup failed\n"); return CMD_WARNING; } - if (uj) - json = json_object_new_object(); if (!json) { vty_out(vty, "%-40s %s %s\n", "Prefix", "Prefix Length", @@ -1549,20 +1605,22 @@ DEFPY (show_rpki_cache_connection, json = json_object_new_object(); if (!is_synchronized()) { - if (!json) - vty_out(vty, "No connection to RPKI cache server.\n"); - else + if (json) { + json_object_string_add(json, "error", "No connection to RPKI cache server."); vty_json(vty, json); + } else + vty_out(vty, "No connection to RPKI cache server.\n"); return CMD_SUCCESS; } group = get_connected_group(); if (!group) { - if (!json) - vty_out(vty, "Cannot find a connected group.\n"); - else + if (json) { + json_object_string_add(json, "error", "Cannot find a connected group."); vty_json(vty, json); + } else + vty_out(vty, "Cannot find a connected group.\n"); return CMD_SUCCESS; } @@ -1656,6 +1714,48 @@ DEFPY (show_rpki_cache_connection, return CMD_SUCCESS; } +DEFPY(show_rpki_configuration, show_rpki_configuration_cmd, + "show rpki configuration [json$uj]", + SHOW_STR RPKI_OUTPUT_STRING + "Show RPKI configuration\n" + JSON_STR) +{ + struct json_object *json = NULL; + + if (uj) { + json = json_object_new_object(); + json_object_boolean_add(json, "enabled", + !!listcount(cache_list)); + json_object_int_add(json, "serversCount", listcount(cache_list)); + json_object_int_add(json, "pollingPeriodSeconds", + polling_period); + json_object_int_add(json, "retryIntervalSeconds", + retry_interval); + json_object_int_add(json, "expireIntervalSeconds", + expire_interval); + + vty_json(vty, json); + + return CMD_SUCCESS; + } + + vty_out(vty, "rpki is %s", + listcount(cache_list) ? "Enabled" : "Disabled"); + + if (list_isempty(cache_list)) { + vty_out(vty, "\n"); + return CMD_SUCCESS; + } + + vty_out(vty, " (%d cache servers configured)", listcount(cache_list)); + vty_out(vty, "\n"); + vty_out(vty, "\tpolling period %d\n", polling_period); + vty_out(vty, "\tretry interval %d\n", retry_interval); + vty_out(vty, "\texpire interval %d\n", expire_interval); + + return CMD_SUCCESS; +} + static int config_on_exit(struct vty *vty) { reset(false); @@ -1677,7 +1777,10 @@ DEFUN (debug_rpki, DEBUG_STR "Enable debugging for rpki\n") { - rpki_debug = true; + if (vty->node == CONFIG_NODE) + rpki_debug_conf = true; + else + rpki_debug_term = true; return CMD_SUCCESS; } @@ -1688,7 +1791,10 @@ DEFUN (no_debug_rpki, DEBUG_STR "Disable debugging for rpki\n") { - rpki_debug = false; + if (vty->node == CONFIG_NODE) + rpki_debug_conf = false; + else + rpki_debug_term = false; return CMD_SUCCESS; } @@ -1769,6 +1875,7 @@ static void install_cli_commands(void) install_element(VIEW_NODE, &show_rpki_cache_server_cmd); install_element(VIEW_NODE, &show_rpki_prefix_cmd); install_element(VIEW_NODE, &show_rpki_as_number_cmd); + install_element(VIEW_NODE, &show_rpki_configuration_cmd); /* Install debug commands */ install_element(CONFIG_NODE, &debug_rpki_cmd); diff --git a/doc/user/babeld.rst b/doc/user/babeld.rst index bda0045a60..b7b7c1fcb4 100644 --- a/doc/user/babeld.rst +++ b/doc/user/babeld.rst @@ -26,8 +26,7 @@ The *zebra* daemon must be running before *babeld* is invoked. Also, if *zebra* is restarted then *babeld* must be too. -Configuration of *babeld* is done in its configuration file -:file:`babeld.conf`. +.. include:: config-include.rst .. _babel-configuration: diff --git a/doc/user/basic.rst b/doc/user/basic.rst index 24978b2f79..4fd4f5f7c4 100644 --- a/doc/user/basic.rst +++ b/doc/user/basic.rst @@ -11,45 +11,22 @@ The following sections discuss commands common to all the routing daemons. Config Commands =============== - - - - -In a config file, you can write the debugging options, a vty's password, +In the config file, you can write the debugging options, a vty's password, routing daemon configurations, a log file name, and so forth. This information forms the initial command set for a routing beast as it is starting. -Config files are generally found in |INSTALL_PREFIX_ETC|. - -Config Methods --------------- - -There are two ways of configuring FRR. - -Traditionally each of the daemons had its own config file. The daemon name plus -``.conf`` was the default config file name. For example, zebra's default config -file was :file:`zebra.conf`. This method is deprecated. - -Because of the amount of config files this creates, and the tendency of one -daemon to rely on others for certain functionality, most deployments now use -"integrated" configuration. In this setup all configuration goes into a single -file, typically :file:`/etc/frr/frr.conf`. When starting up FRR using an init -script or systemd, ``vtysh`` is invoked to read the config file and send the -appropriate portions to only the daemons interested in them. Running -configuration updates are persisted back to this single file using ``vtysh``. -This is the recommended method. To use this method, add the following line to -:file:`/etc/frr/vtysh.conf`: - -.. code-block:: frr - - service integrated-vtysh-config +.. _config-file: -If you installed from source or used a package, this is probably already -present. +Integrated Config File +---------------------- -If desired, you can specify a config file using the :option:`-f` or -:option:`--config_file` options when starting a daemon. +FRR uses a single configuration file located in |INSTALL_PREFIX_ETC|/frr.conf. +When FRR is started using an init script or ``systemd``, ``vtysh`` is invoked to +read the config file and send the appropriate portions to only the daemons +interested in them. Running configuration updates are persisted back to this +single file using ``vtysh`` as well. +.. include:: prior-config-files.rst .. _basic-config-commands: diff --git a/doc/user/bfd.rst b/doc/user/bfd.rst index 6c57822510..6915885f45 100644 --- a/doc/user/bfd.rst +++ b/doc/user/bfd.rst @@ -27,6 +27,8 @@ This document will focus on the later implementation: *bfdd*. Starting BFD ============ +.. include:: config-include.rst + *bfdd* default configuration file is :file:`bfdd.conf`. *bfdd* searches the current directory first then |INSTALL_PREFIX_ETC|/bfdd.conf. All of *bfdd*'s command must be configured in :file:`bfdd.conf`. diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst index 050141203b..11bd676740 100644 --- a/doc/user/bgp.rst +++ b/doc/user/bgp.rst @@ -14,10 +14,7 @@ interdomain routing protocol. BGP-4 is described in :rfc:`1771` and updated by Starting BGP ============ -The default configuration file of *bgpd* is :file:`bgpd.conf`. *bgpd* searches -the current directory first, followed by |INSTALL_PREFIX_ETC|/bgpd.conf. All of -*bgpd*'s commands must be configured in :file:`bgpd.conf` when the integrated -config is not being used. +.. include:: config-include.rst *bgpd* specific invocation options are described below. Common options may also be specified (:ref:`common-invocation-options`). diff --git a/doc/user/config-include.rst b/doc/user/config-include.rst new file mode 100644 index 0000000000..3a341513b4 --- /dev/null +++ b/doc/user/config-include.rst @@ -0,0 +1,12 @@ +.. +.. January 12 2024, Christian Hopps <chopps@labn.net> +.. +.. Copyright (c) 2024, LabN Consulting, L.L.C. +.. +.. + +Configuration for the daemon should be saved in the FRR integrated configuration +file located in |INSTALL_PREFIX_ETC|/frr.conf, see :ref:`config-file` for more +information on system configuration. + +.. include:: prior-config-files.rst diff --git a/doc/user/eigrpd.rst b/doc/user/eigrpd.rst index fa157c4659..58a2957ad0 100644 --- a/doc/user/eigrpd.rst +++ b/doc/user/eigrpd.rst @@ -24,21 +24,17 @@ known topology. Starting and Stopping eigrpd ============================ -The default configuration file name of *eigrpd*'s is :file:`eigrpd.conf`. When -invocation *eigrpd* searches directory |INSTALL_PREFIX_ETC|. If -:file:`eigrpd.conf` is not there next search current directory. If an -integrated config is specified configuration is written into :file:`frr.conf`. +.. include:: config-include.rst -The EIGRP protocol requires interface information maintained by *zebra* daemon. -So running *zebra* is mandatory to run *eigrpd*. Thus minimum sequence for -running EIGRP is: +If starting daemons by hand then please note, the EIGRP protocol requires +interface information maintained by *zebra* daemon. So running *zebra* is +mandatory to run *eigrpd*. Thus minimum sequence for running EIGRP is: :: # zebra -d # eigrpd -d - Please note that *zebra* must be invoked before *eigrpd*. To stop *eigrpd*, please use:: diff --git a/doc/user/isisd.rst b/doc/user/isisd.rst index 63c921330b..d37dfa64c6 100644 --- a/doc/user/isisd.rst +++ b/doc/user/isisd.rst @@ -22,8 +22,7 @@ interface information from *zebra* in order to function. Therefore *zebra* must be running before invoking *isisd*. Also, if *zebra* is restarted then *isisd* must be too. -Like other daemons, *isisd* configuration is done in :abbr:`ISIS` specific -configuration file :file:`isisd.conf`. +.. include:: config-include.rst .. _isis-router: diff --git a/doc/user/ldpd.rst b/doc/user/ldpd.rst index 682443a456..cbed734e42 100644 --- a/doc/user/ldpd.rst +++ b/doc/user/ldpd.rst @@ -32,9 +32,7 @@ options (:ref:`common-invocation-options`). The *zebra* daemon must be running before *ldpd* is invoked. -Configuration of *ldpd* is done in its configuration file -:file:`ldpd.conf`. - +.. include:: config-include.rst .. _understanding-ldp: diff --git a/doc/user/ospf6d.rst b/doc/user/ospf6d.rst index 12b368d431..ad5861051d 100644 --- a/doc/user/ospf6d.rst +++ b/doc/user/ospf6d.rst @@ -9,8 +9,13 @@ described in :rfc:`2740`. .. _ospf6-router: -OSPF6 router -============ +Configuring OSPF6 +***************** + +.. include:: config-include.rst + +Configuration Commands +====================== .. clicmd:: router ospf6 [vrf NAME] diff --git a/doc/user/ospfd.rst b/doc/user/ospfd.rst index 2f88f24599..3bc4487f64 100644 --- a/doc/user/ospfd.rst +++ b/doc/user/ospfd.rst @@ -32,8 +32,7 @@ Configuring OSPF Therefore *zebra* must be running before invoking *ospfd*. Also, if *zebra* is restarted then *ospfd* must be too. -Like other daemons, *ospfd* configuration is done in :abbr:`OSPF` specific -configuration file :file:`ospfd.conf` when the integrated config is not used. +.. include:: config-include.rst .. _ospf-multi-instance: diff --git a/doc/user/pbr.rst b/doc/user/pbr.rst index 7a4effd3fc..6ea153cc35 100644 --- a/doc/user/pbr.rst +++ b/doc/user/pbr.rst @@ -15,11 +15,7 @@ the default Linux kernel dataplane provider. Starting PBR ============ -Default configuration file for *pbrd* is :file:`pbrd.conf`. The typical -location of :file:`pbrd.conf` is |INSTALL_PREFIX_ETC|/pbrd.conf. - -If FRR is using integrated config, then :file:`pbrd.conf` need not be -present and the :file:`frr.conf` is read instead. +.. include:: config-include.rst .. program:: pbrd diff --git a/doc/user/pim.rst b/doc/user/pim.rst index d70c3c0e64..80a6a2787c 100644 --- a/doc/user/pim.rst +++ b/doc/user/pim.rst @@ -23,12 +23,11 @@ network for optimizing forwarding of overlay BUM traffic. Starting and Stopping pimd ========================== -The default configuration file name of *pimd*'s is :file:`pimd.conf`. When -invoked *pimd* searches directory |INSTALL_PREFIX_ETC|. If -:file:`pimd.conf` is not there then next search current directory. +.. include:: config-include.rst -*pimd* requires zebra for proper operation. Additionally *pimd* depends on -routing properly setup and working in the network that it is working on. +If starting daemons by hand then please note, *pimd* requires zebra for proper +operation. Additionally *pimd* depends on routing properly setup and working in +the network that it is working on. :: diff --git a/doc/user/pimv6.rst b/doc/user/pimv6.rst index 856939038f..d550c8e89c 100644 --- a/doc/user/pimv6.rst +++ b/doc/user/pimv6.rst @@ -15,12 +15,11 @@ do S,G mrouting. Starting and Stopping pim6d =========================== -The default configuration file name of *pim6d*'s is :file:`pim6d.conf`. When -invoked *pim6d* searches directory |INSTALL_PREFIX_ETC|. If -:file:`pim6d.conf` is not there then next search current directory. +.. include:: config-include.rst -*pim6d* requires zebra for proper operation. Additionally *pim6d* depends on -routing properly setup and working in the network that it is working on. +If starting daemons by hand then please note, *pim6d* requires zebra for proper +operation. Additionally *pim6d* depends on routing properly setup and working in +the network that it is working on. :: diff --git a/doc/user/prior-config-files.rst b/doc/user/prior-config-files.rst new file mode 100644 index 0000000000..a01b688859 --- /dev/null +++ b/doc/user/prior-config-files.rst @@ -0,0 +1,23 @@ +.. +.. January 12 2024, Christian Hopps <chopps@labn.net> +.. +.. Copyright (c) 2024, LabN Consulting, L.L.C. +.. +.. + +Prior versions of FRR supported reading and writing per-daemon config files; +however, with the introduction of the centralized management daemon ``mgmtd`` +this could no longer be supported. + +In order to allow for an orderly transition from per-daemon config files to the +integrated config file, FRR daemons will continue to try and **read** their +specific per-daemon configuration file as before. Additionally the config can +still be loaded directly using the ``-f`` or ``--config-file`` CLI options; +however, these files will **not** be updated when the configuration is written +(e.g., with the ``write mem`` command). + +.. warning:: + + Per-daemon files will **no longer** be updated when the user issues a ``write + memory`` command. Therefore these per-daemon config files should only be used + as a mechanism for transitioning to the integrated config, and then removed. diff --git a/doc/user/ripd.rst b/doc/user/ripd.rst index f9c7724302..ea13dc92df 100644 --- a/doc/user/ripd.rst +++ b/doc/user/ripd.rst @@ -21,15 +21,15 @@ version 1 as described in RFC1058. Starting and Stopping ripd ========================== -The default configuration file name of *ripd*'s is :file:`ripd.conf`. When -invocation *ripd* searches directory |INSTALL_PREFIX_ETC|. If :file:`ripd.conf` -is not there next search current directory. +.. include:: config-include.rst RIP uses UDP port 520 to send and receive RIP packets. So the user must have the capability to bind the port, generally this means that the user must have -superuser privileges. RIP protocol requires interface information maintained by -*zebra* daemon. So running *zebra* is mandatory to run *ripd*. Thus minimum -sequence for running RIP is like below: +superuser privileges. + +If starting daemons by hand then please note, RIP protocol requires interface +information maintained by *zebra* daemon. So running *zebra* is mandatory to run +*ripd*. Thus minimum sequence for running RIP is like below: :: diff --git a/doc/user/ripngd.rst b/doc/user/ripngd.rst index 1e78294f32..f898bed57a 100644 --- a/doc/user/ripngd.rst +++ b/doc/user/ripngd.rst @@ -12,6 +12,8 @@ reincarnation of the RIP protocol. Invoking ripngd =============== +.. include:: config-include.rst + There are no `ripngd` specific invocation options. Common options can be specified (:ref:`common-invocation-options`). diff --git a/doc/user/rpki.rst b/doc/user/rpki.rst index 4053536247..06fb3c2f29 100644 --- a/doc/user/rpki.rst +++ b/doc/user/rpki.rst @@ -200,6 +200,10 @@ Debugging Displaying RPKI --------------- +.. clicmd:: show rpki configuration [json] + + Display RPKI configuration state including timers values. + .. clicmd:: show rpki prefix <A.B.C.D/M|X:X::X:X/M> [(1-4294967295)] [json] Display validated prefixes received from the cache servers filtered diff --git a/doc/user/sharp.rst b/doc/user/sharp.rst index 3e73a599ed..355b6b4d59 100644 --- a/doc/user/sharp.rst +++ b/doc/user/sharp.rst @@ -13,11 +13,7 @@ labs. Starting SHARP ============== -Default configuration file for *sharpd* is :file:`sharpd.conf`. The typical -location of :file:`sharpd.conf` is |INSTALL_PREFIX_ETC|/sharpd.conf. - -If the user is using integrated config, then :file:`sharpd.conf` need not be -present and the :file:`frr.conf` is read instead. +.. include:: config-include.rst .. program:: sharpd diff --git a/doc/user/static.rst b/doc/user/static.rst index d405276573..922c71a073 100644 --- a/doc/user/static.rst +++ b/doc/user/static.rst @@ -12,21 +12,13 @@ of static routes. Starting STATIC =============== -Default configuration file for *staticd* is :file:`staticd.conf`. The typical -location of :file:`staticd.conf` is |INSTALL_PREFIX_ETC|/staticd.conf. - -If the user is using integrated config, then :file:`staticd.conf` need not be -present and the :file:`frr.conf` is read instead. - -If the user has not fully upgraded to using the staticd.conf and still has -a non-integrated config with zebra.conf holding the static routes, *staticd* -will read in the :file:`zebrad.conf` as a backup. - .. program:: staticd :abbr:`STATIC` supports all the common FRR daemon start options which are documented elsewhere. +.. include:: config-include.rst + .. _static-route-commands: Static Route Commands diff --git a/doc/user/vrrp.rst b/doc/user/vrrp.rst index ef3aebeafa..d99fc23ef5 100644 --- a/doc/user/vrrp.rst +++ b/doc/user/vrrp.rst @@ -24,11 +24,7 @@ protocol. Starting VRRP ============= -The configuration file for *vrrpd* is :file:`vrrpd.conf`. The typical location -of :file:`vrrpd.conf` is |INSTALL_PREFIX_ETC|/vrrpd.conf. - -If using integrated config, then :file:`vrrpd.conf` need not be present and -:file:`frr.conf` is read instead. +.. include:: config-include.rst .. program:: vrrpd diff --git a/doc/user/vtysh.rst b/doc/user/vtysh.rst index adbdf3451a..9722231d33 100644 --- a/doc/user/vtysh.rst +++ b/doc/user/vtysh.rst @@ -131,14 +131,14 @@ could be made SGID (set group ID) to the |INSTALL_VTY_GROUP| group. at all. -.. _integrated-configuration-mode: +.. _integrated-configuration-file: -Integrated configuration mode +Integrated configuration file ============================= -Integrated configuration mode uses a single configuration file, -:file:`frr.conf`, for all daemons. This replaces the individual files like -:file:`zebra.conf` or :file:`bgpd.conf`. +FRR uses a single configuration file, :file:`frr.conf`, for all daemons. This +replaces the individual files like :file:`zebra.conf` or :file:`bgpd.conf` used +in previous versions of the software. :file:`frr.conf` is located in |INSTALL_PREFIX_ETC|. All daemons check for the existence of this file at startup, and if it exists will not load their @@ -1677,7 +1677,7 @@ lib_interface_state_mtu_get_elem(struct nb_cb_get_elem_args *args) { const struct interface *ifp = args->list_entry; - return yang_data_new_uint16(args->xpath, ifp->mtu); + return yang_data_new_uint32(args->xpath, ifp->mtu); } /* diff --git a/tests/topotests/bgp_rpki_topo1/__init__.py b/tests/topotests/bgp_rpki_topo1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/__init__.py diff --git a/tests/topotests/bgp_rpki_topo1/r1/bgpd.conf b/tests/topotests/bgp_rpki_topo1/r1/bgpd.conf new file mode 100644 index 0000000000..437d393c75 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r1/bgpd.conf @@ -0,0 +1,14 @@ +router bgp 65530 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.0.2.2 remote-as 65002 + neighbor 192.0.2.2 timers 1 3 + neighbor 192.0.2.2 timers connect 1 + neighbor 192.0.2.2 ebgp-multihop 3 + neighbor 192.0.2.2 update-source 192.0.2.1 + address-family ipv4 unicast + network 198.51.100.0/24 + network 203.0.113.0/24 + network 10.0.0.0/24 + exit-address-family +! diff --git a/tests/topotests/bgp_rpki_topo1/r1/rtrd.py b/tests/topotests/bgp_rpki_topo1/r1/rtrd.py new file mode 100755 index 0000000000..bca58a66ac --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r1/rtrd.py @@ -0,0 +1,330 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: GPL-2.0-or-later + +# Copyright (C) 2023 Tomas Hlavacek (tmshlvck@gmail.com) + +from typing import List, Tuple, Callable, Type +import socket +import threading +import socketserver +import struct +import ipaddress +import csv +import os +import sys + +LISTEN_HOST, LISTEN_PORT = "0.0.0.0", 15432 +VRPS_FILE = os.path.join(sys.path[0], "vrps.csv") + + +def dbg(m: str): + print(m) + sys.stdout.flush() + + +class RTRDatabase(object): + def __init__(self, vrps_file: str) -> None: + self.last_serial = 0 + self.ann4 = [] + self.ann6 = [] + self.withdraw4 = [] + self.withdraw6 = [] + + with open(vrps_file, "r") as fh: + for rasn, rnet, rmaxlen, _ in csv.reader(fh): + try: + net = ipaddress.ip_network(rnet) + asn = int(rasn[2:]) + maxlen = int(rmaxlen) + if net.version == 6: + self.ann6.append((asn, str(net), maxlen)) + elif net.version == 4: + self.ann4.append((asn, str(net), maxlen)) + else: + raise ValueError(f"Unknown AFI: {net.version}") + except Exception as e: + dbg( + f"VRPS load: ignoring {str((rasn, rnet,rmaxlen))} because {str(e)}" + ) + + def get_serial(self) -> int: + return self.last_serial + + def set_serial(self, serial: int) -> None: + self.last_serial = serial + + def get_announcements4(self, serial: int = 0) -> List[Tuple[int, str, int]]: + if serial > self.last_serial: + return self.ann4 + else: + return [] + + def get_withdrawals4(self, serial: int = 0) -> List[Tuple[int, str, int]]: + if serial > self.last_serial: + return self.withdraw4 + else: + return [] + + def get_announcements6(self, serial: int = 0) -> List[Tuple[int, str, int]]: + if serial > self.last_serial: + return self.ann6 + else: + return [] + + def get_withdrawals6(self, serial: int = 0) -> List[Tuple[int, str, int]]: + if serial > self.last_serial: + return self.withdraw6 + else: + return [] + + +class RTRConnHandler(socketserver.BaseRequestHandler): + PROTO_VERSION = 0 + + def setup(self) -> None: + self.session_id = 2345 + self.serial = 1024 + + dbg(f"New connection from: {str(self.client_address)} ") + # TODO: register for notifies + + def finish(self) -> None: + pass + # TODO: de-register + + HEADER_LEN = 8 + + def decode_header(self, buf: bytes) -> Tuple[int, int, int, int]: + # common header in all received packets + return struct.unpack("!BBHI", buf) + # reutnrs (proto_ver, pdu_type, sess_id, length) + + SERNOTIFY_TYPE = 0 + SERNOTIFY_LEN = 12 + + def send_sernotify(self, serial: int) -> None: + # serial notify PDU + dbg(f"<Serial Notify session_id={self.session_id} serial={serial}") + self.request.send( + struct.pack( + "!BBHII", + self.PROTO_VERSION, + self.SERNOTIFY_TYPE, + self.session_id, + self.SERNOTIFY_LEN, + serial, + ) + ) + + CACHERESPONSE_TYPE = 3 + CACHERESPONSE_LEN = 8 + + def send_cacheresponse(self) -> None: + # cache response PDU + dbg(f"<Cache response session_id={self.session_id}") + self.request.send( + struct.pack( + "!BBHI", + self.PROTO_VERSION, + self.CACHERESPONSE_TYPE, + self.session_id, + self.CACHERESPONSE_LEN, + ) + ) + + FLAGS_ANNOUNCE = 1 + FLAGS_WITHDRAW = 0 + + IPV4_TYPE = 4 + IPV4_LEN = 20 + + def send_ipv4(self, ipnet: str, asn: int, maxlen: int, flags: int): + # IPv4 PDU + dbg(f"<IPv4 net={ipnet} asn={asn} maxlen={maxlen} flags={flags}") + ip = ipaddress.IPv4Network(ipnet) + self.request.send( + struct.pack( + "!BBHIBBBB4sI", + self.PROTO_VERSION, + self.IPV4_TYPE, + 0, + self.IPV4_LEN, + flags, + ip.prefixlen, + maxlen, + 0, + ip.network_address.packed, + asn, + ) + ) + + def announce_ipv4(self, ipnet, asn, maxlen): + self.send_ipv4(ipnet, asn, maxlen, self.FLAGS_ANNOUNCE) + + def withdraw_ipv4(self, ipnet, asn, maxlen): + self.send_ipv4(ipnet, asn, maxlen, self.FLAGS_WITHDRAW) + + IPV6_TYPE = 6 + IPV6_LEN = 32 + + def send_ipv6(self, ipnet: str, asn: int, maxlen: int, flags: int): + # IPv6 PDU + dbg(f"<IPv6 net={ipnet} asn={asn} maxlen={maxlen} flags={flags}") + ip = ipaddress.IPv6Network(ipnet) + self.request.send( + struct.pack( + "!BBHIBBBB16sI", + self.PROTO_VERSION, + self.IPV6_TYPE, + 0, + self.IPV6_LEN, + flags, + ip.prefixlen, + maxlen, + 0, + ip.network_address.packed, + asn, + ) + ) + + def announce_ipv6(self, ipnet: str, asn: int, maxlen: int): + self.send_ipv6(ipnet, asn, maxlen, self.FLAGS_ANNOUNCE) + + def withdraw_ipv6(self, ipnet: str, asn: int, maxlen: int): + self.send_ipv6(ipnet, asn, maxlen, self.FLAGS_WITHDRAW) + + EOD_TYPE = 7 + EOD_LEN = 12 + + def send_endofdata(self, serial: int): + # end of data PDU + dbg(f"<End of Data session_id={self.session_id} serial={serial}") + self.server.db.set_serial(serial) + self.request.send( + struct.pack( + "!BBHII", + self.PROTO_VERSION, + self.EOD_TYPE, + self.session_id, + self.EOD_LEN, + serial, + ) + ) + + CACHERESET_TYPE = 8 + CACHERESET_LEN = 8 + + def send_cachereset(self): + # cache reset PDU + dbg("<Cache Reset") + self.request.send( + struct.pack( + "!BBHI", + self.PROTO_VERSION, + self.CACHERESET_TYPE, + 0, + self.CACHERESET_LEN, + ) + ) + + SERIAL_QUERY_TYPE = 1 + SERIAL_QUERY_LEN = 12 + + def handle_serial_query(self, buf: bytes, sess_id: int): + serial = struct.unpack("!I", buf)[0] + dbg(f">Serial query: {serial}") + if sess_id: + self.server.db.set_serial(serial) + else: + self.server.db.set_serial(0) + self.send_cacheresponse() + + for asn, ipnet, maxlen in self.server.db.get_announcements4(serial): + self.announce_ipv4(ipnet, asn, maxlen) + + for asn, ipnet, maxlen in self.server.db.get_withdrawals4(serial): + self.withdraw_ipv4(ipnet, asn, maxlen) + + for asn, ipnet, maxlen in self.server.db.get_announcements6(serial): + self.announce_ipv6(ipnet, asn, maxlen) + + for asn, ipnet, maxlen in self.server.db.get_withdrawals6(serial): + self.withdraw_ipv6(ipnet, asn, maxlen) + + self.send_endofdata(self.serial) + + RESET_TYPE = 2 + + def handle_reset(self): + dbg(">Reset") + self.session_id += 1 + self.server.db.set_serial(0) + self.send_cacheresponse() + + for asn, ipnet, maxlen in self.server.db.get_announcements4(self.serial): + self.announce_ipv4(ipnet, asn, maxlen) + + for asn, ipnet, maxlen in self.server.db.get_announcements6(self.serial): + self.announce_ipv6(ipnet, asn, maxlen) + + self.send_endofdata(self.serial) + + ERROR_TYPE = 10 + + def handle_error(self, buf: bytes): + dbg(f">Error: {str(buf)}") + self.server.shutdown() + self.server.stopped = True + raise ConnectionError("Received an RPKI error packet from FRR. Exiting") + + def handle(self): + while True: + b = self.request.recv(self.HEADER_LEN, socket.MSG_WAITALL) + if len(b) == 0: + break + proto_ver, pdu_type, sess_id, length = self.decode_header(b) + dbg( + f">Header proto_ver={proto_ver} pdu_type={pdu_type} sess_id={sess_id} length={length}" + ) + + if sess_id: + self.session_id = sess_id + + if pdu_type == self.SERIAL_QUERY_TYPE: + b = self.request.recv( + self.SERIAL_QUERY_LEN - self.HEADER_LEN, socket.MSG_WAITALL + ) + self.handle_serial_query(b, sess_id) + + elif pdu_type == self.RESET_TYPE: + self.handle_reset() + + elif pdu_type == self.ERROR_TYPE: + b = self.request.recv(length - self.HEADER_LEN, socket.MSG_WAITALL) + self.handle_error(b) + + +class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): + def __init__( + self, bind: Tuple[str, int], handler: Type[RTRConnHandler], db: RTRDatabase + ) -> None: + super().__init__(bind, handler) + self.db = db + + +def main(): + db = RTRDatabase(VRPS_FILE) + server = ThreadedTCPServer((LISTEN_HOST, LISTEN_PORT), RTRConnHandler, db) + dbg(f"Server listening on {LISTEN_HOST} port {LISTEN_PORT}") + server.serve_forever() + + +if __name__ == "__main__": + if len(sys.argv) > 1: + f = open(sys.argv[1], "w") + sys.__stdout__ = f + sys.stdout = f + sys.__stderr__ = f + sys.stderr = f + + main() diff --git a/tests/topotests/bgp_rpki_topo1/r1/staticd.conf b/tests/topotests/bgp_rpki_topo1/r1/staticd.conf new file mode 100644 index 0000000000..7f2f057bfe --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r1/staticd.conf @@ -0,0 +1 @@ +ip route 192.0.2.2/32 192.168.1.2 diff --git a/tests/topotests/bgp_rpki_topo1/r1/vrps.csv b/tests/topotests/bgp_rpki_topo1/r1/vrps.csv new file mode 100644 index 0000000000..5a6e023bb9 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r1/vrps.csv @@ -0,0 +1,3 @@ +ASN,IP Prefix,Max Length,Trust Anchor +AS65530,198.51.100.0/24,24,private +AS65530,203.0.113.0/24,24,private diff --git a/tests/topotests/bgp_rpki_topo1/r1/zebra.conf b/tests/topotests/bgp_rpki_topo1/r1/zebra.conf new file mode 100644 index 0000000000..b742b70356 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r1/zebra.conf @@ -0,0 +1,6 @@ +interface lo + ip address 192.0.2.1/32 +! +interface r1-eth0 + ip address 192.168.1.1/24 +! diff --git a/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_any.json b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_any.json new file mode 100644 index 0000000000..a04e9ef6ce --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_any.json @@ -0,0 +1,37 @@ +{ + "routerId": "192.0.2.2", + "defaultLocPrf": 100, + "localAS": 65002, + "routes": { + "198.51.100.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "198.51.100.0", + "prefixLen": 24, + "network": "198.51.100.0/24", + "metric": 0, + "weight": 0, + "path": "65530", + "origin": "IGP" + } + ], + "203.0.113.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "203.0.113.0", + "prefixLen": 24, + "network": "203.0.113.0/24", + "metric": 0, + "weight": 0, + "path": "65530", + "origin": "IGP" + } + ] + } +} diff --git a/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_notfound.json b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_notfound.json new file mode 100644 index 0000000000..01e288c86f --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_notfound.json @@ -0,0 +1,7 @@ +{ + "routerId": "192.0.2.2", + "defaultLocPrf": 100, + "localAS": 65002, + "routes": { + } +} diff --git a/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_valid.json b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_valid.json new file mode 120000 index 0000000000..2645bfaf0e --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_valid.json @@ -0,0 +1 @@ +bgp_table_rpki_valid.json
\ No newline at end of file diff --git a/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_any.json b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_any.json new file mode 100644 index 0000000000..5546d45c67 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_any.json @@ -0,0 +1,52 @@ +{ + "routerId": "192.0.2.2", + "defaultLocPrf": 100, + "localAS": 65002, + "routes": { + "10.0.0.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "10.0.0.0", + "prefixLen": 24, + "network": "10.0.0.0/24", + "metric": 0, + "weight": 0, + "path": "65530", + "origin": "IGP" + } + ], + "198.51.100.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "198.51.100.0", + "prefixLen": 24, + "network": "198.51.100.0/24", + "metric": 0, + "weight": 0, + "path": "65530", + "origin": "IGP" + } + ], + "203.0.113.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "203.0.113.0", + "prefixLen": 24, + "network": "203.0.113.0/24", + "metric": 0, + "weight": 0, + "path": "65530", + "origin": "IGP" + } + ] + } +} diff --git a/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_notfound.json b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_notfound.json new file mode 100644 index 0000000000..7b9a5c875b --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_notfound.json @@ -0,0 +1,22 @@ +{ + "routerId": "192.0.2.2", + "defaultLocPrf": 100, + "localAS": 65002, + "routes": { + "10.0.0.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "10.0.0.0", + "prefixLen": 24, + "network": "10.0.0.0/24", + "metric": 0, + "weight": 0, + "path": "65530", + "origin": "IGP" + } + ] + } +} diff --git a/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_valid.json b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_valid.json new file mode 100644 index 0000000000..eb3852a789 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_valid.json @@ -0,0 +1,35 @@ +{ + "routerId": "192.0.2.2", + "defaultLocPrf": 100, + "localAS": 65002, + "routes": { + "198.51.100.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "198.51.100.0", + "prefixLen": 24, + "network": "198.51.100.0/24", + "metric": 0, + "weight": 0, + "path": "65530", + "origin": "IGP" + } + ], + "203.0.113.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "203.0.113.0", + "prefixLen": 24, + "network": "203.0.113.0/24", + "metric": 0, + "weight": 0, + "path": "65530", + "origin": "IGP" + } + ] + } +} diff --git a/tests/topotests/bgp_rpki_topo1/r2/bgpd.conf b/tests/topotests/bgp_rpki_topo1/r2/bgpd.conf new file mode 100644 index 0000000000..95b1e5bdc1 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/bgpd.conf @@ -0,0 +1,19 @@ +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.0.2.1 remote-as 65530 + neighbor 192.0.2.1 timers connect 1 + neighbor 192.0.2.1 ebgp-multihop 3 + neighbor 192.0.2.1 update-source 192.0.2.2 +! +router bgp 65002 vrf vrf10 + no bgp ebgp-requires-policy + neighbor 192.0.2.3 remote-as 65530 + neighbor 192.0.2.3 timers 1 3 + neighbor 192.0.2.3 timers connect 1 + neighbor 192.0.2.3 ebgp-multihop 3 + neighbor 192.0.2.3 update-source 192.0.2.2 +! +rpki + rpki retry_interval 5 + rpki cache 192.0.2.1 15432 preference 1 +exit diff --git a/tests/topotests/bgp_rpki_topo1/r2/rpki_prefix_table.json b/tests/topotests/bgp_rpki_topo1/r2/rpki_prefix_table.json new file mode 100644 index 0000000000..fbc5cc9f07 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/rpki_prefix_table.json @@ -0,0 +1,18 @@ +{ + "prefixes":[ + { + "prefix":"198.51.100.0", + "prefixLenMin":24, + "prefixLenMax":24, + "asn":65530 + }, + { + "prefix":"203.0.113.0", + "prefixLenMin":24, + "prefixLenMax":24, + "asn":65530 + } + ], + "ipv4PrefixCount":2, + "ipv6PrefixCount":0 +} diff --git a/tests/topotests/bgp_rpki_topo1/r2/staticd.conf b/tests/topotests/bgp_rpki_topo1/r2/staticd.conf new file mode 100644 index 0000000000..e3f5d7dba0 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/staticd.conf @@ -0,0 +1 @@ +ip route 192.0.2.1/32 192.168.1.1 diff --git a/tests/topotests/bgp_rpki_topo1/r2/zebra.conf b/tests/topotests/bgp_rpki_topo1/r2/zebra.conf new file mode 100644 index 0000000000..96865f0b62 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/zebra.conf @@ -0,0 +1,9 @@ +interface lo + ip address 192.0.2.2/32 +! +interface vrf10 vrf vrf10 + ip address 192.0.2.2/32 +! +interface r2-eth0 + ip address 192.168.1.2/24 +! diff --git a/tests/topotests/bgp_rpki_topo1/test_bgp_rpki_topo1.py b/tests/topotests/bgp_rpki_topo1/test_bgp_rpki_topo1.py new file mode 100644 index 0000000000..36bc0b7200 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/test_bgp_rpki_topo1.py @@ -0,0 +1,288 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright 2023 6WIND S.A. + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_STATIC, os.path.join(CWD, "{}/staticd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, + os.path.join(CWD, "{}/bgpd.conf".format(rname)), + " -M bgpd_rpki" if rname == "r2" else "", + ) + + tgen.start_router() + + global rtrd_process + + rname = "r1" + + rtr_path = os.path.join(CWD, rname) + log_dir = os.path.join(tgen.logdir, rname) + log_file = os.path.join(log_dir, "rtrd.log") + + tgen.gears[rname].cmd("chmod u+x {}/rtrd.py".format(rtr_path)) + rtrd_process = tgen.gears[rname].popen("{}/rtrd.py {}".format(rtr_path, log_file)) + + +def teardown_module(mod): + tgen = get_topogen() + + logger.info("r1: sending SIGTERM to rtrd RPKI server") + rtrd_process.kill() + tgen.stop_topology() + + +def show_rpki_prefixes(rname, expected, vrf=None): + tgen = get_topogen() + + if vrf: + cmd = "show rpki prefix-table vrf {} json".format(vrf) + else: + cmd = "show rpki prefix-table json" + + output = json.loads(tgen.gears[rname].vtysh_cmd(cmd)) + + return topotest.json_cmp(output, expected) + + +def show_bgp_ipv4_table_rpki(rname, rpki_state, expected, vrf=None): + tgen = get_topogen() + + cmd = "show bgp" + if vrf: + cmd += " vrf {}".format(vrf) + cmd += " ipv4 unicast" + if rpki_state: + cmd += " rpki {}".format(rpki_state) + cmd += " json" + + output = json.loads(tgen.gears[rname].vtysh_cmd(cmd)) + + expected_nb = len(expected.get("routes")) + output_nb = len(output.get("routes", {})) + + if expected_nb != output_nb: + return {"error": "expected {} prefixes. Got {}".format(expected_nb, output_nb)} + + return topotest.json_cmp(output, expected) + + +def test_show_bgp_rpki_prefixes(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for rname in ["r1", "r3"]: + logger.info("{}: checking if rtrd is running".format(rname)) + if rtrd_process.poll() is not None: + pytest.skip(tgen.errors) + + rname = "r2" + + step("Check RPKI prefix table") + + expected = open(os.path.join(CWD, "{}/rpki_prefix_table.json".format(rname))).read() + expected_json = json.loads(expected) + test_func = functools.partial(show_rpki_prefixes, rname, expected_json) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see RPKI prefixes on {}".format(rname) + + for rpki_state in ["valid", "notfound", None]: + if rpki_state: + step("Check RPKI state of prefixes in BGP table: {}".format(rpki_state)) + else: + step("Check prefixes in BGP table") + expected = open( + os.path.join( + CWD, + "{}/bgp_table_rpki_{}.json".format( + rname, rpki_state if rpki_state else "any" + ), + ) + ).read() + expected_json = json.loads(expected) + test_func = functools.partial( + show_bgp_ipv4_table_rpki, rname, rpki_state, expected_json + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Unexpected prefixes RPKI state on {}".format(rname) + + +def test_show_bgp_rpki_prefixes_no_rpki_cache(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for rname in ["r1", "r3"]: + logger.info("{}: checking if rtrd is running".format(rname)) + if rtrd_process.poll() is not None: + pytest.skip(tgen.errors) + + def _show_rpki_no_connection(rname): + output = json.loads( + tgen.gears[rname].vtysh_cmd("show rpki cache-connection json") + ) + + return output == {"error": "No connection to RPKI cache server."} + + step("Remove RPKI server from configuration") + rname = "r2" + tgen.gears[rname].vtysh_cmd( + """ +configure +rpki + no rpki cache 192.0.2.1 15432 preference 1 +exit +""" + ) + + step("Check RPKI connection state") + + test_func = functools.partial(_show_rpki_no_connection, rname) + _, result = topotest.run_and_expect(test_func, True, count=60, wait=0.5) + assert result, "RPKI is still connected on {}".format(rname) + + +def test_show_bgp_rpki_prefixes_reconnect(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for rname in ["r1", "r3"]: + logger.info("{}: checking if rtrd is running".format(rname)) + if rtrd_process.poll() is not None: + pytest.skip(tgen.errors) + + step("Restore RPKI server configuration") + + rname = "r2" + tgen.gears[rname].vtysh_cmd( + """ +configure +rpki + rpki cache 192.0.2.1 15432 preference 1 +exit +""" + ) + + step("Check RPKI prefix table") + + expected = open(os.path.join(CWD, "{}/rpki_prefix_table.json".format(rname))).read() + expected_json = json.loads(expected) + test_func = functools.partial(show_rpki_prefixes, rname, expected_json) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see RPKI prefixes on {}".format(rname) + + for rpki_state in ["valid", "notfound", None]: + if rpki_state: + step("Check RPKI state of prefixes in BGP table: {}".format(rpki_state)) + else: + step("Check prefixes in BGP table") + expected = open( + os.path.join( + CWD, + "{}/bgp_table_rpki_{}.json".format( + rname, rpki_state if rpki_state else "any" + ), + ) + ).read() + expected_json = json.loads(expected) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Unexpected prefixes RPKI state on {}".format(rname) + + +def test_show_bgp_rpki_route_map(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for rname in ["r1", "r3"]: + logger.info("{}: checking if rtrd is running".format(rname)) + if rtrd_process.poll() is not None: + pytest.skip(tgen.errors) + + step("Apply RPKI valid route-map on neighbor") + + rname = "r2" + tgen.gears[rname].vtysh_cmd( + """ +configure +route-map RPKI permit 10 + match rpki valid +! +router bgp 65002 + address-family ipv4 unicast + neighbor 192.0.2.1 route-map RPKI in +""" + ) + + for rpki_state in ["valid", "notfound", None]: + if rpki_state: + step("Check RPKI state of prefixes in BGP table: {}".format(rpki_state)) + else: + step("Check prefixes in BGP table") + expected = open( + os.path.join( + CWD, + "{}/bgp_table_rmap_rpki_{}.json".format( + rname, rpki_state if rpki_state else "any" + ), + ) + ).read() + expected_json = json.loads(expected) + test_func = functools.partial( + show_bgp_ipv4_table_rpki, + rname, + rpki_state, + expected_json, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Unexpected prefixes RPKI state on {}".format(rname) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/yang/frr-interface.yang b/yang/frr-interface.yang index 012c96b600..fc5a290908 100644 --- a/yang/frr-interface.yang +++ b/yang/frr-interface.yang @@ -241,17 +241,18 @@ module frr-interface { } leaf mtu { - type uint16; + type uint32; description - "The size of the largest IPV4 packet that the interface - will send and receive."; + "The size of the largest IPV4 packet that the interface will send. + Normally this will never be larger than 65535; however, some devices + (e.g., vrf) can have larger values"; } leaf mtu6 { type uint32; description "The size of the largest IPV6 packet that the interface - will send and receive."; + will send."; } leaf speed { |
