/* * $Id: snmpstats.c 1425 2006-12-18 22:22:51Z jmagder $ * * SNMPStats Module * Copyright (C) 2006 SOMA Networks, INC. * Written by: Jeffrey Magder (jmagder@somanetworks.com) * * This file is part of openser, a free SIP server. * * openser is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version * * openser is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA * * History: * -------- * 2006-11-23 initial version (jmagder) * * There are some important points to understanding the SNMPStat modules * architecture. * * 1) The SNMPStats module will fork off a new process in mod_child_init when * the rank is equal to PROC_MAIN_PROCESS. The sub-process will be * responsible for registering with a master agent (the source of snmp * requests), and handling all received requests. * * 2) The Module will register a periodic alarm checking function with a sip * timer using register_timer(). This function checks for alarm conditions, * and will send out traps to the master agent when it detects their * presence. * * 3) The SNMPStats module is required to run an external application upon * startup, to collect sysUpTime data from the master agent. This involves * spawning a short-lived process. For this reason, the module temporarily * installs a new SIGCHLD handler to deal specifically with this process. It * does not change the normal SIGCHLD behaviour for any process except for * this short lived sysUpTime process. * * 4) mod_init() will initialize some interprocess communication buffers, as * well as callback mechanisms for the usrloc module. To understand what the * interprocess buffer and callbacks are and are for, please see the comments * at the beginning of openserSIPRegUserTable.c * */ #include #include #include #include #include #include #include #include "snmpstats.h" #include "snmpstats_globals.h" #include "../../timer.h" #include #include #include #include "openserSIPRegUserTable.h" #include "openserSIPContactTable.h" #include "interprocess_buffer.h" #include "hashTable.h" #include "alarm_checks.h" #include "utilities.h" #include "sub_agent.h" /* Required in every OpenSER Module. */ MODULE_VERSION /* * The module will fork off a child process to run an snmp command via execve(). * We need a customized handler to ignore the SIGCHLD when the execve() * finishes. We keep around the child process's pid for the customized * handler. * * Specifically, If the process that generated the SIGCHLD doesn't match this * pid, we call OpenSER's default handlers. Otherwise, we just ignore SIGCHLD. */ volatile pid_t sysUpTime_pid; /* The functions spawns a sysUpTime child. See the function definition below * for a full description. */ static int spawn_sysUpTime_child(); /* Storage for the "snmpgetPath" and "snmpCommunity" openser.cfg parameters. * The parameters are used to define what happens with the sysUpTime child. */ char *snmpget_path = NULL; char *snmp_community = NULL; /* * This module replaces the default SIGCHLD handler with our own, as explained * in the documentation for sysUpTime_pid above. This structure holds the old * handler so we can call and restore OpenSER's usual handler when appropriate */ static struct sigaction old_sigchld_handler; /* The following message codes are from Wikipedia at: * * http://en.wikipedia.org/wiki/SIP_Responses * * If there are more message codes added at a later time, they should be added * here, and to out_message_code_names below. * * The array is used to register the statistics keeping track of the number of * messages received with the response code X. */ char *in_message_code_names[] = { "100_in", "180_in", "181_in", "182_in", "183_in", "200_in", "202_in", "300_in", "301_in", "302_in", "305_in", "380_in", "400_in", "401_in", "402_in", "403_in", "404_in", "405_in", "406_in", "407_in", "408_in", "410_in", "413_in", "414_in", "415_in", "416_in", "420_in", "421_in", "423_in", "480_in", "481_in", "482_in", "483_in", "484_in", "485_in", "486_in", "487_in", "488_in", "491_in", "492_in", "494_in", "500_in", "501_in", "502_in", "503_in", "504_in", "505_in", "513_in", "600_in", "603_in", "604_in", "606_in" }; /* The following message codes are from Wikipedia at: * * http://en.wikipedia.org/wiki/SIP_Responses * * If there are more message codes added at a later time, they should be added * here, and to in_message_code_names above. * * The array is used to register the statistics keeping track of the number of * messages send out with the response code X. */ char *out_message_code_names[] = { "100_out", "180_out", "181_out", "182_out", "183_out", "200_out", "202_out", "300_out", "301_out", "302_out", "305_out", "380_out", "400_out", "401_out", "402_out", "403_out", "404_out", "405_out", "406_out", "407_out", "408_out", "410_out", "413_out", "414_out", "415_out", "416_out", "420_out", "421_out", "423_out", "480_out", "481_out", "482_out", "483_out", "484_out", "485_out", "486_out", "487_out", "488_out", "491_out", "492_out", "494_out", "500_out", "501_out", "502_out", "503_out", "504_out", "505_out", "513_out", "600_out", "603_out", "604_out", "606_out" }; /* message_code_stat_array[0] will be the data source for message_code_array[0] * message_code_stat_array[3] will be the data source for message_code_array[3] * and so on. */ stat_var **in_message_code_stats = NULL; stat_var **out_message_code_stats = NULL; /* Adds the message code statistics to the statistics framework */ static int register_message_code_statistics() { int i; int number_of_message_codes = sizeof(in_message_code_names) / sizeof(char *); in_message_code_stats = shm_malloc(sizeof(stat_var) * number_of_message_codes); out_message_code_stats = shm_malloc(sizeof(stat_var) * number_of_message_codes); /* We can only proceed if we had enough memory to allocate the * statistics. Note that we don't free the memory, but we don't care * because the system is going to shut down */ if (in_message_code_stats == NULL || out_message_code_stats == NULL) { return -1; } /* Make sure everything is zeroed out */ memset(in_message_code_stats, 0, number_of_message_codes); memset(out_message_code_stats, 0, number_of_message_codes); for (i = 0; i < number_of_message_codes; i++) { register_stat(SNMPSTATS_MODULE_NAME, in_message_code_names[i], &in_message_code_stats[i], 0); register_stat(SNMPSTATS_MODULE_NAME, out_message_code_names[i], &out_message_code_stats[i], 0); } return 0; } /* This is the first function to be called by OpenSER, to initialize the module. * This call must always return a value as soon as possible. If it were not to * return, then OpenSER would not be able to initialize any of the other * modules. */ static int mod_init(void) { LOG(L_INFO, "INFO: SNMPStats: Starting up the SNMPStats Module\n"); if (register_message_code_statistics() < 0) { return -1; } /* Initialize shared memory used to buffer communication between the * usrloc module and the snmpstats module. */ initInterprocessBuffers(); /* We need to register for callbacks with usrloc module, for whenever a * contact is added or removed from the system. We need to do it now * before OpenSER's functions get a chance to load up old user data from * the database. That load will happen if a lookup() function is come * across in openser.cfg. */ if (!registerForUSRLOCCallbacks()) { /* Originally there were descriptive error messages here to help * the operator debug problems. Turns out this may instead * alarm them about problems they don't need to worry about. So * the messages are commented out for now */ /* LOG(L_ERR, "ERROR: snmpstats module was unable to register callbacks" " with the usrloc module\n"); LOG(L_ERR, " Are you sure that the usrloc module was loaded" "before the snmpstats module in "); LOG(L_ERR, " openser.cfg? openserSIPRegUserTable will not be " "updated."); */ } /* Register the alarm checking function to run periodically */ register_timer(run_alarm_check, 0, ALARM_AGENT_FREQUENCY_IN_SECONDS); return 0; } /* This function is called when OpenSER has finished creating all instances of * itself. It is at this point that we want to create our AgentX sub-agent * process, and register a handler for any state changes of our child. */ static int mod_child_init(int rank) { /* We only want to setup a single process, under the main attendant. */ if (rank != PROC_MAIN) { return 0; } /* If we couldn't spawn a child, we can't continue. */ if (spawn_agentx_child() != 0) { return -1; } /* Spawn a child that will check the system up time. */ spawn_sysUpTime_child(); return 0; } /* This function is called when OpenSER is shutting down. When this happens, we * log a useful message and kill the AgentX Sub-Agent child process */ static void mod_destroy() { LOG(L_INFO, "INFO: SNMPStats: The SNMPStats module got the kill " "signal\n"); LOG(L_INFO, " Shutting down the AgentX Sub-Agent!\n"); } /* The SNMPStats module forks off a child process to run an snmp command via * execve(). We need a customized handler to catch and ignore its SIGCHLD when it * terminates. We also need to make sure to forward other processes SIGCHLD's to * OpenSER's usual SIGCHLD handler. We do this by resetting back OpenSER's own * signal handlers after we caught our appropriate SIGCHLD. */ static void sigchld_handler(int signal) { int pid_of_signalled_process_status; int pid_of_signalled_process; /* We need to lookout for the expected SIGCHLD from our * sysUpTime child process, and ignore it. If the SIGCHLD is * from another process, we need to call OpenSER's usual * handlers */ pid_of_signalled_process = waitpid(-1, &pid_of_signalled_process_status, WNOHANG); if (pid_of_signalled_process == sysUpTime_pid) { /* It was the sysUpTime process which died, which was expected. * At this point we will never see any SIGCHLDs from any other * SNMPStats process. This means that we can restore OpenSER's * original handlers. */ sigaction(SIGCHLD, &old_sigchld_handler, NULL); } else { /* We need this 'else-block' in case another OpenSER process dies * unexpectantly before the sysUpTime process dies. If this * doesn't happen, then this code will never be called, because * the block above re-assigns OpenSER's original SIGCHLD * handler. If it does happen, then we make sure to call the * default signal handlers. */ if (old_sigchld_handler.sa_handler != SIG_IGN && old_sigchld_handler.sa_handler != SIG_DFL) { (*(old_sigchld_handler.sa_handler))(signal); } } } /* * This function will spawn a child that retrieves the sysUpTime, and stores the * result in a file. This file will be read by the AgentX Sub-agent process to * supply the openserSIPServiceStartTime time. This function never returns, * but it will generated a SIGCHLD when it terminates. There must a SIGCHLD * handler to ignore the SIGCHLD for only this process. (See sigchld_handler * above). * * NOTE: sysUpTime is a scalar provided by netsnmp. It is not the same thing as * a normal system uptime. Support for this has been provided to try to * match the IETF Draft SIP MIBs as closely as possible. */ static int spawn_sysUpTime_child() { struct sigaction new_sigchld_handler; char *local_path_to_snmpget = "/usr/local/bin/"; char *snmpget_binary_name = "/snmpget"; char *full_path_to_snmpget = NULL; char *snmp_community_string = "public"; /* Set up a new SIGCHLD handler. The handler will be responsible for * ignoring SIGCHLDs generated by our sysUpTime child process. Every * other SIGCHLD will be redirected to the old SIGCHLD handler. */ sigfillset(&new_sigchld_handler.sa_mask); new_sigchld_handler.sa_flags = SA_RESTART; new_sigchld_handler.sa_handler = sigchld_handler; sigaction(SIGCHLD, &new_sigchld_handler, &old_sigchld_handler); int result_pid = fork(); if (result_pid < 0) { LOG(L_ERR, "ERROR: SNMPStats: Could not spawn an agent to " "check sysUpTime\n"); return -1; } else if (result_pid != 0) { /* Keep around the PID of the sysUpTime process so that the * customized SIGCHLD handler knows to ignore the SIGCHLD we * generate when we terminate. */ sysUpTime_pid = result_pid; return 0; } /* If we are here, then we are the child process. Lets set up the file * descriptors so we can capture the output of snmpget. */ int snmpget_fd = open(SNMPGET_TEMP_FILE, O_CREAT|O_TRUNC|O_RDWR, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); if (snmpget_fd == -1) { LOG(L_ERR, "ERROR: SNMPStats: Could not open a temporary file " "for snmpget to write to\n"); return -1; } /* Redirect Standard Output to our temporary file. */ dup2(snmpget_fd, 1); if (snmp_community != NULL) { snmp_community_string = snmp_community; } else { LOG(L_INFO, "INFO: SNMPStats: An snmpCommunity parameter was" " not provided. Defaulting to %s\n", snmp_community_string); } char *args[] = {"-Ov", "-c", snmp_community_string, "localhost", SYSUPTIME_OID, (char *) 0}; /* Make sure we have a path to snmpget, so we can retrieve the * sysUpTime. */ if (snmpget_path == NULL) { LOG(L_INFO, "INFO: SNMPStats: An snmpgetPath parameter was not" " specified. Defaulting to %s\n", local_path_to_snmpget); } else { local_path_to_snmpget = snmpget_path; } int local_path_to_snmpget_length = strlen(local_path_to_snmpget); int snmpget_binary_name_length = strlen(snmpget_binary_name); /* Allocate enough memory to hold the path, the binary name, and the * null character. We don't use pkg_memory here. */ full_path_to_snmpget = malloc(sizeof(char) * (local_path_to_snmpget_length + snmpget_binary_name_length + 1)); if (full_path_to_snmpget == NULL) { LOG(L_ERR, "ERROR: SNMPStats: Ran out of memory while trying to" " retrieve sysUpTime. "); LOG(L_ERR, " openserSIPServiceStartTime is " "defaulting to zero\n"); return -1; } else { /* Make a new string containing the full path to the binary. */ strcpy(full_path_to_snmpget, local_path_to_snmpget); strcpy(&full_path_to_snmpget[local_path_to_snmpget_length], snmpget_binary_name); } /* snmpget -Ov -c public localhost .1.3.6.1.2.1.1.3.0 */ if (execve(full_path_to_snmpget, args, NULL) == -1) { LOG(L_ERR, "ERROR: SNMPStats: snmpget failed to run. Did you " "supply the snmpstats\n"); LOG(L_ERR, " module with a proper snmpgetPath parameter?" " The \n"); LOG(L_ERR, " openserSIPServiceStartTime is defaulting to " "zero\n"); close(snmpget_fd); free(full_path_to_snmpget); exit(-1); } /* We should never be able to get here, because execve() is never * supposed to return. */ free(full_path_to_snmpget); exit(-1); } /* This function is called whenever the openser.cfg file specifies the * snmpgetPath parameter. The function will set the snmpget_path parameter. */ int set_snmpget_path( modparam_t type, void *val) { if (!stringHandlerSanityCheck(type, val, "snmpgetPath" )) { return -1; } snmpget_path = (char *)val; return 0; } /* Handles setting of the snmp community string. */ int set_snmp_community( modparam_t type, void *val) { if (!stringHandlerSanityCheck(type, val, "snmpCommunity")) { return -1; } snmp_community = (char *)val; return 0; }