diff --git a/modules/sipcapture/examples/kamailio.cfg b/modules/sipcapture/examples/kamailio.cfg index 18c026988c0..56fd2888cea 100644 --- a/modules/sipcapture/examples/kamailio.cfg +++ b/modules/sipcapture/examples/kamailio.cfg @@ -81,7 +81,7 @@ route { $sht(a=>method::all) = $sht(a=>method::all) + 1; if($sht(b=>$rm::$cs::$ci) != $null) { - $var(a) = "sip_capture"; + $var(a) = "sip_capture_call" + "_%Y%m%d"; sip_capture("$var(a)"); drop; } @@ -191,11 +191,28 @@ route { } - $var(a) = "sip_capture"; - # Kamailio 4.1 only - #sip_capture("$var(a)"); - - sip_capture(); + #Sharding + if(is_method("REGISTER")) { + $var(table) = "sip_capture_registration"; + } + else if(is_method("INVITE|BYE|CANCEL|UPDATE|ACK|PRACK|REFER")) + { + $var(table) = "sip_capture_call"; + } + else if(is_method("INFO")) + { + $var(table) = "sip_capture_call"; + } + else if(is_method("OPTIONS|PUBLISH")) + { + $var(table) = "sip_capture_rest"; + } + else { + $var(table) = "sip_capture_rest"; + } + + $var(a) = $var(table) + "_%Y%m%d"; + sip_capture("$var(a)"); drop; } @@ -306,7 +323,29 @@ onreply_route { } } - sip_capture(); + #sharding + if($rm == "REGISTER") { + $var(table) = "sip_capture_registration"; + } + else if($rm =~ "(INVITE|UPDATE|BYE|ACK|PRACK|REFER)$") + { + $var(table) = "sip_capture_call"; + } + else if($rm =~ "(INFO)$") + { + $var(table) = "sip_capture_call"; + } + else if($rm =~ "(OPTIONS|PUBLISH)$" ) + { + $var(table) = "sip_capture_rest"; + } + else { + $var(table) = "sip_capture_rest"; + } + + $var(a) = $var(table) + "_%Y%m%d"; + + sip_capture("$var(a)"); drop; } diff --git a/modules/sipcapture/examples/partrotate_unixtimestamp.pl b/modules/sipcapture/examples/partrotate_unixtimestamp.pl deleted file mode 100644 index cda5518d5d4..00000000000 --- a/modules/sipcapture/examples/partrotate_unixtimestamp.pl +++ /dev/null @@ -1,161 +0,0 @@ -#!/usr/bin/perl -# -# partrotate_unixtimestamp - perl script for mySQL partition rotation -# -# Copyright (C) 2011-2014 Alexandr Dubovikov (alexandr.dubovikov@gmail.com) -# -# This file is part of webhomer, a free capture server. -# -# partrotate_unixtimestamp 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 3 of the License, or -# (at your option) any later version -# -# partrotate_unixtimestamp 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 - -use DBI; - -$version = "0.3.1k"; -$mysql_table = "sip_capture"; -$mysql_dbname = "homer_db"; -$mysql_user = "mysql_login"; -$mysql_password = "mysql_password"; -$mysql_host = "localhost"; -$maxparts = 6; #6 days How long keep the data in the DB -$newparts = 2; #new partitions for 2 days. Anyway, start this script daily! -@stepsvalues = (86400, 3600, 1800, 900); -$partstep = 0; # 0 - Day, 1 - Hour, 2 - 30 Minutes, 3 - 15 Minutes -$engine = "InnoDB"; #MyISAM or InnoDB -$compress = "ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8"; #Enable this if you want use barracuda format or set var to empty. -$sql_schema_version = 2; -$auth_column = "auth"; -$check_table = 1; #Check if table exists. For PostgreSQL change creation schema! - -#Check it -$partstep=0 if(!defined $stepsvalues[$partstep]); -#Mystep -$mystep = $stepsvalues[$partstep]; -#Coof - -# Optionally load override configuration. perl format -$rc = "/etc/sysconfig/partrotaterc"; -if (-e $rc) { - do $rc; -} - -$coof=int(86400/$mystep); - -#How much partitions -$maxparts*=$coof; -$newparts*=$coof; -$totalparts = ($maxparts+$newparts); - -my $db = DBI->connect("DBI:mysql:$mysql_dbname:$mysql_host:3306", $mysql_user, $mysql_password); - -$auth_column = "authorization" if($sql_schema_version == 1); - -#$db->{PrintError} = 0; - -#check if the table has partitions. If not, create one -my $query = "SHOW TABLE STATUS FROM ".$mysql_dbname. " WHERE Name='".$mysql_table."'"; -$sth = $db->prepare($query); -$sth->execute(); -my $tstatus = $sth->fetchrow_hashref()->{Create_options}; -if ($tstatus !~ /partitioned/) { - my $query = "ALTER TABLE ".$mysql_table. " PARTITION BY RANGE ( UNIX_TIMESTAMP(`date`)) (PARTITION pmax VALUES LESS THAN MAXVALUE)"; - $sth = $db->prepare($query); - $sth->execute(); -} - -my $query = "SELECT UNIX_TIMESTAMP(CURDATE() - INTERVAL 1 DAY)"; -$sth = $db->prepare($query); -$sth->execute(); -my ($curtstamp) = $sth->fetchrow_array(); -$curtstamp+=0; -$todaytstamp+=0; - - - -my %PARTS; -#Geting all partitions -$query = "SELECT PARTITION_NAME, PARTITION_DESCRIPTION" - ."\n FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME='".$mysql_table."'" - ."\n AND TABLE_SCHEMA='".$mysql_dbname."' ORDER BY PARTITION_DESCRIPTION ASC;"; -$sth = $db->prepare($query); -$sth->execute(); -my @oldparts; -my @partsremove; -while(my @ref = $sth->fetchrow_array()) -{ - - my $minpart = $ref[0]; - my $todaytstamp = $ref[1]; - - next if($minpart eq "pmax"); - - if($curtstamp <= $todaytstamp) { - $PARTS{$minpart."_".$todaytstamp} = 1; - } - else { push(@oldparts, \@ref); } - -} - -my $partcount = $#oldparts; -if($partcount > $maxparts) -{ - foreach my $ref (@oldparts) { - - $minpart = $ref->[0]; - $todaytstamp = $ref->[1]; - - push(@partsremove,$minpart); - - $partcount--; - last if($partcount <= $maxparts); - } -} - - -if($#partsremove > 0) -{ - - $query = "ALTER TABLE ".$mysql_table." DROP PARTITION ".join(',', @partsremove); - $db->do($query); - if (!$db->{Executed}) { - print "Couldn't drop partition: $minpart\n"; - break; - } -} - -# < condition -$curtstamp+=(86400); - -#Create new partitions -for(my $i=0; $i<$newparts; $i++) { - - $oldstamp = $curtstamp; - $curtstamp+=$mystep; - - ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($oldstamp); - - my $newpartname = sprintf("p%04d%02d%02d%02d",($year+=1900),(++$mon),$mday,$hour); - $newpartname.= sprintf("%02d", $min) if($partstep > 1); - - if(!defined $PARTS{$newpartname."_".$curtstamp}) { - - # Fix MAXVALUE. Thanks Dorn B. for report and fix. - $query = "ALTER TABLE ".$mysql_table." REORGANIZE PARTITION pmax INTO (PARTITION ".$newpartname - ."\n VALUES LESS THAN (".$curtstamp.") ENGINE = ".$engine.", PARTITION pmax VALUES LESS THAN MAXVALUE ENGINE = ".$engine.")"; - $db->do($query); - if (!$db->{Executed}) { - print "Couldn't add partition: $newpartname\n"; - } - } -} diff --git a/modules/sipcapture/hep.c b/modules/sipcapture/hep.c index a14fcd9f7dd..e729e3410d1 100644 --- a/modules/sipcapture/hep.c +++ b/modules/sipcapture/hep.c @@ -37,7 +37,7 @@ #include "sipcapture.h" -static int show_error = 0; + static int count = 0; struct hep_timehdr* heptime; @@ -58,10 +58,7 @@ int hep_msg_received(void *data) struct receive_info *ri; if(!hep_capture_on) { - if(show_error == 0) { - LOG(L_ERR, "sipcapture:hep_msg_received HEP is not enabled\n"); - show_error = 1; - } + LOG(L_ERR, "sipcapture:hep_msg_received HEP is not enabled\n"); return -1; } @@ -487,9 +484,11 @@ int parsing_hepv3_message(char *buf, unsigned int len) { if(payload != NULL ) { - /* and now recieve message */ + /* and now recieve message */ if (hg->proto_t->data == 5) receive_logging_json_msg(payload, payload_len, hg, "rtcp_capture"); - else if (hg->proto_t->data == 100) receive_logging_json_msg(payload, payload_len, hg, "logs_capture"); + else if (hg->proto_t->data == 32) receive_logging_json_msg(payload, payload_len, hg, "report_capture"); + else if (hg->proto_t->data == 99) receive_logging_json_msg(payload, payload_len, hg, "report_capture"); + else if (hg->proto_t->data == 100) receive_logging_json_msg(payload, payload_len, hg, "logs_capture"); else receive_msg(payload, payload_len, &ri); } diff --git a/modules/sipcapture/sipcapture.c b/modules/sipcapture/sipcapture.c index 3d434555443..75db72d0539 100644 --- a/modules/sipcapture/sipcapture.c +++ b/modules/sipcapture/sipcapture.c @@ -3,7 +3,7 @@ * * sipcapture module - helper module to capture sip messages * - * Copyright (C) 2011-2014 Alexandr Dubovikov (QSC AG) (alexandr.dubovikov@gmail.com) + * Copyright (C) 2011-2015 Alexandr Dubovikov (alexandr.dubovikov@gmail.com) * * This file is part of Kamailio, a free SIP server. * @@ -131,9 +131,15 @@ static int sipcapture_init_rpc(void); static int child_init(int rank); static void destroy(void); static int sipcapture_fixup(void** param, int param_no); -static int sip_capture(struct sip_msg *msg, str *dtable, _capture_mode_data_t *cm_data); +static int reportcapture_fixup(void** param, int param_no); +static int float2int_fixup(void** param, int param_no); +static int sip_capture(struct sip_msg *msg, str *dtable, _capture_mode_data_t *cm_data); +static int report_capture(struct sip_msg *msg, str *_table, str* _corr, str *_data); static int w_sip_capture(struct sip_msg* _m, char* _table, _capture_mode_data_t * _cm_data, char* s2); +static int w_report_capture(struct sip_msg* _m, char* _table, char* _corr, char* _data); +static int w_float2int(struct sip_msg* _m, char* _val, char* _coof, char* s2); + int init_rawsock_children(void); int extract_host_port(void); int raw_capture_socket(struct ip_addr* ip, str* iface, int port_start, int port_end, int proto); @@ -146,6 +152,7 @@ static struct mi_root* sip_capture_mi(struct mi_root* cmd, void* param ); static str db_url = str_init(DEFAULT_DB_URL); static str table_name = str_init("sip_capture"); static str hash_source = str_init("call_id"); +static str table_time_sufix = str_init("%Y%m%D"); static str mt_mode = str_init("rand"); static str date_column = str_init("date"); static str micro_ts_column = str_init("micro_ts"); @@ -243,8 +250,12 @@ struct hep_timehdr* heptime; */ static cmd_export_t cmds[] = { {"sip_capture", (cmd_function)w_sip_capture, 0, 0, 0, ANY_ROUTE}, - {"sip_capture", (cmd_function)w_sip_capture, 1, sipcapture_fixup, 0, ANY_ROUTE }, + {"sip_capture", (cmd_function)w_sip_capture, 1, sipcapture_fixup, 0, ANY_ROUTE }, {"sip_capture", (cmd_function)w_sip_capture, 2, sipcapture_fixup, 0, ANY_ROUTE }, + {"report_capture", (cmd_function)w_report_capture, 1, reportcapture_fixup, 0, ANY_ROUTE }, + {"report_capture", (cmd_function)w_report_capture, 2, reportcapture_fixup, 0, ANY_ROUTE }, + {"report_capture", (cmd_function)w_report_capture, 3, reportcapture_fixup, 0, ANY_ROUTE }, + {"float2int", (cmd_function)w_float2int, 2, float2int_fixup, 0, ANY_ROUTE }, {0, 0, 0, 0, 0, 0} }; @@ -310,9 +321,10 @@ static param_export_t params[] = { {"raw_moni_bpf_on", INT_PARAM, &bpf_on }, {"callid_aleg_header", PARAM_STR, &callid_aleg_header}, {"capture_mode", PARAM_STRING|USE_FUNC_PARAM, (void *)capture_mode_param}, - {"insert_retries", INT_PARAM, &insert_retries }, - {"insert_retry_timeout",INT_PARAM, &insert_retry_timeout }, - {0, 0, 0} + {"insert_retries", INT_PARAM, &insert_retries }, + {"insert_retry_timeout", INT_PARAM, &insert_retry_timeout }, + {"table_time_sufix", PARAM_STR, &table_time_sufix }, + {0, 0, 0} }; @@ -908,6 +920,62 @@ static int sipcapture_fixup(void** param, int param_no) return 0; } + +static int reportcapture_fixup(void** param, int param_no) +{ + + if (param_no == 1 ) { + return fixup_var_pve_str_12(param, 1); + } + if (param_no == 2 ){ + return fixup_var_pve_str_12(param, 1); + } + if (param_no == 3 ){ + return fixup_var_pve_str_12(param, 1); + } + + return 0; +} + +static int float2int_fixup(void** param, int param_no) +{ + + if (param_no == 1 ) { + return fixup_var_pve_str_12(param, 1); + } + if (param_no == 2 ){ + return fixup_var_pve_str_12(param, 1); + } + + return 0; +} + + + +static int w_float2int(struct sip_msg* _m, char* _val, char* _coof, char* s2) +{ + str value = {0}; + str coof = {0}; + int ret = 0; + + if(_val!=NULL && (get_str_fparam(&value, _m, (fparam_t*)_val) < 0)) + { + LM_ERR("invalid table parameter [%s] [%s]\n", _val, value.s); + return -1; + } + + if(_coof!=NULL && (get_str_fparam(&coof, _m, (fparam_t*)_coof) < 0)) + { + LM_ERR("invalid data parameter [%s] [%s]\n", _coof, coof.s); + return -1; + } + + ret = (int) (atof (value.s) * atoi(coof.s)); + + return ret ? ret : -1; +} + + static int w_sip_capture(struct sip_msg* _m, char* _table, _capture_mode_data_t * cm_data, char* s2) { @@ -922,6 +990,40 @@ static int w_sip_capture(struct sip_msg* _m, char* _table, _capture_mode_data_t return sip_capture(_m, (table.len>0)?&table:NULL, cm_data ); } +static int w_report_capture(struct sip_msg* _m, char* _table, char* _corr, char* _data) +{ + str table = {0}; + str corr = {0}; + str data = {0}; + + if(_table!=NULL && (get_str_fparam(&table, _m, (fparam_t*)_table) < 0)) + { + LM_ERR("invalid table parameter [%s] [%s]\n", _table, table.s); + return -1; + } + + if(_corr!=NULL && (get_str_fparam(&corr, _m, (fparam_t*)_corr) < 0)) + { + LM_ERR("invalid corr parameter [%s] [%s]\n", _corr, corr.s); + return -1; + } + + if(_data!=NULL && (get_str_fparam(&data, _m, (fparam_t*)_data) < 0)) + { + + + LM_ERR("invalid data parameter [%s] [%s]\n", _data, data.s); + return -1; + } + + /* workaround for data function */ + if(data.len > 0 && !strncmp(data.s, "report_capture", data.len)) data.len = 0; + + return report_capture(_m, (table.len>0)?&table:NULL, (corr.len>0)?&corr:NULL ,(data.len>0)?&data:NULL ); + +} + + int extract_host_port(void) { @@ -1093,13 +1195,13 @@ static int sip_capture_store(struct _sipcapture_object *sco, str *dtable, _captu int counter = 0; db_insert_f insert; time_t retry_failed_time = 0; - + struct tm capt_ts; + + /* new */ str *table = NULL; _capture_mode_data_t *c = NULL; - char strftime_buf[128]; - time_t tvsec_; - struct tm capt_ts; - + char strftime_buf[128]; + time_t tvsec_; c = (cm_data)? cm_data:capture_def; if (!c){ @@ -1310,8 +1412,7 @@ static int sip_capture_store(struct _sipcapture_object *sco, str *dtable, _captu if (dtable){ table = dtable; - } - + } else if (c->no_tables > 0 ){ if ( c->mtmode == mode_hash ){ @@ -1335,19 +1436,24 @@ static int sip_capture_store(struct _sipcapture_object *sco, str *dtable, _captu } table = &c->table_names[ii]; } + else { + table->s = table_name.s; + table->len = table_name.len; + } - tvsec_ = (time_t) (sco->tmstamp/1000000); - if(gmtime_r( &tvsec_, &capt_ts) == NULL) + + tvsec_ = (time_t) (sco->tmstamp/1000000); + if(gmtime_r( &tvsec_, &capt_ts) == NULL) { - LM_ERR("unable to set gmtime for sipcapture\n"); + LM_ERR("unable to set time to attributes\n"); return -1; } table->len = strftime(strftime_buf, sizeof(strftime_buf), table->s, &capt_ts); table->s = strftime_buf; - + /* check dynamic table */ - LM_DBG("insert into homer table: [%.*s]\n", table->len, table->s); + LM_DBG("insert into homer table [1]: [%.*s]\n", table->len, table->s); c->db_funcs.use_table(c->db_con, table); LM_DBG("storing info...\n"); @@ -2079,6 +2185,11 @@ int receive_logging_json_msg(char * buf, unsigned int len, struct hep_generic_re LM_ERR("no connection mode available to store data\n"); return -1; } + + if(!correlation_id || strlen(correlation_id) == 0) { + LM_ERR("no correlation id defined\n"); + return -1; + } memset(&sco, 0, sizeof(struct _sipcapture_object)); gettimeofday( &tvb, &tz ); @@ -2101,6 +2212,13 @@ int receive_logging_json_msg(char * buf, unsigned int len, struct hep_generic_re } + /* type of proto */ + if (hg->proto_t->data == 5) sco.type = 1; + else if (hg->proto_t->data == 32) sco.type = 2; + else if (hg->proto_t->data == 99) sco.type = 1; + else if (hg->proto_t->data == 100) sco.type = 1; + + /*source ip*/ sco.source_ip.s = ipstr_src; sco.source_ip.len = strlen(ipstr_src); @@ -2214,4 +2332,174 @@ int receive_logging_json_msg(char * buf, unsigned int len, struct hep_generic_re return -1; } +static int report_capture(struct sip_msg *msg, str *_table, str* _corr, str *_data) +{ + struct _sipcapture_object sco; + db_key_t db_keys[RTCP_NR_KEYS]; + db_val_t db_vals[RTCP_NR_KEYS]; + char buf_ip[IP_ADDR_MAX_STR_SIZE+12]; + struct timeval tvb; + struct timezone tz; + char tmp_node[100]; + time_t epoch_time_as_time_t; + str corrtmp, tmp; + + + _capture_mode_data_t *c = NULL; + c = capture_def; + if (!c){ + LM_ERR("no connection mode available to store data\n"); + return -1; + } + + + LM_DBG("CAPTURE DEBUG...\n"); + + gettimeofday( &tvb, &tz ); + + if(msg==NULL) { + LM_DBG("nothing to capture\n"); + return -1; + } + memset(&sco, 0, sizeof(struct _sipcapture_object)); + + if(capture_on_flag==NULL || *capture_on_flag==0) { + LM_DBG("capture off...\n"); + return -1; + } + + sco.proto = msg->rcv.proto; + + /* FAMILY TYPE */ + sco.family = msg->rcv.src_ip.af; + + /* MESSAGE TYPE */ + sco.type = msg->first_line.type; + + /* MSG */ + sco.msg.s = msg->buf; + sco.msg.len = msg->len; + //EMPTY_STR(sco.msg); + + /* IP source and destination */ + + strcpy(buf_ip, ip_addr2a(&msg->rcv.src_ip)); + sco.source_ip.s = buf_ip; + sco.source_ip.len = strlen(buf_ip); + sco.source_port = msg->rcv.src_port; + + /*source ip*/ + sco.destination_ip.s = ip_addr2a(&msg->rcv.dst_ip); + sco.destination_ip.len = strlen(sco.destination_ip.s); + sco.destination_port = msg->rcv.dst_port; + + + if(heptime && heptime->tv_sec != 0) { + sco.tmstamp = (unsigned long long)heptime->tv_sec*1000000+heptime->tv_usec; /* micro ts */ + snprintf(tmp_node, 100, "%.*s:%i", capture_node.len, capture_node.s, heptime->captid); + sco.node.s = tmp_node; + sco.node.len = strlen(tmp_node); + epoch_time_as_time_t = heptime->tv_sec;; + } + else { + sco.tmstamp = (unsigned long long)tvb.tv_sec*1000000+tvb.tv_usec; /* micro ts */ + sco.node = capture_node; + epoch_time_as_time_t = tvb.tv_sec; + } + + if(_corr && _corr->len > 0) { + corrtmp.s = _corr->s; + corrtmp.len = _corr->len; + } + else if(correlation_id) { + corrtmp.s = correlation_id; + corrtmp.len = strlen(correlation_id); + if(!strncmp(_table->s, "rtcp_capture",12)) corrtmp.len--; + } + + db_keys[0] = &date_column; + db_vals[0].type = DB1_DATETIME; + db_vals[0].nul = 0; + db_vals[0].val.time_val = epoch_time_as_time_t; + + db_keys[1] = µ_ts_column; + db_vals[1].type = DB1_BIGINT; + db_vals[1].nul = 0; + db_vals[1].val.ll_val = sco.tmstamp; + + db_keys[2] = &correlation_column; + db_vals[2].type = DB1_STR; + db_vals[2].nul = 0; + db_vals[2].val.str_val = corrtmp; + + db_keys[3] = &source_ip_column; + db_vals[3].type = DB1_STR; + db_vals[3].nul = 0; + db_vals[3].val.str_val = sco.source_ip; + + db_keys[4] = &source_port_column; + db_vals[4].type = DB1_INT; + db_vals[4].nul = 0; + db_vals[4].val.int_val = sco.source_port; + + db_keys[5] = &dest_ip_column; + db_vals[5].type = DB1_STR; + db_vals[5].nul = 0; + db_vals[5].val.str_val = sco.destination_ip; + + db_keys[6] = &dest_port_column; + db_vals[6].type = DB1_INT; + db_vals[6].nul = 0; + db_vals[6].val.int_val = sco.destination_port; + + db_keys[7] = &proto_column; + db_vals[7].type = DB1_INT; + db_vals[7].nul = 0; + db_vals[7].val.int_val = sco.proto; + + db_keys[8] = &family_column; + db_vals[8].type = DB1_INT; + db_vals[8].nul = 0; + db_vals[8].val.int_val = sco.family; + + db_keys[9] = &type_column; + db_vals[9].type = DB1_INT; + db_vals[9].nul = 0; + db_vals[9].val.int_val = sco.type; + + db_keys[10] = &node_column; + db_vals[10].type = DB1_STR; + db_vals[10].nul = 0; + db_vals[10].val.str_val = sco.node; + + db_keys[11] = &msg_column; + db_vals[11].type = DB1_BLOB; + db_vals[11].nul = 0; + + if(_data && _data->len > 0) + { + tmp.s = _data->s; + tmp.len = _data->len; + } + else { + /* MSG */ + tmp.s = msg->buf; + tmp.len = msg->len; + } + + db_vals[11].val.blob_val = tmp; + + c->db_funcs.use_table(c->db_con, _table); + + if (c->db_funcs.insert(c->db_con, db_keys, db_vals, RTCP_NR_KEYS) < 0) { + LM_ERR("failed to insert into database\n"); + goto error; + } + + return 1; + +error: + return -1; + +} diff --git a/modules/sipcapture/sipcapture.h b/modules/sipcapture/sipcapture.h index cd85c84cd92..db8fd632578 100644 --- a/modules/sipcapture/sipcapture.h +++ b/modules/sipcapture/sipcapture.h @@ -61,6 +61,7 @@ struct _sipcapture_object { str rtp_stat; int type; long long tmstamp; + long timestamp; str node; str msg; #ifdef STATISTICS