summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bgpd/bgp_debug.c9
-rw-r--r--bgpd/bgp_debug.h6
-rw-r--r--bgpd/bgp_rpki.c167
-rw-r--r--doc/user/rpki.rst4
-rw-r--r--doc/user/zebra.rst3
-rw-r--r--lib/if.c2
-rw-r--r--lib/mgmt_fe_client.c19
-rw-r--r--lib/mgmt_fe_client.h11
-rw-r--r--lib/mgmt_msg_native.c1
-rw-r--r--lib/mgmt_msg_native.h28
-rw-r--r--lib/northbound_oper.c12
-rw-r--r--lib/vty.c15
-rw-r--r--lib/vty.h4
-rw-r--r--lib/yang.c27
-rw-r--r--mgmtd/mgmt_fe_adapter.c18
-rw-r--r--mgmtd/mgmt_main.c7
-rw-r--r--mgmtd/mgmt_txn.c64
-rw-r--r--mgmtd/mgmt_txn.h4
-rw-r--r--mgmtd/mgmt_vty.c14
-rw-r--r--tests/topotests/bfd_vrf_topo1/test_bfd_vrf_topo1.py1
-rw-r--r--tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py1
-rw-r--r--tests/topotests/bgp_rpki_topo1/__init__.py0
-rw-r--r--tests/topotests/bgp_rpki_topo1/r1/bgpd.conf14
-rwxr-xr-xtests/topotests/bgp_rpki_topo1/r1/rtrd.py330
-rw-r--r--tests/topotests/bgp_rpki_topo1/r1/staticd.conf1
-rw-r--r--tests/topotests/bgp_rpki_topo1/r1/vrps.csv3
-rw-r--r--tests/topotests/bgp_rpki_topo1/r1/zebra.conf6
-rw-r--r--tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_any.json37
-rw-r--r--tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_notfound.json7
l---------tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_valid.json1
-rw-r--r--tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_any.json52
-rw-r--r--tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_notfound.json22
-rw-r--r--tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_valid.json35
-rw-r--r--tests/topotests/bgp_rpki_topo1/r2/bgpd.conf19
-rw-r--r--tests/topotests/bgp_rpki_topo1/r2/rpki_prefix_table.json18
-rw-r--r--tests/topotests/bgp_rpki_topo1/r2/staticd.conf1
-rw-r--r--tests/topotests/bgp_rpki_topo1/r2/zebra.conf9
-rw-r--r--tests/topotests/bgp_rpki_topo1/test_bgp_rpki_topo1.py288
-rw-r--r--tests/topotests/bgp_vrf_netns/test_bgp_vrf_netns_topo.py2
-rwxr-xr-xtests/topotests/conftest.py4
-rw-r--r--tests/topotests/mgmt_oper/oper.py4
-rw-r--r--tests/topotests/mgmt_oper/r1/frr-simple.conf4
-rw-r--r--tests/topotests/mgmt_oper/simple-results/result-intf-description.json14
-rw-r--r--tests/topotests/mgmt_oper/simple-results/result-intf-eth0-description-exact.json3
-rw-r--r--tests/topotests/mgmt_oper/simple-results/result-intf-eth0-exact.json22
-rw-r--r--tests/topotests/mgmt_oper/simple-results/result-intf-eth0-only-config.json10
-rw-r--r--tests/topotests/mgmt_oper/simple-results/result-intf-eth0-with-config.json25
-rw-r--r--tests/topotests/mgmt_oper/test_simple.py25
-rw-r--r--tests/topotests/munet/base.py23
-rw-r--r--tests/topotests/munet/linux.py11
-rw-r--r--tests/topotests/ospf_netns_vrf/test_ospf_netns_vrf.py1
-rw-r--r--vtysh/vtysh.h4
-rw-r--r--yang/frr-interface.yang9
53 files changed, 1320 insertions, 101 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/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/zebra.rst b/doc/user/zebra.rst
index 2b737c1a2f..71201b4278 100644
--- a/doc/user/zebra.rst
+++ b/doc/user/zebra.rst
@@ -50,7 +50,8 @@ Besides the common invocation options (:ref:`common-invocation-options`), the
When *Zebra* starts with this option, the VRF backend is based on Linux
network namespaces. That implies that all network namespaces discovered by
ZEBRA will create an associated VRF. The other daemons will operate on the VRF
- VRF defined by *Zebra*, as usual.
+ VRF defined by *Zebra*, as usual. If this option is specified when running
+ *Zebra*, one must also specify the same option for *mgmtd*.
.. seealso:: :ref:`zebra-vrf`
diff --git a/lib/if.c b/lib/if.c
index a68f7f21e1..1328e21874 100644
--- a/lib/if.c
+++ b/lib/if.c
@@ -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/lib/mgmt_fe_client.c b/lib/mgmt_fe_client.c
index 0bea663004..57ac071ecf 100644
--- a/lib/mgmt_fe_client.c
+++ b/lib/mgmt_fe_client.c
@@ -306,25 +306,26 @@ int mgmt_fe_send_regnotify_req(struct mgmt_fe_client *client,
}
/*
- * Send get-tree request.
+ * Send get-data request.
*/
-int mgmt_fe_send_get_tree_req(struct mgmt_fe_client *client,
- uint64_t session_id, uint64_t req_id,
- LYD_FORMAT result_type, const char *xpath)
+int mgmt_fe_send_get_data_req(struct mgmt_fe_client *client, uint64_t session_id,
+ uint64_t req_id, LYD_FORMAT result_type,
+ uint8_t flags, const char *xpath)
{
- struct mgmt_msg_get_tree *msg;
+ struct mgmt_msg_get_data *msg;
size_t xplen = strlen(xpath);
int ret;
- msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_get_tree, xplen + 1,
- MTYPE_MSG_NATIVE_GET_TREE);
+ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_get_data, xplen + 1,
+ MTYPE_MSG_NATIVE_GET_DATA);
msg->refer_id = session_id;
msg->req_id = req_id;
- msg->code = MGMT_MSG_CODE_GET_TREE;
+ msg->code = MGMT_MSG_CODE_GET_DATA;
msg->result_type = result_type;
+ msg->flags = flags;
strlcpy(msg->xpath, xpath, xplen + 1);
- MGMTD_FE_CLIENT_DBG("Sending GET_TREE_REQ session-id %" PRIu64
+ MGMTD_FE_CLIENT_DBG("Sending GET_DATA_REQ session-id %" PRIu64
" req-id %" PRIu64 " xpath: %s",
session_id, req_id, xpath);
diff --git a/lib/mgmt_fe_client.h b/lib/mgmt_fe_client.h
index f3292d18fd..3abe29b1cf 100644
--- a/lib/mgmt_fe_client.h
+++ b/lib/mgmt_fe_client.h
@@ -15,6 +15,7 @@ extern "C" {
#include "mgmt_pb.h"
#include "frrevent.h"
#include "mgmt_defines.h"
+#include "mgmt_msg_native.h"
/***************************************************************
* Macros
@@ -367,7 +368,7 @@ extern int mgmt_fe_send_regnotify_req(struct mgmt_fe_client *client,
int num_reqs);
/*
- * Send GET-TREE to MGMTD daemon.
+ * Send GET-DATA to MGMTD daemon.
*
* client
* Client object.
@@ -381,15 +382,19 @@ extern int mgmt_fe_send_regnotify_req(struct mgmt_fe_client *client,
* result_type
* The LYD_FORMAT of the result.
*
+ * flags
+ * Flags to control the behavior of the request.
+ *
* xpath
* the xpath to get.
*
* Returns:
* 0 on success, otherwise msg_conn_send_msg() return values.
*/
-extern int mgmt_fe_send_get_tree_req(struct mgmt_fe_client *client,
+extern int mgmt_fe_send_get_data_req(struct mgmt_fe_client *client,
uint64_t session_id, uint64_t req_id,
- LYD_FORMAT result_type, const char *xpath);
+ LYD_FORMAT result_type, uint8_t flags,
+ const char *xpath);
/*
* Destroy library and cleanup everything.
diff --git a/lib/mgmt_msg_native.c b/lib/mgmt_msg_native.c
index b6dc126d49..a9b26718db 100644
--- a/lib/mgmt_msg_native.c
+++ b/lib/mgmt_msg_native.c
@@ -13,6 +13,7 @@ DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_MSG, "native mgmt msg");
DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_ERROR, "native error msg");
DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_GET_TREE, "native get tree msg");
DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_TREE_DATA, "native tree data msg");
+DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_GET_DATA, "native get data msg");
int vmgmt_msg_native_send_error(struct msg_conn *conn, uint64_t sess_or_txn_id,
uint64_t req_id, bool short_circuit_ok,
diff --git a/lib/mgmt_msg_native.h b/lib/mgmt_msg_native.h
index 3f6283025c..069cb9b150 100644
--- a/lib/mgmt_msg_native.h
+++ b/lib/mgmt_msg_native.h
@@ -142,6 +142,7 @@ DECLARE_MTYPE(MSG_NATIVE_MSG);
DECLARE_MTYPE(MSG_NATIVE_ERROR);
DECLARE_MTYPE(MSG_NATIVE_GET_TREE);
DECLARE_MTYPE(MSG_NATIVE_TREE_DATA);
+DECLARE_MTYPE(MSG_NATIVE_GET_DATA);
/*
* Native message codes
@@ -149,6 +150,7 @@ DECLARE_MTYPE(MSG_NATIVE_TREE_DATA);
#define MGMT_MSG_CODE_ERROR 0
#define MGMT_MSG_CODE_GET_TREE 1
#define MGMT_MSG_CODE_TREE_DATA 2
+#define MGMT_MSG_CODE_GET_DATA 3
/**
* struct mgmt_msg_header - Header common to all native messages.
@@ -193,7 +195,7 @@ _Static_assert(sizeof(struct mgmt_msg_error) ==
"Size mismatch");
/**
- * struct mgmt_msg_get_tree - Message carrying xpath query request.
+ * struct mgmt_msg_get_tree - backend oper data request.
*
* @result_type: ``LYD_FORMAT`` for the returned result.
* @xpath: the query for the data to return.
@@ -231,6 +233,30 @@ _Static_assert(sizeof(struct mgmt_msg_tree_data) ==
offsetof(struct mgmt_msg_tree_data, result),
"Size mismatch");
+/* Flags for get-data request */
+#define GET_DATA_FLAG_STATE 0x01 /* get only "config false" data */
+#define GET_DATA_FLAG_CONFIG 0x02 /* get only "config true" data */
+#define GET_DATA_FLAG_EXACT 0x04 /* get exact data node instead of the full tree */
+
+/**
+ * struct mgmt_msg_get_data - frontend get-data request.
+ *
+ * @result_type: ``LYD_FORMAT`` for the returned result.
+ * @flags: combination of ``GET_DATA_FLAG_*`` flags.
+ * @xpath: the query for the data to return.
+ */
+struct mgmt_msg_get_data {
+ struct mgmt_msg_header;
+ uint8_t result_type;
+ uint8_t flags;
+ uint8_t resv2[6];
+
+ alignas(8) char xpath[];
+};
+_Static_assert(sizeof(struct mgmt_msg_get_data) ==
+ offsetof(struct mgmt_msg_get_data, xpath),
+ "Size mismatch");
+
#define MGMT_MSG_VALIDATE_NUL_TERM(msgp, len) \
((len) >= sizeof(*msg) + 1 && ((char *)msgp)[(len)-1] == 0)
diff --git a/lib/northbound_oper.c b/lib/northbound_oper.c
index afce773181..2394b5e865 100644
--- a/lib/northbound_oper.c
+++ b/lib/northbound_oper.c
@@ -515,8 +515,18 @@ static enum nb_error nb_op_ys_init_node_infos(struct nb_op_yield_state *ys)
/* Move up to the container if on a leaf currently. */
if (node &&
- !CHECK_FLAG(node->schema->nodetype, LYS_CONTAINER | LYS_LIST))
+ !CHECK_FLAG(node->schema->nodetype, LYS_CONTAINER | LYS_LIST)) {
+ struct lyd_node *leaf = node;
+
node = &node->parent->node;
+
+ /*
+ * If the leaf is not a key, delete it, because it has a wrong
+ * empty value.
+ */
+ if (!lysc_is_key(leaf->schema))
+ lyd_free_tree(leaf);
+ }
assert(!node ||
CHECK_FLAG(node->schema->nodetype, LYS_CONTAINER | LYS_LIST));
if (!node)
diff --git a/lib/vty.c b/lib/vty.c
index 5f9f0dc243..3fc7c38083 100644
--- a/lib/vty.c
+++ b/lib/vty.c
@@ -4105,23 +4105,24 @@ int vty_mgmt_send_get_req(struct vty *vty, bool is_config,
return 0;
}
-int vty_mgmt_send_get_tree_req(struct vty *vty, LYD_FORMAT result_type,
- const char *xpath)
+int vty_mgmt_send_get_data_req(struct vty *vty, LYD_FORMAT result_type,
+ uint8_t flags, const char *xpath)
{
LYD_FORMAT intern_format = result_type;
vty->mgmt_req_id++;
- if (mgmt_fe_send_get_tree_req(mgmt_fe_client, vty->mgmt_session_id,
- vty->mgmt_req_id, intern_format, xpath)) {
- zlog_err("Failed to send GET-TREE to MGMTD session-id: %" PRIu64
+ if (mgmt_fe_send_get_data_req(mgmt_fe_client, vty->mgmt_session_id,
+ vty->mgmt_req_id, intern_format, flags,
+ xpath)) {
+ zlog_err("Failed to send GET-DATA to MGMTD session-id: %" PRIu64
" req-id %" PRIu64 ".",
vty->mgmt_session_id, vty->mgmt_req_id);
- vty_out(vty, "Failed to send GET-TREE to MGMTD!\n");
+ vty_out(vty, "Failed to send GET-DATA to MGMTD!\n");
return -1;
}
- vty->mgmt_req_pending_cmd = "MESSAGE_GET_TREE_REQ";
+ vty->mgmt_req_pending_cmd = "MESSAGE_GET_DATA_REQ";
vty->mgmt_req_pending_data = result_type;
return 0;
diff --git a/lib/vty.h b/lib/vty.h
index 5866eccde0..73e0d238ad 100644
--- a/lib/vty.h
+++ b/lib/vty.h
@@ -420,8 +420,8 @@ extern int vty_mgmt_send_commit_config(struct vty *vty, bool validate_only,
extern int vty_mgmt_send_get_req(struct vty *vty, bool is_config,
Mgmtd__DatastoreId datastore,
const char **xpath_list, int num_req);
-extern int vty_mgmt_send_get_tree_req(struct vty *vty, LYD_FORMAT result_type,
- const char *xpath);
+extern int vty_mgmt_send_get_data_req(struct vty *vty, LYD_FORMAT result_type,
+ uint8_t flags, const char *xpath);
extern int vty_mgmt_send_lockds_req(struct vty *vty, Mgmtd__DatastoreId ds_id,
bool lock, bool scok);
extern void vty_mgmt_resume_response(struct vty *vty, int ret);
diff --git a/lib/yang.c b/lib/yang.c
index 5b177f7fbc..7d35fb0d3d 100644
--- a/lib/yang.c
+++ b/lib/yang.c
@@ -1089,7 +1089,7 @@ LY_ERR yang_lyd_trim_xpath(struct lyd_node **root, const char *xpath)
}
return LY_SUCCESS;
#else
- struct lyd_node *node;
+ struct lyd_node *node, *sib;
struct lyd_node **remove = NULL;
struct ly_set *set = NULL;
uint32_t i;
@@ -1123,18 +1123,21 @@ LY_ERR yang_lyd_trim_xpath(struct lyd_node **root, const char *xpath)
}
darr_ensure_cap(remove, 128);
- LYD_TREE_DFS_BEGIN (*root, node) {
- /*
- * If this is a direct matching node then include it's subtree
- * which won't be marked and would otherwise be removed.
- */
- if (node->priv == (void *)2)
- LYD_TREE_DFS_continue = 1;
- else if (!node->priv) {
- *darr_append(remove) = node;
- LYD_TREE_DFS_continue = 1;
+ LY_LIST_FOR(*root, sib) {
+ LYD_TREE_DFS_BEGIN (sib, node) {
+ /*
+ * If this is a direct matching node then include its
+ * subtree which won't be marked and would otherwise
+ * be removed.
+ */
+ if (node->priv == (void *)2)
+ LYD_TREE_DFS_continue = 1;
+ else if (!node->priv) {
+ *darr_append(remove) = node;
+ LYD_TREE_DFS_continue = 1;
+ }
+ LYD_TREE_DFS_END(sib, node);
}
- LYD_TREE_DFS_END(*root, node);
}
darr_foreach_i (remove, i) {
if (remove[i] == *root)
diff --git a/mgmtd/mgmt_fe_adapter.c b/mgmtd/mgmt_fe_adapter.c
index a69d27fc5c..d91987d888 100644
--- a/mgmtd/mgmt_fe_adapter.c
+++ b/mgmtd/mgmt_fe_adapter.c
@@ -1132,15 +1132,15 @@ done:
}
/**
- * fe_adapter_handle_get_tree() - Handle a get-tree message from a FE client.
+ * fe_adapter_handle_get_data() - Handle a get-tree message from a FE client.
* @session: the client session.
* @msg_raw: the message data.
* @msg_len: the length of the message data.
*/
-static void fe_adapter_handle_get_tree(struct mgmt_fe_session_ctx *session,
+static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session,
void *__msg, size_t msg_len)
{
- struct mgmt_msg_get_tree *msg = __msg;
+ struct mgmt_msg_get_data *msg = __msg;
struct lysc_node **snodes = NULL;
char *xpath_resolved = NULL;
uint64_t req_id = msg->req_id;
@@ -1149,7 +1149,7 @@ static void fe_adapter_handle_get_tree(struct mgmt_fe_session_ctx *session,
LY_ERR err;
int ret;
- MGMTD_FE_ADAPTER_DBG("Received get-tree request from client %s for session-id %" PRIu64
+ MGMTD_FE_ADAPTER_DBG("Received get-data request from client %s for session-id %" PRIu64
" req-id %" PRIu64,
session->adapter->name, session->session_id,
msg->req_id);
@@ -1181,7 +1181,7 @@ static void fe_adapter_handle_get_tree(struct mgmt_fe_session_ctx *session,
darr_free(snodes);
clients = mgmt_be_interested_clients(msg->xpath, false);
- if (!clients) {
+ if (!clients && !CHECK_FLAG(msg->flags, GET_DATA_FLAG_CONFIG)) {
MGMTD_FE_ADAPTER_DBG("No backends provide xpath: %s for txn-id: %" PRIu64
" session-id: %" PRIu64,
msg->xpath, session->txn_id,
@@ -1207,8 +1207,8 @@ static void fe_adapter_handle_get_tree(struct mgmt_fe_session_ctx *session,
/* Create a GET-TREE request under the transaction */
ret = mgmt_txn_send_get_tree_oper(session->txn_id, req_id, clients,
- msg->result_type, simple_xpath,
- msg->xpath);
+ msg->result_type, msg->flags,
+ simple_xpath, msg->xpath);
if (ret) {
/* destroy the just created txn */
mgmt_destroy_txn(&session->txn_id);
@@ -1238,8 +1238,8 @@ static void fe_adapter_handle_native_msg(struct mgmt_fe_client_adapter *adapter,
assert(session->adapter == adapter);
switch (msg->code) {
- case MGMT_MSG_CODE_GET_TREE:
- fe_adapter_handle_get_tree(session, msg, msg_len);
+ case MGMT_MSG_CODE_GET_DATA:
+ fe_adapter_handle_get_data(session, msg, msg_len);
break;
default:
MGMTD_FE_ADAPTER_ERR("unknown native message session-id %" PRIu64
diff --git a/mgmtd/mgmt_main.c b/mgmtd/mgmt_main.c
index 72bb353b20..743091e5c4 100644
--- a/mgmtd/mgmt_main.c
+++ b/mgmtd/mgmt_main.c
@@ -22,6 +22,7 @@ static const struct option longopts[] = {
{"skip_runas", no_argument, NULL, 'S'},
{"no_zebra", no_argument, NULL, 'Z'},
{"socket_size", required_argument, NULL, 's'},
+ {"vrfwnetns", no_argument, NULL, 'n'},
{0}};
static void mgmt_exit(int);
@@ -237,6 +238,9 @@ int main(int argc, char **argv)
case 's':
buffer_size = atoi(optarg);
break;
+ case 'n':
+ vrf_configure_backend(VRF_BACKEND_NETNS);
+ break;
default:
frr_help_exit(1);
break;
@@ -249,6 +253,9 @@ int main(int argc, char **argv)
/* VRF commands initialization. */
vrf_cmd_init(NULL);
+ /* Interface commands initialization. */
+ if_cmd_init(NULL);
+
/* MGMTD related initialization. */
mgmt_init();
diff --git a/mgmtd/mgmt_txn.c b/mgmtd/mgmt_txn.c
index 679eaa7d7f..842e13cf11 100644
--- a/mgmtd/mgmt_txn.c
+++ b/mgmtd/mgmt_txn.c
@@ -176,6 +176,7 @@ struct txn_req_get_tree {
uint64_t recv_clients; /* Bitmask of clients recv reply from */
int32_t partial_error; /* an error while gather results */
uint8_t result_type; /* LYD_FORMAT for results */
+ uint8_t exact; /* if exact node is requested */
uint8_t simple_xpath; /* if xpath is simple */
struct lyd_node *client_results; /* result tree from clients */
};
@@ -1258,6 +1259,7 @@ static int txn_get_tree_data_done(struct mgmt_txn_ctx *txn,
{
struct txn_req_get_tree *get_tree = txn_req->req.get_tree;
uint64_t req_id = txn_req->req_id;
+ struct lyd_node *result;
int ret = NB_OK;
/* cancel timer and send reply onward */
@@ -1272,12 +1274,17 @@ static int txn_get_tree_data_done(struct mgmt_txn_ctx *txn,
ret = NB_ERR;
}
+ result = get_tree->client_results;
+
+ if (ret == NB_OK && result && get_tree->exact)
+ result = yang_dnode_get(result, get_tree->xpath);
+
if (ret == NB_OK)
ret = mgmt_fe_adapter_send_tree_data(txn->session_id,
txn->txn_id,
txn_req->req_id,
get_tree->result_type,
- get_tree->client_results,
+ result,
get_tree->partial_error,
false);
@@ -2364,7 +2371,8 @@ int mgmt_txn_send_get_req(uint64_t txn_id, uint64_t req_id,
*/
int mgmt_txn_send_get_tree_oper(uint64_t txn_id, uint64_t req_id,
uint64_t clients, LYD_FORMAT result_type,
- bool simple_xpath, const char *xpath)
+ uint8_t flags, bool simple_xpath,
+ const char *xpath)
{
struct mgmt_msg_get_tree *msg;
struct mgmt_txn_ctx *txn;
@@ -2382,9 +2390,61 @@ int mgmt_txn_send_get_tree_oper(uint64_t txn_id, uint64_t req_id,
txn_req = mgmt_txn_req_alloc(txn, req_id, MGMTD_TXN_PROC_GETTREE);
get_tree = txn_req->req.get_tree;
get_tree->result_type = result_type;
+ get_tree->exact = CHECK_FLAG(flags, GET_DATA_FLAG_EXACT);
get_tree->simple_xpath = simple_xpath;
get_tree->xpath = XSTRDUP(MTYPE_MGMTD_XPATH, xpath);
+ if (CHECK_FLAG(flags, GET_DATA_FLAG_CONFIG)) {
+ struct mgmt_ds_ctx *ds =
+ mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_RUNNING);
+ struct nb_config *config = mgmt_ds_get_nb_config(ds);
+
+ if (config) {
+ struct ly_set *set = NULL;
+ LY_ERR err;
+
+ err = lyd_find_xpath(config->dnode, xpath, &set);
+ if (err) {
+ get_tree->partial_error = err;
+ goto state;
+ }
+
+ /*
+ * If there's a single result, duplicate the returned
+ * node. If there are multiple results, duplicate the
+ * whole config and mark simple_xpath as false so the
+ * result is trimmed later in txn_get_tree_data_done.
+ */
+ if (set->count == 1) {
+ err = lyd_dup_single(set->dnodes[0], NULL,
+ LYD_DUP_WITH_PARENTS |
+ LYD_DUP_WITH_FLAGS |
+ LYD_DUP_RECURSIVE,
+ &get_tree->client_results);
+ if (!err)
+ while (get_tree->client_results->parent)
+ get_tree->client_results = lyd_parent(
+ get_tree->client_results);
+ } else if (set->count > 1) {
+ err = lyd_dup_siblings(config->dnode, NULL,
+ LYD_DUP_RECURSIVE |
+ LYD_DUP_WITH_FLAGS,
+ &get_tree->client_results);
+ if (!err)
+ get_tree->simple_xpath = false;
+ }
+
+ if (err)
+ get_tree->partial_error = err;
+
+ ly_set_free(set, NULL);
+ }
+ }
+state:
+ /* If we are only getting config, we are done */
+ if (!CHECK_FLAG(flags, GET_DATA_FLAG_STATE) || !clients)
+ return txn_get_tree_data_done(txn, txn_req);
+
msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_get_tree, slen + 1,
MTYPE_MSG_NATIVE_GET_TREE);
msg->refer_id = txn_id;
diff --git a/mgmtd/mgmt_txn.h b/mgmtd/mgmt_txn.h
index 39d8cde169..3f27f2f07b 100644
--- a/mgmtd/mgmt_txn.h
+++ b/mgmtd/mgmt_txn.h
@@ -203,6 +203,7 @@ extern int mgmt_txn_send_get_req(uint64_t txn_id, uint64_t req_id,
* req_id: FE client request identifier.
* clients: Bitmask of clients to send get-tree to.
* result_type: LYD_FORMAT result format.
+ * flags: option flags for the request.
* simple_xpath: true if xpath is simple (only key predicates).
* xpath: The xpath to get the tree from.
*
@@ -211,7 +212,8 @@ extern int mgmt_txn_send_get_req(uint64_t txn_id, uint64_t req_id,
*/
extern int mgmt_txn_send_get_tree_oper(uint64_t txn_id, uint64_t req_id,
uint64_t clients, LYD_FORMAT result_type,
- bool simple_xpath, const char *xpath);
+ uint8_t flags, bool simple_xpath,
+ const char *xpath);
/*
* Notifiy backend adapter on connection.
diff --git a/mgmtd/mgmt_vty.c b/mgmtd/mgmt_vty.c
index 2591930e4a..f4b24acf3a 100644
--- a/mgmtd/mgmt_vty.c
+++ b/mgmtd/mgmt_vty.c
@@ -251,17 +251,27 @@ DEFPY(show_mgmt_get_config, show_mgmt_get_config_cmd,
}
DEFPY(show_mgmt_get_data, show_mgmt_get_data_cmd,
- "show mgmt get-data WORD$path [json|xml]$fmt",
+ "show mgmt get-data WORD$path [with-config|only-config]$content [exact]$exact [json|xml]$fmt",
SHOW_STR
MGMTD_STR
"Get a data from the operational datastore\n"
"XPath expression specifying the YANG data root\n"
+ "Include \"config true\" data\n"
+ "Get only \"config true\" data\n"
+ "Get exact node instead of the whole data tree\n"
"JSON output format\n"
"XML output format\n")
{
LYD_FORMAT format = (fmt && fmt[0] == 'x') ? LYD_XML : LYD_JSON;
int plen = strlen(path);
char *xpath = NULL;
+ uint8_t flags = content ? GET_DATA_FLAG_CONFIG : GET_DATA_FLAG_STATE;
+
+ if (content && content[0] == 'w')
+ flags |= GET_DATA_FLAG_STATE;
+
+ if (exact)
+ flags |= GET_DATA_FLAG_EXACT;
/* get rid of extraneous trailing slash-* or single '/' unless root */
if (plen > 2 && ((path[plen - 2] == '/' && path[plen - 1] == '*') ||
@@ -272,7 +282,7 @@ DEFPY(show_mgmt_get_data, show_mgmt_get_data_cmd,
path = xpath;
}
- vty_mgmt_send_get_tree_req(vty, format, path);
+ vty_mgmt_send_get_data_req(vty, format, flags, path);
if (xpath)
XFREE(MTYPE_TMP, xpath);
diff --git a/tests/topotests/bfd_vrf_topo1/test_bfd_vrf_topo1.py b/tests/topotests/bfd_vrf_topo1/test_bfd_vrf_topo1.py
index a532f3a2d7..9e5a68f0a3 100644
--- a/tests/topotests/bfd_vrf_topo1/test_bfd_vrf_topo1.py
+++ b/tests/topotests/bfd_vrf_topo1/test_bfd_vrf_topo1.py
@@ -84,6 +84,7 @@ def setup_module(mod):
router.net.set_intf_netns(rname + "-eth2", ns, up=True)
for rname, router in router_list.items():
+ router.load_config(TopoRouter.RD_MGMTD, None, "--vrfwnetns")
router.load_config(
TopoRouter.RD_ZEBRA,
os.path.join(CWD, "{}/zebra.conf".format(rname)),
diff --git a/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py b/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py
index 7e4bcc8ada..d9177b4d25 100644
--- a/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py
+++ b/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py
@@ -132,6 +132,7 @@ def setup_module(mod):
for rname, router in router_list.items():
if rname == "r1":
+ router.load_config(TopoRouter.RD_MGMTD, None, "--vrfwnetns")
router.load_config(
TopoRouter.RD_ZEBRA,
os.path.join(CWD, "{}/zebra.conf".format(rname)),
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/tests/topotests/bgp_vrf_netns/test_bgp_vrf_netns_topo.py b/tests/topotests/bgp_vrf_netns/test_bgp_vrf_netns_topo.py
index 8457b752af..028bc35358 100644
--- a/tests/topotests/bgp_vrf_netns/test_bgp_vrf_netns_topo.py
+++ b/tests/topotests/bgp_vrf_netns/test_bgp_vrf_netns_topo.py
@@ -94,6 +94,7 @@ def setup_module(module):
router.net.set_intf_netns("r1-eth0", ns, up=True)
# run daemons
+ router.load_config(TopoRouter.RD_MGMTD, None, "--vrfwnetns")
router.load_config(
TopoRouter.RD_ZEBRA,
os.path.join(CWD, "{}/zebra.conf".format("r1")),
@@ -205,7 +206,6 @@ def test_bgp_vrf_netns():
if __name__ == "__main__":
-
args = ["-s"] + sys.argv[1:]
ret = pytest.main(args)
diff --git a/tests/topotests/conftest.py b/tests/topotests/conftest.py
index b1f8d50d06..23eab68db4 100755
--- a/tests/topotests/conftest.py
+++ b/tests/topotests/conftest.py
@@ -17,6 +17,7 @@ from pathlib import Path
import lib.fixtures
import pytest
+from lib.common_config import generate_support_bundle
from lib.micronet_compat import Mininet
from lib.topogen import diagnose_env, get_topogen
from lib.topolog import get_test_logdir, logger
@@ -26,7 +27,7 @@ from munet.base import Commander, proc_error
from munet.cleanup import cleanup_current, cleanup_previous
from munet.config import ConfigOptionsProxy
from munet.testing.util import pause_test
-from lib.common_config import generate_support_bundle
+
from lib import topolog, topotest
try:
@@ -598,6 +599,7 @@ def pytest_runtest_setup(item):
module = item.parent.module
script_dir = os.path.abspath(os.path.dirname(module.__file__))
os.environ["PYTEST_TOPOTEST_SCRIPTDIR"] = script_dir
+ os.environ["CONFIGDIR"] = script_dir
def pytest_exception_interact(node, call, report):
diff --git a/tests/topotests/mgmt_oper/oper.py b/tests/topotests/mgmt_oper/oper.py
index e3386067bc..0f6c3cdf5f 100644
--- a/tests/topotests/mgmt_oper/oper.py
+++ b/tests/topotests/mgmt_oper/oper.py
@@ -66,7 +66,7 @@ def do_oper_test(tgen, query_results):
r1 = tgen.gears["r1"].net
qcmd = (
- r"vtysh -c 'show mgmt get-data {}' "
+ r"vtysh -c 'show mgmt get-data {} {}' "
r"""| sed -e 's/"phy-address": ".*"/"phy-address": "rubout"/'"""
r"""| sed -e 's/"uptime": ".*"/"uptime": "rubout"/'"""
r"""| sed -e 's/"vrf": "[0-9]*"/"vrf": "rubout"/'"""
@@ -81,7 +81,7 @@ def do_oper_test(tgen, query_results):
if doreset:
doreset = False
expected = open(qr[1], encoding="ascii").read()
- output = r1.cmd_nostatus(qcmd.format(qr[0]))
+ output = r1.cmd_nostatus(qcmd.format(qr[0], qr[2] if len(qr) > 2 else ""))
try:
ojson = json.loads(output)
diff --git a/tests/topotests/mgmt_oper/r1/frr-simple.conf b/tests/topotests/mgmt_oper/r1/frr-simple.conf
index cf8ba160f4..d262afe359 100644
--- a/tests/topotests/mgmt_oper/r1/frr-simple.conf
+++ b/tests/topotests/mgmt_oper/r1/frr-simple.conf
@@ -14,10 +14,12 @@ debug mgmt client backend
interface r1-eth0
ip address 1.1.1.1/24
+ description r1-eth0-desc
exit
interface r1-eth1 vrf red
ip address 3.3.3.1/24
+ description r1-eth1-desc
exit
ip route 11.11.11.11/32 1.1.1.2
-!ip route 13.13.13.13/32 3.3.3.2 vrf red \ No newline at end of file
+!ip route 13.13.13.13/32 3.3.3.2 vrf red
diff --git a/tests/topotests/mgmt_oper/simple-results/result-intf-description.json b/tests/topotests/mgmt_oper/simple-results/result-intf-description.json
new file mode 100644
index 0000000000..8f8092ec1a
--- /dev/null
+++ b/tests/topotests/mgmt_oper/simple-results/result-intf-description.json
@@ -0,0 +1,14 @@
+{
+ "frr-interface:lib": {
+ "interface": [
+ {
+ "name": "r1-eth0",
+ "description": "r1-eth0-desc"
+ },
+ {
+ "name": "r1-eth1",
+ "description": "r1-eth1-desc"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-description-exact.json b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-description-exact.json
new file mode 100644
index 0000000000..e00f23c3dd
--- /dev/null
+++ b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-description-exact.json
@@ -0,0 +1,3 @@
+{
+ "frr-interface:description": "r1-eth0-desc"
+}
diff --git a/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-exact.json b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-exact.json
new file mode 100644
index 0000000000..f04e3a55e1
--- /dev/null
+++ b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-exact.json
@@ -0,0 +1,22 @@
+{
+ "frr-interface:interface": [
+ {
+ "name": "r1-eth0",
+ "vrf": "default",
+ "state": {
+ "if-index": "rubout",
+ "mtu": 1500,
+ "mtu6": 1500,
+ "speed": 10000,
+ "metric": 0,
+ "phy-address": "rubout"
+ },
+ "frr-zebra:zebra": {
+ "state": {
+ "up-count": 0,
+ "down-count": 0
+ }
+ }
+ }
+ ]
+}
diff --git a/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-only-config.json b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-only-config.json
new file mode 100644
index 0000000000..adcf99053f
--- /dev/null
+++ b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-only-config.json
@@ -0,0 +1,10 @@
+{
+ "frr-interface:lib": {
+ "interface": [
+ {
+ "name": "r1-eth0",
+ "description": "r1-eth0-desc"
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-with-config.json b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-with-config.json
new file mode 100644
index 0000000000..47c9686252
--- /dev/null
+++ b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-with-config.json
@@ -0,0 +1,25 @@
+{
+ "frr-interface:lib": {
+ "interface": [
+ {
+ "name": "r1-eth0",
+ "vrf": "default",
+ "description": "r1-eth0-desc",
+ "state": {
+ "if-index": "rubout",
+ "mtu": 1500,
+ "mtu6": 1500,
+ "speed": 10000,
+ "metric": 0,
+ "phy-address": "rubout"
+ },
+ "frr-zebra:zebra": {
+ "state": {
+ "up-count": 0,
+ "down-count": 0
+ }
+ }
+ }
+ ]
+ }
+}
diff --git a/tests/topotests/mgmt_oper/test_simple.py b/tests/topotests/mgmt_oper/test_simple.py
index 008733ee72..a52d125ecf 100644
--- a/tests/topotests/mgmt_oper/test_simple.py
+++ b/tests/topotests/mgmt_oper/test_simple.py
@@ -112,6 +112,31 @@ def test_oper_simple(tgen):
'route[prefix="1.1.1.0/24"]/route-entry[protocol="connected"]/metric',
"simple-results/result-singleton-metric.json",
),
+ (
+ '/frr-interface:lib/interface[name="r1-eth0"]',
+ "simple-results/result-intf-eth0-with-config.json",
+ "with-config",
+ ),
+ (
+ '/frr-interface:lib/interface[name="r1-eth0"]',
+ "simple-results/result-intf-eth0-only-config.json",
+ "only-config",
+ ),
+ (
+ "/frr-interface:lib/interface/description",
+ "simple-results/result-intf-description.json",
+ "with-config",
+ ),
+ (
+ '/frr-interface:lib/interface[name="r1-eth0"]',
+ "simple-results/result-intf-eth0-exact.json",
+ "exact",
+ ),
+ (
+ '/frr-interface:lib/interface[name="r1-eth0"]/description',
+ "simple-results/result-intf-eth0-description-exact.json",
+ "with-config exact",
+ ),
# Interface state
(
'/frr-interface:lib/interface[name="r1-eth0"]/state',
diff --git a/tests/topotests/munet/base.py b/tests/topotests/munet/base.py
index 43a45da07c..c007ba36cd 100644
--- a/tests/topotests/munet/base.py
+++ b/tests/topotests/munet/base.py
@@ -1234,8 +1234,14 @@ class Commander: # pylint: disable=R0904
else:
# This is the command to execute to be inside the namespace.
# We are getting into trouble with quoting.
- # Why aren't we passing in MUNET_RUNDIR?
- cmd = f"/usr/bin/env MUNET_NODENAME={self.name} {cmd}"
+ envvars = f"MUNET_NODENAME={self.name} NODENAME={self.name}"
+ if hasattr(self, "rundir"):
+ envvars += f" RUNDIR={self.rundir}"
+ if self.unet.config_dirname:
+ envvars += f" CONFIGDIR={self.unet.config_dirname}"
+ elif "CONFIGDIR" in os.environ:
+ envvars += f" CONFIGDIR={os.environ['CONFIGDIR']}"
+ cmd = f"/usr/bin/env {envvars} {cmd}"
# We need sudo b/c we are executing as the user inside the window system.
sudo_path = get_exec_path_host(["sudo"])
nscmd = (
@@ -1931,18 +1937,19 @@ class LinuxNamespace(Commander, InterfaceMixin):
assert unet is None
self.uflags = uflags
#
- # Open file descriptors for current namespaces for later resotration.
+ # Open file descriptors for current namespaces for later restoration.
#
try:
+ # pidfd_open is actually present in 5.4, is this 5.8 check for another
+ # aspect of what the pidfd_open code is relying on, something in the
+ # namespace code? If not we can simply check for os.pidfd_open() being
+ # present as our compat module linux.py runtime patches it in if
+ # supported by the kernel.
kversion = [int(x) for x in platform.release().split("-")[0].split(".")]
kvok = kversion[0] > 5 or (kversion[0] == 5 and kversion[1] >= 8)
except ValueError:
kvok = False
- if (
- not kvok
- or sys.version_info[0] < 3
- or (sys.version_info[0] == 3 and sys.version_info[1] < 9)
- ):
+ if not kvok:
# get list of namespace file descriptors before we unshare
self.p_ns_fds = []
self.p_ns_fnames = []
diff --git a/tests/topotests/munet/linux.py b/tests/topotests/munet/linux.py
index 417f74566a..519c55f84d 100644
--- a/tests/topotests/munet/linux.py
+++ b/tests/topotests/munet/linux.py
@@ -132,8 +132,17 @@ def pidfd_open(pid, flags=0):
return fd
+# Runtime patch if kernel supports the call.
if not hasattr(os, "pidfd_open"):
- os.pidfd_open = pidfd_open
+ try:
+ import platform
+
+ kversion = [int(x) for x in platform.release().split("-")[0].split(".")]
+ kvok = kversion[0] > 5 or (kversion[0] == 5 and kversion[1] >= 4)
+ except ValueError:
+ kvok = False
+ if kvok:
+ os.pidfd_open = pidfd_open
def setns(fd, nstype): # noqa: D402
diff --git a/tests/topotests/ospf_netns_vrf/test_ospf_netns_vrf.py b/tests/topotests/ospf_netns_vrf/test_ospf_netns_vrf.py
index 2716f63348..23eef8f5a6 100644
--- a/tests/topotests/ospf_netns_vrf/test_ospf_netns_vrf.py
+++ b/tests/topotests/ospf_netns_vrf/test_ospf_netns_vrf.py
@@ -87,6 +87,7 @@ def setup_module(mod):
router.net.set_intf_netns(rname + "-eth0", ns, up=True)
router.net.set_intf_netns(rname + "-eth1", ns, up=True)
+ router.load_config(TopoRouter.RD_MGMTD, None, "--vrfwnetns")
router.load_config(
TopoRouter.RD_ZEBRA,
os.path.join(CWD, "{}/zebra.conf".format(rname)),
diff --git a/vtysh/vtysh.h b/vtysh/vtysh.h
index 65733ca61b..11751d027e 100644
--- a/vtysh/vtysh.h
+++ b/vtysh/vtysh.h
@@ -56,9 +56,9 @@ extern struct event_loop *master;
VTYSH_ZEBRA | VTYSH_RIPD | VTYSH_RIPNGD | VTYSH_OSPFD | VTYSH_OSPF6D | \
VTYSH_ISISD | VTYSH_PIMD | VTYSH_PIM6D | VTYSH_NHRPD | \
VTYSH_EIGRPD | VTYSH_BABELD | VTYSH_PBRD | VTYSH_FABRICD | \
- VTYSH_VRRPD
+ VTYSH_VRRPD | VTYSH_MGMTD
#define VTYSH_INTERFACE VTYSH_INTERFACE_SUBSET | VTYSH_BGPD
-#define VTYSH_VRF VTYSH_INTERFACE_SUBSET | VTYSH_MGMTD
+#define VTYSH_VRF VTYSH_INTERFACE_SUBSET
#define VTYSH_KEYS VTYSH_RIPD | VTYSH_EIGRPD | VTYSH_OSPF6D | VTYSH_OSPFD
/* Daemons who can process nexthop-group configs */
#define VTYSH_NH_GROUP VTYSH_PBRD|VTYSH_SHARPD
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 {