/* * $Id$ * * SNMPStats Module * Copyright (C) 2006 SOMA Networks, INC. * Written by: Jeffrey Magder (jmagder@somanetworks.com) * * This file is part of Kamailio, a free SIP server. * * Kamailio 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 * * Kamailio 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) */ /*! * \file * \brief SNMP statistic module, interprocess buffer * * This file implements the interprocess buffer, used for marshalling data * exchange from the usrloc module to the kamailioSIPRegUserTable, * kamailioSIPContactTable, and indirectly the kamailioSIPRegUserLookupTable. * * Details on why the interprocess buffer is needed can be found in the comments * at the top of interprocess_buffer.h * \ingroup snmpstats * - Module: \ref snmpstats */ #include #include #include #include "interprocess_buffer.h" #include "snmpSIPContactTable.h" #include "snmpSIPRegUserTable.h" #include "hashTable.h" #include "utilities.h" #include "../usrloc/ul_callback.h" /*! * The hash table: * * 1) maps all aor's to snmp's UserIndex for help in deleting SNMP Rows. * * 2) maps a given aor to a contact list. */ hashSlot_t *hashTable = NULL; /*! All interprocess communication is stored between these two declarations. */ interprocessBuffer_t *frontRegUserTableBuffer = NULL; interprocessBuffer_t *endRegUserTableBuffer = NULL; /*! This is to protect the potential racecondition in which a command is added to * the buffer while it is being consumed */ gen_lock_t *interprocessCBLock = NULL; /*! * This function takes an element of the interprocess buffer passed to it, and * handles populating the respective user and contact tables with its contained * data. */ static void executeInterprocessBufferCmd(interprocessBuffer_t *currentBuffer); /*! * Initialize shared memory used to buffer communication between the usrloc * module and the SNMPStats module. (Specifically, the user and contact tables) */ int initInterprocessBuffers(void) { /* Initialize the shared memory that will be used to buffer messages * over the usrloc module to RegUserTable callback. */ frontRegUserTableBuffer = shm_malloc(sizeof(interprocessBuffer_t)); endRegUserTableBuffer = shm_malloc(sizeof(interprocessBuffer_t)); if(frontRegUserTableBuffer == NULL || endRegUserTableBuffer == NULL) { LM_ERR("no more shared memory\n"); return -1; } memset(frontRegUserTableBuffer, 0x00, sizeof(interprocessBuffer_t)); memset(endRegUserTableBuffer, 0x00, sizeof(interprocessBuffer_t)); /* Initialize a lock to the interprocess buffer. The lock will be used * to control race-conditions that would otherwise occur if an snmp * command was received while the interprocess buffer was being consumed. */ interprocessCBLock = lock_alloc(); if(interprocessCBLock==NULL) { LM_ERR("cannot allocate the lock\n"); shm_free(frontRegUserTableBuffer); frontRegUserTableBuffer = NULL; shm_free(endRegUserTableBuffer); endRegUserTableBuffer = NULL; return -1; } lock_init(interprocessCBLock); hashTable = createHashTable(HASH_SIZE); if(hashTable == NULL) { LM_ERR("no more shared memory\n"); lock_destroy(interprocessCBLock); lock_dealloc(interprocessCBLock); shm_free(frontRegUserTableBuffer); frontRegUserTableBuffer = NULL; shm_free(endRegUserTableBuffer); endRegUserTableBuffer = NULL; return -1; } return 1; } /*! USRLOC Callback Handler: * * This function should be registered to receive callbacks from the usrloc * module. It can be called for any of the callbacks listed in ul_callback.h. * The callback type will be passed in 'type', and the contact the callback * applies to will be supplied in 'contactInfo. This information will be copied * into the interprocess buffer. The interprocess buffer will be consumed at a * later time, when consumeInterprocessBuffer() is called. * * This callback is thread safe with respect to the consumeInterprocessBuffer() * function. Specifically, the interprocess buffer should not be corrupted by * any race conditions between this function and the consumeInterprocessBuffer() * function. */ void handleContactCallbacks(ucontact_t *contactInfo, int type, void *param) { char *addressOfRecord; char *contact; interprocessBuffer_t *currentBufferElement; currentBufferElement = shm_malloc(sizeof(interprocessBuffer_t)); if (currentBufferElement == NULL) { goto error; } /* We need to maintain our own copies of the AOR and contact address to * prevent the corruption of our internal data structures. * * If we do not maintain our own copies, then the AOR and contact adress * pointed to could be removed and reallocated to another thread before * we get a chance to consume our interprocess buffer. */ convertStrToCharString(contactInfo->aor, &addressOfRecord); convertStrToCharString(&(contactInfo->c), &contact); currentBufferElement->stringName = addressOfRecord; currentBufferElement->stringContact = contact; currentBufferElement->contactInfo = contactInfo; currentBufferElement->callbackType = type; currentBufferElement->next = NULL; /* A lock is necessary to prevent a race condition. Specifically, it * could happen that we find the front of the buffer to be non-null, * are scheduled out, the entire buffer (or part of it) is consumed and * freed, and then we assign our list to deallocated memory. */ lock_get(interprocessCBLock); /* This is the first element to be added. */ if (frontRegUserTableBuffer->next == NULL) { frontRegUserTableBuffer->next = currentBufferElement; } else { endRegUserTableBuffer->next->next = currentBufferElement; } endRegUserTableBuffer->next = currentBufferElement; lock_release(interprocessCBLock); return; error: LM_ERR("Not enough shared memory for kamailioSIPRegUserTable insert." " (%s)\n", contactInfo->c.s); } /*! Interprocess Buffer consumption Function. This function will iterate over * every element of the interprocess buffer, and add or remove the specified * contacts and users. Whether the contacts are added or removed is dependent * on if the original element was added as a result of a UL_CONTACT_INSERT or * UL_CONTACT_EXPIRE callback. * * The function will free any memory occupied by the interprocess buffer. * * \note This function is believed to be thread safe. Specifically, it protects * corruption of the interprocess buffer through the interprocessCBLock. * This ensures no corruption of the buffer by race conditions. The lock * has been designed to be occupied for as short a period as possible, so * as to prevent long waits. Specifically, once we start consumption of * the list, other processes are free to continue even before we are done. * This is made possible by simply changing the head of the interprocess * buffer, and then releasing the lock. */ void consumeInterprocessBuffer(void) { interprocessBuffer_t *previousBuffer; interprocessBuffer_t *currentBuffer; /* There is nothing to consume, so just exit. */ if (frontRegUserTableBuffer->next == NULL) { return; } /* We are going to consume the entire buffer, but we don't want the * buffer to change midway through. So assign the front of the buffer * to NULL so that any other callbacks from the usrloc module will be * appended to a new list. We need to be careful to get a lock first * though, to avoid race conditions. */ lock_get(interprocessCBLock); currentBuffer = frontRegUserTableBuffer->next; frontRegUserTableBuffer->next = NULL; endRegUserTableBuffer->next = NULL; lock_release(interprocessCBLock); while (currentBuffer != NULL) { executeInterprocessBufferCmd(currentBuffer); /* We need to assign the current buffer to a temporary place * before we move onto the next buffer. Otherwise the memory * could be modified between freeing it and moving onto the next * buffer element. */ previousBuffer = currentBuffer; currentBuffer = currentBuffer->next; shm_free(previousBuffer->stringName); shm_free(previousBuffer->stringContact); shm_free(previousBuffer); } } /*! * This function takes an element of the interprocess buffer passed to it, and * handles populating the respective user and contact tables with its contained * data. */ static void executeInterprocessBufferCmd(interprocessBuffer_t *currentBuffer) { int delContactIndex; aorToIndexStruct_t *currentUser; if (currentBuffer->callbackType == UL_CONTACT_INSERT) { /* Add the user if the user doesn't exist, or increment its * contact index otherwise. */ updateUser(currentBuffer->stringName); } else if (currentBuffer->callbackType != UL_CONTACT_EXPIRE) { /* Currently we only support UL_CONTACT_INSERT and * UL_CONTACT_EXPIRE. If we receive another callback type, this * is a bug. */ LM_ERR("found a command on the interprocess buffer that" " was not an INSERT or EXPIRE"); return; } currentUser = findHashRecord(hashTable, currentBuffer->stringName, HASH_SIZE); /* This should never happen. This is more of a sanity check. */ if (currentUser == NULL) { LM_ERR("Received a request for contact: %s for user: %s who doesn't " "exists\n", currentBuffer->stringName, currentBuffer->stringContact); return; } /* This buffer element specified that we need to add a contact. So lets * add them */ if (currentBuffer->callbackType == UL_CONTACT_INSERT) { /* Increment the contact index, which will be used to generate * our new row. */ currentUser->contactIndex++; /* We should do this after we create the row in the snmptable. * Its easier to delete the SNMP Row than the contact record. */ if(!insertContactRecord(&(currentUser->contactList), currentUser->contactIndex, currentBuffer->stringContact)) { LM_ERR("kamailioSIPRegUserTable was unable to allocate memory for " "adding contact: %s to user %s.\n", currentBuffer->stringName, currentBuffer->stringContact); /* We didn't use the index, so decrement it so we can * use it next time around. */ currentUser->contactIndex--; return; } if (!createContactRow(currentUser->userIndex, currentUser->contactIndex, currentBuffer->stringContact, currentBuffer->contactInfo)) { deleteContactRecord(&(currentUser->contactList), currentBuffer->stringContact); } } else { delContactIndex = deleteContactRecord(&(currentUser->contactList), currentBuffer->stringContact); /* This should never happen. But its probably wise to check and * to print out debug messages in case there is a hidden bug. */ if(delContactIndex == 0) { LM_ERR("Received a request to delete contact: %s for user: %s" " who doesn't exist\n", currentBuffer->stringName, currentBuffer->stringContact); return; } deleteContactRow(currentUser->userIndex, delContactIndex); deleteUser(hashTable, currentBuffer->stringName, HASH_SIZE); } } void freeInterprocessBuffer(void) { interprocessBuffer_t *currentBuffer, *previousBuffer; if (frontRegUserTableBuffer==NULL || frontRegUserTableBuffer->next == NULL || endRegUserTableBuffer==NULL) { LM_DBG("Nothing to clean\n"); return; } currentBuffer = frontRegUserTableBuffer->next; frontRegUserTableBuffer->next = NULL; endRegUserTableBuffer->next = NULL; while (currentBuffer != NULL) { previousBuffer = currentBuffer; currentBuffer = currentBuffer->next; shm_free(previousBuffer->stringName); shm_free(previousBuffer->stringContact); shm_free(previousBuffer); } if(frontRegUserTableBuffer) shm_free(frontRegUserTableBuffer); if(endRegUserTableBuffer) shm_free(endRegUserTableBuffer); }