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.
rtpengine/utils/kernel-intercept-dump.pl

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);
}