snmpstats.c

Go to the documentation of this file.
00001 /*
00002  * $Id: snmpstats.c 4764 2008-08-28 14:41:06Z henningw $
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 
00055 /*!
00056  * \file
00057  * \brief SNMP statistic module
00058  * \ingroup snmpstats
00059  * - Module: \ref snmpstats
00060  */
00061 
00062 /*!
00063  * \defgroup snmpstats SNMPSTATS :: The Kamailio snmpstats Module
00064  *
00065  * The SNMPStats module provides an SNMP management interface to OpenSER.
00066  * Specifically, it provides general SNMP queryable scalar statistics, table
00067  * representations of more complicated data such as user and contact information,
00068  * and alarm monitoring capabilities.
00069  */
00070 
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 
00083 #include <net-snmp/net-snmp-config.h>
00084 #include <net-snmp/net-snmp-includes.h>
00085 #include <net-snmp/agent/net-snmp-agent-includes.h>
00086 
00087 #include "openserSIPRegUserTable.h"
00088 #include "openserSIPContactTable.h"
00089 
00090 #include "interprocess_buffer.h"
00091 
00092 #include "hashTable.h"
00093 #include "alarm_checks.h"
00094 #include "utilities.h"
00095 #include "sub_agent.h"
00096 
00097 /* Required in every Kamailio Module. */
00098 MODULE_VERSION
00099 
00100 /*!
00101  * The module will fork off a child process to run an snmp command via execve().
00102  * We need a customized handler to ignore the SIGCHLD when the execve()
00103  * finishes.  We keep around the child process's pid for the customized
00104  * handler.
00105  *
00106  * Specifically, If the process that generated the SIGCHLD doesn't match this
00107  * pid, we call Kamailio's default handlers.  Otherwise, we just ignore SIGCHLD.
00108  */
00109 volatile pid_t sysUpTime_pid;
00110 
00111 /*! The functions spawns a sysUpTime child.  See the function definition below
00112  * for a full description. */
00113 static int spawn_sysUpTime_child();
00114 
00115 /*! Storage for the "snmpgetPath" and "snmpCommunity" kamailio.cfg parameters.
00116  * The parameters are used to define what happens with the sysUpTime child.  */
00117 char *snmpget_path   = NULL;
00118 char *snmp_community = NULL;
00119 
00120 /*!
00121  * This module replaces the default SIGCHLD handler with our own, as explained
00122  * in the documentation for sysUpTime_pid above.  This structure holds the old
00123  * handler so we can call and restore Kamailio's usual handler when appropriate
00124  */
00125 static struct sigaction old_sigchld_handler;
00126 
00127 /*! The following message codes are from Wikipedia at:
00128  *
00129  *     http://en.wikipedia.org/wiki/SIP_Responses 
00130  *
00131  * If there are more message codes added at a later time, they should be added
00132  * here, and to out_message_code_names below.  
00133  *
00134  * The array is used to register the statistics keeping track of the number of
00135  * messages received with the response code X.
00136  */
00137 char *in_message_code_names[] = 
00138 {
00139    "100_in", "180_in", "181_in", "182_in", "183_in", 
00140    
00141    "200_in", "202_in", 
00142    
00143    "300_in", "301_in", "302_in", "305_in", "380_in",
00144    
00145    "400_in", "401_in", "402_in", "403_in", "404_in", "405_in", "406_in", 
00146    "407_in", "408_in", "410_in", "413_in", "414_in", "415_in", "416_in", 
00147    "420_in", "421_in", "423_in", "480_in", "481_in", "482_in", "483_in", 
00148    "484_in", "485_in", "486_in", "487_in", "488_in", "491_in", "492_in",   
00149    "494_in", 
00150    
00151    "500_in", "501_in", "502_in", "503_in", "504_in", "505_in", "513_in",
00152    "600_in", "603_in", "604_in", "606_in"
00153 };
00154 
00155 /*! The following message codes are from Wikipedia at:
00156  *
00157  *     http://en.wikipedia.org/wiki/SIP_Responses 
00158  *
00159  * If there are more message codes added at a later time, they should be added
00160  * here, and to in_message_code_names above.
00161  *
00162  * The array is used to register the statistics keeping track of the number of
00163  * messages send out with the response code X.
00164  */
00165 char *out_message_code_names[] = 
00166 {
00167    "100_out", "180_out", "181_out", "182_out", "183_out", 
00168    
00169    "200_out", "202_out", 
00170    
00171    "300_out", "301_out", "302_out", "305_out", "380_out",
00172    
00173    "400_out", "401_out", "402_out", "403_out", "404_out", "405_out", "406_out", 
00174    "407_out", "408_out", "410_out", "413_out", "414_out", "415_out", "416_out", 
00175    "420_out", "421_out", "423_out", "480_out", "481_out", "482_out", "483_out", 
00176    "484_out", "485_out", "486_out", "487_out", "488_out", "491_out", "492_out",  
00177    "494_out", 
00178    
00179    "500_out", "501_out", "502_out", "503_out", "504_out", "505_out", "513_out",
00180    "600_out", "603_out", "604_out", "606_out"
00181 };
00182 
00183 /*! message_code_stat_array[0] will be the data source for message_code_array[0]
00184  * message_code_stat_array[3] will be the data source for message_code_array[3]
00185  * and so on. */
00186 stat_var **in_message_code_stats  = NULL;
00187 stat_var **out_message_code_stats = NULL;
00188 
00189 /*! Adds the message code statistics to the statistics framework */
00190 static int register_message_code_statistics(void) 
00191 {
00192    int i;
00193 
00194    int number_of_message_codes = 
00195       sizeof(in_message_code_names) / sizeof(char *);
00196 
00197    in_message_code_stats = 
00198       shm_malloc(sizeof(stat_var) * number_of_message_codes);
00199 
00200    out_message_code_stats = 
00201       shm_malloc(sizeof(stat_var) * number_of_message_codes);
00202 
00203    /* We can only proceed if we had enough memory to allocate the
00204     * statistics.  Note that we don't free the memory, but we don't care
00205     * because the system is going to shut down */
00206    if (in_message_code_stats == NULL || 
00207          out_message_code_stats == NULL)
00208    {
00209       return -1;
00210    }
00211 
00212    /* Make sure everything is zeroed out */
00213    memset(in_message_code_stats,  0, number_of_message_codes);
00214    memset(out_message_code_stats, 0, number_of_message_codes);
00215 
00216    for (i = 0; i < number_of_message_codes; i++) 
00217    {
00218       register_stat(SNMPSTATS_MODULE_NAME, in_message_code_names[i], 
00219             &in_message_code_stats[i], 0);
00220       register_stat(SNMPSTATS_MODULE_NAME, out_message_code_names[i], 
00221             &out_message_code_stats[i], 0);
00222    }
00223 
00224    return 0;
00225 }
00226 
00227 /*! This is the first function to be called by Kamailio, to initialize the module.
00228  * This call must always return a value as soon as possible.  If it were not to
00229  * return, then Kamailio would not be able to initialize any of the other
00230  * modules. */
00231 static int mod_init(void) 
00232 {
00233    if (register_message_code_statistics() < 0) 
00234    {
00235       return -1;
00236    }
00237 
00238    /* Initialize shared memory used to buffer communication between the
00239     * usrloc module and the snmpstats module.  */
00240    initInterprocessBuffers();
00241    
00242    /* We need to register for callbacks with usrloc module, for whenever a
00243     * contact is added or removed from the system.  We need to do it now
00244     * before Kamailio's functions get a chance to load up old user data from
00245     * the database.  That load will happen if a lookup() function is come
00246     * across in kamailio.cfg. */
00247 
00248    if (!registerForUSRLOCCallbacks()) 
00249    {
00250       /* Originally there were descriptive error messages here to help
00251        * the operator debug problems.  Turns out this may instead
00252        * alarm them about problems they don't need to worry about.  So
00253        * the messages are commented out for now */
00254       
00255       /*
00256       LM_ERR("snmpstats module was unable to register callbacks" 
00257                   " with the usrloc module\n");
00258       LM_ERR("Are you sure that the usrloc module was loaded"
00259             " before the snmpstats module in ");
00260       LM_ERR("kamailio.cfg?  openserSIPRegUserTable will not be "
00261             "updated.");
00262       */
00263    } 
00264 
00265    
00266    /* Register the alarm checking function to run periodically */
00267    register_timer(run_alarm_check, 0, ALARM_AGENT_FREQUENCY_IN_SECONDS);
00268 
00269    return 0;
00270 }
00271 
00272 
00273 /*! This function is called when Kamailio has finished creating all instances of
00274  * itself.  It is at this point that we want to create our AgentX sub-agent
00275  * process, and register a handler for any state changes of our child. */
00276 static int mod_child_init(int rank) 
00277 {
00278    /* We only want to setup a single process, under the main attendant. */
00279    if (rank != PROC_MAIN) {
00280       return 0;
00281    }
00282 
00283    /* Spawn a child that will check the system up time. */
00284    spawn_sysUpTime_child();
00285 
00286    return 0;
00287 }
00288 
00289 /*! This function is called when Kamailio is shutting down. When this happens, we
00290  * log a useful message and kill the AgentX Sub-Agent child process */
00291 static void mod_destroy(void) 
00292 {
00293    LM_INFO("The SNMPStats module got the kill signal\n");
00294    
00295    freeInterprocessBuffer();
00296 
00297    LM_INFO("Shutting down the AgentX Sub-Agent!\n");
00298 }
00299 
00300 
00301 /*! The SNMPStats module forks off a child process to run an snmp command via
00302  * execve(). We need a customized handler to catch and ignore its SIGCHLD when 
00303  * it terminates. We also need to make sure to forward other processes 
00304  * SIGCHLD's to Kamailio's usual SIGCHLD handler.  We do this by resetting back
00305  * Kamailio's own signal handlers after we caught our appropriate SIGCHLD. */
00306 static void sigchld_handler(int signal)
00307 {
00308    int pid_of_signalled_process_status;
00309    int pid_of_signalled_process;
00310 
00311    /* We need to lookout for the expected SIGCHLD from our
00312     * sysUpTime child process, and ignore it.  If the SIGCHLD is
00313     * from another process, we need to call Kamailio's usual
00314     * handlers */
00315    pid_of_signalled_process = 
00316          waitpid(-1, &pid_of_signalled_process_status, WNOHANG);
00317 
00318    if (pid_of_signalled_process == sysUpTime_pid)
00319    {
00320       /* It was the sysUpTime process which died, which was expected.
00321        * At this point we will never see any SIGCHLDs from any other
00322        * SNMPStats process.  This means that we can restore Kamailio's
00323        * original handlers. */
00324       sigaction(SIGCHLD, &old_sigchld_handler, NULL);
00325    } else 
00326    {
00327 
00328       /* We need this 'else-block' in case another Kamailio process dies
00329        * unexpectantly before the sysUpTime process dies.  If this
00330        * doesn't happen, then this code will never be called, because
00331        * the block above re-assigns Kamailio's original SIGCHLD
00332        * handler.  If it does happen, then we make sure to call the
00333        * default signal handlers. */
00334       if (old_sigchld_handler.sa_handler != SIG_IGN &&
00335             old_sigchld_handler.sa_handler != SIG_DFL)
00336       {
00337          (*(old_sigchld_handler.sa_handler))(signal);
00338       }
00339    }
00340 
00341 }
00342 
00343 /*!
00344  * This function will spawn a child that retrieves the sysUpTime and stores the
00345  * result in a file. This file will be read by the AgentX Sub-agent process to
00346  * supply the openserSIPServiceStartTime time. This function never returns,
00347  * but it will generated a SIGCHLD when it terminates.  There must a SIGCHLD
00348  * handler to ignore the SIGCHLD for only this process. (See sigchld_handler
00349  * above).
00350  *
00351  * \note sysUpTime is a scalar provided by netsnmp.  It is not the same thing as 
00352  *       a normal system uptime. Support for this has been provided to try to
00353  *       match the IETF Draft SIP MIBs as closely as possible. 
00354  */
00355 static int spawn_sysUpTime_child(void) 
00356 {
00357    struct sigaction new_sigchld_handler;
00358 
00359    char *local_path_to_snmpget = "/usr/local/bin/";
00360    char *snmpget_binary_name   = "/snmpget";
00361    char *full_path_to_snmpget  = NULL;
00362 
00363    char *snmp_community_string = "public";
00364 
00365    /* Set up a new SIGCHLD handler.  The handler will be responsible for
00366     * ignoring SIGCHLDs generated by our sysUpTime child process.  Every
00367     * other SIGCHLD will be redirected to the old SIGCHLD handler. */
00368    sigfillset(&new_sigchld_handler.sa_mask);
00369    new_sigchld_handler.sa_flags   = SA_RESTART;
00370    new_sigchld_handler.sa_handler = sigchld_handler;
00371    sigaction(SIGCHLD, &new_sigchld_handler, &old_sigchld_handler);
00372 
00373    pid_t result_pid = fork();
00374 
00375    if (result_pid < 0) {
00376       LM_ERR("failed to not spawn an agent to check sysUpTime\n");
00377       return -1;
00378    } else if (result_pid != 0) {
00379 
00380       /* Keep around the PID of the sysUpTime process so that the
00381        * customized SIGCHLD handler knows to ignore the SIGCHLD we
00382        * generate when we terminate. */
00383       sysUpTime_pid = result_pid;
00384 
00385       return 0;
00386 
00387    }
00388 
00389    /* If we are here, then we are the child process.  Lets set up the file
00390     * descriptors so we can capture the output of snmpget. */
00391    int snmpget_fd = 
00392       open(SNMPGET_TEMP_FILE, O_CREAT|O_TRUNC|O_RDWR,
00393             S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
00394 
00395 
00396    if (snmpget_fd == -1) {
00397       LM_ERR("failed to open a temporary file "
00398             "for snmpget to write to\n");
00399       return -1;
00400    }
00401 
00402    /* Redirect Standard Output to our temporary file. */
00403    dup2(snmpget_fd, 1); 
00404 
00405    if (snmp_community != NULL) {
00406       snmp_community_string = snmp_community;
00407    } else {
00408       LM_INFO("An snmpCommunity parameter was not provided."
00409             "  Defaulting to %s\n", snmp_community_string);
00410    }
00411 
00412    char *args[] = {"-Ov", "-c",  snmp_community_string, "localhost", 
00413       SYSUPTIME_OID, (char *) 0};
00414 
00415    /* Make sure we have a path to snmpget, so we can retrieve the
00416     * sysUpTime. */
00417    if (snmpget_path == NULL) 
00418    {
00419       LM_INFO("An snmpgetPath parameter was not specified."
00420             "  Defaulting to %s\n", local_path_to_snmpget);
00421    }
00422    else 
00423    {
00424       local_path_to_snmpget = snmpget_path;
00425    }
00426 
00427    int local_path_to_snmpget_length = strlen(local_path_to_snmpget);
00428    int snmpget_binary_name_length   = strlen(snmpget_binary_name);
00429             
00430    /* Allocate enough memory to hold the path, the binary name, and the
00431     * null character.  We don't use pkg_memory here. */
00432    full_path_to_snmpget = 
00433       malloc(sizeof(char) * 
00434             (local_path_to_snmpget_length + 
00435              snmpget_binary_name_length   + 1));
00436 
00437    if (full_path_to_snmpget == NULL) 
00438    {
00439       LM_ERR("Ran out of memory while trying to retrieve sysUpTime.  ");
00440       LM_ERR( "                  openserSIPServiceStartTime is "
00441             "defaulting to zero\n");
00442       return -1;
00443    }
00444    else
00445    {
00446       /* Make a new string containing the full path to the binary. */
00447       strcpy(full_path_to_snmpget, local_path_to_snmpget);
00448       strcpy(&full_path_to_snmpget[local_path_to_snmpget_length], 
00449             snmpget_binary_name);
00450    }
00451 
00452    /* snmpget -Ov -c public localhost .1.3.6.1.2.1.1.3.0  */
00453    if (execve(full_path_to_snmpget, args, NULL) == -1) {
00454       LM_ERR( "snmpget failed to run.  Did you supply the snmpstats module"
00455             " with a proper snmpgetPath parameter? The "
00456             "openserSIPServiceStartTime is defaulting to zero\n");
00457       close(snmpget_fd);
00458       free(full_path_to_snmpget);
00459       exit(-1);
00460    }
00461    
00462    /* We should never be able to get here, because execve() is never
00463     * supposed to return. */
00464    free(full_path_to_snmpget);
00465    exit(-1);
00466 }
00467 
00468 
00469 /*! This function is called whenever the kamailio.cfg file specifies the
00470  * snmpgetPath parameter.  The function will set the snmpget_path parameter. */
00471 int set_snmpget_path( modparam_t type, void *val) 
00472 {
00473    if (!stringHandlerSanityCheck(type, val, "snmpgetPath" )) {
00474       return -1;
00475    }
00476 
00477    snmpget_path = (char *)val;
00478 
00479    return 0;
00480 }
00481 
00482 /* Handles setting of the snmp community string. */
00483 int set_snmp_community( modparam_t type, void *val)
00484 {
00485    if (!stringHandlerSanityCheck(type, val, "snmpCommunity")) {
00486       return -1;
00487    }
00488 
00489    snmp_community = (char *)val;
00490 
00491    return 0;
00492 }

Generated on Thu May 24 12:00:31 2012 for Kamailio - The Open Source SIP Server by  doxygen 1.5.6