Browse code

Moving socket related code from snmpstats module into libkcore

Other modules will be able to access socket related statistics directly
from the shared library (this is currently used by two modules,
ratelimit and snmpstats.

Signed-off-by: Jan Janak <jan@iptel.org>

Ovidiu Sas authored on 27/03/2009 12:28:18
Showing 2 changed files
... ...
@@ -34,17 +34,21 @@
34 34
 
35 35
 
36 36
 #include <string.h>
37
+#include <stdio.h>
37 38
 
38 39
 #include "../../mem/shm_mem.h"
39 40
 #include "../kmi/mi.h"
40 41
 #include "../../ut.h"
41 42
 #include "../../dprint.h"
42 43
 #include "../../locking.h"
44
+#include "../../socket_info.h"
43 45
 #include "core_stats.h"
44 46
 #include "statistics.h"
45 47
 
46 48
 #ifdef STATISTICS
47 49
 
50
+#define MAX_PROC_BUFFER 256
51
+
48 52
 static stats_collector *collector;
49 53
 
50 54
 static struct mi_root *mi_get_stats(struct mi_root *cmd, void *param);
... ...
@@ -364,6 +368,361 @@ stat_var* get_stat( str *name )
364 364
 	return 0;
365 365
 }
366 366
 
367
+/*!
368
+ * This function will retrieve a list of all ip addresses and ports that OpenSER
369
+ * is listening on, with respect to the transport protocol specified with
370
+ * 'protocol'. 
371
+ *
372
+ * The first parameter, ipList, is a pointer to a pointer. It will be assigned a
373
+ * new block of memory holding the IP Addresses and ports being listened to with
374
+ * respect to 'protocol'.  The array maps a 2D array into a 1 dimensional space,
375
+ * and is layed out as follows:
376
+ *
377
+ * The first NUM_IP_OCTETS indices will be the IP address, and the next index
378
+ * the port.  So if NUM_IP_OCTETS is equal to 4 and there are two IP addresses
379
+ * found, then:
380
+ *
381
+ *  - ipList[0] will be the first octet of the first ip address
382
+ *  - ipList[3] will be the last octet of the first ip address.
383
+ *  - iplist[4] will be the port of the first ip address
384
+ *  - 
385
+ *  - iplist[5] will be the first octet of the first ip address, 
386
+ *  - and so on.  
387
+ *
388
+ * The function will return the number of sockets which were found.  This can be
389
+ * used to index into ipList.
390
+ *
391
+ * \note This function assigns a block of memory equal to:
392
+ *
393
+ *            returnedValue * (NUM_IP_OCTETS + 1) * sizeof(int);
394
+ *
395
+ *       Therefore it is CRUCIAL that you free ipList when you are done with its
396
+ *       contents, to avoid a nasty memory leak.
397
+ */
398
+int get_socket_list_from_proto(int **ipList, int protocol) {
399
+
400
+	struct socket_info  *si;
401
+	struct socket_info** list;
402
+
403
+	int num_ip_octets   = 4;
404
+	int numberOfSockets = 0;
405
+	int currentRow      = 0;
406
+
407
+	/* I hate to use #ifdefs, but this is necessary because of the way 
408
+	 * get_sock_info_list() is defined.  */
409
+#ifndef USE_TCP
410
+	if (protocol == PROTO_TCP) 
411
+	{
412
+		return 0;
413
+	}
414
+#endif
415
+
416
+#ifndef USE_TLS
417
+	if (protocol == PROTO_TLS)
418
+	{
419
+		return 0;
420
+	}
421
+#endif
422
+
423
+	/* Retrieve the list of sockets with respect to the given protocol. */
424
+	list=get_sock_info_list(protocol);
425
+
426
+	/* Find out how many sockets are in the list.  We need to know this so
427
+	 * we can malloc an array to assign to ipList. */
428
+	for(si=list?*list:0; si; si=si->next){
429
+		/* We only support IPV4 at this point. */
430
+		if (si->address.af == AF_INET) {
431
+			numberOfSockets++;
432
+		}
433
+	}
434
+
435
+	/* There are no open sockets with respect to the given protocol. */
436
+	if (numberOfSockets == 0)
437
+	{
438
+		return 0;
439
+	}
440
+
441
+	*ipList = pkg_malloc(numberOfSockets * (num_ip_octets + 1) * sizeof(int));
442
+
443
+	/* We couldn't allocate memory for the IP List.  So all we can do is
444
+	 * fail. */
445
+	if (*ipList == NULL) {
446
+		LM_ERR("no more pkg memory");
447
+		return 0;
448
+	}
449
+
450
+
451
+	/* We need to search the list again.  So find the front of the list. */
452
+	list=get_sock_info_list(protocol);
453
+
454
+	/* Extract out the IP Addresses and ports.  */
455
+	for(si=list?*list:0; si; si=si->next){
456
+
457
+		/* We currently only support IPV4. */
458
+		if (si->address.af != AF_INET) {
459
+			continue;
460
+		}
461
+
462
+		(*ipList)[currentRow*(num_ip_octets + 1)  ] = 
463
+			si->address.u.addr[0];
464
+		(*ipList)[currentRow*(num_ip_octets + 1)+1] = 
465
+			si->address.u.addr[1];
466
+		(*ipList)[currentRow*(num_ip_octets + 1)+2] = 
467
+			si->address.u.addr[2];
468
+		(*ipList)[currentRow*(num_ip_octets + 1)+3] = 
469
+			si->address.u.addr[3];
470
+		(*ipList)[currentRow*(num_ip_octets + 1)+4] = 
471
+			si->port_no;
472
+		
473
+		currentRow++;
474
+	}
475
+
476
+	return numberOfSockets;
477
+}
478
+
479
+/*!
480
+ * Takes a 'line' (from the proc file system), parses out the ipAddress,
481
+ * address, and stores the number of bytes waiting in 'rx_queue'
482
+ *
483
+ * Returns 1 on success, and 0 on a failed parse.
484
+ *
485
+ * Note: The format of ipAddress is as defined in the comments of
486
+ * get_socket_list_from_proto() in this file. 
487
+ *
488
+ */
489
+static int parse_proc_net_line(char *line, int *ipAddress, int *rx_queue) 
490
+{
491
+	int i;
492
+
493
+	int ipOctetExtractionMask = 0xFF;
494
+
495
+	char *currColonLocation;
496
+	char *nextNonNumericalChar;
497
+	char *currentLocationInLine = line;
498
+
499
+	int parsedInteger[4];
500
+
501
+	/* Example line from /proc/net/tcp or /proc/net/udp:
502
+	 *
503
+	 *	sl  local_address rem_address   st tx_queue rx_queue  
504
+	 *	21: 5A0A0B0A:CAC7 1C016E0A:0016 01 00000000:00000000
505
+	 *
506
+	 * Algorithm:
507
+	 *
508
+	 * 	1) Find the location of the first  ':'
509
+	 * 	2) Parse out the IP Address into an integer
510
+	 * 	3) Find the location of the second ':'
511
+	 * 	4) Parse out the port number.
512
+	 * 	5) Find the location of the fourth ':'
513
+	 * 	6) Parse out the rx_queue.
514
+	 */
515
+
516
+	for (i = 0; i < 4; i++) {
517
+
518
+		currColonLocation = strchr(currentLocationInLine, ':'); 
519
+
520
+		/* We didn't find all the needed ':', so fail. */
521
+		if (currColonLocation == NULL) {
522
+			return 0;
523
+		}
524
+
525
+		/* Parse out the integer, keeping the location of the next 
526
+		 * non-numerical character.  */
527
+		parsedInteger[i] = 
528
+			(int) strtol(++currColonLocation, &nextNonNumericalChar,
529
+					16);
530
+
531
+		/* strtol()'s specifications specify that the second parameter
532
+		 * is set to the first parameter when a number couldn't be
533
+		 * parsed out.  This means the parse was unsuccesful.  */
534
+		if (nextNonNumericalChar == currColonLocation) {
535
+			return 0;
536
+		}
537
+		
538
+		/* Reset the currentLocationInLine to the last non-numerical 
539
+		 * character, so that next iteration of this loop, we can find
540
+		 * the next colon location. */
541
+		currentLocationInLine = nextNonNumericalChar;
542
+
543
+	}
544
+
545
+	/* Extract out the segments of the IP Address.  They are stored in
546
+	 * reverse network byte order. */
547
+	for (i = 0; i < NUM_IP_OCTETS; i++) {
548
+		
549
+		ipAddress[i] = 
550
+			parsedInteger[0] & (ipOctetExtractionMask << i*8); 
551
+
552
+		ipAddress[i] >>= i*8;
553
+
554
+	}
555
+
556
+	ipAddress[NUM_IP_OCTETS] = parsedInteger[1];
557
+
558
+	*rx_queue = parsedInteger[3];
559
+	
560
+	return 1;
561
+ 
562
+}
563
+
564
+
565
+/*!
566
+ * Returns 1 if ipOne was found in ipArray, and 0 otherwise. 
567
+ *
568
+ * The format of ipOne and ipArray are described in the comments of 
569
+ * get_socket_list_from_proto() in this file.
570
+ *
571
+ * */
572
+static int match_ip_and_port(int *ipOne, int *ipArray, int sizeOf_ipArray) 
573
+{
574
+	int curIPAddrIdx;
575
+	int curOctetIdx;
576
+	int ipArrayIndex;
577
+
578
+	/* Loop over every IP Address */
579
+	for (curIPAddrIdx = 0; curIPAddrIdx < sizeOf_ipArray; curIPAddrIdx++) {
580
+
581
+		/* Check for octets that don't match.  If one is found, skip the
582
+		 * rest.  */
583
+		for (curOctetIdx = 0; curOctetIdx < NUM_IP_OCTETS + 1; curOctetIdx++) {
584
+			
585
+			/* We've encoded a 2D array as a 1D array.  So find out
586
+			 * our position in the 1D array. */
587
+			ipArrayIndex = 
588
+				curIPAddrIdx * (NUM_IP_OCTETS + 1) + curOctetIdx;
589
+
590
+			if (ipOne[curOctetIdx] != ipArray[ipArrayIndex]) {
591
+				break;
592
+			}
593
+		}
594
+
595
+		/* If the index from the inner loop is equal to NUM_IP_OCTETS
596
+		 * + 1, then that means that every octet (and the port with the
597
+		 * + 1) matched. */
598
+		if (curOctetIdx == NUM_IP_OCTETS + 1) {
599
+			return 1;
600
+		}
601
+
602
+	}
603
+
604
+	return 0;
605
+}
606
+
607
+
608
+/*!
609
+ * Returns the number of bytes waiting to be consumed on the network interfaces
610
+ * assigned the IP Addresses specified in interfaceList.  The check will be
611
+ * limited to the TCP or UDP transport exclusively.  Specifically:
612
+ *
613
+ * - If forTCP is non-zero, the check involves only the TCP transport.
614
+ * - if forTCP is zero, the check involves only the UDP transport.
615
+ *
616
+ * Note: This only works on linux systems supporting the /proc/net/[tcp|udp]
617
+ *       interface.  On other systems, zero will always be returned. 
618
+ */
619
+static int get_used_waiting_queue(
620
+		int forTCP, int *interfaceList, int listSize) 
621
+{
622
+	FILE *fp;
623
+	char *fileToOpen;
624
+	
625
+	char lineBuffer[MAX_PROC_BUFFER];
626
+	int  ipAddress[NUM_IP_OCTETS+1];
627
+	int  rx_queue;
628
+	
629
+	int  waitingQueueSize = 0;
630
+
631
+	/* Set up the file we want to open. */
632
+	if (forTCP) {
633
+		fileToOpen = "/proc/net/tcp";
634
+	} else {
635
+		fileToOpen = "/proc/net/udp";
636
+	}
637
+	
638
+	fp = fopen(fileToOpen, "r");
639
+
640
+	if (fp == NULL) {
641
+		LM_ERR("Could not open %s. openserMsgQueu eDepth and its related"
642
+				" alarms will not be available.\n", fileToOpen);
643
+		return 0;
644
+	}
645
+
646
+	/* Read in every line of the file, parse out the ip address, port, and
647
+	 * rx_queue, and compare to our list of interfaces we are listening on.
648
+	 * Add up rx_queue for those lines which match our known interfaces. */
649
+	while (fgets(lineBuffer, MAX_PROC_BUFFER, fp)!=NULL) {
650
+
651
+		/* Parse out the ip address, port, and rx_queue. */
652
+		if(parse_proc_net_line(lineBuffer, ipAddress, &rx_queue)) {
653
+
654
+			/* Only add rx_queue if the line just parsed corresponds 
655
+			 * to an interface we are listening on.  We do this
656
+			 * check because it is possible that this system has
657
+			 * other network interfaces that OpenSER has been told
658
+			 * to ignore. */
659
+			if (match_ip_and_port(ipAddress, interfaceList, listSize)) {
660
+				waitingQueueSize += rx_queue;
661
+			}
662
+		}
663
+	}
664
+
665
+	fclose(fp);
666
+
667
+	return waitingQueueSize;
668
+}
669
+
670
+/*!
671
+ * Returns the sum of the number of bytes waiting to be consumed on all network
672
+ * interfaces and transports that OpenSER is listening on. 
673
+ *
674
+ * Note: This currently only works on systems supporting the /proc/net/[tcp|udp]
675
+ *       interface.  On other systems, zero will always be returned.  To change
676
+ *       this in the future, add an equivalent for get_used_waiting_queue(). 
677
+ */
678
+int get_total_bytes_waiting(void) 
679
+{
680
+	int bytesWaiting = 0;
681
+
682
+	int *UDPList  = NULL;
683
+	int *TCPList  = NULL;
684
+	int *TLSList  = NULL;
685
+
686
+	int numUDPSockets  = 0;
687
+	int numTCPSockets  = 0; 
688
+	int numTLSSockets  = 0;
689
+
690
+	/* Extract out the IP address address for UDP, TCP, and TLS, keeping
691
+	 * track of the number of IP addresses from each transport  */
692
+	numUDPSockets  = get_socket_list_from_proto(&UDPList,  PROTO_UDP);
693
+	numTCPSockets  = get_socket_list_from_proto(&TCPList,  PROTO_TCP);
694
+	numTLSSockets  = get_socket_list_from_proto(&TLSList,  PROTO_TLS);
695
+
696
+	/* Find out the number of bytes waiting on our interface list over all
697
+	 * UDP and TCP transports. */
698
+	bytesWaiting  += get_used_waiting_queue(0, UDPList,  numUDPSockets);
699
+	bytesWaiting  += get_used_waiting_queue(1, TCPList,  numTCPSockets);
700
+	bytesWaiting  += get_used_waiting_queue(1, TLSList,  numTLSSockets);
701
+
702
+	/* get_socket_list_from_proto() allocated a chunk of memory, so we need
703
+	 * to free it. */
704
+	if (numUDPSockets > 0)
705
+	{
706
+		pkg_free(UDPList);
707
+	}
708
+
709
+	if (numTCPSockets > 0) 
710
+	{
711
+		pkg_free(TCPList);
712
+	}
713
+
714
+	if (numTLSSockets > 0)
715
+	{
716
+		pkg_free(TLSList);
717
+	}
718
+
719
+	return bytesWaiting;
720
+}
721
+
367 722
 
368 723
 
369 724
 /***************************** MI STUFF ********************************/
... ...
@@ -41,6 +41,7 @@
41 41
 #define STATS_HASH_POWER   8
42 42
 #define STATS_HASH_SIZE    (1<<(STATS_HASH_POWER))
43 43
 
44
+#define NUM_IP_OCTETS 4
44 45
 
45 46
 #define STAT_NO_RESET  (1<<0)
46 47
 #define STAT_NO_SYNC   (1<<1)
... ...
@@ -195,5 +196,50 @@ extern gen_lock_t *stat_lock;
195 195
 	#define if_reset_stat( _c, _var)
196 196
 #endif /*STATISTICS*/
197 197
 
198
+/*!
199
+ * This function will retrieve a list of all ip addresses and ports that OpenSER
200
+ * is listening on, with respect to the transport protocol specified with
201
+ * 'protocol'. 
202
+ *
203
+ * The first parameter, ipList, is a pointer to a pointer. It will be assigned a
204
+ * new block of memory holding the IP Addresses and ports being listened to with
205
+ * respect to 'protocol'.  The array maps a 2D array into a 1 dimensional space,
206
+ * and is layed out as follows:
207
+ *
208
+ * The first NUM_IP_OCTETS indices will be the IP address, and the next index
209
+ * the port.  So if NUM_IP_OCTETS is equal to 4 and there are two IP addresses
210
+ * found, then:
211
+ *
212
+ *  - ipList[0] will be the first octet of the first ip address
213
+ *  - ipList[3] will be the last octet of the first ip address.
214
+ *  - iplist[4] will be the port of the first ip address
215
+ *  - 
216
+ *  - iplist[5] will be the first octet of the first ip address, 
217
+ *  - and so on.  
218
+ *
219
+ * The function will return the number of sockets which were found.  This can be
220
+ * used to index into ipList.
221
+ *
222
+ * \note This function assigns a block of memory equal to:
223
+ *
224
+ *            returnedValue * (NUM_IP_OCTETS + 1) * sizeof(int);
225
+ *
226
+ *       Therefore it is CRUCIAL that you free ipList when you are done with its
227
+ *       contents, to avoid a nasty memory leak.
228
+ */
229
+int get_socket_list_from_proto(int **ipList, int protocol);
230
+
231
+
232
+/*!
233
+ * Returns the sum of the number of bytes waiting to be consumed on all network
234
+ * interfaces and transports that OpenSER is listening on. 
235
+ *
236
+ * Note: This currently only works on systems supporting the /proc/net/[tcp|udp]
237
+ *       interface.  On other systems, zero will always be returned.  Details of
238
+ *       why this is so can be found in network_stats.c
239
+ */
240
+int get_total_bytes_waiting(void);
241
+
242
+
198 243
 
199 244
 #endif