From 9beefb8f83872fb2715eef78b81cfb9cd42e49f1 Mon Sep 17 00:00:00 2001 From: Andreas Granig Date: Mon, 5 Aug 2013 23:27:04 +0200 Subject: [PATCH] Implement backend for downloading pcaps. This is the first part of the callflow analyzer. --- lib/NGCP/Panel/Controller/Subscriber.pm | 27 +++ lib/NGCP/Panel/Utils/Callflow.pm | 223 ++++++++++++++++++++++++ ngcp_panel.conf | 8 + 3 files changed, 258 insertions(+) create mode 100644 lib/NGCP/Panel/Utils/Callflow.pm 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 +