// Author: Ricardas Stoma // Company: Kolmisoft // Year: 2015 // About: Script finds and merges pcap files #define SCRIPT_VERSION "2.3" #define SCRIPT_NAME "m2_pcap" #define PCAP_ROOT "/var/log/m2/pcap" #define FIVE_MB_IN_B 5242880 #define SEARCH_INTERVAL 60 #define NO_PROCESS_LOCK 1 #include "m2_functions.c" // GLOBAL VARIABLES MYSQL_RES *result; MYSQL_ROW row; char query[2048] = ""; char date[20] = ""; char dst[256] = ""; char localized_dst[256] = ""; char src[256] = ""; char originator_ip[256] = ""; char terminator_ip[256] = ""; char directory[256] = ""; char filename[320] = ""; char buffer_year[20] = ""; char buffer_month[20] = ""; char buffer_day[20] = ""; char buffer_hour[20] = ""; char buffer_minute[20] = ""; char buffer_second[20] = ""; char system_cmd[1024] = ""; char uniqueid[256] = ""; unsigned long long int call_id = 0; unsigned long long int call_details_id = 0; char terminator_ip_array[256][50]; int terminator_ip_count = 0; float call_attempt_duration[50]; int call_attempt_duration_count = 0; int i = 0; int j = 0; // FUNCTION DECLARATIONS int file_count(char *path); int find_legA_pcap(); int find_legB_pcap(); void merge_pcap_files(); void insert_pcap_files_to_db(); void find_invite_packets(); void find_initial_packets(); void get_additional_data(); int insert_to_mysql_bin(char *file, char *row); int insert_to_mysql_hex(char* file, char *row); char *strmov(register char *dst, register const char *src) { while ((*dst++ = *src++)); return dst - 1; } // MAIN FUNCTION int main(int argc, char *argv[]) { // starting sript m2_init("Starting M2 PCAP script\n"); if (argc == 7) { strcpy(date, argv[1]); strcpy(dst, argv[2]); strcpy(src, argv[3]); strcpy(originator_ip, argv[4]); strcpy(terminator_ip, argv[5]); call_id = atoll(argv[6]); } else { m2_log("Wrong number of arguments!\n"); m2_log("Accepted arguments: [date] [dst] [src] [originator_ip] [terminator_ip] [call_id]\n"); return 1; } m2_log("\n"); m2_log("*** Search parameters ***\n"); m2_log("\n"); m2_log("Date: %s\n", date); m2_log("Destination: %s\n", dst); m2_log("Source: %s\n", src); m2_log("Originator IP: %s\n", originator_ip); m2_log("Terminator IP: %s\n", terminator_ip); m2_log("Call id: %llu\n", call_id); // get additional data from database get_additional_data(); // find initial packets by callerid and destination number find_initial_packets(); // filter only invite packets (skip OPTIONS and other non INVITE requests) find_invite_packets(); // find legA pcap files by source and ip find_legA_pcap(); // find legB pcap files by dst and ip find_legB_pcap(); // merge pcap files and create txt/png/pcap files merge_pcap_files(); // insert pcap files to database insert_pcap_files_to_db(); m2_log("\n"); m2_log("Script completed\n"); return 0; } /* Check if directory is empty */ int file_count(char *path) { char command[512] = ""; char pipe_buffer[64] = ""; sprintf(command, "ls %s | wc -l", path); FILE *pipe = popen(command, "r"); if (pipe == NULL) { m2_log("Command failed: %s\n", command); exit(1); } fgets(pipe_buffer, 64, pipe); pclose(pipe); return atoi(pipe_buffer); } /* Find legA PCAP files by source and ip */ int find_legA_pcap() { char src_string[256] = ""; int files_before = 0; int files_after = 0; m2_log("\n"); m2_log("*** Checking PCAP for legA ***\n"); m2_log("\n"); // create new directory for final filter system("mkdir -p " PCAP_ROOT "/pcap_tmp/invite_packets/final"); // get last 5 characters from callerid if (strlen(src) > 5) { sprintf(src_string, "%s", src + (strlen(src) - 5)); } else { strcpy(src_string, src); } // check how many files where in folder before cp files_before = file_count(PCAP_ROOT "/pcap_tmp/invite_packets/final/"); // try to find legA by callerid and ip sprintf(system_cmd, "grep -Plir '^(Contact|From|P-Asserted-Identity|Remote-Party-ID).*%s@%s' " PCAP_ROOT "/pcap_tmp/invite_packets/* | xargs cp -t " PCAP_ROOT "/pcap_tmp/invite_packets/final/", src_string, originator_ip); system(system_cmd); m2_log("%s\n", system_cmd); // check if we have found anything files_after = file_count(PCAP_ROOT "/pcap_tmp/invite_packets/final/"); // we found pcap for legA if (files_after > files_before) { m2_log("PCAP file for legA was found\n"); return 0; } // if we did not find anything by source AND ip, then try to find by ip only sprintf(system_cmd, "grep -Plir '^(Contact|From|P-Asserted-Identity|Remote-Party-ID|Via).*%s' " PCAP_ROOT "/pcap_tmp/invite_packets/* | xargs cp -t " PCAP_ROOT "/pcap_tmp/invite_packets/final/", originator_ip); system(system_cmd); m2_log("%s\n", system_cmd); // check if we have found anything files_after = file_count(PCAP_ROOT "/pcap_tmp/invite_packets/final/"); // we found pcap for legA by ip if (files_after > files_before) { m2_log("PCAP file for legA was found\n"); return 0; } // if we did not find anything by ip, then try to find by callerid only sprintf(system_cmd, "grep -Plir '^(Contact|From|P-Asserted-Identity|Remote-Party-ID).*%s@' " PCAP_ROOT "/pcap_tmp/invite_packets/* | xargs cp -t " PCAP_ROOT "/pcap_tmp/invite_packets/final/", src_string); system(system_cmd); m2_log("%s\n", system_cmd); // check if we have found anything files_after = file_count(PCAP_ROOT "/pcap_tmp/invite_packets/final/"); // we found pcap for legA by callerid if (files_after > files_before) { m2_log("PCAP file for legA was found\n"); return 0; } m2_log("WARNING! PCAP file for legA was not found!\n"); // if we did not find anything for legA, return 1 return 1; } /* Find legB PCAP files by dst and ip */ int find_legB_pcap() { char dst_string[256] = ""; int files_before = 0; int files_after = 0; // check legB only if there were call attempts if (terminator_ip_count) { m2_log("\n"); m2_log("*** Checking PCAP for legB ***\n"); m2_log("\n"); // check how many files where in folder before cp files_before = file_count(PCAP_ROOT "/pcap_tmp/invite_packets/final/"); for (i = 0; i < terminator_ip_count; i++) { // get last 5 characters from destination number if (strlen(localized_dst) > 5) { sprintf(dst_string, "%s", localized_dst + (strlen(localized_dst) - 5)); } else { strcpy(dst_string, localized_dst); } sprintf(system_cmd, "grep -lir '^To.*%s@%s' " PCAP_ROOT "/pcap_tmp/invite_packets/* | xargs cp -t " PCAP_ROOT "/pcap_tmp/invite_packets/final/", dst_string, terminator_ip_array[i]); system(system_cmd); m2_log("%s\n", system_cmd); } // check if we have found anything files_after = file_count(PCAP_ROOT "/pcap_tmp/invite_packets/final/"); // we found pcap for legB by callerid if (files_after > files_before) { m2_log("PCAP file for legB was found\n"); return 0; } m2_log("WARNING! PCAP file for legB was not found!\n"); // if we did not find anything for legB, return 1 return 1; } return 0; } /* Merge PCAP files and create txt/png/pcap formats */ void merge_pcap_files() { m2_log("\n"); m2_log("*** Merging PCAP files to create txt/png/pcap files ***\n"); m2_log("\n"); // check if directory is not empty if (!file_count(PCAP_ROOT "/pcap_tmp/invite_packets/final/")) { m2_log("Directory " PCAP_ROOT "/pcap_tmp/invite_packets/final/ is empty, there is nothing to merge\n"); exit(1); } // merge packets system("/usr/sbin/mergecap -w " PCAP_ROOT "/pcap_tmp/invite_packets/final/out.pcap " PCAP_ROOT "/pcap_tmp/invite_packets/final/*"); m2_log("/usr/sbin/mergecap -w " PCAP_ROOT "/pcap_tmp/invite_packets/final/out.pcap " PCAP_ROOT "/pcap_tmp/invite_packets/final/*\n"); // generate txt sip trace system("/usr/sbin/tshark -n -r " PCAP_ROOT "/pcap_tmp/invite_packets/final/out.pcap > " PCAP_ROOT "/pcap_tmp/invite_packets/final/out.txt"); m2_log("/usr/sbin/tshark -n -r " PCAP_ROOT "/pcap_tmp/invite_packets/final/out.pcap > " PCAP_ROOT "/pcap_tmp/invite_packets/final/out.txt\n"); // generate png sip trace system("/usr/src/m2/scripts/pcap2msc " PCAP_ROOT "/pcap_tmp/invite_packets/final/out.pcap sip | /usr/src/m2/scripts/mscgen -T png -o " PCAP_ROOT "/pcap_tmp/invite_packets/final/out.png"); m2_log("/usr/src/m2/scripts/pcap2msc " PCAP_ROOT "/pcap_tmp/invite_packets/final/out.pcap sip | /usr/src/m2/scripts/mscgen -T png -o " PCAP_ROOT "/pcap_tmp/invite_packets/final/out.png\n"); // check if files exist // pcap FILE *fp = fopen(PCAP_ROOT "/pcap_tmp/invite_packets/final/out.pcap", "r"); if (fp == NULL) { m2_log("PCAP file does not exist!\n"); fclose(fp); exit(1); } fclose(fp); // png fp = fopen(PCAP_ROOT "/pcap_tmp/invite_packets/final/out.png", "r"); if (fp == NULL) { m2_log("PNG file does not exist!\n"); fclose(fp); exit(1); } fclose(fp); // txt fp = fopen(PCAP_ROOT "/pcap_tmp/invite_packets/final/out.txt", "r"); if (fp == NULL) { m2_log("TXT file does not exist!\n"); fclose(fp); exit(1); } fclose(fp); m2_log("PCAP files merged successfully\n"); } /* Create call details record (if not exists) and insert PCAP files to that record */ void insert_pcap_files_to_db() { m2_log("\n"); m2_log("*** Inserting PCAP files to database ***\n"); m2_log("\n"); // check if call_details record exist for current call sprintf(query, "SELECT id FROM call_details WHERE call_id = %llu", call_id); if (m2_mysql_query(query)) { exit(1); } result = mysql_store_result(&mysql); if (mysql_num_rows(result)) { while (( row = mysql_fetch_row(result) )) { if (row[0]) { call_details_id = atoll(row[0]); } } } mysql_free_result(result); if (!call_details_id) { m2_log("Call details record does not exist. New call details record will be created.\n"); sprintf(query, "INSERT INTO call_details(call_id) VALUES(%llu)", call_id); if (m2_mysql_query(query)) { exit(1); } // check AGAIN if call_details record exist for current call sprintf(query, "SELECT id FROM call_details WHERE call_id = %llu", call_id); if (m2_mysql_query(query)) { exit(1); } result = mysql_store_result(&mysql); if (mysql_num_rows(result)) { while (( row = mysql_fetch_row(result) )) { if (row[0] && atoll(row[0])) { call_details_id = atoll(row[0]); } } } mysql_free_result(result); if (!call_details_id) { m2_log("Could not insert new call details record\n"); exit(1); } } if (insert_to_mysql_hex(PCAP_ROOT "/pcap_tmp/invite_packets/final/out.pcap", "pcap")) { m2_log("ERROR: PCAP file was not inserted into the database\n"); } if (insert_to_mysql_hex(PCAP_ROOT "/pcap_tmp/invite_packets/final/out.txt", "pcap_text")) { m2_log("ERROR: PCAP text was not inserted into the database\n"); } if (insert_to_mysql_hex(PCAP_ROOT "/pcap_tmp/invite_packets/final/out.png", "pcap_graph")) { m2_log("ERROR: PCAP png was not inserted into the database\n"); } m2_log("Data insertion into the database completed\n"); } /* Find only INVITE pcap files */ void find_invite_packets() { m2_log("\n"); m2_log("*** Finding PCAP files that contain INVITE requests ***\n"); m2_log("\n"); // create directory for INVITE packets system("mkdir -p " PCAP_ROOT "/pcap_tmp/invite_packets/"); // copy only invite packet system("grep -lir 'INVITE sip:' " PCAP_ROOT "/pcap_tmp/* | xargs cp -t " PCAP_ROOT "/pcap_tmp/invite_packets/"); m2_log("grep -lir 'INVITE sip:' " PCAP_ROOT "/pcap_tmp/* | xargs cp -t " PCAP_ROOT "/pcap_tmp/invite_packets/\n"); // check if directory is not empty if (!file_count(PCAP_ROOT "/pcap_tmp/invite_packets/")) { m2_log("Directory " PCAP_ROOT "/pcap_tmp/invite_packets/ is empty. PCAP files that contain INVITE requests were not found\n"); exit(1); } m2_log("INVITE packets were found\n"); } /* Find initial packets by source and destination */ void find_initial_packets() { float total_duration = 0; char dst_string[100] = ""; struct tm tm; time_t timestamp; char calldate[20] = ""; char *tz; m2_log("\n"); m2_log("*** Finding initial PCAP files that contain specified callerid and destination number\n"); m2_log("\n"); // create tmp directory system("rm -fr " PCAP_ROOT "/pcap_tmp/"); system("mkdir -p " PCAP_ROOT "/pcap_tmp/"); // get last 5 characters from destination number if (strlen(localized_dst) > 5) { sprintf(dst_string, "*%s", localized_dst + (strlen(localized_dst) - 5)); } else { strcpy(dst_string, localized_dst); } // set timezone to UTC to prevent daylight savings tz = getenv("TZ"); if (tz) tz = strdup(tz); setenv("TZ", "UTC", 1); tzset(); // all call attempts are saved with the same calldate in database (first attempt calldate is used for all other attempts) // but pcap files for call atempts ar saved with the date at wich they were initiated // to find when call attempt was actually initiated, we need to know duration for previous call attempt // for example: // in LCR we have 2 providers // call was initiated at 12:00:00 and after 10 seconds first attempt failed (provider did not respond in 10 seconds) // then core tried another provider at 12:00:10 and call was answered for 30 seconds // in calls table we will have two CDRs with the same date 12:00:00 // first CDR will have calldate = 12:00:00, disposition = FAILED and duration = 10 (sec) // second CDR will have calldate = 12:00:00, disposition = ANSWERED and duration = 30 (sec) // to get REAL calldate for second CDR have to add duration for ALL previous CDRs to calldate // second CDR started at 12:00:00 + 10 sec = 12:00:10 // we need this time because PCAP file will contain this date // this method is not very accurate so there might be cases when calculated time is different from PCAP time (by 1 second or so) for (i = 0; i < call_attempt_duration_count; i++) { total_duration += call_attempt_duration[i]; // generate additional filenames for +- 1sec offset for (j = -1*(SEARCH_INTERVAL/2); j <= SEARCH_INTERVAL/2; j++) { // format calldate to timestamp memset(&tm, 0, sizeof(struct tm)); strptime(date, DATE_FORMAT, &tm); timestamp = mktime(&tm); // add duration offset timestamp += round(total_duration); // add search interval offset timestamp += j; localtime_r(×tamp, &tm); // convert back to date strftime(calldate, sizeof(calldate), DATE_FORMAT, &tm); // generate filename strncpy(buffer_year, calldate, 4); strncpy(buffer_month, calldate + 5, 2); strncpy(buffer_day, calldate + 8, 2); strncpy(buffer_hour, calldate + 11, 2); strncpy(buffer_minute, calldate + 14, 2); strncpy(buffer_second, calldate + 17, 2); sprintf(directory, PCAP_ROOT "/%s%s%s/%s", buffer_year, buffer_month, buffer_day, buffer_hour); sprintf(filename, PCAP_ROOT "/%s%s%s/%s/%s%s%s-%s%s%s-*-%s*", buffer_year, buffer_month, buffer_day, buffer_hour, buffer_year, buffer_month, buffer_day, buffer_hour, buffer_minute, buffer_second, dst_string); m2_log("Generated filename: %s\n", filename); // copy matching files to this directory sprintf(system_cmd, "cp -fr %s " PCAP_ROOT "/pcap_tmp/", filename); system(system_cmd); m2_log("%s\n", system_cmd); } } // restore timezone session variable if (tz) { setenv("TZ", tz, 1); free(tz); } else { unsetenv("TZ"); } tzset(); // check if directory is not empty if (!file_count(PCAP_ROOT "/pcap_tmp/")) { m2_log("Directory " PCAP_ROOT "/pcap_tmp/ is empty. Initial PCAP files were not found by callerid and destination number\n"); exit(1); } m2_log("Initial PCAP files were found\n"); } /* Get additional data from calls table (localized dst, call attempts) */ void get_additional_data() { m2_log("\n"); m2_log("*** Fetching additional data from calls table ***\n"); m2_log("\n"); // get uniqueid for call sprintf(query, "SELECT uniqueid, localized_dst FROM calls WHERE id = %llu", call_id); if (m2_mysql_query(query)) { exit(1); } result = mysql_store_result(&mysql); if (mysql_num_rows(result)) { while (( row = mysql_fetch_row(result) )) { if (row[0]) { strcpy(uniqueid, row[0]); strcpy(localized_dst, row[1]); } } } mysql_free_result(result); if (!strlen(uniqueid)) { m2_log("Uniqueid was not found!\n"); exit(1); } // get call attempts sprintf(query, "SELECT real_duration, terminator_ip FROM calls WHERE calldate = '%s' AND uniqueid = '%s' ORDER BY id ASC", date, uniqueid); if (m2_mysql_query(query)) { exit(1); } result = mysql_store_result(&mysql); if (mysql_num_rows(result)) { while (( row = mysql_fetch_row(result) )) { if (row[0]) { call_attempt_duration[call_attempt_duration_count + 1] = atof(row[0]); call_attempt_duration_count++; } if (row[1]) { strcpy(terminator_ip_array[terminator_ip_count], row[1]); terminator_ip_count++; } } } mysql_free_result(result); // set first call duration offset call_attempt_duration[0] = 0; m2_log("Call attempts to legB: %d\n", terminator_ip_count); } /* Inserts a file into the database as a byte stream. Not used anymore because of some client's issues */ int insert_to_mysql_bin(char* file, char *row) { // Opens a file for byte reading FILE *fp = fopen(file, "rb"); if (fp == NULL) { m2_log("ERROR: Cannot open file: %s\n", file); return 1; } // Finds the end of a file fseek(fp, 0, SEEK_END); if (ferror(fp)) { fclose(fp); m2_log("ERROR: Cannot find file end\n"); return 1; } // Determines the length of a file int flen = ftell(fp); if (flen == -1) { fclose(fp); m2_log("ERROR: File reading error\n"); return 1; } // File too big if (flen > FIVE_MB_IN_B) { fclose(fp); m2_log("ERROR: File bigger than 5MB\n"); return 1; } // Rewinds the file to its beginning fseek(fp, 0, SEEK_SET); if (ferror(fp)) { fclose(fp); m2_log("ERROR: Cannot find file beginning\n"); return 1; } // Allocates memory for a byte stream char *bin_data = calloc(flen + 1, 1); if (bin_data == NULL) { fclose(fp); m2_log("ERROR: Cannot allocate memory for file\n"); return 1; } // Reads a needed amount of bytes int bin_size = fread(bin_data, 1, flen, fp); if (ferror(fp)) { free(bin_data); fclose(fp); m2_log("ERROR: Cannot read binary data from file\n"); return 1; } fclose(fp); // Escaped bytes need at most twice as much memory int escaped_buff_size = 2 * bin_size + 1; char *bin_chunk = calloc(escaped_buff_size, 1); if (bin_chunk == NULL) { free(bin_data); m2_log("ERROR: Cannot allocate memory for an escaped byte stream\n"); return 1; } // Escape the byte stream mysql_real_escape_string(&mysql, bin_chunk, bin_data, bin_size); free(bin_data); // Query needs a little more memory than the byte stream char *query = calloc(128 + escaped_buff_size, 1); if (query == NULL) { free(bin_chunk); m2_log("ERROR: Cannot allocate memory for query\n"); return 1; } // Form the query snprintf(query, 128 + escaped_buff_size + 1, "UPDATE call_details SET %s = '%s' WHERE id = %llu", row, bin_chunk, call_details_id); free(bin_chunk); if (mysql_real_query(&mysql, query, strlen(query))) { free(query); return 1; } free(query); return 0; } /* Inserts a file into the database as a hexadecimal string. */ int insert_to_mysql_hex(char* file, char *row) { // Puts a command on a buffer. char command[256] = ""; // xxd turns a file into a hex string. For more run: xxd -h snprintf(command, 256, "xxd -c %u -p %s", FIVE_MB_IN_B, file); // Runs the command and captures its output on a pipe FILE *pp = popen(command, "r"); if (pp == NULL) { m2_log("ERROR: Cannot run command: %s\n", command); return 1; } // Allocates 5 MB of heap char *hex_data = calloc(FIVE_MB_IN_B, 1); if (hex_data == NULL) { pclose(pp); m2_log("ERROR: Cannot allocate memory for file\n"); return 1; } // Reads 5 MB from file fgets(hex_data, FIVE_MB_IN_B, pp); pclose(pp); char *query = calloc(strlen(hex_data) + 128, 1); if (query == NULL) { free(hex_data); m2_log("ERROR: Cannot allocate memory for query\n"); return 1; } snprintf(query, strlen(hex_data) + 128, "UPDATE call_details SET %s = '%s' WHERE id = %llu", row, hex_data, call_details_id); free(hex_data); if (m2_mysql_query(query)) { free(query); return 1; } free(query); return 0; }