De la non asynchronicité du protocole AgentX dans Net-SNMP
Vincent Bernat
Une des façons les plus commodes pour ajouter un support SNMP est d’utiliser le protocole AgentX : une application se comporte comme un sous-agent et traitera les requêtes déléguées par un agent maître via ce protocole. L’implémentation la plus courante est celle de Net-SNMP.
En bref
Une application exploitant l’implémentation AgentX de Net-SNMP de manière asynchrone peut se bloquer à de nombreuses occasions en raison principalement de l’indisponibilité temporaire de l’agent maître. Une contre-mesure assez efficace est d’utiliser comme agent maître un agent dédié à cette tâche et déléguer le traitement des MIB à un sous-agent.
Test de disponibilité de l’agent maître#
Net-SNMP exploite un modèle de programmation évènementielle. Il fournit également des fonctions synchrones classiques mais celles-ci sont construites à partir des versions asynchrones. Il exploite sa propre boucle d’évènements. Son implémentation d’AgentX semble alors un choix naturel pour ajouter un support SNMP dans une application asynchrone comme Keepalived, une solution de haute disponibilité et de répartition de charge.
Malheureusement, un sous-agent doit vérifier régulièrement si l’agent
maître est toujours disponible. Cette opération est effectuée
automatiquement du moment que vous utilisez la fonction run_alarms()
qui va à son tour faire appel à agentx_check_session()
:
/* * check a session validity for connectivity to the master agent. If * not functioning, close and start attempts to reopen the session */ void agentx_check_session(unsigned int clientreg, void *clientarg) { /* […] */ DEBUGMSGTL(("agentx/subagent", "checking status of session %p\n", ss)); if (!agentx_send_ping(ss)) { snmp_log(LOG_WARNING, "AgentX master agent failed to respond to ping. Attempting to re-register.\n"); /* * master agent disappeared? Try and re-register. * close first, just to be sure . */ agentx_unregister_callbacks(ss); agentx_close_session(ss, AGENTX_CLOSE_TIMEOUT); /* […] */ if (main_session != NULL) { /* […] */ main_session = NULL; agentx_reopen_session(0, NULL); } else { snmp_close(main_session); main_session = NULL; } } else { DEBUGMSGTL(("agentx/subagent", "session %p responded to ping\n", ss)); } }
Ce code appelle directement ou indirectement les fonctions
suivantes1 qui font ensuite appel à
agentx_synch_response()
:
agentx_send_ping()
agentx_open_session()
agentx_reopen_session()
agentx_close_session()
agentx_register()
agentx_unregister()
agentx_register_index()
agentx_unregister_index()
agentx_add_agentcaps()
agentx_remove_agentcaps()
agentx_synch_response()
va attendre de manière synchrone une
réponse de l’agent maître. Votre programme asynchrone va donc rester
bloqué à ne rien faire pendant ce laps de temps. Par exemple,
keepalived sera incapable d’envoyer les paquets VRRP nécessaires au
bon fonctionnement du cluster.
Les fonctions préfixées par agentx_
ne font pas partie de l’API de
Net-SNMP et pourraient donc être corrigées.
Plus facile à dire qu’à faire. De plus, il
existe d’autres points de blocage enfouis dans Net-SNMP. Ainsi, la
connexion de bas niveau (TCP ou socket Unix) avec l’agent maître
utilise la fonction connect()
de manière synchrone. Cet aspect a de
nombreuses ramifications difficiles à évoluer.
Voici quelques contre-mesures (partielles) :
-
Appeler
agentx_check_session()
moins souvent. Par défaut, cette fonction est invoquée toutes les 15 secondes. La désactiver complètement ou utiliser une valeur très élevée est déconseillé car le sous-agent ne se reconnectera pas à l’agent maître en cas de redémarrage de ce dernier. Voici comme porter l’intervalle à 20 secondes :netsnmp_ds_set_int(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_AGENTX_PING_INTERVAL, 120);
-
Ne pas appeler
agentx_check_session()
si l’agent maître a donné des signes de vie récemment, notamment en envoyant une requête à traiter. J’ai écrit un patch mettant en place une telle contre-mesure. L’ABI reste inchangée et il n’y a donc besoin que de recompiler les bibliothèques de Net-SNMP. -
Utiliser des valeurs plus faibles pour le délai d’attente et le nombre d’essais. Par défaut, le délai d’attente est de 1 seconde et le nombre d’essais est fixé à 5. En raison de l’utilisation de plusieurs fonctions synchrones à la suite, il est possible de rester bloqué jusqu’à 30 secondes. Voici comment2 désactiver les essais supplémentaires et diminuer fortement le délai d’attente :
static int snmp_setup_session_cb(int majorID, int minorID, void *serverarg, void *clientarg) { netsnmp_session *sess = serverarg; sess->timeout = ONE_SEC / 3; sess->retries = 0; return 0; } void some_init_function() { /* […] */ snmp_register_callback(SNMP_CALLBACK_LIBRARY, SNMP_CALLBACK_SESSION_INIT, snmp_setup_session_cb, NULL); /* […] */ }
Non réponse de l’agent maître#
Pourquoi l’agent maître peut ne pas répondre aux requêtes ? snmpd
utilise également la programmation évènementielle et il va se bloquer
à de nombreuses occassions. Voici deux exemples :
-
Les directives
pass
etpass_persist
ne rendent pas la main avant que la commande à exécuter n’affiche une ligne complète./* var_extensible_pass() in agent/mibgroup/ucd-snmp/pass.c */ /* * valid call. Exec and get output */ if ((fd = get_exec_output(passthru)) != -1) { file = fdopen(fd, "r"); if (fgets(buf, sizeof(buf), file) == NULL) { fclose(file); wait_on_exec(passthru); /* […] */ continue; } /* […] */ fclose(file); wait_on_exec(passthru); /* […] */ }
-
L’implémentation de
DISMAN-PING-MIB
se bloque pendant le déroulement des pings vers l’hôte distant. Si ce dernier est inaccessible,snmpd
peut se bloquer pendant de longues secondes. Un problème similaire existe pourDISMAN-TRACEROUTE-MIB
.
Agent maître minimal#
De plus en plus de code est exécuté par l’agent maître et donc la situation ne va pas aller en s’améliorant. Pour pallier à notre problème, nous allons intercaler un agent maître minimal. L’agent qui jouait jusqu’ici le rôle d’agent maître et servait un certain nombre de MIB va devenir un sous-agent. Les sous-agents existant se connecteront au nouvel agent maître.
Transformer snmpd
en sous-agent est simple : il suffit d’utiliser le
drapeau -X
. Nous devons également désactiver certaines MIB selon le
rôle de l’agent. Cela se fait avec le drapeau -I
. L’agent minimal
n’utilisera que les modules strictement nécessaires à son
fonctionnement. Tous les autres modules3 seront servis par
snmpd
en tant que sous-agent.
$ MODS="snmp_mib,sysORTable,usmConf,usmStats,usmUser,vacm_conf,vacm_context,vacm_vars" $ snmpd -Lsd -Lf /dev/null -u snmp -g snmp \ > -C -c /etc/snmp/snmpd.master.conf \ > -p /var/run/snmpd.master.pid \ > -I $MODS $ snmpd -Lsd -Lf /dev/null -u snmp -g snmp \ > -p /var/run/snmpd.pid -X \ > -I -$MODS
La configuration des accès et la directive master agentx
doivent se
trouver dans /etc/snmp/snmpd.master.conf
. Les autres directives
(telles que pass
, pass_persist
, load
, sysname
, …) doivent
être dans /etc/snmp/snmpd.conf
.
Il est désormais peu probable d’observer un bloquage de l’agent
maître. Bien que le problème soit potentiellement toujours présent, sa
surface est considérablement réduite. Il est possible d’étendre cette
solution en saucissonnant snmpd
en plusieurs sous-agents: l’un pour
les directives comme pass
et pass_persist
, un autre pour la
plupart des modules et un dernier pour les modules plus litigieux,
tels que DISMAN-PING-MIB
.
-
agentx_reopen_session()
appelleagentx_close_session()
etagentx_open_session()
.agentx_register()
et les fonctions suivantes sont appelées par ces deux fonctions. ↩︎ -
Les variables
NETSNMP_DS_AGENT_AGENTX_TIMEOUT
etNETSNMP_DS_AGENT_AGENTX_RETRIES
ne sont utilisées que pour l’agent maître, pas pour un sous-agent. ↩︎ -
La commande
snmpd -Dmib_init -H
permet d’obtenir la liste des modules. Le modulesmux
, s’il est utilisé, doit être ajouté à l’agent maître. ↩︎