diff --git a/lib/NGCP/Panel/Controller/Subscriber.pm b/lib/NGCP/Panel/Controller/Subscriber.pm
index b90b6dc655..f95fdd8d91 100644
--- a/lib/NGCP/Panel/Controller/Subscriber.pm
+++ b/lib/NGCP/Panel/Controller/Subscriber.pm
@@ -7,6 +7,7 @@ use NGCP::Panel::Utils::Navigation;
use NGCP::Panel::Utils::Contract;
use NGCP::Panel::Utils::Subscriber;
use NGCP::Panel::Utils::Datatables;
+use NGCP::Panel::Utils::Callflow;
use NGCP::Panel::Form::Subscriber;
use NGCP::Panel::Form::SubscriberEdit;
use NGCP::Panel::Form::SubscriberCFSimple;
@@ -2356,6 +2357,32 @@ sub edit_speeddial :Chained('speeddial') :PathPart('edit') :Args(0) {
);
}
+sub callflow_base :Chained('base') :PathPart('callflow') :CaptureArgs(1) {
+ my ($self, $c, $callid) = @_;
+
+ my $decoder = URI::Encode->new;
+ $c->stash->{callid} = $decoder->decode($callid);
+}
+
+sub generate_pcap :Chained('callflow_base') :PathPart('pcap') :Args(0) {
+ my ($self, $c) = @_;
+ my $cid = $c->stash->{callid};
+
+ my $packet_rs = $c->model('DB')->resultset('packets')->search({
+ 'message.call_id' => { -in => [ $cid, $cid.'_b2b-1' ] },
+ }, {
+ join => { message_packets => 'message' },
+ });
+
+ my $packets = [ $packet_rs->all ];
+ my $pcap = NGCP::Panel::Utils::Callflow::generate_pcap($packets);
+
+ $c->response->header ('Content-Disposition' => 'attachment; filename="' . $cid . '.pcap"');
+ $c->response->content_type('application/octet-stream');
+ $c->response->body($pcap);
+
+}
+
=head1 AUTHOR
diff --git a/lib/NGCP/Panel/Utils/Callflow.pm b/lib/NGCP/Panel/Utils/Callflow.pm
new file mode 100644
index 0000000000..09c3774da6
--- /dev/null
+++ b/lib/NGCP/Panel/Utils/Callflow.pm
@@ -0,0 +1,223 @@
+package NGCP::Panel::Utils::Callflow;
+use strict;
+use warnings;
+
+use GD::Simple;
+
+sub generate_pcap {
+ my $packets = shift;
+
+ my $pcap = pack("LSSlLLL",
+ 0xa1b2c3d4, # magic number
+ 2, 4, # major/minor version number
+ 0, 0, # gmt offset and timestamp accuracy
+ 0xffff, # snap length
+ 1, # data link type (http://www.tcpdump.org/linktypes.html)
+ );
+
+ foreach my $pkg(@{$packets}) {
+ my($ts_sec, $ts_usec) = $pkg->timestamp =~ /^(\d+)\.(\d+)$/;
+ my $len = length($pkg->header) + length($pkg->payload) + length($pkg->trailer);
+
+ $pcap .= pack("LLLLa*a*a*",
+ $ts_sec, $ts_usec, # timestamp
+ $len, $len, # bytes on-wire/off-wire
+ $pkg->header,
+ $pkg->payload,
+ $pkg->trailer,
+ );
+ }
+ return $pcap;
+}
+
+sub draw_line {
+ my ($c, $from_x, $from_y, $to_x, $to_y, $width, $color) = @_;
+ $c->fgcolor($color);
+ $c->moveTo($from_x, $from_y);
+ $c->penSize($width, $width);
+ $c->lineTo($to_x, $to_y);
+}
+
+sub draw_arrow {
+ my ($c, $from_x, $from_y, $to_x, $to_y, $width, $color) = @_;
+ $c->fgcolor($color);
+ $c->moveTo($from_x, $from_y);
+ $c->penSize($width, $width);
+ $c->lineTo($to_x, $to_y);
+ my $poly = new GD::Polygon;
+ $poly->addPt($to_x, $to_y);
+ my $dir = ($to_x > $from_x) ? -1 : 1;
+ $poly->addPt($to_x + 4*$width*$dir, $to_y - 2*$width-(($width%2)?0:1));
+ $poly->addPt($to_x + 4*$width*$dir, $to_y + 2*$width);
+ my $oldbgcolor = $c->bgcolor();
+ $c->bgcolor($color);
+ $c->penSize(1,1);
+ $c->polygon($poly);
+ $c->bgcolor($oldbgcolor);
+}
+
+sub draw_text {
+ my ($c, $x, $y, $ftype, $fsize, $fcolor, $txt) = @_;
+ $c->font($ftype);
+ $c->fontsize($fsize);
+ $c->fgcolor($fcolor);
+ $c->moveTo($x, $y);
+ $c->string($txt);
+ my @b = $c->stringBounds($txt);
+ my %bounds = ('x', $x, 'y', $y, 'dx', $b[0], 'dy', $b[1]);
+ return %bounds;
+}
+
+sub process_callmap {
+ my $c = shift;
+ my $packets = shift;
+ my $r_png = shift;
+ my $r_info = shift;
+ my $i = 0;
+
+ my %int_uas = (
+ $c->config->{callflow}->{lb_int}, 'lb',
+ $c->config->{callflow}->{lb_ext}, 'lb',
+ $c->config->{callflow}->{proxy}, 'proxy',
+ $c->config->{callflow}->{sbc}, 'sbc',
+ $c->config->{callflow}->{app}, 'app',
+ );
+
+ my $canvas_margin = 100; # enough free space around diagram for text etc
+ my $canvas_elem_distance = 220; # horizontal distance between element lines
+ my $canvas_pkg_distance = 30; # vertical distance between packet arrows
+
+ my $canvas_elem_line_width = 2;
+ my $canvas_elem_line_color = 'darkgray';
+ my $canvas_elem_font = 'Courier:bold';
+ my $canvas_elem_font_size = 8;
+ my $canvas_elem_font_color = 'darkgray';
+
+ my $canvas_pkg_line_width = 2;
+ my $canvas_pkg_line_color = 'green';
+ my %canvas_pkg_line_colors = (TCP => 'blue');
+ my $canvas_pkg_font = 'Courier:bold';
+ my $canvas_pkg_font_size = 8;
+ my $canvas_pkg_font_color = 'dimgray';
+
+ my $html_padding = 5;
+
+ my %ext_uas = ();
+ my @uas = ();
+
+ ### gather all involved elements
+ foreach my $packet(@{$packets}) {
+ if(exists($int_uas{$packet->{src_ip}.':'.$packet->{src_port}})) {
+ #print "skipping internal elem ".$packet->{src_ip}.':'.$packet->{src_port}." (".$int_uas{$packet->{src_ip}.':'.$packet->{src_port}}.")\n";
+ }
+ elsif(exists($ext_uas{$packet->{src_ip}.':'.$packet->{src_port}})) {
+ #print "skipping known external elem ".$packet->{src_ip}.':'.$packet->{src_port}."\n";
+ }
+ else {
+ #print "adding new src elem ".$packet->{src_ip}.':'.$packet->{src_port}."\n";
+ $ext_uas{$packet->{src_ip}.':'.$packet->{src_port}} = 1;
+ # TODO: prefix "proto:" as well
+ push @uas, $packet->{src_ip}.':'.$packet->{src_port};
+ }
+
+ if(exists($int_uas{$packet->{dst_ip}.':'.$packet->{dst_port}})) {
+ #print "skipping internal elem ".$packet->{dst_ip}.':'.$packet->{dst_port}." (".$int_uas{$packet->{dst_ip}.':'.$packet->{dst_port}}.")\n";
+ }
+ elsif(exists($ext_uas{$packet->{dst_ip}.':'.$packet->{dst_port}})) {
+ #print "skipping known external elem ".$packet->{dst_ip}.':'.$packet->{dst_port}."\n";
+ }
+ else {
+ #print "adding new dst elem ".$packet->{dst_ip}.':'.$packet->{dst_port}."\n";
+ $ext_uas{$packet->{dst_ip}.':'.$packet->{dst_port}} = 1;
+ # TODO: prefix "proto:" as well
+ push @uas, $packet->{dst_ip}.':'.$packet->{dst_port};
+ }
+ }
+ push @uas, ('lb', 'sbc', 'proxy', 'app');
+
+ ### calculate x position of all uas
+ my %uas_pos_x = ();
+ $i = 0;
+ foreach my $ua(@uas) {
+ my $name = $ua;
+ foreach my $k(keys %int_uas) {
+ if($ua eq $int_uas{$k}) {
+ $uas_pos_x{$k} = $canvas_margin + $canvas_elem_distance*$i;
+ }
+ }
+ $uas_pos_x{$ua} = $canvas_margin + $canvas_elem_distance*$i;
+ ++$i;
+ }
+
+ ### calculate canvas size
+ # TODO: take into account length of "proto:[ipv6]:port"
+ my $canvas_width = 2*$canvas_margin + $canvas_elem_distance*(@uas - 1);
+ my $canvas_height = 2*$canvas_margin + $canvas_pkg_distance*(@{$packets} + 1); # leave one pkg_distance free at begin and end
+ my $canvas = GD::Simple->new($canvas_width, $canvas_height);
+ $canvas->bgcolor('white');
+
+ ### prepare html
+ $r_info->{width} = $canvas_width;
+ $r_info->{height} = $canvas_height;
+ $r_info->{areas} = ();
+
+ ### draw vertical lines
+ my $offset = $canvas_margin;
+ foreach my $ua(@uas) {
+ draw_line($canvas, $offset, $canvas_margin, $offset, $canvas_height-$canvas_margin, $canvas_elem_line_width, $canvas_elem_line_color);
+ my @bounds = $canvas->stringBounds($ua); # get bounds for text centering
+ draw_text($canvas, $offset-int(abs($bounds[0])/2), $canvas_margin-abs($bounds[1]), $canvas_elem_font, $canvas_elem_font_size, $canvas_elem_font_color, $ua);
+ $offset += $canvas_elem_distance;
+ }
+
+ ### draw arrows
+ my $last_timestamp = undef;
+ my $y_offset = $canvas_margin + $canvas_pkg_distance;
+ $i = 1;
+ foreach my $packet(@{$packets}) {
+ my $time_offset = defined $last_timestamp ? ($packet->{timestamp} - $last_timestamp) : 0;
+ $last_timestamp = $packet->{timestamp};
+ my $from_x = $uas_pos_x{$packet->{src_ip}.':'.$packet->{src_port}};
+ my $to_x = $uas_pos_x{$packet->{dst_ip}.':'.$packet->{dst_port}};
+ #print "arrow from ".$packet->{src_ip}.':'.$packet->{src_port}." to ".$packet->{dst_ip}.':'.$packet->{dst_port}.": $from_x - $to_x\n";
+ draw_arrow($canvas, $from_x, $y_offset, $to_x, $y_offset, $canvas_pkg_line_width,
+ $canvas_pkg_line_colors{$packet->{transport}} || $canvas_pkg_line_color);
+ $packet->{payload} =~ /\ncseq:\s*(\d+)\s+[a-zA-Z]+/i;
+ my $cseq = $1 ? $1 : '?';
+ my $txt = sprintf($i.'. '.$packet->{method}.' ('.$cseq.', +%0.3fs)', $time_offset);
+ my @bounds = $canvas->stringBounds($txt); # get bounds for text centering
+ if($from_x < $to_x) {
+ $from_x = $from_x+int($canvas_elem_distance/2)-int($bounds[0]/2);
+ } elsif($from_x > $to_x) {
+ $from_x = $from_x-int($canvas_elem_distance/2)-int($bounds[0]/2);
+ } else {
+ $from_x += 10; # call to itself, e.g. in cf loop
+ }
+ draw_text($canvas, $from_x, $y_offset-int(abs($bounds[1])/2), $canvas_pkg_font, $canvas_pkg_font_size, $canvas_pkg_font_color, $txt);
+
+ push @{$r_info->{areas}}, {"id", $packet->{id}, "coords", ($from_x-$html_padding).','.($y_offset-abs($bounds[1])-$html_padding).','.($from_x+abs($bounds[0])+$html_padding).','.($y_offset)};
+
+ $y_offset += $canvas_pkg_distance;
+ ++$i;
+ }
+ $$r_png = $canvas->png;
+}
+
+
+sub generate_callmap {
+ my $c = shift;
+ my $packets = shift;
+ my $png; my %info;
+ process_callmap($c, $packets, \$png, \%info);
+ return \%info;
+}
+
+sub generate_callmap_png {
+ my $c = shift;
+ my $packets = shift;
+ my $png; my %info;
+ process_callmap($c, $packets, \$png, \%info);
+ return $png;
+}
+
+1;
diff --git a/ngcp_panel.conf b/ngcp_panel.conf
index be8038bb79..5444538939 100644
--- a/ngcp_panel.conf
+++ b/ngcp_panel.conf
@@ -58,3 +58,11 @@ log4perl.appender.Default.layout.ConversionPattern=%d{ISO8601} [%p] [%F +%L] %m{
failed_auth_attempts 3
+
+
+ lb_int 127.0.0.1:5060
+ lb_ext 1.2.3.4:5060
+ proxy 127.0.0.1:5062
+ sbc 127.0.0.1:5080
+ app 127.0.0.1:5070
+