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 }
1.5.6