Commit 72500253d0 for frr
commit 72500253d0e1678d426b68bc8c448ba899ae17ac
Author: Chirag Shah <chirag@nvidia.com>
Date: Fri Oct 23 16:02:10 2026 -0700
lib: add debug agentx command
AgentX traces had no command of their own, so they could not be turned
on for one daemon and kept in its configuration. One debug flag covers
the master session, the descriptors polled for it, and Net-SNMP's own
debug messages.
The command lives in libfrr, so vtysh can send it to every daemon that
accepts agentx, including one that was started without the SNMP module.
Set in configuration mode it is written back out. Set in enable mode it
lasts until the daemon restarts.
Ticket: #5219067
Assisted-by: Cursor Grok 4.7
Signed-off-by: Chirag Shah <chirag@nvidia.com>
diff --git a/doc/user/snmp.rst b/doc/user/snmp.rst
index 277d13baf2..51b8099ffb 100644
--- a/doc/user/snmp.rst
+++ b/doc/user/snmp.rst
@@ -261,4 +261,14 @@ Here is the syntax for using AgentX:
Once enabled, it can't be unconfigured. Only removing from the daemons file
the keyword ``agentx`` takes an effect.
+.. clicmd:: debug agentx
+
+ Log AgentX subagent activity: the connection to the master agent going up
+ and down, and the file descriptors polled for it. Net-SNMP's own debug
+ messages are also only logged while this is enabled.
+
+ The command is available in every daemon that supports ``agentx``,
+ including bgpd. When given in configuration mode it is saved to the
+ configuration; in enable mode it lasts until the daemon restarts.
+
.. include:: snmptrap.rst
diff --git a/lib/agentx.c b/lib/agentx.c
index c4fca5a068..384508062e 100644
--- a/lib/agentx.c
+++ b/lib/agentx.c
@@ -24,6 +24,7 @@
#include "libfrr.h"
#include "xref.h"
#include "lib/libagentx.h"
+#include "lib/debug.h"
XREF_SETUP();
@@ -43,6 +44,11 @@ static struct list *events = NULL;
static void agentx_events_update(void);
+static inline bool agentx_debug(void)
+{
+ return DEBUG_MODE_CHECK(&agentx_dbg, DEBUG_MODE_ALL);
+}
+
static void agentx_timeout(struct event *t)
{
snmp_timeout();
@@ -130,6 +136,7 @@ static void agentx_events_update(void)
if (thr_fd == fd) {
struct listnode *nextln = listnextnode(ln);
if (!netsnmp_large_fd_is_set(fd, &lfds)) {
+ DEBUGD(&agentx_dbg, "AgentX: stop polling fd %d", fd);
event_cancel(thr);
XFREE(MTYPE_TMP, thr);
list_delete_node(events, ln);
@@ -150,6 +157,7 @@ static void agentx_events_update(void)
newthr = XCALLOC(MTYPE_TMP, sizeof(struct event *));
newln = listnode_add_before(events, ln, newthr);
event_add_read(agentx_tm, agentx_read, newln, fd, newthr);
+ DEBUGD(&agentx_dbg, "AgentX: start polling fd %d", fd);
}
}
@@ -158,6 +166,8 @@ static void agentx_events_update(void)
while (ln) {
struct listnode *nextln = listnextnode(ln);
thr = listgetdata(ln);
+ DEBUGD(&agentx_dbg, "AgentX: stop polling fd %d",
+ (thr && *thr) ? EVENT_FD(*thr) : -1);
event_cancel(thr);
XFREE(MTYPE_TMP, thr);
list_delete_node(events, ln);
@@ -171,7 +181,12 @@ static int agentx_log_callback(int major, int minor, void *serverarg,
void *clientarg)
{
struct snmp_log_message *slm = (struct snmp_log_message *)serverarg;
- char *msg = XSTRDUP(MTYPE_TMP, slm->msg);
+ char *msg;
+
+ if (slm->priority == LOG_DEBUG && !agentx_debug())
+ return SNMP_ERR_NOERROR;
+
+ msg = XSTRDUP(MTYPE_TMP, slm->msg);
if (msg)
msg[strlen(msg) - 1] = '\0';
switch (slm->priority) {
@@ -208,6 +223,7 @@ static int agentx_log_callback(int major, int minor, void *serverarg,
static int agentx_cli_on(void)
{
if (!agentx_enabled) {
+ DEBUGD(&agentx_dbg, "AgentX: enable subagent, connecting to master agent");
init_snmp(FRR_SMUX_NAME);
events = list_new();
agentx_events_update();
@@ -232,6 +248,7 @@ static int agentx_master_up_cb(int majorID, int minorID, void *serverarg, void *
return SNMP_ERR_NOERROR;
agentx_connected = true;
+ DEBUGD(&agentx_dbg, "AgentX: master agent session up");
return SNMP_ERR_NOERROR;
}
@@ -241,6 +258,7 @@ static int agentx_master_down_cb(int majorID, int minorID, void *serverarg, void
return SNMP_ERR_NOERROR;
agentx_connected = false;
+ DEBUGD(&agentx_dbg, "AgentX: master agent session down");
return SNMP_ERR_NOERROR;
}
@@ -282,6 +300,7 @@ void smux_init(struct event_loop *tm)
void smux_agentx_enable(void)
{
if (!agentx_enabled) {
+ DEBUGD(&agentx_dbg, "AgentX: enable subagent, connecting to master agent");
init_snmp(FRR_SMUX_NAME);
events = list_new();
agentx_events_update();
diff --git a/lib/libagentx.c b/lib/libagentx.c
index 23826572ed..3c9cf35d2e 100644
--- a/lib/libagentx.c
+++ b/lib/libagentx.c
@@ -6,13 +6,25 @@
#include "lib/hook.h"
#include "lib/libagentx.h"
+#include "lib/debug.h"
#include "command.h"
+#include "lib/libagentx_clippy.c"
+
DEFINE_HOOK(agentx_cli_enabled, (), ());
DEFINE_HOOK(agentx_cli_disabled, (), ());
bool agentx_enabled;
+/*
+ * Lives in libfrr rather than the SNMP module so the command is accepted by
+ * every daemon vtysh sends it to, whether or not the module is loaded.
+ */
+struct debug agentx_dbg = {
+ .conf = "debug agentx",
+ .desc = "SNMP AgentX",
+};
+
/* AgentX node. */
static int config_write_agentx(struct vty *vty)
{
@@ -53,6 +65,17 @@ DEFUN(no_agentx, no_agentx_cmd, "no agentx",
return CMD_SUCCESS;
}
+DEFPY(debug_agentx,
+ debug_agentx_cmd,
+ "[no] debug agentx",
+ NO_STR DEBUG_STR
+ "SNMP AgentX subagent activity\n")
+{
+ DEBUG_MODE_SET(&agentx_dbg, DEBUG_NODE2MODE(vty->node), !no);
+
+ return CMD_SUCCESS;
+}
+
void libagentx_init(void)
{
agentx_enabled = false;
@@ -60,4 +83,8 @@ void libagentx_init(void)
install_node(&agentx_node);
install_element(CONFIG_NODE, &agentx_enable_cmd);
install_element(CONFIG_NODE, &no_agentx_cmd);
+
+ debug_install(&agentx_dbg);
+ install_element(ENABLE_NODE, &debug_agentx_cmd);
+ install_element(CONFIG_NODE, &debug_agentx_cmd);
}
diff --git a/lib/libagentx.h b/lib/libagentx.h
index 6426d4ed0c..da35314c6a 100644
--- a/lib/libagentx.h
+++ b/lib/libagentx.h
@@ -7,8 +7,11 @@
#include "lib/hook.h"
+struct debug;
+
extern void libagentx_init(void);
extern bool agentx_enabled;
+extern struct debug agentx_dbg;
DECLARE_HOOK(agentx_cli_enabled, (), ());
DECLARE_HOOK(agentx_cli_disabled, (), ());
diff --git a/lib/subdir.am b/lib/subdir.am
index f6f802d559..b6c83d1afc 100644
--- a/lib/subdir.am
+++ b/lib/subdir.am
@@ -176,6 +176,7 @@ lib_libfrr_la_SOURCES += lib/db.c
endif
clippy_scan += \
+ lib/libagentx.c \
lib/affinitymap_cli.c \
lib/host_cli.c \
lib/if.c \
diff --git a/tests/topotests/bgp_snmp_bgp4v2_notification/test_bgp_snmp_bgp4v2_notification.py b/tests/topotests/bgp_snmp_bgp4v2_notification/test_bgp_snmp_bgp4v2_notification.py
index c41ef30bb5..1c36241c3f 100755
--- a/tests/topotests/bgp_snmp_bgp4v2_notification/test_bgp_snmp_bgp4v2_notification.py
+++ b/tests/topotests/bgp_snmp_bgp4v2_notification/test_bgp_snmp_bgp4v2_notification.py
@@ -69,6 +69,27 @@ def teardown_module(mod):
tgen.stop_topology()
+def test_agentx_debug_config():
+ tgen = get_topogen()
+ r2 = tgen.gears["r2"]
+
+ r2.vtysh_multicmd(
+ """
+configure terminal
+debug agentx
+"""
+ )
+
+ running = r2.vtysh_cmd("show running-config")
+ assert "debug agentx" in running, "debug agentx missing from running-config"
+
+ # bgpd's "show debugging [bgp]" is DEFUN_NOSH, so vtysh does not accept
+ # the "bgp" keyword. "show debugging" is vtysh's command; it asks bgpd,
+ # which prints the lib debug flags.
+ debugs = r2.vtysh_cmd("show debugging")
+ assert "SNMP AgentX debugging is on" in debugs, "debug agentx not reported"
+
+
def test_bgp_open_notification_change_configuration():
tgen = get_topogen()