/* VoIPong Voice Over IP Sniffer Copyright (C) 2004 Murat Balaban All rights reserved. This program 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. This program 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern char goutdir[256]; extern char gsoxpath[256]; extern char gsoxmixpath[256]; extern int grtp_idle_time; extern int gsoxmixflag; extern vocoder vocoders[MAXCODECS]; worker *workers[MAXNODE]; worker *curw = NULL; pid_t mypid = -1; static char *rtp_pt_desc[] = {"0-PCMU-8KHz", "1-1016-8KHz", "2-G726-32-8KHz", "3-GSM-8KHz", "4-G723.1-8KHz", "5-DVI4-8KHz", "6-DVI4-16KHz", "7-LPC-8KHz", "8-PCMA-8KHz", "9-G722-8KHz", "10-L16-44.1KHz", "11-L16-44.1KHz", "12-QCELP-8KHz", "13-CN-8KHz", "14-MPA-90KHz", "15-G728-8KHz", "16-DVI4-11KHz", "17-DVI4-22KHz", "18-G729-8KHz", "19-reserved", "20-unassigned", "21-unassigned", "22-unassigned", "23-unassigned", "24-unassigned", "25-CelB-90KHz", "26-JPEG-90KHz", "27-unassigned" }; int create_wave() { char outf1[1024]; char outf2[1024]; char mixout[1024]; char inrate[32]; char insize[4]; char indenc[4]; struct stat st; int outcnt = 0; if (vocoders[curw->rtp->enc].f == vocoder_default) { misc_debug(0, "[%d] create_wave: no modules have been loaded to decode payload type: %d\n", getpid(), curw->rtp->enc); misc_debug(0, "[%d] create_wave: leaving *.raw files untouched.\n", getpid()); return -1; } snprintf(inrate, sizeof(inrate) - 1, "%d", vocoders[curw->rtp->enc].rate); strcpy(insize, "-w"); strcpy(indenc, "-s"); strncpy(outf1, curw->file1name, sizeof(outf1) - 2); memcpy(outf1 + (strlen(outf1) - 4), ".wav", 4); if (stat(curw->file1name, &st) == 0) { if (exec_sox(inrate, insize, indenc, curw->file1name, outf1) == 0) { if (unlink(curw->file1name) == -1) misc_debug(0, "[%d] Cannot remove file [%s]: %s\n", mypid, curw->file1name, strerror(errno)); } else { misc_debug(0, "[%d] exec_sox failed!\n", mypid); return -1; } misc_debug(0, "[%d] .WAV file %s has been created successfully\n", mypid, outf1); outcnt++; } strncpy(outf2, curw->file2name, sizeof(outf2) - 2); memcpy(outf2 + (strlen(outf2) - 4), ".wav", 4); if (stat(curw->file2name, &st) == 0) { if (exec_sox(inrate, insize, indenc, curw->file2name, outf2) == 0) { if (unlink(curw->file2name) == -1) misc_debug(0, "[%d] Cannot remove file [%s]: %s\n", mypid, curw->file2name, strerror(errno)); } else { misc_debug(0, "[%d] exec_sox failed!\n", mypid); return -1; } misc_debug(0, "[%d] .WAV file %s has been created successfully\n", mypid, outf2); outcnt++; } if (gsoxmixflag && (outcnt > 1)) { strncpy(mixout, curw->file1name, sizeof(mixout) - 13); strcpy(mixout + (strlen(mixout) - 4), "-mixed.wav"); if (exec_soxmix(outf1, outf2, mixout) == 0) { misc_debug(0, "[%d] Mixed output files: %s\n", mypid, mixout); /* if(unlink(outf1) == -1) misc_debug(0, "[%d] Cannot remove file [%s]: %s\n", mypid, outf1, strerror(errno)); if(unlink(outf2) == -1) misc_debug(0, "[%d] Cannot remove file [%s]: %s\n", mypid, outf2, strerror(errno)); */ } else { misc_debug(0, "[%d] Could not mix output files: exec_soxmix failed!\n", mypid); } } return 0; } int exec_sox(char *rt, char *sz, char *dc, char *srcfile, char *dstfile) { pid_t pid, wpid; int stat = 0; int exitcode = 0; switch(pid = fork()) { case -1: misc_debug(0, "[%d] exec_sox: cannot fork new process to create .WAV file: %s\n", mypid, strerror(errno)); return -1; break; case 0: if (execl(gsoxpath, "sox", "-r", rt, sz, dc, srcfile, dstfile, NULL) == -1) { misc_debug(0, "[%d] execl(%s -r %s %s %s %s %s) error: %s\n", mypid, gsoxpath, rt, sz, dc, srcfile, dstfile, strerror(errno)); exit(100); } exit(1); break; default: if ((wpid = waitpid(pid, &stat, 0)) == -1) { misc_debug(0, "[%d] exec_sox: waitpid: %s\n", mypid, strerror(errno)); return -1; } if ((exitcode = WEXITSTATUS(stat)) != 0) { misc_debug(0, "[%d] exec_sox: sox helper process failed, return value: %d\n", mypid, exitcode); return -1; } else if (WIFSIGNALED(stat)) { misc_debug(0, "[%d] exec_sox: sox helper process recv'd siganal %d\n", mypid, pid, WTERMSIG(stat)); return -1; } break; } return 0; } int exec_soxmix(char *f1, char *f2, char *dstfile) { pid_t pid, wpid; int stat = 0; int exitcode = 0; switch(pid = fork()) { case -1: misc_debug(0, "[%d] exec_soxmix: cannot fork new process to create mixed .WAV file: %s\n", mypid, strerror(errno)); return -1; break; case 0: if (execl(gsoxmixpath, "soxmix", f1, f2, dstfile, NULL) == -1) { misc_debug(0, "[%d] execl(%s %s %s %s) error: %s\n", mypid, gsoxmixpath, f1, f2, dstfile, strerror(errno)); exit(100); } exit(1); break; default: if ((wpid = waitpid(pid, &stat, 0)) == -1) { misc_debug(0, "[%d] exec_soxmix: waitpid: %s\n", mypid, strerror(errno)); return -1; } if ((exitcode = WEXITSTATUS(stat)) != 0) { misc_debug(0, "[%d] exec_soxmix: soxmix helper process failed, return value: %d\n", mypid, exitcode); return -1; } else if (WIFSIGNALED(stat)) { misc_debug(0, "[%d] exec_soxmix: soxmix helper process recv'd siganal %d\n", mypid, pid, WTERMSIG(stat)); return -1; } break; } return 0; } void init_workers() { int i = 0; for (i = 0; i < MAXNODE; i++) workers[i] = NULL; } int worker_create(worker **w, int pllen, u_char *packet) { int h = 0; switch(((*w)->pid = fork())) { case -1: misc_debug(0, "worker_create: fork: %s\n", strerror(errno)); break; case 0: worker_main(*w, pllen, packet); exit(0); default: time(&(*w)->rtp->stime); h = tuplehash((*w)->rtp->ip1, (*w)->rtp->ip2, (*w)->rtp->port1, (*w)->rtp->port2); (*w)->next = workers[h]; workers[h] = (*w); break; } return (*w)->pid; } worker * getworkerbypid(pid_t pid) { worker *w; int i; for (i = 0; i < MAXNODE; i++) for (w = workers[i]; w != NULL; w = w->next) if (w->pid == pid) return w; return NULL; } int worker_isexist(u_int32_t ip1, u_int32_t ip2, u_int16_t port1, u_int16_t port2) { worker *w; int h; h = tuplehash(ip1, ip2, port1, port2); for (w = workers[h]; w != NULL; w = w->next) if ((ip1 == w->rtp->ip1 || ip1 == w->rtp->ip2) && (ip2 == w->rtp->ip1 || ip2 == w->rtp->ip2) && (port1 == w->rtp->port1 || port1 == w->rtp->port2) && (port2 == w->rtp->port1 || port2 == w->rtp->port2)) return 1; return 0; } void worker_remove(worker *in) { worker *w; worker *prev = NULL; int h = 0; /* Remove related RTCP session */ removertcp(in->rtcp); h = tuplehash(in->rtp->ip1, in->rtp->ip2, in->rtp->port1, in->rtp->port2); for (w = workers[h]; w != NULL; w = w->next) { if (in->pid == w->pid) { if (prev == NULL) workers[h] = w->next; else prev->next = w->next; } prev = w; } } void kill_workers() { worker *w = NULL; int i = 0; for (i = 0; i < MAXNODE; i++) for (w = workers[i]; w != NULL; w = w->next) if (w->pid > 1) { misc_debug(0, "Terminating session [%d]\n", w->pid); kill(w->pid, SIGTERM); } } void worker_main(worker *w, int pllen, u_char *payload) { char filterstr[512]; char errbuf[ERRBUFSIZ]; char strpt[32]; char tfmt[64]; char strip1[32]; char strip2[32]; int pd = -1; pcap_t *pcapptr; fd_set set; time_t tnow, tidle; struct timeval tv; struct sigaction sa, sa1; struct sigaction sa_old, sa_old1; sa.sa_handler = sigworkerhandler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_SIGINFO; sigaction(SIGHUP, &sa, &sa_old); sigaction(SIGUSR2, &sa, &sa_old); sigaction(SIGTERM, &sa, &sa_old); sigaction(SIGINT, &sa, &sa_old); sigaction(SIGSTOP, &sa, &sa_old); sigaction(SIGQUIT, &sa, &sa_old); sigaction(SIGALRM, &sa, &sa_old); sa.sa_handler = SIG_IGN; sigemptyset(&sa1.sa_mask); sigaction(SIGCHLD, &sa1, &sa_old1); curw = w; mypid = getpid(); misc_strftime(tfmt, sizeof(tfmt) - 2, "%Y%m%d"); snprintf(strip1, sizeof(strip1) - 2, misc_inet_ntoa(w->rtp->ip1)); snprintf(strip2, sizeof(strip2) - 2, misc_inet_ntoa(w->rtp->ip2)); if (create_outpath() == -1) { misc_debug(0, "[%d] create_path failed!\n", mypid); exit(1); } if (w->rtp->enc > MAXCODECS) { misc_debug(0, "Encoding type %d is higher than MAXCODECS value [%d], giving up...\n", w->rtp->enc, MAXCODECS); exit(0); } if (w->rtp->enc > 27) snprintf(strpt, sizeof(strpt) - 2, "%d-unknown", w->rtp->enc); else snprintf(strpt, sizeof(strpt), "%s", rtp_pt_desc[w->rtp->enc]); snprintf(w->file1name, MAXLBUFSIZ - 2, "%s/%s/session-enc%s-%s,%d-%s,%d.raw", goutdir, tfmt, strpt, strip1, w->rtp->port1, strip2, w->rtp->port2); if ((w->ip1fd = creat(w->file1name, S_IRWXU)) == -1) { misc_debug(0, "[%d] Cannot create raw voice output file %s: %s\n", mypid, w->file1name, strerror(errno)); exit(1); } snprintf(w->file2name, MAXLBUFSIZ - 2, "%s/%s/session-enc%s-%s,%d-%s,%d.raw", goutdir, tfmt, strpt, strip2, w->rtp->port2, strip1, w->rtp->port1); if ((w->ip2fd = creat(w->file2name, S_IRWXU)) == -1) { misc_debug(0, "[%d] Cannot create raw voice output file %s: %s\n", mypid, w->file2name, strerror(errno)); exit(1); } switch(w->rtp->enc) { case PT_ULAW: w->rtp->rate = 8000; break; default: w->rtp->rate = 0; break; } misc_debug(0, "[%d] VoIP call has been detected.\n", mypid); misc_debug(0, "[%d] %s:%d <--> %s:%d\n", mypid, strip1, w->rtp->port1, strip2, w->rtp->port2); misc_debug(0, "[%d] Encoding %s, recording.......\n", mypid, strpt); /* Now, let's talk real bussiness ! */ /* First RTP packet, inherited from parent process */ vocoders[curw->rtp->enc].f (w->ip1fd, payload, pllen); /* Reopen pcap interface for a fixed port tuple ! */ snprintf(filterstr, sizeof(filterstr) - 2, "(host %s and port %d) and (host %s and port %d) and udp", strip1, w->rtp->port1, strip2, w->rtp->port2); if ((pd = initpcap(0, filterstr, errbuf)) == -1) { misc_debug(0, "[%d] initpcap: %s\n", mypid, errbuf); exit(1); } pcapptr = getpcapt(); tv.tv_sec = grtp_idle_time; tv.tv_usec = 0; time(&tnow); tidle = tnow; for ( ; ; ) { FD_ZERO(&set); FD_SET(pd, &set); switch(select(pd + 1, &set, NULL, NULL, &tv)) { case -1: misc_debug(0, "[%d] select: error: %s\n", mypid, strerror(errno)); exit(1); break; case 0: time(&tnow); if ((tnow - tidle) > grtp_idle_time) { misc_debug(0, "[%d] maximum idle time [%d secs] has been elapsed for this call, the call might have been ended.\n", mypid, grtp_idle_time); w->etime = time(&w->etime) - grtp_idle_time; if (create_wave() == 0) { unlink(curw->file1name); unlink(curw->file2name); } worker_graceful_exit(0); } break; default: time(&tidle); peekpcap(-1, dumprtppayload); break; } } exit(0); } void dumprtppayload(u_char *udata, const struct pcap_pkthdr *pkthdr, const u_char *packet) { u_char *pl; int plen = 0; struct ip *ip; int fd = -1; int off = 0; int iphlen = 0; unsigned short padlen = 0; struct rtphdr *rtp; unsigned short cur_seq = 0; static unsigned short last_seq1 = 0, last_seq2 = 0; off = getdllen(); ip = (struct ip *)(packet + off); iphlen = ip->ip_hl << 2; rtp = (struct rtphdr *)(packet + off + iphlen + UDP_L); pl = (u_char *)(packet + off + iphlen + UDP_L + RTP_L); plen = pkthdr->len - (getdllen() + iphlen + UDP_L + RTP_L); if (rtp->pad) { /* If padding is enabled, calc real payload length */ padlen = (unsigned short)(pl[plen]); plen -= padlen; } /* printf("\n[-------- START RTP HEADER ----------]\n"); printf("Version : %d\n", rtp->ver); printf("Padding : %d\n", rtp->pad); printf("Extension : %d\n", rtp->ext); printf("CSRC count: %d\n", rtp->cc); printf("Mark : %d\n", rtp->mark); printf("Payload : %d\n", rtp->pt); printf("SeqNo : %d\n", rtp->seq); printf("Timestamp : %d\n", rtp->timestamp); printf("SSRC : %d\n", rtp->ssrc); printf("CSRC : %d\n", rtp->csrc); printf("[---------- END RTP HEADER ----------]\n"); */ /* Avoid duplicate packets */ cur_seq = ntohs(rtp->seq); if (cur_seq == last_seq1 || cur_seq == last_seq2) return; if (ip->ip_src.s_addr == curw->rtp->ip1) { fd = curw->ip1fd; last_seq1 = cur_seq; } else { fd = curw->ip2fd; last_seq2 = cur_seq; } vocoders[curw->rtp->enc].f (fd, pl, plen); } int create_outpath() { char tfmt[64]; char filefmt[512]; struct stat st; if (stat(goutdir, &st) == -1) if (errno == ENOENT) { if (mkdir(goutdir, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0) { misc_debug(0, "cannot create outputdir %s: %s\n", goutdir, strerror(errno)); return -1; } } misc_strftime(tfmt, sizeof(tfmt) - 1, "%Y%m%d"); snprintf(filefmt, sizeof(filefmt) - 2, "%s/%s", goutdir, tfmt); if (stat(filefmt, &st) == -1) if (errno == ENOENT) { if (mkdir(filefmt, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0) { misc_debug(0, "cannot create daily outputdir %s: %s\n", filefmt, strerror(errno)); return -1; } } return 0; } void worker_graceful_exit(int code) { if (code == 0) add2cdr(curw); exit(code); }