/* * $Id$ * * Copyright (C) 2006 iptelorg GmbH * * This file is part of ser, a free SIP server. * * ser 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 * * For a license to use the ser software under conditions * other than those described here, or to purchase support for this * software, please contact iptel.org by e-mail at the following addresses: * info@iptel.org * * ser 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #define NAME "SER mod_pike top console" #define VERSION "1.0" // XML elements in result // they MUST NOT have a number on the tail // because pike uses it for "row" numbering static const char * const MAX_HITS = "max_hits"; static const char * const IP_ADDR = "ip_addr"; static const char * const LEAF_HITS_PREV = "leaf_hits_prev"; static const char * const LEAF_HITS_CURR = "leaf_hits_curr"; static const char * const EXPIRES = "expires"; static const char * const STATUS = "status"; static const char * const NUMBER_OF_ROWS = "number_of_rows"; #define IP_ADDR_MAX_LENGTH 40 #define STATUS_MAX_LENGTH 10 typedef struct TopItem { char ip_addr[IP_ADDR_MAX_LENGTH]; in_addr_t ipv4_addr; /* uint32_t */ struct in6_addr ipv6_addr; unsigned short leaf_hits[2]; unsigned int expires; char status[STATUS_MAX_LENGTH]; int num_of_ips; /* number of IP addresses in aggregated result */ } TopItem; int compare_TopItem_hits(const void* left, const void *right) { TopItem *li = (TopItem *)left; TopItem *ri = (TopItem *)right; return li->leaf_hits[0] + li->leaf_hits[1] - ri->leaf_hits[0] - ri->leaf_hits[1]; } /** Compare function to qsort array in reverse order (bigger first) */ int compare_TopItem_hits_reverse(const void* left, const void *right) { return compare_TopItem_hits(right, left); } int compare_TopItem_ipv4_addr(const void *item1, const void *item2) { return ((TopItem*)item1)->ipv4_addr - ((TopItem*)item2)->ipv4_addr; } /** * @return concatenated string in newly allocated memory */ static char *concat( const char *name, int index ) { char *ptr; int rv; rv = asprintf(&ptr, "%s%d", name, index); if ( rv == -1 ) return 0; return ptr; } static void strfree( char *ptr ) { if (ptr) free(ptr); } static void die_if_fault_occurred (xmlrpc_env *env) { if (env->fault_occurred) { fprintf(stderr, "XML-RPC Fault: %s (%d)\n", env->fault_string, env->fault_code); exit(1); } } /** @return 0 if everything is OK, 1 otherwise */ static int fault_occurred (xmlrpc_env *env) { if (env->fault_occurred) { fprintf(stderr, "XML-RPC Fault: %s (%d)\n", env->fault_string, env->fault_code); return 1; } return 0; } static void die_if_fault_occurred_line (xmlrpc_env *env, int line) { if (env->fault_occurred) fprintf(stderr, "LINE: %d\n", line); die_if_fault_occurred(env); } void print_help() { printf("\n"); if ( isatty(1) ) printf("usage: \033[1mpike_top : [--hot|--warm|--all] [--mask]\033[0m\n"); else printf("usage: pike_top : [--hot|--warm|--all] [--mask] [--ipleaf|--inner]\n"); printf("\n"); printf("\toptions:\n" "\t\t--hot ... show hot IP leaves\n" "\t\t--all ... show all IP leaves\n" "\t\t--mask ... aggregate results regarding IP mask length\n" "\t\t (default 32, i.e. not aggregate)\n" "\t\t IPv4 only at this time\n\n" "\t\t\tdefault is to show HOT nodes\n\n"); if ( isatty(1) ) printf("You can use \033[1mwatch\033[0m(1) utility for periodical output.\n\n"); else printf("You can use watch(1) utility for periodical output.\n\n"); /* printf("Note:\n" "It is a question if reporting warm nodes is useful and if yes, how to report\n" "them. I feel that should be more welcome to report parents of warm nodes,\n" "or something like this to show which subnets could be problematic...\n\n"); */ } /* Following options are conforming to definition of node status defined in pike/ip_tree.h */ #define OPT_WARM 1 #define OPT_HOT 2 #define OPT_ALL 3 /** * @param options ORed cmdline options * @return position of first non option parameter */ int process_options(int argc, char *argv[], int *options, int *mask_length) { static struct option long_options[] = { {"hot", 0, 0, 'h'}, /* {"warm", 0, 0, 'w'}, */ {"all", 0, 0, 'a'}, {"mask", 0, 0, 'm'}, {"help", 0, 0, '?'}, {0,0,0,0} }; int c, index, counter; *options = 0; counter = 0; while ( (c=getopt_long(argc, argv, "hwam:", long_options, &index)) != -1 ) { switch (c) { case 'h': *options = OPT_HOT; ++counter; break; /* case 'w': *options = OPT_WARM; ++counter; break; */ case 'a': *options = OPT_ALL; ++counter; break; case 'm': *mask_length = atoi(optarg); ; break; case '?': default: print_help(); exit(0); break; } } if ( counter > 1 ) { fprintf(stderr, "ERROR: Node type selectors are exclusive, only one of " "them can be used\n"); print_help(); exit(1); } if ( *options == 0 ) *options = OPT_HOT; return optind; } /* Get an integer value from struct result of xmlrpc_client_call() * @param structP pointer to a result struct * @param element_name name of structure item * @param rv returned value * @return 1 if succeed and 0 otherwise */ int get_int_from_struct_by_name(xmlrpc_value *structP, const char *element_name, int *rv) { xmlrpc_env env; xmlrpc_env_init(&env); xmlrpc_value *valueP; xmlrpc_struct_find_value(&env, structP, element_name, &valueP); if ( env.fault_occurred ) goto error; xmlrpc_read_int(&env, valueP, rv); if ( env.fault_occurred ) goto error1; xmlrpc_DECREF(valueP); return 1; error1: xmlrpc_DECREF(valueP); error: return 0; } /* Get a new string value from struct result of xmlrpc_client_call() * @param structP pointer to a result struct * @param element_name name of structure item * @param rv contains newly allocated string or NULL if fails * @return 1 if succeed and 0 otherwise */ /* FIXME terminates the programme if it fails */ int get_string_from_struct_by_name(xmlrpc_value *structP, const char *element_name, char **rv) { xmlrpc_env env; xmlrpc_env_init(&env); xmlrpc_value *valueP; int length; xmlrpc_struct_find_value(&env, structP, element_name, &valueP); die_if_fault_occurred_line(&env, __LINE__); xmlrpc_read_string(&env, valueP, (const char **)rv); die_if_fault_occurred_line(&env, __LINE__); xmlrpc_DECREF(valueP); return 1; } /* Get an integer value from struct result of xmlrpc_client_call() * @param structP pointer to a result struct * @param index index of requested element * @param rv returned value * @return 1 if succeed and 0 otherwise */ /* FIXME terminates the program if it fails */ int get_int_from_struct_by_idx(xmlrpc_value *structP, int index, int *rv) { xmlrpc_env env; xmlrpc_env_init(&env); xmlrpc_value *keyP; xmlrpc_value *valueP; xmlrpc_struct_read_member(&env, structP, index, &keyP, &valueP); /* increment refcount of returned values */ die_if_fault_occurred_line(&env, __LINE__); xmlrpc_read_int(&env, valueP, rv); die_if_fault_occurred_line(&env, __LINE__); xmlrpc_DECREF(valueP); return 1; } enum _value_type { TYPE_NOT_DEF = 0, TYPE_INTEGER = 1, TYPE_STRING = 2 }; typedef enum _value_type value_type; struct _key_value_pair { char *key; value_type type; union value { int integer; char *string; void *value; } value; }; typedef struct _key_value_pair key_value_pair; void key_value_pair_cleanup(key_value_pair *kvp) { if (kvp && kvp->key) { free(kvp->key); } if (kvp && kvp->type == TYPE_STRING && kvp->value.string) { free(kvp->value.string); } kvp->key = 0; kvp->type = TYPE_NOT_DEF; kvp->value.integer = 0; } /** Get a string value from struct result of xmlrpc_client_call() * @param structP pointer to a result struct * @param index index of requested element * @param rv pointer to key_value_pair * @return 1 if succeed and 0 otherwise */ /* FIXME terminates the programme if it fails */ int get_struct_item_by_idx(xmlrpc_value *structP, int index, key_value_pair *rv) { xmlrpc_env env; xmlrpc_env_init(&env); xmlrpc_value *keyP; xmlrpc_value *valueP; int length; const char *string; xmlrpc_struct_read_member(&env, structP, index, &keyP, &valueP); /* increment refcount of returned values */ die_if_fault_occurred_line(&env, __LINE__); xmlrpc_read_string(&env, keyP, (const char **)&rv->key); /* handle value type */ switch ( xmlrpc_value_type(valueP) ) { case XMLRPC_TYPE_INT: xmlrpc_read_int(&env, valueP, &rv->value.integer); die_if_fault_occurred_line(&env, __LINE__); rv->type = TYPE_INTEGER; break; case XMLRPC_TYPE_STRING: xmlrpc_read_string(&env, valueP, &string); printf("get_struct_item_by_idx: ptr = %p, string value = '%s'\n", string, string); die_if_fault_occurred_line(&env, __LINE__); rv->value.string = (char *)string; rv->type = TYPE_STRING; break; default: fprintf(stderr, "Wrong type of return value in key: '%s', exiting...\n", rv->key); exit(1); } xmlrpc_DECREF(keyP); /* decrement refcount */ xmlrpc_DECREF(valueP); die_if_fault_occurred_line(&env, __LINE__); /* FIXME add error handling */ return 1; } /** * Reads one toprow from given structure * @param structP * @param rownum * @param top_item */ int read_row(xmlrpc_value *structP, int index, TopItem *top_item) { char *elem; char *string = 0; elem = concat(IP_ADDR, index); if ( ! get_string_from_struct_by_name(structP, elem, &string) ) goto error; strncpy(top_item->ip_addr, string, sizeof(top_item->ip_addr)); free(string); string = 0; free(elem); elem = concat(LEAF_HITS_PREV, index); if ( ! get_int_from_struct_by_name(structP, elem, (unsigned int *)&top_item->leaf_hits[0]) ) goto error; free(elem); elem = concat(LEAF_HITS_CURR, index); if ( ! get_int_from_struct_by_name(structP, elem, (unsigned int *)&top_item->leaf_hits[1]) ) goto error; free(elem); elem = concat(EXPIRES, index); if ( ! get_int_from_struct_by_name(structP, elem, (unsigned int *)&top_item->expires) ) goto error; free(elem); elem = concat(STATUS, index); if ( ! get_string_from_struct_by_name(structP, elem, &string) ) goto error; strncpy(top_item->status, string, sizeof(top_item->status)); free(string); free(elem); return 1; error: if ( string ) free(string); free(elem); return 0; } void print_row(TopItem *ti) { char *fmt; if ( ! ti ) { printf("%-15s %10s %10s %10s %-10s\n", "IP address", "HITS PREV", "HITS CURR", "EXPIRES", "STATUS"); return; } if ( strlen(ti->ip_addr) > 15 ) // IPv6 addr fmt = "%s\n %10d %10d %10d %-10s\n"; else fmt = "%-15s %10d %10d %10d %-10s\n"; printf(fmt, ti->ip_addr, ti->leaf_hits[0], ti->leaf_hits[1], ti->expires, ti->status); } void print_row_agg(TopItem *ti) /* IPv4 only */ { char *fmt; if ( ! ti ) { printf("%-15s %10s %10s %5s\n", "IP address", "HITS PREV", "HITS CURR", "COUNT"); return; } fmt = "%-15s %10d %10d %5d\n"; printf(fmt, ti->ip_addr, ti->leaf_hits[0], ti->leaf_hits[1], ti->num_of_ips); } uint32_t mask( int msklen ) { if ( msklen ) return 0xffffffff ^ ((1 << (32-msklen)) - 1); else return 0; } void print_rows(TopItem *root, int nmemb, int mask_length) { int i; void (*print_function)(TopItem *); if (mask_length == 32) print_function = print_row; else print_function = print_row_agg; print_function(0); for ( i = 0; i < nmemb; ++i, ++root ) { print_function(root); } } int main( int argc, char *argv[] ) { xmlrpc_env env; xmlrpc_value * resultP; xmlrpc_value * keyP; xmlrpc_value * valueP; int struct_size; int i, j; size_t length; const char * str_key_value; xmlrpc_int int_key_value; unsigned int max_hits = 0; unsigned int rows = 0; int rv; char *uri; int options; /* what kind of nodes should be processed */ int uri_pos; /* position of first non option argument */ char stropts[16]; int pos = 0; int mask_length = 32; /* 32 means NO aggregate */ if (argc-1 < 1) { print_help(); exit(0); } uri_pos = process_options(argc, argv, &options, &mask_length); switch (options) { case OPT_HOT: sprintf(stropts, "HOT"); break; case OPT_ALL: sprintf(stropts, "ALL"); break; case OPT_WARM: sprintf(stropts, "WARM"); break; } printf("Nodes = %s\n", stropts); printf("Mask = /%d\n", mask_length); /* Start up our XML-RPC client library. */ xmlrpc_client_init(XMLRPC_CLIENT_NO_FLAGS, NAME, VERSION); /* Initialize our error-handling environment. */ xmlrpc_env_init(&env); /* prototype: xmlrpc_value * xmlrpc_client_call(xmlrpc_env * const envP, const char * const server_url, const char * const method_name, const char * const format, ...); */ asprintf(&uri, "http://%s/RPC2", argv[uri_pos]); resultP = xmlrpc_client_call(&env, uri, "pike.top", "(s)", stropts); free(uri); die_if_fault_occurred_line(&env, __LINE__); /* parse returned structure */ if ( xmlrpc_value_type(resultP) != XMLRPC_TYPE_STRUCT ) { printf("unexpected result - should be structure\n"); xmlrpc_env_clean(&env); xmlrpc_client_cleanup(); exit(1); } struct_size = xmlrpc_struct_size(&env, resultP); die_if_fault_occurred_line(&env, __LINE__); // printf("Struct size: %d\n", struct_size); if ( ! get_int_from_struct_by_name(resultP, MAX_HITS, &max_hits) ) { fprintf(stderr, "ERROR: %s not found in result\n", MAX_HITS); exit (1); } printf("max_hits = %d\n", max_hits); if ( ! get_int_from_struct_by_name(resultP, NUMBER_OF_ROWS, &rows) ) { fprintf(stderr, "ERROR: %s not found in result\n", NUMBER_OF_ROWS); exit (1); } printf("rows = %d\n", rows); TopItem top_items[rows]; TopItem *item; /* tmp item ptr */ TopItem *result_items = top_items; /* if no aggregation use this */ memset(top_items, 0, sizeof(top_items)); /* aggregated values */ if ( rows == 0 ) return 0; for ( i = 0, item = top_items; i < rows; ++i, ++item ) { if ( ! read_row(resultP, i, item) ) { fprintf(stderr, "ERROR: while reading row number %d\n", i); } /* fill in ipv4 addr */ // printf("item[%d].ip_addr = %s, len = %d\n", i, item->ip_addr, strlen(item->ip_addr)); rv = inet_pton(AF_INET, item->ip_addr, &item->ipv4_addr); if ( rv > 0 ) { // printf("IPv4 addr: %x\n", item->ipv4_addr); } else { fprintf(stderr, "IP conversion failed - not an IPv4 address: '%s'\n", item->ip_addr); /* conversion failed from any reason */ printf("item[%d].ipv4_addr = %x\n", i, item->ipv4_addr); } } assert( rows > 0 ); /* if IP mask length is shorter than 32 then aggregate list according to the mask */ if ( mask_length < 32 ) { uint32_t ip_mask = htonl(mask(mask_length)); qsort(top_items, rows, sizeof(TopItem), compare_TopItem_ipv4_addr); /* sort by IPv4 */ /* skip items without ipv4 address */ i = 0; /* index of non aggregated items */ while (!top_items[i].ipv4_addr && i < rows ) { printf("Skip item[%d] - do not has IPv4 address: %s\n", i, top_items[i].ip_addr); memset(&top_items[i], 0, sizeof(TopItem)); ++i; } j = 0; /* index of aggregated items */ if ( i == 0 ) ++i; top_items[0].ipv4_addr &= ip_mask; top_items[0].num_of_ips = 1; inet_ntop(AF_INET, &top_items[0].ipv4_addr, top_items[0].ip_addr, sizeof(top_items[0].ip_addr)); while ( i < rows ) { top_items[i].ipv4_addr &= ip_mask; if ( top_items[j].ipv4_addr == top_items[i].ipv4_addr ) { top_items[j].leaf_hits[0] += top_items[i].leaf_hits[0]; top_items[j].leaf_hits[1] += top_items[i].leaf_hits[1]; ++(top_items[j].num_of_ips); ++i; } else { ++j; top_items[j] = top_items[i]; top_items[j].num_of_ips = 1; inet_ntop(AF_INET, &top_items[j].ipv4_addr, top_items[j].ip_addr, sizeof(top_items[j].ip_addr)); ++i; } } rows = j + 1; } qsort(top_items, rows, sizeof(TopItem), compare_TopItem_hits_reverse); print_rows( top_items, rows, mask_length ); /* Dispose of our result value. */ xmlrpc_DECREF(resultP); /* Clean up our error-handling environment. */ xmlrpc_env_clean(&env); /* Shutdown our XML-RPC client library. */ xmlrpc_client_cleanup(); return 0; }