ngcp-panel/lib/NGCP/Panel/Utils/Callflow.pm

229 lines
8.2 KiB

package NGCP::Panel::Utils::Callflow;
use strict;
use warnings;
use NGCP::Panel::Utils::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 = GD::Polygon->new;
$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',
$c->config->{callflow}->{pbx}, 'pbx',
$c->config->{callflow}->{b2b}, 'b2b',
);
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";
my $ua = $int_uas{$packet->src_ip.':'.$packet->src_port};
push (@uas, $ua) unless grep {$_ eq $ua} @uas;
}
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";
my $ua = $int_uas{$packet->dst_ip.':'.$packet->dst_port};
push (@uas, $ua) unless grep {$_ eq $ua} @uas;
}
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;
}
}
### 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 = NGCP::Panel::Utils::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->hires_epoch - $last_timestamp->hires_epoch) : 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->get_column('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;