Étendre les fonctionnalités de Net-⁠SNMP

Vincent Bernat

SNMP si­gni­fie « Simple Net­work Ma­na­ge­ment Pro­to­col ». Il per­met à un su­per­vi­seur (ma­na­ger) d’ob­te­nir des in­for­ma­tions au­près d’un agent. Un usage par­ti­cu­liè­re­ment ré­pandu de SNMP est d’ob­te­nir les courbes de tra­fic de di­vers équi­pe­ments. Bien que le « S » de SNMP si­gni­fie simple, la né­bu­leuse au­tour de ce pro­to­cole peut s’avé­rer as­sez com­plexe à mettre en œuvre. Il s’agit tou­te­fois du stan­dard le plus cou­rant pour ré­cu­pé­rer des mé­triques en en­vi­ron­ne­ment hé­té­ro­gène. Net-⁠SNMP est une suite d’ap­pli­ca­tions SNMP in­cluant no­tam­ment un agent. Ce der­nier per­met d’ex­por­ter de nom­breuses in­for­ma­tions et peut être étendu par dif­fé­rents moyens. Voyons lesquels.

En bref

À mon avis, étendre Net-⁠SNMP passe par l’une de ces mé­thodes : di­rec­tive extend, di­rec­tive pass_persist, pro­to­cole AgentX.

Ce qu’il faut savoir sur SNMP#

Les va­riables ac­ces­sibles à tra­vers un agent SNMP sont or­ga­ni­sées sous forme d’un arbre. Chaque va­riable dis­pose d’un type et est as­so­ciée à un iden­ti­fiant d’ob­jet (ou OID). Ainsi, .1.3.6.1.2.1.31.1.1.1.1.2 est l’OID cor­res­pon­dant au nom de la se­conde carte ré­seau du système.

Ha­bi­tuel­le­ment, SNMP est trans­porté au-des­sus d’UDP, sur le port 161. Pour les be­soins de cet ar­ticle, nous pou­vons li­mi­ter notre com­pré­hen­sion de l’agent à sa ca­pa­cité à trai­ter trois types de requêtes :

GET
ré­cu­pé­rer la va­leur d’une variable.
GETNEXT
ré­cu­pé­rer la va­riable sui­vante, dans l’ordre lexi­co­gra­phique des OID. Cette opé­ra­tion per­met au lo­gi­ciel de su­per­vi­sion de par­cou­rir l’en­semble des va­riables dis­po­nibles et ainsi de dé­cou­vrir, par exemple, l’en­semble des in­ter­faces ré­seau. Cette fonc­tion­na­lité rend la concep­tion de l’agent plus com­plexe mais il s’agit d’une fonc­tion­na­lité cru­ciale de SNMP.
SET
mo­di­fier la va­leur d’une variable.

Un OID est peu na­tu­rel à ma­ni­pu­ler pour un hu­main. Aussi, les va­riables sont ha­bi­tuel­le­ment dé­crites dans une MIB qui est un fi­chier dont le contenu dé­crit la cor­res­pon­dance entre des noms de va­riables et des OID. Ainsi, il est pos­sible de dé­si­gner l’OID ci-des­sus par IF-MIB::ifName.2. Ces mo­dules sont écrits en uti­li­sant un sous-en­semble d’ASN.1 dé­fini dans la RFC 2578.

Une MIB per­met éga­le­ment de grou­per plu­sieurs va­riables sous forme d’une table. Ainsi, IF-MIB::ifDescr, IF-MIB::ifType, IF-MIB::ifSpeed sont les co­lonnes de IF-MIB::ifTable. Chaque ligne est re­pré­sen­tée par un in­dex. Dans le cas de IF-MIB::ifTable, il s’agit sim­ple­ment de l’in­dex de l’in­ter­face. Net-⁠SNMP four­nit snmptable qui per­met d’af­fi­cher de telles tables1 :

$ snmptable localhost IF-MIB::ifTable
SNMP table: IF-MIB::ifTable

 ifIndex ifDescr           ifType ifMtu   ifSpeed
       1      lo softwareLoopback 16436  10000000
       2    eth0   ethernetCsmacd  1500 100000000
       3    eth1   ethernetCsmacd  1500  10000000
       4     br0   ethernetCsmacd  1500         0

En tant que pro­to­cole, SNMP ne connaît pas l’or­ga­ni­sa­tion des va­riables (ta­bleau ou non). Une va­riable est dé­crite par un OID et un type, c’est tout.

Parmi les ou­tils in­clus dans Net-⁠SNMP et sou­vent usi­tés, on peut ci­ter snmpget pour ré­cu­pé­rer la va­leur d’une va­riable, snmpwalk pour par­cou­rir les va­riables ex­po­sées (avec l’opé­ra­tion GETNEXT) et snmptranslate qui per­met de tra­duire un nom en OID ou vice-versa.

$ snmpget localhost IF-MIB::ifDescr.2
IF-MIB::ifDescr.2 = STRING: eth0
$ snmpwalk localhost IF-MIB::ifDescr
IF-MIB::ifDescr.1 = STRING: lo
IF-MIB::ifDescr.2 = STRING: eth0
IF-MIB::ifDescr.3 = STRING: eth1
IF-MIB::ifDescr.4 = STRING: br0
$ snmpwalk -On public localhost .1.3.6.1.2.1.2.2.1.2
.1.3.6.1.2.1.2.2.1.2.1 = STRING: lo
.1.3.6.1.2.1.2.2.1.2.2 = STRING: eth0
.1.3.6.1.2.1.2.2.1.2.3 = STRING: eth1
.1.3.6.1.2.1.2.2.1.2.4 = STRING: br0
$ snmptranslate -On IF-MIB::ifDescr.3
.1.3.6.1.2.1.2.2.1.2.3

Extensions pour Net-⁠SNMP#

ethtool -S per­met d’ob­te­nir dif­fé­rentes sta­tis­tiques liées à une carte ré­seau, comme le nombre d’oc­tets re­çus ou le nombre de col­li­sions. Cer­taines de ces sta­tis­tiques sont ac­ces­sibles à tra­vers IF-MIB, RMON-MIB et EtherLike-MIB, mais d’autres sont très spé­ci­fiques et dé­pendent de la carte ré­seau. Com­ment ex­por­ter ces sta­tis­tiques à tra­vers SNMP ?

À ma connais­sance, il n’existe pas de MIB déjà écrite pour cet usage. La pre­mière étape est donc d’en écrire une. Ap­pe­lons-la ETHTOOL-MIB. Elle ne contient qu’une seule table. Celle-ci est in­dexée par l’in­dex de l’in­ter­face ainsi que par le nom de la sta­tis­tique. Voici ce qu’on pour­rait ob­te­nir comme sortie :

$ snmpwalk localhost ETHTOOL-MIB::ethtoolStat.2
ethtoolStat.2.'align_errors' = Counter64: 0
ethtoolStat.2.'broadcast' = Counter64: 25
ethtoolStat.2.'multicast' = Counter64: 345
ethtoolStat.2.'rx_errors' = Counter64: 0
ethtoolStat.2.'rx_missed' = Counter64: 0
ethtoolStat.2.'rx_packets' = Counter64: 262011
ethtoolStat.2.'tx_aborted' = Counter64: 0
ethtoolStat.2.'tx_errors' = Counter64: 0
ethtoolStat.2.'tx_multi_collisions' = Counter64: 0
ethtoolStat.2.'tx_packets' = Counter64: 296170
ethtoolStat.2.'tx_single_collisions' = Counter64: 0
ethtoolStat.2.'tx_underrun' = Counter64: 0
ethtoolStat.2.'unicast' = Counter64: 261641

J’ai écrit dif­fé­rentes im­plé­men­ta­tions de cette MIB que vous pou­vez ré­cu­pé­rer dans un dépôt Git.

Avec des commandes arbitraires#

La pre­mière so­lu­tion pour étendre Net-⁠SNMP est de faire usage de la di­rec­tive extend. Celle-ci per­met d’exé­cu­ter des com­mandes ar­bi­traires et de ré­cu­pé­rer les ré­sul­tats à tra­vers NET-SNMP-EXTEND-MIB. À l’aide d’un script en­cap­su­lant la sor­tie de ethtool -S, les sta­tis­tiques pour eth0 peuvent être ex­por­tées en uti­li­sant la confi­gu­ra­tion sui­vante dans snmpd.conf :

# Export eth0 statistics
extend eth0-names  /full/path/to/ethtool-stats eth0 names
extend eth0-values /full/path/to/ethtool-stats eth0 values

Le ré­sul­tat est en­suite ré­cu­péré à tra­vers NET-SNMP-EXTEND-MIB::‌nsExtendOutput2Table :

$ snmpwalk localhost NET-SNMP-EXTEND-MIB::nsExtendOutput2Table
nsExtendOutLine."eth0-names".1 = STRING: tx_packets
nsExtendOutLine."eth0-names".2 = STRING: rx_packets
nsExtendOutLine."eth0-names".3 = STRING: tx_errors
nsExtendOutLine."eth0-names".4 = STRING: rx_errors
nsExtendOutLine."eth0-names".5 = STRING: rx_missed
nsExtendOutLine."eth0-names".6 = STRING: align_errors
nsExtendOutLine."eth0-names".7 = STRING: tx_single_collisions
nsExtendOutLine."eth0-names".8 = STRING: tx_multi_collisions
nsExtendOutLine."eth0-values".1 = STRING: 246309
nsExtendOutLine."eth0-values".2 = STRING: 223941
nsExtendOutLine."eth0-values".3 = STRING: 0
nsExtendOutLine."eth0-values".4 = STRING: 0
nsExtendOutLine."eth0-values".5 = STRING: 0
nsExtendOutLine."eth0-values".6 = STRING: 0
nsExtendOutLine."eth0-values".7 = STRING: 0
nsExtendOutLine."eth0-values".8 = STRING: 0

Il est aussi pos­sible d’ef­fec­tuer cette confi­gu­ra­tion à la vo­lée. La com­mande sui­vante per­met d’ajou­ter les sta­tis­tiques liées à l’in­ter­face eth2 :

$ snmpset -m +NET-SNMP-EXTEND-MIB localhost \
>    'nsExtendStatus."eth2-names"'  = createAndGo \
>    'nsExtendCommand."eth2-names"' = /full/path/to/ethtool-stats \
>    'nsExtendArgs."eth2-names"'    = 'eth2 names' \
>    'nsExtendStatus."eth2-values"'  = createAndGo \
>    'nsExtendCommand."eth2-values"' = /full/path/to/ethtool-stats \
>    'nsExtendArgs."eth2-values"'    = 'eth2 values'
nsExtendStatus."eth2-names" = INTEGER: createAndGo(4)
nsExtendCommand."eth2-names" = STRING: /full/path/to/ethtool-stats
nsExtendArgs."eth2-names" = STRING: eth2 names
nsExtendStatus."eth2-values" = INTEGER: createAndGo(4)
nsExtendCommand."eth2-values" = STRING: /full/path/to/ethtool-stats
nsExtendArgs."eth2-values" = STRING: eth2 values
$ snmpgetnext -m +NET-SNMP-EXTEND-MIB localhost \
>    'nsExtendOutLine."eth2-names"'
nsExtendOutLine."eth2-names".1 = STRING: tx_packets

Cela peut pa­raître plu­tôt pra­tique mais cela re­pré­sente éga­le­ment un risque im­por­tant. Il est pré­fé­rable de désac­ti­ver cette fonc­tion­na­lité ou de ne l’ac­ti­ver que pour un uti­li­sa­teur SNMPv3 par­ticulier.

L’uti­li­sa­tion de extend ne per­met pas d’im­plé­men­ter n’im­porte quelle MIB. Si le mé­ca­nisme de cache n’est pas désac­tivé, cela peut tou­te­fois être une so­lu­tion rai­son­nable pour des be­soins simples tels qu’ob­te­nir le contenu d’un fi­chier d’une ligne ou la sor­tie d’un plu­gin Na­gios. Tou­te­fois, quand il s’agit d’ex­por­ter des don­nées sous forme de ta­bleau, cette so­lu­tion est peu ap­propriée.

Avec des scripts lancés par l’agent#

Net-⁠SNMP est ca­pable de dé­lé­guer un sous-arbre à un script dé­fini par la di­rec­tive pass_persist. Un tel script va re­ce­voir les re­quêtes à trai­ter sur son en­trée stan­dard à l’aide d’un pro­to­cole très simple. Ce­lui-ci est dé­crit dans la page de ma­nuel de snmpd.conf. Voici un exemple d’in­teraction :

→  PING
←  PONG
→  get
→  .1.3.6.1.4.1.39178.100.1.1.1.2.2.109.117.108.116.105.99.97.115.116
←  .1.3.6.1.4.1.39178.100.1.1.1.2.2.109.117.108.116.105.99.97.115.116
←  counter64
←  223941
→  getnext
→  .1.3.6.1.4.1.39178.100.1.1.1.2.2
←  .1.3.6.1.4.1.39178.100.1.1.1.2.2.97.108.105.103.110.95.101.114
←  counter64
←  0

Ce pro­to­cole est suf­fi­sam­ment simple pour être im­plé­menté di­rec­te­ment à l’aide d’un lan­gage de scripts. Tou­te­fois, en Perl, il est pré­fé­rable d’uti­li­ser l’ex­ten­sion SNMP::‌ExtensionSNMP::‌PassPersist de Sé­bas­tien Aper­ghis-Tra­moni. Voici un ex­trait de l’im­plé­men­ta­tion de ETHTOOL-MIB en uti­li­sant cette ex­ten­sion. Le code com­plet se trouve sur GitHub.

use SNMP::Extension::PassPersist;
my $extsnmp = SNMP::Extension::PassPersist->new(
    backend_collect => \&update_tree
    );
$extsnmp->run;

sub update_tree {
    my @interfaces = </sys/class/net/*>;
    foreach my $interface (@interfaces) {
        $interface =~ s/^.*\///;
        open(ETHTOOL, "ethtool -S $interface 2>/dev/null |") or next;
        while (<ETHTOOL>) {
            /^\s+(\w+): (\d+)$/ or next;
            my $name  = $1;
            my $value = int($2);
            my $oid   = oid_compute($interface, $name);
            $extsnmp->add_oid_entry($oid, "counter", $value);
        }
        close(ETHTOOL);
    }
}

Avec Py­thon, le mo­dule snmp-pass­per­sist de Ni­co­las Agius four­nit une in­ter­face si­mi­laire. L’exemple com­plet se trouve sur GitHub.

La di­rec­tive pass_persist per­met d’im­plé­men­ter une MIB avec des per­for­mances tout à fait hon­nêtes. Il faut tou­te­fois gar­der en tête les li­mi­ta­tions les plus im­por­tantes de cette approche :

  • À moins de dis­po­ser d’une ver­sion très ré­cente de Net-⁠SNMP, les types 64 bits ne sont pas sup­por­tés et les types 32 bits équi­va­lents se­ront sub­sti­tués. Cela peut être gê­nant pour les comp­teurs qui aug­mentent ra­pidement.
  • Lors du trai­te­ment d’une re­quête, l’agent ne ré­pon­dra pas aux autres re­quêtes. Toute pause, par exemple pour al­ler ré­cu­pé­rer une in­for­ma­tion dis­tante, est donc pro­hi­bée. Les don­nées doivent être ré­cu­pé­rées en avance et dis­po­nible im­mé­diatement.

Avec le protocole AgentX#

AgentX est un pro­to­cole dé­fini par la RFC 2741 et per­met­tant à un agent maître d’être com­plété par plu­sieurs sous-agents in­dé­pen­dants. Pour au­to­ri­ser snmpd à ac­cep­ter des sous-agents, il suf­fit d’ajou­ter la di­rec­tive master agentx dans snmpd.conf. Voici les bé­né­fices aux­quels on peut s’at­tendre par rap­port à la di­rec­tive pass_persist :

  1. Au­cune confi­gu­ra­tion n’est né­ces­saire au ni­veau de l’agent maître pour ac­cep­ter un sous-agent sup­plé­men­taire. C’est ce der­nier qui va in­di­quer à l’agent maître les MIB (ou seule­ment cer­taines par­ties) dont il veut s’occuper.
  2. Un sous-agent est dé­cou­plé de l’agent maître. Il peut uti­li­ser une iden­tité dif­fé­rente ou se trou­ver in­té­gré à un lo­gi­ciel tiers per­met­tant ainsi l’ex­port des don­nées in­ternes ou la confi­gu­ra­tion à dis­tance, à tra­vers SNMP, de ce dernier.
  3. Le pro­to­cole AgentX peut être trans­porté au-des­sus de TCP. Un sous-agent peut donc se trou­ver sur une autre ma­chine ou dans un en­vi­ron­ne­ment restreint.
  4. Les types 64 bits sont to­ta­le­ment sup­por­tés. De plus, il est pos­sible d’en­voyer des alertes (traps).

Perl et Python#

Net-⁠SNMP four­nit un mo­dule Perl, Net-⁠SNMP::agent, pour écrire un sous-agent avec le pro­to­cole AgentX. Il s’agit d’un mo­dule de plus bas ni­veau que ce­lui que nous avons uti­lisé avec pass_persist. Voici quelques exemples de son uti­lisation :

Il n’existe ac­tuel­le­ment pas de mo­dule si­mi­laire pour Py­thon mais il existe une ex­ten­sion AgentX uti­li­sant ctypes et four­nis­sant une abs­trac­tion d’as­sez haut ni­veau. Je n’ai pas écrit d’exemple l’uti­li­sant, mais un il y en a un donné par l’auteur.

C avec la « nouvelle » API#

Pour écrire un sous-agent en C, il faut choi­sir entre deux API :

  1. celle hé­ri­tée d’UCD-SNMP qui est par­fois ap­pe­lée l’API « tra­di­tionnelle » ;
  2. l’API dé­ve­lop­pée pour Net-⁠SNMP 5.x.

Les deux API per­mettent d’en­re­gis­trer des fonc­tions gé­rant cha­cune une ou plu­sieurs par­ties de l’arbre. Avec l’API tra­di­tion­nelle, ces fonc­tions sont ap­pe­lées avec très peu d’in­for­ma­tions. Il est donc né­ces­saire de re­trou­ver à par­tir de l’OID les ob­jets à ex­por­ter. Cela de­vient par­ti­cu­liè­re­ment com­pli­qué en pré­sence de ta­bleaux dont les in­dex sont complexes.

La nou­velle API pro­pose dif­fé­rentes aides pour écrire ces fonc­tions. Cer­taines per­mettent de co­pier les ob­jets dans des ta­bleaux ou des listes et dé­chargent l’au­teur de toutes les autres tâches. D’autres at­tendent de l’au­teur qu’il écrive un ité­ra­teur pour par­cou­rir les ob­jets. D’autres en­core per­mettent d’ajou­ter un sys­tème de cache.

Au-des­sus de cette nou­velle API, Net-⁠SNMP ap­porte un uti­li­taire, mib2c, conver­tis­sant une MIB en un sque­lette en C. Il suf­fit en­suite de com­plé­ter ce­lui-ci avec le code né­ces­saire pour ré­cu­pé­rer les ob­jets à ex­por­ter. Le sque­lette est do­cu­menté et une com­mande per­met de ré­cu­pé­rer un guide d’im­plé­men­ta­tion com­pre­nant toutes les étapes né­cessaires.

Im­plé­men­ter ETHTOOL-MIB re­vient donc à faire tour­ner mib2c, ré­pondre à quelques ques­tions et ajou­ter le code man­quant dans ethtoolStatTable_data_access.c. Le ré­sul­tat est dis­po­nible sur GitHub. Voici une ver­sion rac­cour­cie2 de ce qui a été écrit :

/* Socket for ioctl */
skfd = socket(AF_INET, SOCK_DGRAM, 0);

/* Iterate through all interfaces */
getifaddrs(&ifap);
for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
    /* Grab statistics name and values */
    strcpy(ifr.ifr_name, ifa->ifa_name);
    drvinfo.cmd         = ETHTOOL_GDRVINFO;
    ifr.ifr_data        = (caddr_t) &drvinfo;
    ioctl(skfd, SIOCETHTOOL, &ifr);
    n_stats             = drvinfo.n_stats;
    strings->cmd        = ETHTOOL_GSTRINGS;
    strings->string_set = ETH_SS_STATS;
    strings->len        = n_stats;
    ifr.ifr_data        = (caddr_t) strings;
    ioctl(skfd, SIOCETHTOOL, &ifr);
    stats->cmd          = ETHTOOL_GSTATS;
    stats->n_stats      = n_stats;
    ifr.ifr_data        = (caddr_t) stats;
    ioctl(skfd, SIOCETHTOOL, &ifr);

    ifIndex = if_nametoindex(ifa->ifa_name);

    /* Iterate through statistics */
    for (i = 0; i < n_stats; i++) {

        /* Declare the appropriate index */
        strncpy(ethtoolStatName,
                (char *)&strings->data[i * ETH_GSTRING_LEN],
                ETH_GSTRING_LEN);
        ethtoolStatName[sizeof(ethtoolStatName) - 1] = '\0';
        ethtoolStatName_len = strlen(ethtoolStatName);

        /* Append this new variable to the container. */
        rowreq_ctx = ethtoolStatTable_allocate_rowreq_ctx();
        ethtoolStatTable_indexes_set(rowreq_ctx,
                    ifIndex,
                    ethtoolStatName, ethtoolStatName_len);
        rowreq_ctx->data.ethtoolStat.high = stats->data[i] >> 32;
        rowreq_ctx->data.ethtoolStat.low = (uint32_t)stats->data[i];

        CONTAINER_INSERT(container, rowreq_ctx);
    }
}

C’est plu­tôt simple. J’énu­mère sim­ple­ment les ob­jets que je veux ex­po­ser. Je n’ai même pas eu be­soin de conver­tir les noms des sta­tis­tiques en un in­dex : mib2c a écrit la fonc­tion ethtoolStatTable_indexes_set() à cet ef­fet. Le pro­jet OpenHPI dis­pose d’une do­cu­men­ta­tion com­plète sur l’écri­ture d’un agent en uti­li­sant cette approche.

Tout n’est ce­pen­dant pas rose. Pour im­plé­men­ter une seule table, je me re­trouve avec 14 fi­chiers gé­né­rés dont le style est im­propre à l’in­té­gra­tion dans un pro­jet ex­terne. Une so­lu­tion consis­te­rait à uti­li­ser la nou­velle API sans mib2c. Mal­heu­reu­se­ment, même si la do­cu­men­ta­tion existe, tous les exemples se basent uni­que­ment sur l’ou­til mib2c. À moins d’être ca­pable d’uti­li­ser la nou­velle API sans ce­lui-ci, je pense qu’il est pré­fé­rable de res­treindre son usage aux deux cas suivants :

  • Écri­ture d’un mo­dule pour in­té­gra­tion dans Net-⁠SNMP. Étant donné que la plu­part des mo­dules exis­tants sont écrits avec l’aide de mib2c, le style de code est com­patible.
  • Écri­ture d’un sous-agent to­ta­le­ment au­to­nome. Comme il n’y a pas de code exis­tant, il n’y a pas non plus de pro­blème de com­pa­ti­bi­lité avec le style de code.

Pour les autres usages, re­gar­dons plu­tôt du côté de l’API tra­di­tionnelle.

C avec l’API « traditionnelle »#

Cette API est bien plus ba­sique et bien que très an­cienne, on trouve en­core de nom­breux exemples de son uti­li­sa­tion. Voici com­ment ini­tia­li­ser l’agent (en­core une fois, la ver­sion com­plète de ce code se trouve sur GitHub) :

static oid ethtool_oid[] = {1, 3, 6, 1, 4, 1, 39178, 100, 1};
static struct variable3 ethtool_vars[] = {
  {1, ASN_COUNTER64, RONLY, ethtool_stat, 3, {1, 1, 2}}
};

int main()
{
  netsnmp_enable_subagent();
  snmp_disable_log();
  snmp_enable_stderrlog();
  init_agent("ethtoolAgent");
  REGISTER_MIB("ethtoolStatTable", ethtool_vars,
               variable3, ethtool_oid);
  init_snmp("ethtoolAgent");
  while (1)
    agent_check_and_process(1);
}

La ma­cro REGISTER_MIB() per­met d’en­re­gis­trer l’OID ra­cine (ethtool_oid dans notre exemple) fourni au­près de l’agent maître. De plus, à tra­vers le ta­bleau ethtool_vars[], elle as­so­cie des OID aux fonc­tions char­gées de les prendre en charge. Les membres de ce ta­bleau contiennent les champs suivants :

  • magic est un en­tier per­met­tant de dif­fé­ren­cier les OID gé­rés par une même fonc­tion. C’est très utile quand il s’agit de gé­rer dif­fé­rentes co­lonnes d’un ta­bleau car elles par­tagent le même index.
  • type re­pré­sente le type de la variable.
  • acl in­dique si un ob­jet est en lec­ture seule (RONLY) ou en lecture/écriture (RWRITE).
  • findVar est la fonc­tion dont la tâche sera de trou­ver l’ob­jet adé­quat et d’en re­tour­ner sa valeur.
  • Les deux der­niers champs in­diquent l’OID re­la­tif (et sa lon­gueur) pour le­quel cette en­trée est res­ponsable.

Le pro­to­type de la fonc­tion ethtool_stat() est le suivant :

static u_char*
ethtool_stat(struct variable *vp, oid *name, size_t *length,
             int exact, size_t *var_len, WriteMethod **write_method);

Cette fonc­tion doit re­tour­ner un poin­teur sur la va­leur de la va­riable de­man­dée ou NULL si celle-ci ne peut pas être lo­ca­li­sée. Voici la des­crip­tion de cha­cun des ar­guments :

  • struct variable *vp est une co­pie de la struc­ture qui a per­mis d’as­so­cier l’OID à cette fonc­tion. Tou­te­fois, vp->name est main­te­nant un OID com­plet (et non plus re­la­tif). Si be­soin, il est pos­sible d’uti­li­ser vp->magic pour sa­voir quelle co­lonne trai­ter si la fonc­tion est mu­tua­li­sée à tra­vers plu­sieurs OID.
  • oid *name et size_t *length in­diquent l’OID exact de­mandé. Si on veut re­tour­ner un autre OID, il est pos­sible de le mo­di­fier (par exemple pour une re­quête de type GETNEXT). name pointe sur un ta­bleau suf­fi­sam­ment grand pour conte­nir n’im­porte quel OID.
  • int exact in­dique si la re­quête est de type GET/SET ou de type GETNEXT.
  • size_t *var_len per­met d’in­di­quer la taille de l’ob­jet retourné.
  • WriteMethod **write_method est uti­lisé uni­que­ment si la va­riable est mo­di­fiable. Si c’est le cas, il faut sto­cker dans cette va­riable la fonc­tion qui s’oc­cu­pera de l’opé­ra­tion d’écriture3.

L’im­plé­men­ta­tion de cette fonc­tion est to­ta­le­ment libre. No­tam­ment, si vous de­vez ré­cu­pé­rer des don­nées ex­ternes, il fau­dra gé­rer une sorte de cache. Dans le cadre de ETHTOOL-MIB, j’ai opté pour un arbre bi­co­lore. Cela per­met de plus de gé­rer fa­ci­le­ment l’opé­ra­tion GETNEXT.

Voici trois pro­jets uti­li­sant l’API tra­di­tion­nelle afin d’ajou­ter un sup­port SNMP :

  • Kee­pa­li­ved est un dé­mon VRRP ainsi qu’un dé­mon de su­per­vi­sion pour les clus­ters LVS. J’y ai ajouté un sup­port SNMP com­plet. Les fi­chiers les plus in­té­res­sants sont core/­snmp.c, check/­check_­snmp.c et vrrp/­vrrp_­snmp.c. Kee­pa­li­ved uti­lise une boucle d’évè­ne­ments dans la­quelle il a fallu in­té­grer la boucle d’évè­ne­ments de Net-⁠SNMP. Toutes les struc­tures in­ternes de Kee­pa­li­ved (confi­gu­ra­tion, état et sta­tis­tiques) sont ex­por­tées en lec­ture seule. Des alertes sont gé­né­rées pour cer­tains évè­ne­ments et il est pos­sible de mo­di­fier quelques va­riables.
  • lldpd est une im­plé­men­ta­tion de 802.1AB (LLDP), un pro­to­cole de dé­cou­verte qui per­met à un équi­pe­ment de se ma­ni­fes­ter au­près de ces voi­sins au ni­veau Ether­net. Ce pro­to­cole va de pair avec une MIB as­sez consé­quente com­pre­nant de nom­breuses tables. lldpd in­clut un sup­port as­sez com­plet en lec­ture de celle-ci. L’es­sen­tiel du code est concen­tré dans agent.c. Il n’y a ac­tuel­le­ment au­cun sup­port en écri­ture. De plus, lldpd uti­lise la sé­pa­ra­tion des pri­vi­lèges tout en res­tant com­pa­tible avec l’uti­li­sa­tion d’une so­cket Unix pour la conver­sa­tion entre l’agent maître et lldpd.
  • As­te­risk, un PABX libre, four­nit un sous-agent per­met­tant de ré­cu­pé­rer cer­taines mé­triques. Je­tez un œil sur res/­snmp/­agent.c.

Mise à jour (03.2012)

Si votre pro­jet est sou­mis à des contraintes fortes en temps de ré­ponse, sa­chez que l’im­plé­men­ta­tion du pro­to­cole AgentX par Net-⁠SNMP a cer­tains points noirs. Je­tez un œil sur mon autre post sur le sujet.

Autres solutions#

La page de ma­nuel de snmpd.conf ré­per­to­rie les pos­si­bi­li­tés d’ex­ten­sion sous la sec­tion « Ex­ten­ding agent func­tio­nality »:

  • exec et sh sont ob­so­lètes. Il faut dé­sor­mais uti­li­ser extend.
  • pass est un peu plus simple que pass_persist. La com­mande est exé­cu­tée pour chaque re­quête. Il est as­sez simple de trans­for­mer une telle com­mande en un script de type pass_persist script. De plus, Net-⁠SNMP ne ré­pon­dra à au­cune re­quête tant que la com­mande est en cours d’exécution.
  • proxy re­di­rige les re­quêtes vers un autre agent SNMP. À moins d’avoir un agent qui ne peut pas être trans­formé en sous-agent (ab­sence de code source), il est pré­fé­rable d’uti­li­ser AgentX. Si le pro­gramme uti­lise l’API de Net-⁠SNMP, la trans­for­ma­tion en un sous-agent ne né­ces­site de ra­jou­ter qu’une seule ligne de code. Un sous-agent met­tant en jeu un code plus ré­duit qu’un agent com­plet, il sera plus ef­fi­cace et plus sûr.
  • smux est ob­so­lète. Il faut dé­sor­mais uti­li­ser AgentX.
  • perl et dlmod per­mettent de char­ger et d’exé­cu­ter du code ex­terne di­rec­te­ment dans l’agent. À moins d’avoir des be­soins très im­por­tants en per­for­mance, il est pré­fé­rable d’uti­li­ser un sous-agent. Si un bug sur­vient, seul le sous-agent sera af­fecté. Un sous-agent cor­res­pond mieux à la phi­lo­so­phie Unix : « écrire des pro­grammes qui ne font qu’une chose mais qui la font bien ». Tou­te­fois, la FAQ de Net-⁠SNMP indique :

Im­plé­men­ter un mo­dule C à l’in­té­rieur de l’agent prin­ci­pal (di­rec­te­ment ou via dlmod) est sans doute la ma­nière la plus ef­fi­cace et la plus sûre, sui­vie de peu par l’uti­li­sa­tion de l’in­ter­pré­teur Perl (ou Py­thon) em­bar­qué. Ils pré­sentent l’avan­tage d’un sur­coût mi­ni­mal entre le code im­plé­men­tant la MIB et l’agent avec no­tam­ment l’ab­sence de toute com­mu­ni­ca­tion entre processus.