1 /*
2  * SNMPStats Module
3  * Copyright (C) 2006 SOMA Networks, INC.
4  * Written by: Jeffrey Magder (jmagder@somanetworks.com)
5  *
6  * This file is part of Kamailio, a free SIP server.
7  *
8  * Kamailio is free software; you can redistribute it and/or modify it
9  * under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version
12  *
13  * Kamailio is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
21  * USA
22  *
23  * Note: this file originally auto-generated by mib2c using
24  * mib2c.array-user.conf
25  *
26  * This file implements the kamailioSIPRegUserTable.  For a full description of
27  * the table, please see the KAMAILIO-SIP-SERVER-MIB.
28  *
29  * Understanding this code will be much simpler with the following information:
30  *
31  * 1) All rows are indexed by an integer user index.  This is different from the
32  *    usrloc module, which indexes by strings.  This less natural indexing
33  *    scheme was required due to SNMP String index limitations.  (for example,
34  *    SNMP has maximum index lengths.)
35  *
36  * 2) We need a quick way of mapping usrloc indices to our integer indices.  For
37  *    this reason a string indexed Hash Table was created, with each entry mapping
38  *    to an integer user index.
39  *
40  *    This hash table is used by the kamailioSIPContactTable (the hash table also
41  *    maps a user to its contacts), as well as the kamailioSIPRegUserLookupTable.
42  *    The hash table is also used for quick lookups when a user expires. (i.e, it
43  *    gives us a more direct reference, instead of having to search the whole
44  *    table).
45  *
46  * 3) We are informed about new/expired users via a callback mechanism from the
47  *    usrloc module.  Because of NetSNMP inefficiencies, we had to abstract this
48  *    process.  Specifically:
49  *
50  *    - It can take a long time for the NetSNMP code base to populate a table with
51  *      a large number of records.
52  *
53  *    - We rely on callbacks for updated user information.
54  *
55  *    Clearly, using the SNMPStats module in this situation could lead to some
56  *    big performance loses if we don't find another way to deal with this.  The
57  *    solution was to use an interprocess communications buffer.
58  *
59  *    Instead of adding the record directly to the table, the callback functions
60  *    now adds either an add/delete command to the interprocessBuffer.  When an
61  *    snmp request is received by the SNMPStats sub-process, it will consume
62  *    this interprocess buffer, adding and deleting users.  When it is finished,
63  *    it can service the SNMP request.
64  *
65  *    This doesn't remove the NetSNMP inefficiency, but instead moves it to a
66  *    non-critical path.  Such an approach allows SNMP support with almost no
67  *    overhead to the rest of Kamailio.
68  */
69 
70 #include <net-snmp/net-snmp-config.h>
71 #include <net-snmp/net-snmp-includes.h>
72 #include <net-snmp/agent/net-snmp-agent-includes.h>
73 
74 #include <net-snmp/library/snmp_assert.h>
75 
76 #include "hashTable.h"
77 #include "interprocess_buffer.h"
78 #include "utilities.h"
79 #include "snmpSIPRegUserTable.h"
80 #include "snmpstats_globals.h"
81 
82 #include "../../core/sr_module.h"
83 #include "../../core/locking.h"
84 #include "../usrloc/usrloc.h"
85 
86 static netsnmp_handler_registration *my_handler = NULL;
87 static netsnmp_table_array_callbacks cb;
88 
89 oid kamailioSIPRegUserTable_oid[] = {kamailioSIPRegUserTable_TABLE_OID};
90 size_t kamailioSIPRegUserTable_oid_len =
91 		OID_LENGTH(kamailioSIPRegUserTable_oid);
92 
93 
94 /* If the usrloc module is loaded, this function will grab hooks into its
95  * callback registration function, and add handleContactCallbacks() as the
96  * callback for UL_CONTACT_INSERT and UL_CONTACT_EXPIRE.
97  *
98  * Returns 1 on success, and zero otherwise. */
registerForUSRLOCCallbacks(void)99 int registerForUSRLOCCallbacks(void)
100 {
101 	bind_usrloc_t bind_usrloc;
102 	usrloc_api_t ul;
103 
104 	bind_usrloc = (bind_usrloc_t)find_export("ul_bind_usrloc", 1, 0);
105 	if(!bind_usrloc) {
106 		LM_ERR("Can't find ul_bind_usrloc\n");
107 		goto error;
108 	}
109 	if(bind_usrloc(&ul) < 0 || ul.register_ulcb == NULL) {
110 		LM_ERR("Can't bind usrloc\n");
111 		goto error;
112 	}
113 
114 	ul.register_ulcb(UL_CONTACT_INSERT, handleContactCallbacks, NULL);
115 
116 	ul.register_ulcb(UL_CONTACT_EXPIRE, handleContactCallbacks, NULL);
117 
118 	return 1;
119 
120 error:
121 	LM_INFO("failed to register for callbacks with the USRLOC module.");
122 	LM_INFO("kamailioSIPContactTable and kamailioSIPUserTable will be"
123 			" unavailable");
124 	return 0;
125 }
126 
127 
128 /* Removes an SNMP row indexed by userIndex, and frees the string and index it
129  * pointed to. */
deleteRegUserRow(int userIndex)130 void deleteRegUserRow(int userIndex)
131 {
132 
133 	kamailioSIPRegUserTable_context *theRow;
134 
135 	netsnmp_index indexToRemove;
136 	oid indexToRemoveOID;
137 
138 	indexToRemoveOID = userIndex;
139 	indexToRemove.oids = &indexToRemoveOID;
140 	indexToRemove.len = 1;
141 
142 	theRow = CONTAINER_FIND(cb.container, &indexToRemove);
143 
144 	/* The userURI is shared memory, the index.oids was allocated from
145 	 * pkg_malloc(), and theRow was made with the NetSNMP API which uses
146 	 * malloc() */
147 	if(theRow != NULL) {
148 		CONTAINER_REMOVE(cb.container, &indexToRemove);
149 		pkg_free(theRow->kamailioSIPUserUri);
150 		pkg_free(theRow->index.oids);
151 		free(theRow);
152 	}
153 }
154 
155 /*
156  * Adds or updates a user:
157  *
158  *   - If a user with the name userName exists, its 'number of contacts' count
159  *     will be incremented.
160  *   - If the user doesn't exist, the user will be added to the table, and its
161  *     number of contacts' count set to 1.
162  */
updateUser(char * userName)163 void updateUser(char *userName)
164 {
165 	int userIndex;
166 
167 	aorToIndexStruct_t *newRecord;
168 
169 	aorToIndexStruct_t *existingRecord =
170 			findHashRecord(hashTable, userName, HASH_SIZE);
171 
172 	/* We found an existing record, so  we need to update its 'number of
173 	 * contacts' count. */
174 	if(existingRecord != NULL) {
175 		existingRecord->numContacts++;
176 		return;
177 	}
178 
179 	/* Make a new row, and insert a record of it into our mapping data
180 	 * structures */
181 	userIndex = createRegUserRow(userName);
182 
183 	if(userIndex == 0) {
184 		LM_ERR("kamailioSIPRegUserTable ran out of memory."
185 			   "  Not able to add user: %s",
186 				userName);
187 		return;
188 	}
189 
190 	newRecord = createHashRecord(userIndex, userName);
191 
192 	/* If we couldn't create a record in the hash table, then we won't be
193 	 * able to access this row properly later.  So remove the row from the
194 	 * table and fail. */
195 	if(newRecord == NULL) {
196 		deleteRegUserRow(userIndex);
197 		LM_ERR("kamailioSIPRegUserTable was not able to push %s into the hash."
198 			   "  User not added to this table\n",
199 				userName);
200 		return;
201 	}
202 
203 	/* Insert the new record of the mapping data structure into the hash
204 	 * table */
205 	/*insertHashRecord(hashTable,
206 			createHashRecord(userIndex, userName),
207 			HASH_SIZE);*/
208 
209 	insertHashRecord(hashTable, newRecord, HASH_SIZE);
210 }
211 
212 
213 /* Creates a row and inserts it.
214  *
215  * Returns: The rows userIndex on success, and 0 otherwise. */
createRegUserRow(char * stringToRegister)216 int createRegUserRow(char *stringToRegister)
217 {
218 	int static index = 0;
219 
220 	index++;
221 
222 	kamailioSIPRegUserTable_context *theRow;
223 
224 	oid *OIDIndex;
225 	int stringLength;
226 
227 	theRow = SNMP_MALLOC_TYPEDEF(kamailioSIPRegUserTable_context);
228 
229 	if(theRow == NULL) {
230 		LM_ERR("failed to create a row for kamailioSIPRegUserTable\n");
231 		return 0;
232 	}
233 
234 	OIDIndex = pkg_malloc(sizeof(oid));
235 
236 	if(OIDIndex == NULL) {
237 		free(theRow);
238 		LM_ERR("failed to create a row for kamailioSIPRegUserTable\n");
239 		return 0;
240 	}
241 
242 	stringLength = strlen(stringToRegister);
243 
244 	OIDIndex[0] = index;
245 
246 	theRow->index.len = 1;
247 	theRow->index.oids = OIDIndex;
248 	theRow->kamailioSIPUserIndex = index;
249 
250 	theRow->kamailioSIPUserUri =
251 			(unsigned char *)pkg_malloc(stringLength * sizeof(char));
252 	if(theRow->kamailioSIPUserUri == NULL) {
253 		pkg_free(OIDIndex);
254 		free(theRow);
255 		LM_ERR("failed to create a row for kamailioSIPRegUserTable\n");
256 		return 0;
257 	}
258 	memcpy(theRow->kamailioSIPUserUri, stringToRegister, stringLength);
259 
260 	theRow->kamailioSIPUserUri_len = stringLength;
261 
262 	theRow->kamailioSIPUserAuthenticationFailures = 0;
263 
264 	CONTAINER_INSERT(cb.container, theRow);
265 
266 	return index;
267 }
268 
269 /* Initializes the kamailioSIPRegUserTable module.  */
init_kamailioSIPRegUserTable(void)270 void init_kamailioSIPRegUserTable(void)
271 {
272 	/* Register this table with the master agent */
273 	initialize_table_kamailioSIPRegUserTable();
274 
275 	/* We need to create a default row, so create DefaultUser */
276 	static char *defaultUser = "DefaultUser";
277 
278 	createRegUserRow(defaultUser);
279 }
280 
281 
282 /*
283  * Initialize the kamailioSIPRegUserTable table by defining its contents and how
284  * it's structured
285  */
initialize_table_kamailioSIPRegUserTable(void)286 void initialize_table_kamailioSIPRegUserTable(void)
287 {
288 	netsnmp_table_registration_info *table_info;
289 
290 	if(my_handler) {
291 		snmp_log(LOG_ERR, "initialize_table_kamailioSIPRegUserTable_hand"
292 						  "ler called again\n");
293 		return;
294 	}
295 
296 	memset(&cb, 0x00, sizeof(cb));
297 
298 	/* create the table structure itself */
299 	table_info = SNMP_MALLOC_TYPEDEF(netsnmp_table_registration_info);
300 	if(table_info==NULL) {
301 		snmp_log(LOG_ERR, "failed to allocate table_info\n");
302 		return;
303 	}
304 
305 	my_handler = netsnmp_create_handler_registration("kamailioSIPRegUserTable",
306 			netsnmp_table_array_helper_handler, kamailioSIPRegUserTable_oid,
307 			kamailioSIPRegUserTable_oid_len, HANDLER_CAN_RONLY);
308 
309 	if(!my_handler) {
310 		SNMP_FREE(table_info);
311 		snmp_log(LOG_ERR, "malloc failed in initialize_table_kamailio"
312 						  "SIPRegUserTable_handler\n");
313 		return; /** mallocs failed */
314 	}
315 
316 	netsnmp_table_helper_add_index(table_info, ASN_UNSIGNED);
317 
318 	table_info->min_column = kamailioSIPRegUserTable_COL_MIN;
319 	table_info->max_column = kamailioSIPRegUserTable_COL_MAX;
320 
321 	cb.get_value = kamailioSIPRegUserTable_get_value;
322 	cb.container = netsnmp_container_find("kamailioSIPRegUserTable_primary:"
323 										  "kamailioSIPRegUserTable:"
324 										  "table_container");
325 
326 	DEBUGMSGTL(("initialize_table_kamailioSIPRegUserTable",
327 			"Registering table kamailioSIPRegUserTable "
328 			"as a table array\n"));
329 
330 	netsnmp_table_container_register(
331 			my_handler, table_info, &cb, cb.container, 1);
332 }
333 
334 
335 /* Handles SNMP GET requests. */
kamailioSIPRegUserTable_get_value(netsnmp_request_info * request,netsnmp_index * item,netsnmp_table_request_info * table_info)336 int kamailioSIPRegUserTable_get_value(netsnmp_request_info *request,
337 		netsnmp_index *item, netsnmp_table_request_info *table_info)
338 {
339 	/* First things first, we need to consume the interprocess buffer, in
340 	 * case something has changed. We want to return the freshest data. */
341 	consumeInterprocessBuffer();
342 
343 	netsnmp_variable_list *var = request->requestvb;
344 
345 	kamailioSIPRegUserTable_context *context =
346 			(kamailioSIPRegUserTable_context *)item;
347 
348 	switch(table_info->colnum) {
349 
350 		case COLUMN_KAMAILIOSIPUSERURI:
351 			/** SnmpAdminString = ASN_OCTET_STR */
352 			snmp_set_var_typed_value(var, ASN_OCTET_STR,
353 					(unsigned char *)context->kamailioSIPUserUri,
354 					context->kamailioSIPUserUri_len);
355 			break;
356 
357 		case COLUMN_KAMAILIOSIPUSERAUTHENTICATIONFAILURES:
358 			/** COUNTER = ASN_COUNTER */
359 			snmp_set_var_typed_value(var, ASN_COUNTER,
360 					(unsigned char *)&context
361 							->kamailioSIPUserAuthenticationFailures,
362 					sizeof(context->kamailioSIPUserAuthenticationFailures));
363 			break;
364 
365 		default: /** We shouldn't get here */
366 			snmp_log(LOG_ERR, "unknown column in "
367 							  "kamailioSIPRegUserTable_get_value\n");
368 
369 			return SNMP_ERR_GENERR;
370 	}
371 
372 	return SNMP_ERR_NOERROR;
373 }
374 
375 
kamailioSIPRegUserTable_get_by_idx(netsnmp_index * hdr)376 const kamailioSIPRegUserTable_context *kamailioSIPRegUserTable_get_by_idx(
377 		netsnmp_index *hdr)
378 {
379 	return (const kamailioSIPRegUserTable_context *)CONTAINER_FIND(
380 			cb.container, hdr);
381 }