snmpstats.c

Go to the documentation of this file.
00001 /*
00002  * $Id$
00003  *
00004  * SNMPStats Module 
00005  * Copyright (C) 2006 SOMA Networks, INC.
00006  * Written by: Jeffrey Magder (jmagder@somanetworks.com)
00007  *
00008  * This file is part of Kamailio, a free SIP server.
00009  *
00010  * Kamailio is free software; you can redistribute it and/or modify it
00011  * under the terms of the GNU General Public License as published by
00012  * the Free Software Foundation; either version 2 of the License, or
00013  * (at your option) any later version
00014  *
00015  * Kamailio is distributed in the hope that it will be useful, but
00016  * WITHOUT ANY WARRANTY; without even the implied warranty of
00017  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00018  * General Public License for more details.
00019  *
00020  * You should have received a copy of the GNU General Public License
00021  * along with this program; if not, write to the Free Software
00022  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
00023  * USA
00024  *
00025  * History:
00026  * --------
00027  * 2006-11-23 initial version (jmagder)
00028  * 
00029  * There are some important points to understanding the SNMPStat modules
00030  * architecture.
00031  *
00032  * 1) The SNMPStats module will fork off a new process in mod_child_init when
00033  *    the rank is equal to PROC_MAIN_PROCESS.  The sub-process will be
00034  *    responsible for registering with a master agent (the source of snmp
00035  *    requests), and handling all received requests. 
00036  *
00037  * 2) The Module will register a periodic alarm checking function with a sip
00038  *    timer using register_timer().  This function checks for alarm conditions,
00039  *    and will send out traps to the master agent when it detects their
00040  *    presence.
00041  *
00042  * 3) The SNMPStats module is required to run an external application upon
00043  *    startup, to collect sysUpTime data from the master agent.  This involves
00044  *    spawning a short-lived process.  For this reason, the module temporarily
00045  *    installs a new SIGCHLD handler to deal specifically with this process.  It
00046  *    does not change the normal SIGCHLD behaviour for any process except for
00047  *    this short lived sysUpTime process. 
00048  *
00049  * 4) mod_init() will initialize some interprocess communication buffers, as
00050  *    well as callback mechanisms for the usrloc module.  To understand what the
00051  *    interprocess buffer and callbacks are and are for, please see the comments
00052  *    at the beginning of openserSIPRegUserTable.c
00053  */
00054 
00071 #include <stdio.h>
00072 #include <unistd.h>
00073 #include <sys/types.h>
00074 #include <sys/stat.h>
00075 #include <fcntl.h>
00076 
00077 #include <signal.h>
00078 #include <sys/wait.h>
00079 #include "snmpstats.h"
00080 #include "snmpstats_globals.h"
00081 #include "../../timer.h"
00082 #include "../../cfg/cfg_struct.h"
00083 
00084 #include <net-snmp/net-snmp-config.h>
00085 #include <net-snmp/net-snmp-includes.h>
00086 #include <net-snmp/agent/net-snmp-agent-includes.h>
00087 
00088 #include "../../lib/kcore/statistics.h"
00089 #include "../../sr_module.h"
00090 #include "../../dprint.h"
00091 #include "../../error.h"
00092 #include "../../ut.h"
00093 #include "../../script_cb.h"
00094 #include "../../mem/mem.h"
00095 #include "../../mem/shm_mem.h"
00096 
00097 #include "snmpSIPRegUserTable.h"
00098 #include "snmpSIPContactTable.h"
00099 
00100 #include "interprocess_buffer.h"
00101 
00102 #include "hashTable.h"
00103 #include "alarm_checks.h"
00104 #include "utilities.h"
00105 #include "sub_agent.h"
00106 
00107 /* Required in every Kamailio Module. */
00108 MODULE_VERSION
00109 
00110 /* module parameter to register for usrloc callbacks or not,
00111  * in order to export registrar records (0 - don't export, 1 - export) */
00112 static int snmp_export_registrar = 0;
00113 
00118 static int  mod_init(void);
00119 
00123 static int  mod_child_init(int rank);
00124 
00125 
00128 static void mod_destroy(void);
00129 
00130 
00131 static proc_export_t mod_procs[] = {
00132         {"SNMP AgentX",  0,  0, agentx_child, 1 },
00133         {0,0,0,0,0}
00134 };
00135 
00136 
00141 static param_export_t mod_params[] =
00142 {
00143         { "sipEntityType",          STR_PARAM|USE_FUNC_PARAM,
00144                         (void *)handleSipEntityType       },
00145         { "MsgQueueMinorThreshold", INT_PARAM|USE_FUNC_PARAM,
00146                         (void *)set_queue_minor_threshold },
00147         { "MsgQueueMajorThreshold", INT_PARAM|USE_FUNC_PARAM,
00148                         (void *)set_queue_major_threshold },
00149         { "dlg_minor_threshold",    INT_PARAM|USE_FUNC_PARAM,
00150                         (void *)set_dlg_minor_threshold   },
00151         { "dlg_major_threshold",    INT_PARAM|USE_FUNC_PARAM,
00152                         (void *)set_dlg_major_threshold   },
00153         { "snmpgetPath",            STR_PARAM|USE_FUNC_PARAM,
00154                         (void *)set_snmpget_path          },
00155         { "snmpCommunity",          STR_PARAM|USE_FUNC_PARAM,
00156                         (void *)set_snmp_community        },
00157         { "export_registrar",       INT_PARAM,
00158                         &snmp_export_registrar            },
00159         { 0,0,0 }
00160 };
00161 
00162 
00163 struct module_exports exports =
00164 {
00165         SNMPSTATS_MODULE_NAME,   /* module's name */
00166         DEFAULT_DLFLAGS,         /* dlopen flags */
00167         0,                       /* exported functions */
00168         mod_params,              /* param exports */
00169         0,                       /* exported statistics */
00170         0,                       /* MI Functions */
00171         0,                       /* pseudo-variables */
00172         mod_procs,               /* extra processes */
00173         mod_init,                /* module initialization function */
00174         0,                       /* reply processing function */
00175         mod_destroy,   /* Destroy function */
00176         mod_child_init /* per-child init function */
00177 };
00178 
00188 volatile pid_t sysUpTime_pid;
00189 
00192 static int spawn_sysUpTime_child();
00193 
00196 char *snmpget_path   = NULL;
00197 char *snmp_community = NULL;
00198 
00204 static struct sigaction old_sigchld_handler;
00205 
00216 char *in_message_code_names[] = 
00217 {
00218         "100_in", "180_in", "181_in", "182_in", "183_in", 
00219         
00220         "200_in", "202_in", 
00221         
00222         "300_in", "301_in", "302_in", "305_in", "380_in",
00223         
00224         "400_in", "401_in", "402_in", "403_in", "404_in", "405_in", "406_in", 
00225         "407_in", "408_in", "410_in", "413_in", "414_in", "415_in", "416_in", 
00226         "420_in", "421_in", "423_in", "480_in", "481_in", "482_in", "483_in", 
00227         "484_in", "485_in", "486_in", "487_in", "488_in", "491_in", "492_in",   
00228         "494_in", 
00229         
00230         "500_in", "501_in", "502_in", "503_in", "504_in", "505_in", "513_in",
00231         "600_in", "603_in", "604_in", "606_in"
00232 };
00233 
00244 char *out_message_code_names[] = 
00245 {
00246         "100_out", "180_out", "181_out", "182_out", "183_out", 
00247         
00248         "200_out", "202_out", 
00249         
00250         "300_out", "301_out", "302_out", "305_out", "380_out",
00251         
00252         "400_out", "401_out", "402_out", "403_out", "404_out", "405_out", "406_out", 
00253         "407_out", "408_out", "410_out", "413_out", "414_out", "415_out", "416_out", 
00254         "420_out", "421_out", "423_out", "480_out", "481_out", "482_out", "483_out", 
00255         "484_out", "485_out", "486_out", "487_out", "488_out", "491_out", "492_out",    
00256         "494_out", 
00257         
00258         "500_out", "501_out", "502_out", "503_out", "504_out", "505_out", "513_out",
00259         "600_out", "603_out", "604_out", "606_out"
00260 };
00261 
00265 stat_var **in_message_code_stats  = NULL;
00266 stat_var **out_message_code_stats = NULL;
00267 
00269 static int register_message_code_statistics(void) 
00270 {
00271         int i;
00272 
00273         int number_of_message_codes = 
00274                 sizeof(in_message_code_names) / sizeof(char *);
00275 
00276         in_message_code_stats = 
00277                 shm_malloc(sizeof(stat_var*) * number_of_message_codes);
00278 
00279         out_message_code_stats = 
00280                 shm_malloc(sizeof(stat_var*) * number_of_message_codes);
00281 
00282         /* We can only proceed if we had enough memory to allocate the
00283          * statistics.  Note that we don't free the memory, but we don't care
00284          * because the system is going to shut down */
00285         if (in_message_code_stats == NULL || 
00286                         out_message_code_stats == NULL)
00287         {
00288                 return -1;
00289         }
00290 
00291         /* Make sure everything is zeroed out */
00292         memset(in_message_code_stats,  0, sizeof(stat_var*) * number_of_message_codes);
00293         memset(out_message_code_stats, 0, sizeof(stat_var*) * number_of_message_codes);
00294 
00295         for (i = 0; i < number_of_message_codes; i++) 
00296         {
00297                 register_stat(SNMPSTATS_MODULE_NAME, in_message_code_names[i], 
00298                                 &in_message_code_stats[i], 0);
00299                 register_stat(SNMPSTATS_MODULE_NAME, out_message_code_names[i], 
00300                                 &out_message_code_stats[i], 0);
00301         }
00302 
00303         return 0;
00304 }
00305 
00310 static int mod_init(void) 
00311 {
00312         if (register_message_code_statistics() < 0) 
00313         {
00314                 return -1;
00315         }
00316 
00317         /* Initialize shared memory used to buffer communication between the
00318          * usrloc module and the snmpstats module.  */
00319         initInterprocessBuffers();
00320         
00321         /* We need to register for callbacks with usrloc module, for whenever a
00322          * contact is added or removed from the system.  We need to do it now
00323          * before Kamailio's functions get a chance to load up old user data from
00324          * the database.  That load will happen if a lookup() function is come
00325          * across in kamailio.cfg. */
00326 
00327         if (snmp_export_registrar!=0)
00328         {
00329                 if(!registerForUSRLOCCallbacks())
00330                 {
00331                         /* Originally there were descriptive error messages here to help
00332                          * the operator debug problems.  Turns out this may instead
00333                          * alarm them about problems they don't need to worry about.  So
00334                          * the messages are commented out for now */
00335                 
00336                         /*
00337                         LM_ERR("snmpstats module was unable to register callbacks"
00338                                         " with the usrloc module\n");
00339                         LM_ERR("Are you sure that the usrloc module was loaded"
00340                                         " before the snmpstats module in ");
00341                         LM_ERR("kamailio.cfg?  openserSIPRegUserTable will not be "
00342                                    "updated.");
00343                         */
00344                 }
00345         }
00346 
00347         /* Register the alarm checking function to run periodically */
00348         register_timer(run_alarm_check, 0, ALARM_AGENT_FREQUENCY_IN_SECONDS);
00349 
00350         /* add space for one extra process */
00351         register_procs(1);
00352         /* add child to update local config framework structures */
00353         cfg_register_child(1);
00354 
00355         return 0;
00356 }
00357 
00358 
00362 static int mod_child_init(int rank) 
00363 {
00364         int pid;
00365 
00366         /* We only want to setup a single process, under the main attendant. */
00367         if (rank != PROC_MAIN) {
00368                 return 0;
00369         }
00370 
00371         /* Spawn SNMP AgentX process */
00372         pid=fork_process(PROC_NOCHLDINIT, "SNMP AgentX", 1);
00373         if (pid<0)
00374                 return -1; /* error */
00375         if(pid==0){
00376                 /* child */
00377                 /* initialize the config framework */
00378                 if (cfg_child_init())
00379                         return -1;
00380 
00381                 agentx_child(1);
00382                 return 0;
00383         }
00384 
00385         /* Spawn a child that will check the system up time. */
00386         spawn_sysUpTime_child();
00387 
00388         return 0;
00389 }
00390 
00393 static void mod_destroy(void) 
00394 {
00395         LM_INFO("The SNMPStats module got the kill signal\n");
00396         
00397         freeInterprocessBuffer();
00398 
00399         LM_INFO("Shutting down the AgentX Sub-Agent!\n");
00400 }
00401 
00402 
00408 static void sigchld_handler(int signal)
00409 {
00410         int pid_of_signalled_process_status;
00411         int pid_of_signalled_process;
00412 
00413         /* We need to lookout for the expected SIGCHLD from our
00414          * sysUpTime child process, and ignore it.  If the SIGCHLD is
00415          * from another process, we need to call Kamailio's usual
00416          * handlers */
00417         pid_of_signalled_process = 
00418                         waitpid(-1, &pid_of_signalled_process_status, WNOHANG);
00419 
00420         if (pid_of_signalled_process == sysUpTime_pid)
00421         {
00422                 /* It was the sysUpTime process which died, which was expected.
00423                  * At this point we will never see any SIGCHLDs from any other
00424                  * SNMPStats process.  This means that we can restore Kamailio's
00425                  * original handlers. */
00426                 sigaction(SIGCHLD, &old_sigchld_handler, NULL);
00427         } else 
00428         {
00429 
00430                 /* We need this 'else-block' in case another Kamailio process dies
00431                  * unexpectantly before the sysUpTime process dies.  If this
00432                  * doesn't happen, then this code will never be called, because
00433                  * the block above re-assigns Kamailio's original SIGCHLD
00434                  * handler.  If it does happen, then we make sure to call the
00435                  * default signal handlers. */
00436                 if (old_sigchld_handler.sa_handler != SIG_IGN &&
00437                                 old_sigchld_handler.sa_handler != SIG_DFL)
00438                 {
00439                         (*(old_sigchld_handler.sa_handler))(signal);
00440                 }
00441         }
00442 
00443 }
00444 
00457 static int spawn_sysUpTime_child(void) 
00458 {
00459         struct sigaction new_sigchld_handler;
00460 
00461         char *local_path_to_snmpget = "/usr/local/bin/";
00462         char *snmpget_binary_name   = "/snmpget";
00463         char *full_path_to_snmpget  = NULL;
00464 
00465         char *snmp_community_string = "public";
00466 
00467         /* Set up a new SIGCHLD handler.  The handler will be responsible for
00468          * ignoring SIGCHLDs generated by our sysUpTime child process.  Every
00469          * other SIGCHLD will be redirected to the old SIGCHLD handler. */
00470         sigfillset(&new_sigchld_handler.sa_mask);
00471         new_sigchld_handler.sa_flags   = SA_RESTART;
00472         new_sigchld_handler.sa_handler = sigchld_handler;
00473         sigaction(SIGCHLD, &new_sigchld_handler, &old_sigchld_handler);
00474 
00475         pid_t result_pid = fork();
00476 
00477         if (result_pid < 0) {
00478                 LM_ERR("failed to not spawn an agent to check sysUpTime\n");
00479                 return -1;
00480         } else if (result_pid != 0) {
00481 
00482                 /* Keep around the PID of the sysUpTime process so that the
00483                  * customized SIGCHLD handler knows to ignore the SIGCHLD we
00484                  * generate when we terminate. */
00485                 sysUpTime_pid = result_pid;
00486 
00487                 return 0;
00488 
00489         }
00490 
00491         /* If we are here, then we are the child process.  Lets set up the file
00492          * descriptors so we can capture the output of snmpget. */
00493         int snmpget_fd = 
00494                 open(SNMPGET_TEMP_FILE, O_CREAT|O_TRUNC|O_RDWR,
00495                                 S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
00496 
00497 
00498         if (snmpget_fd == -1) {
00499                 LM_ERR("failed to open a temporary file "
00500                                 "for snmpget to write to\n");
00501                 return -1;
00502         }
00503 
00504         /* Redirect Standard Output to our temporary file. */
00505         dup2(snmpget_fd, 1); 
00506 
00507         if (snmp_community != NULL) {
00508                 snmp_community_string = snmp_community;
00509         } else {
00510                 LM_INFO("An snmpCommunity parameter was not provided."
00511                                 "  Defaulting to %s\n", snmp_community_string);
00512         }
00513 
00514         char *args[] = {"-Ov", "-c",  snmp_community_string, "localhost", 
00515                 SYSUPTIME_OID, (char *) 0};
00516 
00517         /* Make sure we have a path to snmpget, so we can retrieve the
00518          * sysUpTime. */
00519         if (snmpget_path == NULL) 
00520         {
00521                 LM_INFO("An snmpgetPath parameter was not specified."
00522                                 "  Defaulting to %s\n", local_path_to_snmpget);
00523         }
00524         else 
00525         {
00526                 local_path_to_snmpget = snmpget_path;
00527         }
00528 
00529         int local_path_to_snmpget_length = strlen(local_path_to_snmpget);
00530         int snmpget_binary_name_length   = strlen(snmpget_binary_name);
00531                                 
00532         /* Allocate enough memory to hold the path, the binary name, and the
00533          * null character.  We don't use pkg_memory here. */
00534         full_path_to_snmpget = 
00535                 malloc(sizeof(char) * 
00536                                 (local_path_to_snmpget_length + 
00537                                  snmpget_binary_name_length   + 1));
00538 
00539         if (full_path_to_snmpget == NULL) 
00540         {
00541                 LM_ERR("Ran out of memory while trying to retrieve sysUpTime.  ");
00542                 LM_ERR( "                  openserSIPServiceStartTime is "
00543                                 "defaulting to zero\n");
00544                 return -1;
00545         }
00546         else
00547         {
00548                 /* Make a new string containing the full path to the binary. */
00549                 strcpy(full_path_to_snmpget, local_path_to_snmpget);
00550                 strcpy(&full_path_to_snmpget[local_path_to_snmpget_length], 
00551                                 snmpget_binary_name);
00552         }
00553 
00554         /* snmpget -Ov -c public localhost .1.3.6.1.2.1.1.3.0  */
00555         if (execve(full_path_to_snmpget, args, NULL) == -1) {
00556                 LM_ERR( "snmpget failed to run.  Did you supply the snmpstats module"
00557                                 " with a proper snmpgetPath parameter? The "
00558                                 "openserSIPServiceStartTime is defaulting to zero\n");
00559                 close(snmpget_fd);
00560                 free(full_path_to_snmpget);
00561                 exit(-1);
00562         }
00563         
00564         /* We should never be able to get here, because execve() is never
00565          * supposed to return. */
00566         free(full_path_to_snmpget);
00567         exit(-1);
00568 }
00569 
00570 
00573 int set_snmpget_path( modparam_t type, void *val) 
00574 {
00575         if (!stringHandlerSanityCheck(type, val, "snmpgetPath" )) {
00576                 return -1;
00577         }
00578 
00579         snmpget_path = (char *)val;
00580 
00581         return 0;
00582 }
00583 
00584 /* Handles setting of the snmp community string. */
00585 int set_snmp_community( modparam_t type, void *val)
00586 {
00587         if (!stringHandlerSanityCheck(type, val, "snmpCommunity")) {
00588                 return -1;
00589         }
00590 
00591         snmp_community = (char *)val;
00592 
00593         return 0;
00594 }