mirror of https://github.com/sipwise/rtpengine.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
261 lines
5.5 KiB
261 lines
5.5 KiB
#!/usr/bin/perl
|
|
|
|
use strict;
|
|
use warnings;
|
|
use Linux::Inotify2;
|
|
use AnyEvent::Loop;
|
|
use AnyEvent;
|
|
use Fcntl;
|
|
use Errno qw(EINTR EIO EAGAIN EWOULDBLOCK :POSIX);
|
|
use Net::Pcap;
|
|
use Time::HiRes;
|
|
|
|
my $COMBINE = 1;
|
|
# possible values:
|
|
# 0: don't combine any streams. each stream gets written to its own pcap file
|
|
# 1: combine all streams of one call into one pcap file
|
|
|
|
my $i = new Linux::Inotify2 or die;
|
|
$i->blocking(0);
|
|
|
|
$i->watch('/var/spool/rtpengine', IN_CLOSE_WRITE | IN_DELETE, \&handle_inotify) or die;
|
|
my $i_w = AnyEvent->io(fh => $i->fileno, poll => 'r', cb => sub { $i->poll });
|
|
|
|
setup();
|
|
|
|
AnyEvent::Loop::run();
|
|
|
|
exit;
|
|
|
|
my %metafiles;
|
|
my %callbacks;
|
|
|
|
sub handle_inotify {
|
|
my ($e) = @_;
|
|
my $fn = $e->{w}->{name} . '/' . $e->{name};
|
|
my $mf = ($metafiles{$fn} //= { name => $fn });
|
|
if ($e->IN_DELETE) {
|
|
handle_delete($e, $fn, $mf);
|
|
}
|
|
elsif ($e->IN_CLOSE_WRITE) {
|
|
handle_change($e, $fn, $mf);
|
|
}
|
|
else {
|
|
print("unhandled inotify event on $fn\n");
|
|
}
|
|
}
|
|
|
|
sub handle_change {
|
|
my ($e, $fn, $mf) = @_;
|
|
|
|
print("handling change on $fn\n");
|
|
|
|
my $fd;
|
|
open($fd, '<', $fn) or return;
|
|
|
|
# resume from where we left of
|
|
my $pos = $mf->{pos} // 0;
|
|
seek($fd, $pos, 0);
|
|
|
|
# read as much as we can
|
|
my $buf;
|
|
read($fd, $buf, 100000) or return;
|
|
$mf->{pos} = tell($fd);
|
|
close($fd);
|
|
|
|
# read contents section by section
|
|
while ($buf =~ s/^(.*?)\n//s) {
|
|
my $key = $1;
|
|
$buf =~ s/^(\d+):\n//s or die $buf;
|
|
my $len = $1;
|
|
my $val = substr($buf, 0, $len, '');
|
|
$buf =~ s/^\n\n//s or die;
|
|
|
|
if ($key =~ /^(CALL-ID|PARENT)$/) {
|
|
$mf->{$key} = $val;
|
|
}
|
|
elsif ($key =~ /^STREAM (\d+) interface$/) {
|
|
open_stream($mf, $val, $1);
|
|
}
|
|
elsif ($key =~ /^STREAM (\d+) details$/) {
|
|
stream_details($mf, $val, $1);
|
|
}
|
|
}
|
|
|
|
cb('call_setup', $mf);
|
|
}
|
|
|
|
sub handle_delete {
|
|
my ($e, $fn, $mf) = @_;
|
|
|
|
print("handling delete on $fn\n");
|
|
|
|
cb('call_close', $mf);
|
|
|
|
for my $sn (keys(%{$mf->{streams}})) {
|
|
my $ref = $mf->{streams}->{$sn};
|
|
close_stream($ref);
|
|
}
|
|
|
|
delete($mf->{streams});
|
|
delete($mf->{streams_id});
|
|
delete($metafiles{$fn});
|
|
}
|
|
|
|
|
|
sub get_stream_by_id {
|
|
my ($mf, $id) = @_;
|
|
my $ref = ($mf->{streams_id}->[$id] //= { metafile => $mf, id => $id });
|
|
return $ref;
|
|
}
|
|
|
|
sub open_stream {
|
|
my ($mf, $stream, $id) = @_;
|
|
print("opening $stream for $mf->{'CALL-ID'}\n");
|
|
my $fd;
|
|
sysopen($fd, '/proc/rtpengine/0/calls/' . $mf->{PARENT} . '/' . $stream, O_RDONLY | O_NONBLOCK) or return;
|
|
my $ref = get_stream_by_id($mf, $id);
|
|
$ref->{name} = $stream;
|
|
$ref->{fh} = $fd;
|
|
$ref->{watcher} = AnyEvent->io(fh => $fd, poll => 'r', cb => sub { stream_read($mf, $ref) });
|
|
cb('stream_setup', $ref, $mf);
|
|
$mf->{streams}->{$stream} = $ref;
|
|
$mf->{streams_id}->[$id] = $ref;
|
|
print("opened for reading $stream for $mf->{'CALL-ID'}\n");
|
|
}
|
|
|
|
sub stream_details {
|
|
my ($mf, $val, $id) = @_;
|
|
my $ref = get_stream_by_id($mf, $id);
|
|
my @details = $val =~ /(\w+) (\d+)/g;
|
|
while (@details) {
|
|
my $k = shift(@details);
|
|
my $v = shift(@details);
|
|
$ref->{$k} = $v;
|
|
}
|
|
}
|
|
|
|
sub close_stream {
|
|
my ($ref) = @_;
|
|
# this needs to be done explicitly, otherwise the closure would keep
|
|
# the object from being freed
|
|
delete($ref->{watcher});
|
|
my $mf = $ref->{metafile};
|
|
delete($mf->{streams}->{$ref->{name}});
|
|
cb('stream_close', $ref);
|
|
print("closed $ref->{name}\n");
|
|
}
|
|
|
|
sub stream_read {
|
|
my ($mf, $ref) = @_;
|
|
#print("handling read event for $mf->{name} / $ref->{name}\n");
|
|
while (1) {
|
|
my $buf;
|
|
my $ret = sysread($ref->{fh}, $buf, 65535);
|
|
if (!defined($ret)) {
|
|
if ($!{EAGAIN} || $!{EWOULDBLOCK}) {
|
|
return;
|
|
}
|
|
print("read error on $ref->{name} for $mf->{'CALL-ID'}: $!\n");
|
|
# fall through
|
|
}
|
|
elsif ($ret == 0) {
|
|
print("eof on $ref->{name} for $mf->{'CALL-ID'}\n");
|
|
# fall through
|
|
}
|
|
else {
|
|
# $ret > 0
|
|
#print("$ret bytes read from $ref->{name} for $mf->{'CALL-ID'}\n");
|
|
cb('packet', $ref, $mf, $buf, $ret);
|
|
next;
|
|
}
|
|
|
|
# some kind of error
|
|
close_stream($ref);
|
|
return;
|
|
}
|
|
}
|
|
|
|
sub tvsec_now {
|
|
my ($h) = @_;
|
|
my @now = Time::HiRes::gettimeofday();
|
|
$h->{tv_sec} = $now[0];
|
|
$h->{tv_usec} = $now[1];
|
|
}
|
|
|
|
sub setup {
|
|
if ($COMBINE == 0) {
|
|
$callbacks{stream_setup} = \&stream_pcap;
|
|
$callbacks{stream_close} = \&stream_pcap_close;
|
|
$callbacks{packet} = \&stream_packet;
|
|
}
|
|
elsif ($COMBINE == 1) {
|
|
$callbacks{call_setup} = \&call_pcap;
|
|
$callbacks{call_close} = \&call_pcap_close;
|
|
$callbacks{packet} = \&call_packet;
|
|
}
|
|
}
|
|
sub cb {
|
|
my ($name, @args) = @_;
|
|
my $fn = $callbacks{$name};
|
|
$fn or return;
|
|
return $fn->(@args);
|
|
}
|
|
|
|
|
|
sub dump_open {
|
|
my ($hash, $name) = @_;
|
|
$hash->{pcap} = pcap_open_dead(DLT_RAW, 65535);
|
|
$hash->{dumper} = pcap_dump_open($hash->{pcap}, $name);
|
|
}
|
|
sub dump_close {
|
|
my ($hash) = @_;
|
|
pcap_dump_close($hash->{dumper});
|
|
pcap_close($hash->{pcap});
|
|
delete($hash->{dumper});
|
|
delete($hash->{pcap});
|
|
}
|
|
sub dump_packet {
|
|
my ($hash, $buf, $len) = @_;
|
|
if (!$hash->{dumper}) {
|
|
print("discarding packet (dumper not open) - $hash->{name}\n");
|
|
return;
|
|
}
|
|
my $hdr = { len => $len, caplen => $len };
|
|
tvsec_now($hdr);
|
|
pcap_dump($hash->{dumper}, $hdr, $buf);
|
|
}
|
|
|
|
# COMBINE 0 functions
|
|
sub stream_pcap {
|
|
my ($ref, $mf) = @_;
|
|
dump_open($ref, $mf->{PARENT} . '-' . $ref->{name} . '.pcap');
|
|
}
|
|
sub stream_pcap_close {
|
|
my ($ref) = @_;
|
|
dump_close($ref);
|
|
}
|
|
sub stream_packet {
|
|
my ($ref, $mf, $buf, $ret) = @_;
|
|
dump_packet($ref, $buf, $ret);
|
|
}
|
|
|
|
# COMBINE 1 functions
|
|
sub call_pcap {
|
|
my ($mf) = @_;
|
|
|
|
$mf->{pcap} and return;
|
|
$mf->{PARENT} or return;
|
|
|
|
print("opening pcap for $mf->{PARENT}\n");
|
|
dump_open($mf, $mf->{PARENT} . '.pcap');
|
|
}
|
|
sub call_pcap_close {
|
|
my ($mf) = @_;
|
|
dump_close($mf);
|
|
}
|
|
sub call_packet {
|
|
my ($ref, $mf, $buf, $ret) = @_;
|
|
dump_packet($mf, $buf, $ret);
|
|
}
|