1 /*
2 * Copyright (C) 2006 Voice Sistem SRL
3 *
4 * This file is part of Kamailio, a free SIP server.
5 *
6 * Kamailio is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * Kamailio is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 *
20 *
21 */
22
23 /*!
24 * \file
25 * \brief Statistics support
26 * \author bogdan, andrei
27 * \author Jeffrey Magder - SOMA Networks
28 */
29
30
31 #include <string.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34
35 #include "../../core/ut.h"
36 #include "../../core/dprint.h"
37 #include "../../core/socket_info.h"
38 #include "rl_statistics.h"
39
40 #ifdef STATISTICS
41
42
43 /*! \brief
44 * Returns the statistic associated with 'numerical_code' and 'out_codes'.
45 * Specifically:
46 *
47 * - if out_codes is nonzero, then the stat_var for the number of messages
48 * _sent out_ with the 'numerical_code' will be returned if it exists.
49 * - otherwise, the stat_var for the number of messages _received_ with the
50 * 'numerical_code' will be returned, if the stat exists.
51 */
get_stat_var_from_num_code(unsigned int numerical_code,int out_codes)52 stat_var *get_stat_var_from_num_code(unsigned int numerical_code, int out_codes)
53 {
54 static char msg_code[INT2STR_MAX_LEN+4];
55 str stat_name;
56
57 stat_name.s = int2bstr( (unsigned long)numerical_code, msg_code,
58 &stat_name.len);
59 stat_name.s[stat_name.len++] = '_';
60
61 if (out_codes) {
62 stat_name.s[stat_name.len++] = 'o';
63 stat_name.s[stat_name.len++] = 'u';
64 stat_name.s[stat_name.len++] = 't';
65 } else {
66 stat_name.s[stat_name.len++] = 'i';
67 stat_name.s[stat_name.len++] = 'n';
68 }
69
70 return get_stat(&stat_name);
71 }
72
73
74 #endif /*STATISTICS*/
75
76 #define MAX_PROC_BUFFER 256
77
78 /*!
79 * This function will retrieve a list of all ip addresses and ports that Kamailio
80 * is listening on, with respect to the transport protocol specified with
81 * 'protocol'.
82 *
83 * The first parameter, ipList, is a pointer to a pointer. It will be assigned a
84 * new block of memory holding the IP Addresses and ports being listened to with
85 * respect to 'protocol'. The array maps a 2D array into a 1 dimensional space,
86 * and is layed out as follows:
87 *
88 * The first NUM_IP_OCTETS indices will be the IP address, and the next index
89 * the port. So if NUM_IP_OCTETS is equal to 4 and there are two IP addresses
90 * found, then:
91 *
92 * - ipList[0] will be the first octet of the first ip address
93 * - ipList[3] will be the last octet of the first ip address.
94 * - iplist[4] will be the port of the first ip address
95 * -
96 * - iplist[5] will be the first octet of the first ip address,
97 * - and so on.
98 *
99 * The function will return the number of sockets which were found. This can be
100 * used to index into ipList.
101 *
102 * \note This function assigns a block of memory equal to:
103 *
104 * returnedValue * (NUM_IP_OCTETS + 1) * sizeof(int);
105 *
106 * Therefore it is CRUCIAL that you free ipList when you are done with its
107 * contents, to avoid a nasty memory leak.
108 */
get_socket_list_from_proto(int ** ipList,int protocol)109 int get_socket_list_from_proto(int **ipList, int protocol) {
110 return get_socket_list_from_proto_and_family(ipList, protocol, AF_INET);
111 }
112
113
114 /*!
115 * This function will retrieve a list of all ip addresses and ports that Kamailio
116 * is listening on, with respect to the transport protocol specified with
117 * 'protocol'. This function supports both IPv4 and IPv6
118 *
119 * The first parameter, ipList, is a pointer to a pointer. It will be assigned a
120 * new block of memory holding the IP Addresses and ports being listened to with
121 * respect to 'protocol'. The array maps a 2D array into a 1 dimensional space,
122 * and is layed out as follows:
123 *
124 * The first NUM_IP_OCTETS indices will be the IP address, and the next index
125 * the port. So if NUM_IP_OCTETS is equal to 4 and there are two IP addresses
126 * found, then:
127 *
128 * - ipList[0] will be the first octet of the first ip address
129 * - ipList[3] will be the last octet of the first ip address.
130 * - iplist[4] will be the port of the first ip address
131 * -
132 * - iplist[5] will be the first octet of the first ip address,
133 * - and so on.
134 */
get_socket_list_from_proto_and_family(int ** ipList,int protocol,int family)135 int get_socket_list_from_proto_and_family(int **ipList, int protocol, int family) {
136
137 struct socket_info *si;
138 struct socket_info** list;
139
140 int num_ip_octets = family == AF_INET ? NUM_IP_OCTETS : NUM_IPV6_OCTETS;
141 int numberOfSockets = 0;
142 int currentRow = 0;
143
144 /* I hate to use #ifdefs, but this is necessary because of the way
145 * get_sock_info_list() is defined. */
146 #ifndef USE_TCP
147 if (protocol == PROTO_TCP)
148 {
149 return 0;
150 }
151 #endif
152
153 #ifndef USE_TLS
154 if (protocol == PROTO_TLS)
155 {
156 return 0;
157 }
158 #endif
159 #ifndef USE_SCTP
160 if (protocol == PROTO_SCTP)
161 {
162 return 0;
163 }
164 #endif
165 /* We have no "interfaces" for websockets */
166 if (protocol == PROTO_WS || protocol == PROTO_WSS)
167 return 0;
168
169 /* Retrieve the list of sockets with respect to the given protocol. */
170 list=get_sock_info_list(protocol);
171
172 /* Find out how many sockets are in the list. We need to know this so
173 * we can malloc an array to assign to ipList. */
174 for(si=list?*list:0; si; si=si->next){
175 if (si->address.af == family) {
176 numberOfSockets++;
177 }
178 }
179
180 /* There are no open sockets with respect to the given protocol. */
181 if (numberOfSockets == 0)
182 {
183 return 0;
184 }
185
186 *ipList = pkg_malloc(numberOfSockets * (num_ip_octets + 1) * sizeof(int));
187
188 /* We couldn't allocate memory for the IP List. So all we can do is
189 * fail. */
190 if (*ipList == NULL) {
191 LM_ERR("no more pkg memory");
192 return 0;
193 }
194
195
196 /* We need to search the list again. So find the front of the list. */
197 list=get_sock_info_list(protocol);
198
199 /* Extract out the IP Addresses and ports. */
200 for(si=list?*list:0; si; si=si->next){
201 int i;
202
203 /* We currently only support IPV4. */
204 if (si->address.af != family) {
205 continue;
206 }
207
208 for (i = 0; i < num_ip_octets; i++) {
209 (*ipList)[currentRow*(num_ip_octets + 1) + i ] =
210 si->address.u.addr[i];
211 }
212 (*ipList)[currentRow*(num_ip_octets + 1) + i] =
213 si->port_no;
214
215 currentRow++;
216 }
217
218 return numberOfSockets;
219 }
220
221 /*!
222 * Takes a 'line' (from the proc file system), parses out the ipAddress,
223 * address, and stores the number of bytes waiting in 'rx_queue'
224 *
225 * Returns 1 on success, and 0 on a failed parse.
226 *
227 * Note: The format of ipAddress is as defined in the comments of
228 * get_socket_list_from_proto() in this file.
229 *
230 */
parse_proc_net_line(char * line,int * ipAddress,int * rx_queue)231 static int parse_proc_net_line(char *line, int *ipAddress, int *rx_queue)
232 {
233 int i;
234
235 int ipOctetExtractionMask = 0xFF;
236
237 char *currColonLocation;
238 char *nextNonNumericalChar;
239 char *currentLocationInLine = line;
240
241 int parsedInteger[4];
242
243 /* Example line from /proc/net/tcp or /proc/net/udp:
244 *
245 * sl local_address rem_address st tx_queue rx_queue
246 * 21: 5A0A0B0A:CAC7 1C016E0A:0016 01 00000000:00000000
247 *
248 * Algorithm:
249 *
250 * 1) Find the location of the first ':'
251 * 2) Parse out the IP Address into an integer
252 * 3) Find the location of the second ':'
253 * 4) Parse out the port number.
254 * 5) Find the location of the fourth ':'
255 * 6) Parse out the rx_queue.
256 */
257
258 for (i = 0; i < 4; i++) {
259
260 currColonLocation = strchr(currentLocationInLine, ':');
261
262 /* We didn't find all the needed ':', so fail. */
263 if (currColonLocation == NULL) {
264 return 0;
265 }
266
267 /* Parse out the integer, keeping the location of the next
268 * non-numerical character. */
269 parsedInteger[i] =
270 (int) strtol(++currColonLocation, &nextNonNumericalChar,
271 16);
272
273 /* strtol()'s specifications specify that the second parameter
274 * is set to the first parameter when a number couldn't be
275 * parsed out. This means the parse was unsuccesful. */
276 if (nextNonNumericalChar == currColonLocation) {
277 return 0;
278 }
279
280 /* Reset the currentLocationInLine to the last non-numerical
281 * character, so that next iteration of this loop, we can find
282 * the next colon location. */
283 currentLocationInLine = nextNonNumericalChar;
284
285 }
286
287 /* Extract out the segments of the IP Address. They are stored in
288 * reverse network byte order. */
289 for (i = 0; i < NUM_IP_OCTETS; i++) {
290
291 ipAddress[i] =
292 parsedInteger[0] & (ipOctetExtractionMask << i*8);
293
294 ipAddress[i] >>= i*8;
295
296 }
297
298 ipAddress[NUM_IP_OCTETS] = parsedInteger[1];
299
300 *rx_queue = parsedInteger[3];
301
302 return 1;
303 }
304
305
306 /*!
307 * Returns 1 if ipOne was found in ipArray, and 0 otherwise.
308 *
309 * The format of ipOne and ipArray are described in the comments of
310 * get_socket_list_from_proto() in this file.
311 *
312 * */
match_ip_and_port(int * ipOne,int * ipArray,int sizeOf_ipArray)313 static int match_ip_and_port(int *ipOne, int *ipArray, int sizeOf_ipArray)
314 {
315 int curIPAddrIdx;
316 int curOctetIdx;
317 int ipArrayIndex;
318
319 /* Loop over every IP Address */
320 for (curIPAddrIdx = 0; curIPAddrIdx < sizeOf_ipArray; curIPAddrIdx++) {
321
322 /* Check for octets that don't match. If one is found, skip the
323 * rest. */
324 for (curOctetIdx = 0; curOctetIdx < NUM_IP_OCTETS + 1; curOctetIdx++) {
325
326 /* We've encoded a 2D array as a 1D array. So find out
327 * our position in the 1D array. */
328 ipArrayIndex =
329 curIPAddrIdx * (NUM_IP_OCTETS + 1) + curOctetIdx;
330
331 if (ipOne[curOctetIdx] != ipArray[ipArrayIndex]) {
332 break;
333 }
334 }
335
336 /* If the index from the inner loop is equal to NUM_IP_OCTETS
337 * + 1, then that means that every octet (and the port with the
338 * + 1) matched. */
339 if (curOctetIdx == NUM_IP_OCTETS + 1) {
340 return 1;
341 }
342
343 }
344
345 return 0;
346 }
347
348
349 /*!
350 * Returns the number of bytes waiting to be consumed on the network interfaces
351 * assigned the IP Addresses specified in interfaceList. The check will be
352 * limited to the TCP or UDP transport exclusively. Specifically:
353 *
354 * - If forTCP is non-zero, the check involves only the TCP transport.
355 * - if forTCP is zero, the check involves only the UDP transport.
356 *
357 * Note: This only works on linux systems supporting the /proc/net/[tcp|udp]
358 * interface. On other systems, zero will always be returned.
359 */
get_used_waiting_queue(int forTCP,int * interfaceList,int listSize)360 static int get_used_waiting_queue(
361 int forTCP, int *interfaceList, int listSize)
362 {
363 FILE *fp;
364 char *fileToOpen;
365
366 char lineBuffer[MAX_PROC_BUFFER];
367 int ipAddress[NUM_IP_OCTETS+1];
368 int rx_queue;
369
370 int waitingQueueSize = 0;
371
372 #ifndef __OS_linux
373 /* /proc/net/tcp and /proc/net/udp only exists on Linux systems, so don't bother with
374 trying to open these files */
375 return 0;
376 #endif
377
378 /* Set up the file we want to open. */
379 if (forTCP) {
380 fileToOpen = "/proc/net/tcp";
381 } else {
382 fileToOpen = "/proc/net/udp";
383 }
384
385 fp = fopen(fileToOpen, "r");
386
387 if (fp == NULL) {
388 LM_ERR("Could not open %s. kamailioMsgQueueDepth and its related"
389 " alarms will not be available.\n", fileToOpen);
390 return 0;
391 }
392
393 /* Read in every line of the file, parse out the ip address, port, and
394 * rx_queue, and compare to our list of interfaces we are listening on.
395 * Add up rx_queue for those lines which match our known interfaces. */
396 while (fgets(lineBuffer, MAX_PROC_BUFFER, fp)!=NULL) {
397
398 /* Parse out the ip address, port, and rx_queue. */
399 if(parse_proc_net_line(lineBuffer, ipAddress, &rx_queue)) {
400
401 /* Only add rx_queue if the line just parsed corresponds
402 * to an interface we are listening on. We do this
403 * check because it is possible that this system has
404 * other network interfaces that Kamailio has been told
405 * to ignore. */
406 if (match_ip_and_port(ipAddress, interfaceList, listSize)) {
407 waitingQueueSize += rx_queue;
408 }
409 }
410 }
411
412 fclose(fp);
413
414 return waitingQueueSize;
415 }
416
417 /*!
418 * Returns the sum of the number of bytes waiting to be consumed on all network
419 * interfaces and transports that Kamailio is listening on.
420 *
421 * Note: This currently only works on systems supporting the /proc/net/[tcp|udp]
422 * interface. On other systems, zero will always be returned. To change
423 * this in the future, add an equivalent for get_used_waiting_queue().
424 */
get_total_bytes_waiting(void)425 int get_total_bytes_waiting(void)
426 {
427 int bytesWaiting = 0;
428
429 int *UDPList = NULL;
430 int *TCPList = NULL;
431 int *TLSList = NULL;
432 int *UDP6List = NULL;
433 int *TCP6List = NULL;
434 int *TLS6List = NULL;
435
436 int numUDPSockets = 0;
437 int numTCPSockets = 0;
438 int numTLSSockets = 0;
439 int numUDP6Sockets = 0;
440 int numTCP6Sockets = 0;
441 int numTLS6Sockets = 0;
442
443 /* Extract out the IP address address for UDP, TCP, and TLS, keeping
444 * track of the number of IP addresses from each transport */
445 numUDPSockets = get_socket_list_from_proto(&UDPList, PROTO_UDP);
446 numTCPSockets = get_socket_list_from_proto(&TCPList, PROTO_TCP);
447 numTLSSockets = get_socket_list_from_proto(&TLSList, PROTO_TLS);
448
449 numUDP6Sockets = get_socket_list_from_proto_and_family(&UDP6List, PROTO_UDP, AF_INET6);
450 numTCP6Sockets = get_socket_list_from_proto_and_family(&TCP6List, PROTO_TCP, AF_INET6);
451 numTLS6Sockets = get_socket_list_from_proto_and_family(&TLS6List, PROTO_TLS, AF_INET6);
452
453 /* Deliberately not looking at PROTO_WS or PROTO_WSS here as they are
454 just upgraded TCP/TLS connections */
455
456 /* Find out the number of bytes waiting on our interface list over all
457 * UDP and TCP transports. */
458 bytesWaiting += get_used_waiting_queue(0, UDPList, numUDPSockets);
459 bytesWaiting += get_used_waiting_queue(1, TCPList, numTCPSockets);
460 bytesWaiting += get_used_waiting_queue(1, TLSList, numTLSSockets);
461
462 bytesWaiting += get_used_waiting_queue(0, UDP6List, numUDP6Sockets);
463 bytesWaiting += get_used_waiting_queue(1, TCP6List, numTCP6Sockets);
464 bytesWaiting += get_used_waiting_queue(1, TLS6List, numTLS6Sockets);
465
466 /* get_socket_list_from_proto() allocated a chunk of memory, so we need
467 * to free it. */
468 if (numUDPSockets > 0)
469 {
470 pkg_free(UDPList);
471 }
472 if (numUDP6Sockets > 0)
473 {
474 pkg_free(UDP6List);
475 }
476
477 if (numTCPSockets > 0)
478 {
479 pkg_free(TCPList);
480 }
481 if (numTCP6Sockets > 0)
482 {
483 pkg_free(TCP6List);
484 }
485
486 if (numTLSSockets > 0)
487 {
488 pkg_free(TLSList);
489 }
490 if (numTLS6Sockets > 0)
491 {
492 pkg_free(TLS6List);
493 }
494
495 return bytesWaiting;
496 }
497