#!/usr/bin/perl -w # Graphical shell for the rude/crude traffic generator and analyser # Written by: Rui Prior # Distributed under the GPLv2 license: see http://www.gnu.org/copyleft/gpl.html # for details. use Tk; use Tk::FileSelect; use Tk::Dialog; use Tk::BrowseEntry; use Tk::HList; use IPC::Open3; use IO::Select; use File::Temp "tempdir"; use Cwd; use File::Path; use File::Copy; $version = '1.0'; $end = '$'; # Damned interpolation on regexps... # Parse command line args if ($#ARGV >= 0) { if ($ARGV[0] eq '-h' or $ARGV[0] eq '--help') { print "Usage: grude [file.grd]\n\n"; exit; } $argfile = $ARGV[0]; # emitter() needs an existing MainWindow at $mw $mw = MainWindow->new(-title => 'Grude'); emitter(); @ARGV = (); } else { # The usual main(); } MainLoop; sub main { # General cleanup undef %tred if defined %tred; undef %fsed if defined %fsed; undef %fled if defined %fled; undef %coll if defined %coll; undef %tx if defined %tx; if ($mw) { foreach $widget ($mw->children) { destroy $widget } } else { $mw = MainWindow->new(-title => 'Grude'); } $fchoice = $mw->Frame; $choice = 'emitter'; $fchoice->Radiobutton(-text => 'Emitter (rude)', -variable => \$choice, -value => 'emitter')->grid(-sticky => 'w'); $fchoice->Radiobutton(-text => 'Collector (crude)', -variable => \$choice, -value => 'collector')->grid(-sticky => 'w'); $fchoice->Radiobutton(-text => 'Decode file (crude)', -variable => \$choice, -value => 'decoder')->grid(-sticky => 'w'); $fchoice->grid(-padx => 20, -pady => 5); $fb = $mw->Frame; $fb->Button(-text => 'Go', -command => sub {eval "$choice"})->grid(-row => 0, -column => 0, -padx => 5, -pady => 5); $fb->Button(-text => 'Quit', -command => sub {exit})->grid(-row => 0, -column => 1, -padx => 5, -pady => 5); $fb->grid(); $mw->resizable(0,0); $mw->update; $mw->geometry($mw->reqwidth() . 'x' . $mw->reqheight()); } sub modsort { if ($a eq 'initial') { return -1;} elsif ($b eq 'initial') { return 1;} else { return $a <=> $b} } sub valid_txparms { if ($tx{'start'} ne 'NOW' and ($tx{'starth'} !~ /^(\d\d)$end/ or $1 > 23 or $tx{'startm'} !~ /^(\d\d)$end/ or $1 > 59 or $tx{'starts'} !~ /^(\d\d)$end/ or $1 > 59) ) { $mw->Dialog(-title => 'Warning', -text => 'Invalid global start time: must be HH:MM:SS', -bitmap => 'warning', -buttons => ['Dismiss'])->Show; return 0; } if ($tx{'srt'} and ($tx{'srtprio'} !~ s/^\s*(\d{1,2})\s*$end/$1/ or $1 < 1 or $1 > 90)) { $mw->Dialog(-title => 'Warning', -text => 'Soft real-time priority must be in range 1..90', -bitmap => 'warning', -buttons => ['Dismiss'])->Show; return 0; } if (!defined $fled{'flows'} or ! scalar %{$fled{'flows'}}) { $mw->Dialog(-title => 'Warning', -text => 'Must specify at least one flow', -bitmap => 'warning', -buttons => ['Dismiss'])->Show; return 0; } return 1; # Good } sub valid_flow { my $flow = $_[0]; my $oldid = $_[1]; if ($$flow{'id'} !~ s/^\s*(\d+)\s*$end/$1/) { $mw->Dialog(-title => 'Warning', -text => 'Invalid flow ID', -bitmap => 'warning', -buttons => ['Dismiss'])->Show; return 0; } if ($$flow{'tstart'} !~ s/^\s*(\d+)\s*$end/$1/) { $mw->Dialog(-title => 'Warning', -text => 'Invalid start time', -bitmap => 'warning', -buttons => ['Dismiss'])->Show; return 0; } if ($$flow{'tstop'} !~ s/^\s*(\d+)\s*$end/$1/) { $mw->Dialog(-title => 'Warning', -text => 'Invalid start time', -bitmap => 'warning', -buttons => ['Dismiss'])->Show; return 0; } if ($$flow{'tstop'} <= $$flow{'tstart'}) { $mw->Dialog(-title => 'Warning', -text => 'Stop time must be greater than start time', -bitmap => 'warning', -buttons => ['Dismiss'])->Show; return 0; } my $max = 0; foreach $mod (keys %{$$flow{'mods'}}) { $max = $mod if $mod ne 'initial' and $mod > $max; } if ($max >= $$flow{'tstop'}) { $max++; my $option = $mw->Dialog(-title => 'Warning', -text => 'Start instant must precede '. 'flow stop, at '.$$flow{'tstop'}. ' msecs. Change flow stop time to '.$max.'?', -bitmap => 'warning', -buttons => ['Change','Cancel'])->Show; return if ($option eq 'Cancel'); $$flow{'tstop'} = $max; } if ($$flow{'daddr'} !~ s/^\s*((\w|-)+(\.(\w|-)+)*)\s*$end/$1/) { $mw->Dialog(-title => 'Warning', -text => 'Invalid destination address', -bitmap => 'warning', -buttons => ['Dismiss'])->Show; return 0; } if ($$flow{'dport'} !~ s/^\s*(\d+)\s*$end/$1/ or $$flow{'dport'} > 65535) { $mw->Dialog(-title => 'Warning', -text => 'Invalid destination port', -bitmap => 'warning', -buttons => ['Dismiss'])->Show; return 0; } if ($$flow{'sport'} !~ s/^\s*(\d+)\s*$end/$1/ or $$flow{'sport'} > 65535 or $$flow{'sport'} < 1024) { $mw->Dialog(-title => 'Warning', -text => 'Invalid source port', -bitmap => 'warning', -buttons => ['Dismiss'])->Show; return 0; } my ($key, $val); keys %{$fled{'flows'}}; # Reset "each" while (($key, $val) = each %{$fled{'flows'}}) { if ($$val{'sport'} == $$flow{'sport'} and $key != $oldid and $key != $$flow{'id'}) { $mw->Dialog(-title => 'Warning', -text => "Source port $$flow{'sport'} already in use by flow $key. Please choose another one.", -bitmap => 'warning', -buttons => ['Dismiss'])->Show; return 0; } } if ($$flow{'settos'} and $$flow{'tos'} !~ s/^\s*(0x(\d|[a-f]|[A-F]){2})\s*$end/$1/ and ($$flow{'tos'} !~ s/^\s*(\d{1,3})\s*$end/$1/ or $$flow{'tos'} > 255)) { $mw->Dialog(-title => 'Warning', -text => 'Invalid TOS value', -bitmap => 'warning', -buttons => ['Dismiss'])->Show; return 0; } return 1; # Congratulations! } sub editfspec { my $flow = $_[0]; my $mod; my %fmod; if (defined $_[1]) { $mod = ${$_[1]}; %fmod = %{${$$flow{'mods'}}{$mod}}; } else { $mod = 'new'; } $fsed{'window'} = $fled{'window'}->Toplevel(-title => "Flow ".$$flow{'id'}.": $mod"); my $esw = $fsed{'window'}; $esw->grab; $esw->resizable(0,0); $esw->protocol('WM_DELETE_WINDOW', sub { $fled{'window'}->grab; $esw->destroy }); my $fr; my $optlist; if ($mod ne 'initial') { $fr = $esw->Frame; $fr->grid(-row => 0, -column => 0, -padx => 5, -pady => 5); $fr->Label(-text => 'Instant')->grid(-row => 0, -column => 0); if ($mod eq 'new') { my $n = $$flow{'tstart'} + 1; foreach (keys %{$$flow{'mods'}}) { $n = $_ + 1 if (/^\d+$end/ and $_ >= $n); } $fsed{'inst'} = $n; } else { $fsed{'inst'} = $mod; } $fsed{'einst'} = $fr->Entry(-width => 8, -textvariable => \$fsed{'inst'}); $fsed{'einst'}->grid(-row => 0, -column => 1); $optlist = [['Constant','CONSTANT'],['Trace','TRACE'],['Silent','SILENT']]; } else { $optlist = [['Constant','CONSTANT'],['Trace','TRACE'],['Silent','SILENT']]; } $fr = $esw->Frame; $fr->grid(-row => 1, -column => 0, -padx => 5, -pady => 5); $fr->Label(-text => 'Type')->grid(-row => 0, -column => 0); $fmod{'type'} = 'CONSTANT' if ! defined $fmod{'type'}; undef $fsed{'bedit'}; my $dummyopt; if ($fmod{'type'} eq 'CONSTANT') { $dummyopt = 'Constant'; } elsif ($fmod{'type'} eq 'TRACE') { $dummyopt = 'Trace'; } elsif ($fmod{'type'} eq 'SILENT') { $dummyopt = 'Silent'; } # Must use -textvariable to avoid overwriting the old variable value $fsed{'optmen'} = $fr->Optionmenu(-variable => \$fmod{'type'}, -textvariable => \$dummyopt, -options => $optlist, -command => sub { return if ! defined $fsed{'bedit'}; if ($fmod{'type'} eq 'CONSTANT') { $fsed{'bedit'}->configure(-state => 'disabled'); $fsed{'esize'}->configure(-state => 'normal', -textvariable => \$fmod{'size'}); $fsed{'erate'}->configure(-state => 'normal', -textvariable => \$fmod{'rate'}); } elsif ($fmod{'type'} eq 'TRACE') { $fsed{'bedit'}->configure(-state => 'normal'); $fsed{'esize'}->configure(-state => 'disabled', -text => ''); $fsed{'erate'}->configure(-state => 'disabled', -text => ''); } elsif ($fmod{'type'} eq 'SILENT') { $fsed{'bedit'}->configure(-state => 'disabled'); $fsed{'esize'}->configure(-state => 'disabled', -text => ''); $fsed{'erate'}->configure(-state => 'disabled', -text => ''); } } ); $fsed{'optmen'}->grid(-row => 0, -column => 1); $fr->gridColumnconfigure(2, -minsize => 10); $fsed{'bedit'} = $fr->Button(-text => 'Edit', -command => [ \&edittrace, $$flow{'id'}, $mod, \%fmod ]); $fsed{'bedit'}->grid(-row => 0, -column => 3); $fr = $esw->Frame; $fr->grid(-row => 2, -column => 0, -padx => 5, -pady => 5, -sticky => 'ew'); $fmod{'size'} = 64 if ! defined $fmod{'size'}; $fsed{'lsize'} = $fr->Label(-text => 'Size'); $fsed{'lsize'}->grid(-row => 0, -column => 0); $fsed{'esize'} = $fr->Entry(-width => 8); $fsed{'esize'}->grid(-row => 0, -column => 1); $fr->gridColumnconfigure(2, -minsize => 10); $fmod{'rate'} = 1 if ! defined $fmod{'rate'}; $fsed{'lrate'} = $fr->Label(-text => 'Rate'); $fsed{'lrate'}->grid(-row => 0, -column => 3); $fsed{'erate'} = $fr->Entry(-width => 8); $fsed{'erate'}->grid(-row => 0, -column => 4); if ($fmod{'type'} eq 'CONSTANT') { $fsed{'bedit'}->configure(-state => 'disabled'); $fsed{'esize'}->configure(-state => 'normal', -textvariable => \$fmod{'size'}); $fsed{'erate'}->configure(-state => 'normal', -textvariable => \$fmod{'rate'}); } elsif ($fmod{'type'} eq 'TRACE') { $fsed{'bedit'}->configure(-state => 'normal'); $fsed{'esize'}->configure(-state => 'disabled', -text => ''); $fsed{'erate'}->configure(-state => 'disabled', -text => ''); } elsif ($fmod{'type'} eq 'SILENT') { $fsed{'bedit'}->configure(-state => 'disabled'); $fsed{'esize'}->configure(-state => 'disabled', -text => ''); $fsed{'erate'}->configure(-state => 'disabled', -text => ''); } $fr = $esw->Frame; $fr->grid(-row => 3, -column => 0, -padx => 5, -pady => 5, -sticky => 'ew'); $fr->Button(-text => 'Accept', -command => sub { if ($fmod{'type'} eq 'CONSTANT') { if ($fmod{'size'} !~ s/^\s*0*(\d+)\s*$end/$1/ or $fmod{'size'} < 20 or $fmod{'size'} > 32768) { $mw->Dialog(-title => 'Warning', -text => 'Size must be 20 <= size <= 32768', -bitmap => 'warning', -buttons => ['Dismiss'])->Show; return; } if ($fmod{'rate'} !~ s/^\s*0*(\d+)\s*$end/$1/) { $mw->Dialog(-title => 'Warning', -text => 'Rate must be a positive integer', -bitmap => 'warning', -buttons => ['Dismiss'])->Show; return; } if ($fmod{'rate'} == 0) { $mw->Dialog(-title => 'Warning', -text => 'Rate=0: changing type to Silent', -bitmap => 'warning', -buttons => ['Dismiss'])->Show; $fmod{'type'} = 'SILENT'; } } elsif ($fmod{'type'} eq 'TRACE') { if ($#{$fmod{'trace'}} == -1) { $mw->Dialog(-title => 'Warning', -text => 'Trace list must be non-empty', -bitmap => 'warning', -buttons => ['Dismiss'])->Show; return; } } if ($mod ne 'initial') { if ($fsed{'inst'} !~ s/^\s*0*(\d+)\s*$end/$1/) { $mw->Dialog(-title => 'Warning', -text => 'Start instant must be a positive integer!', -bitmap => 'warning', -buttons => ['Dismiss'])->Show; return; } if ($fsed{'inst'} <= $$flow{'tstart'}) { $mw->Dialog(-title => 'Warning', -text => 'Start instant must be after '. 'flow start, at '.$$flow{'tstart'}. ' msecs', -bitmap => 'warning', -buttons => ['Dismiss'])->Show; return; } elsif ($fsed{'inst'} >= $$flow{'tstop'}) { my $option = $mw->Dialog(-title => 'Warning', -text => 'Start instant must precede '. 'flow stop, at '.$$flow{'tstop'}. ' msecs. Change flow stop time?', -bitmap => 'warning', -buttons => ['Change','Cancel'])->Show; return if ($option eq 'Cancel'); $$flow{'tstop'} = $fsed{'inst'} + 1; } if ($mod ne $fsed{'inst'}) { if (defined ${$$flow{'mods'}}{$fsed{'inst'}}) { my $option = $mw->Dialog(-title => 'Warning', -text => 'Overwrite spec '.$fsed{'inst'}.'?', -bitmap => 'warning', -buttons => ['Confirm', 'Cancel'], -default_button => 'Cancel')->Show; return if ($option eq 'Cancel'); } delete ${$$flow{'mods'}}{$mod} if ($mod ne 'new'); } # This must be the last thing done before commitment $mod = $fsed{'inst'}; } ${$$flow{'mods'}}{$mod} = \%fmod; $fled{'ddmods'}->configure( -options => [ sort modsort keys %{$$flow{'mods'}} ] ); $fled{'window'}->grab; $esw->destroy; } )->grid(-row => 0, -column => 0); $fr->gridColumnconfigure(1, -minsize => 10); $fr->Button(-text => 'Cancel', -command => sub {$fled{'window'}->grab; $esw->destroy } )->grid(-row => 0, -column => 2); $fmod{'trace'} = [] if ! defined $fmod{'trace'}; } sub edittrace { my $flowid = $_[0]; my $mod = $_[1]; my $fmod = $_[2]; $tred{'n'} = 0; my @eplist; my @trace = @{$$fmod{'trace'}}; $tred{'window'} = $fsed{'window'}->Toplevel(-title => "Flow $flowid\: $mod - trace"); my $etw = $tred{'window'}; $etw->grab; $etw->resizable(0,0); $etw->protocol('WM_DELETE_WINDOW', sub { $fsed{'window'}->grab; $etw->destroy }); my $fr = $etw->Frame; $fr->grid(-row => 0, -column => 0, -padx => 5, -pady => 5, -sticky => 'ns'); $tred{'scrl'} = $fr->Scrolled('HList', -header => 1, -columns => 2, -width => 22, -height => 8, -itemtype => 'text', -selectmode => 'single', -scrollbars => 'ose', -bg => 'white'); $tred{'scrl'}->grid(-row => 0, -column => 0, -rowspan => 4, -sticky => 'ns'); $tred{'list'} = $tred{'scrl'}->Subwidget('hlist'); $tred{'list'}->configure(-browsecmd => sub { if (! $tred{'list'}->info('selection') eq '') { $tred{'bdel'}->configure(-state => 'normal'); } }); $tred{'list'}->header('create', 0, -text => 'Size', -headerbackground => $tred{'window'}->cget('bg')); $tred{'list'}->columnWidth(0, -char => 8); $tred{'list'}->header('create', 1, -text => 'Wait', -headerbackground => $tred{'window'}->cget('bg')); $tred{'list'}->columnWidth(1, -char => 12); $tred{'bdel'} = $fr->Button(-text => 'Del', -state => 'disabled', -command => sub { my $sel = $tred{'list'}->info('selection'); if (! defined $sel or $sel eq '') { $tred{'bdel'}->configure(-state => 'disabled'); } else { $tred{'list'}->delete('entry', $sel); my $i; for ($i = 0; $i <= $#eplist; $i++) { last if ($eplist[$i] == $sel); } splice(@eplist, $i, 1); splice(@trace, 2*$i, 2); } }); $tred{'bdel'}->grid(-column => 1, -row => 1, -padx => 3, -sticky => 'ew'); $tred{'esize'} = ''; $tred{'ewait'} = ''; $fr->Button(-text => 'Add', -command => sub { if ($tred{'esize'} !~ s/^\s*(\d+)\s*$end/$1/ or $tred{'esize'} < 20 or $tred{'esize'} > 32768) { $mw->Dialog(-title => 'Warning', -text => 'Size must be 20 <= size <= 32768', -bitmap => 'warning', -buttons => ['Dismiss'])->Show; return; } if ($tred{'ewait'} !~ s/^\s*(\d+|(\d*\.\d+)?)\s*$end/$1/) { $mw->Dialog(-title => 'Warning', -text => 'Bad wait specification', -bitmap => 'warning', -buttons => ['Dismiss'])->Show; return; } # Add after selected entry or at the end if none selected my $i; my $n = $tred{'n'}++; my $sel = $tred{'list'}->info('selection'); if (! defined $sel or $sel eq '') { push(@eplist, $n); push(@trace, ($tred{'esize'}, $tred{'ewait'})); $tred{'list'}->add($n); } else { for ($i = 0; $i <= $#eplist; $i++) { if ($eplist[$i] == $sel) { $i++; last; } } splice(@eplist, $i, 0); splice (@trace, 2*$i, 0, ($tred{'esize'}, $tred{'ewait'})); $tred{'list'}->add($n, -after => $sel); } $tred{'list'}->itemCreate($n, 0, -text => $tred{'esize'}); $tred{'list'}->itemCreate($n, 1, -text => $tred{'ewait'}); $tred{'esize'} = ''; $tred{'ewait'} = ''; } )->grid(-column => 1, -row => 2, -padx => 3, -sticky => 'ew'); $fr = $etw->Frame; $fr->grid(-row => 1, -column => 0, -padx => 5, -pady => 5); $fr->Label(-text => 'Size')->grid(-row => 0, -column => 0); my $ent = $fr->Entry(-width => 6, -textvariable => \$tred{'esize'}); $ent->grid(-row => 0, -column => 1); $fr->gridColumnconfigure(2, -minsize => 10); $fr->Label(-text => 'Wait')->grid(-row => 0, -column => 3); $fr->Entry(-width => 10, -textvariable => \$tred{'ewait'} )->grid(-row => 0, -column => 4); $tred{'list'}->configure(-selectforeground => $ent->cget('selectforeground'), -selectbackground => $ent->cget('selectbackground')); my $n = 0; my $i = 0; foreach (@trace) { if ($i % 2) { $tred{'list'}->itemCreate($n, 1, -text => $trace[$i]); push(@eplist, $n); $n++; } else { $tred{'list'}->add($n); $tred{'list'}->itemCreate($n, 0, -text => $trace[$i]); } $i++; } $tred{'n'} = $n; $fr = $etw->Frame; $fr->grid(-row => 2, -column => 0, -padx => 5, -pady => 5); $fr->Button(-text => 'Accept', -command => sub { $$fmod{'trace'} = \@trace; $fsed{'window'}->grab; $etw->destroy; })->grid(-column => 0, -row => 0, -padx => 3); $fr->gridColumnconfigure(1, -minsize => 20); $fr->Button(-text => 'Cancel', -command => sub {$fsed{'window'}->grab; $etw->destroy } )->grid(-column => 2, -row => 0, -padx => 3); } sub editflow { $fled{'window'} = $mw->Toplevel; my $efw = $fled{'window'}; $efw->grab; $efw->resizable(0,0); $fled{'flows'} = {} if !defined $fled{'flows'}; my $flows; $flows = $fled{'flows'}; my $flowid; if (defined $_[0]) { $efw->configure(-title => 'Edit flow'); $flowid = $_[0]; } else { $efw->configure(-title => 'New flow'); $flowid = 0; foreach $id (keys %{$fled{'flows'}}) { $flowid = $id + 1 if $id >= $flowid; } } my %flow; my $oldflow = 0; if (defined $$flows{$flowid}) { $oldflow = 1; %flow = %{$$flows{$flowid}}; } else { # New flow - defaults $flow{'mods'} = {'initial' => {'type' => 'CONSTANT', 'rate' => '1', 'size' => '64'}}; $flow{'tstart'} = 0; $flow{'tstop'} = $flow{'tstart'} + 1000; $flow{'daddr'} = ''; $flow{'dport'} = 10001; $flow{'sport'} = 1024; $flow{'settos'} = 0; $flow{'tos'} = 0; } my $fr = $efw->Frame; $fr->grid(-row => 0, -column => 0, -padx => 5, -pady => 5); $fr->Label(-text => 'ID')->grid(-row => 0, -column => 0); $flow{'id'} = $flowid; $fr->Entry(-textvariable => \$flow{'id'}, -width => 5 )->grid(-row => 0, -column => 1); $fr->gridColumnconfigure(2, -minsize => 10); $fr->Label(-text => 'Tstart')->grid(-row => 0, -column => 3); $fr->Entry(-textvariable => \$flow{'tstart'}, -width => 8 )->grid(-row => 0, -column => 4); $fr->gridColumnconfigure(5, -minsize => 10); $fr->Label(-text => 'Tstop')->grid(-row => 0, -column => 6); $fr->Entry(-textvariable => \$flow{'tstop'}, -width => 8 )->grid(-row => 0, -column => 7); $fr = $efw->Frame; $fr->grid(-row => 1, -column => 0, -padx => 5, -pady => 5); $fr->Label(-text => 'DST Addr')->grid(-row => 0, -column => 0); $fr->Entry(-textvariable => \$flow{'daddr'}, -width => 20 )->grid(-row => 0, -column => 1); $fr->gridColumnconfigure(2, -minsize => 10); $fr->Label(-text => 'Port')->grid(-row => 0, -column => 3); $fr->Entry(-textvariable => \$flow{'dport'}, -width => 5 )->grid(-row => 0, -column => 4); $fr = $efw->Frame; $fr->grid(-row => 2, -column => 0, -padx => 5, -pady => 5); $fr->Label(-text => 'SRC Port')->grid(-row => 0, -column => 0); $fr->Entry(-textvariable => \$flow{'sport'}, -width => 5 )->grid(-row => 0, -column => 1); $fr->gridColumnconfigure(2, -minsize => 10); $fr->Checkbutton(-text => 'Set TOS to', -variable => \$flow{'settos'} )->grid(-row => 0, -column => 3); $fr->Entry(-textvariable => \$flow{'tos'}, -width => 5 )->grid(-row => 0, -column => 4); $fr = $efw->Frame; $fr->grid(-row => 3, -column => 0, -padx => 5, -pady => 5); $fr->Label(-text => 'Specs')->grid(-row => 0, -column => 0);; $fr->Button(-text => 'Add', -command => sub { if ($flow{'tstart'} !~ /^\s*\d+\s*$end/ or $flow{'tstop'} !~ /^\s*\d+\s*$end/ or $flow{'tstop'} <= $flow{'tstart'}) { $mw->Dialog(-title => 'Warning', -text => 'Please specify valid Tstart and Tstop.', -bitmap => 'warning', -buttons => ['Dismiss'])->Show; return 0; } editfspec(\%flow); } )->grid(-row => 0, -column =>2); $fled{'selmod'} = 'initial'; $fled{'bdel'} = $fr->Button(-text => 'Del', -state => 'disabled', -command => sub { return if $fled{'selmod'} eq 'initial'; delete ${$flow{'mods'}}{$fled{'selmod'}}; $fled{'ddmods'}->configure( -options => [ sort modsort keys %{$flow{'mods'}} ] ); } ); $fled{'bdel'}->grid(-row => 0, -column =>3); $fr->Button(-text => 'Edit', -command => [ \&editfspec, \%flow, \$fled{'selmod'} ] )->grid(-row => 0, -column => 4); $fled{'ddmods'} = $fr->Optionmenu(-variable => \$fled{'selmod'}, -command => sub { if ($fled{'selmod'} eq 'initial') { $fled{'bdel'}->configure(-state => 'disabled'); } else { $fled{'bdel'}->configure(-state => 'normal'); } }); $fled{'ddmods'}->addOptions(sort modsort keys %{$flow{'mods'}}); $fled{'ddmods'}->grid(-row => 0, -column => 1); $fr = $efw->Frame; $fr->grid(-row => 4, -column => 0, -padx => 5, -pady => 5); $fr->Button(-text => 'Accept', -command => sub { return if !valid_flow(\%flow, $flowid); if ($flow{'id'} != $flowid) { if (defined $$flows{$flow{'id'}}) { my $option = $mw->Dialog(-title => 'Warning', -text => "Flow $flow{'id'} already exists. Do you want to overwrite it?", -bitmap => 'warning', -buttons => ['Overwrite', 'Cancel'], -default_button => 'Cancel')->Show; return unless ($option eq 'Overwrite'); my $index; my $menu = $tx{'edflows'}->menu; my $topidx = $menu->index('last'); for ($index = 0; $index <= $topidx; $index++) { if ($menu->entrycget($index, -label)==$flow{'id'}) { $tx{'edflows'}->menu->delete($index); $tx{'delflows'}->menu->delete($index); last; } } print "Flow ",$flow{'id'}," not found in menus.\n" if $index > $topidx; } if ($oldflow) { delete $$flows{$flowid}; my $index; my $menu = $tx{'edflows'}->menu; my $topidx = $menu->index('last'); for ($index = 0; $index <= $topidx; $index++) { if ($menu->entrycget($index, -label)==$flowid) { $tx{'edflows'}->menu->delete($index); $tx{'delflows'}->menu->delete($index); last; } } print "Flow ",$flow{'id'}," not found in menus.\n" if $index > $topidx; } } $$flows{$flow{'id'}} = \%flow; $efw->destroy; $tx{'unsaved'} = 1; if (!$oldflow or $flow{'id'} != $flowid) { my $index; my $menu = $tx{'edflows'}->menu; my $topidx = $menu->index('last'); $topidx = -1 if $topidx eq 'none'; for ($index = 0; $index <= $topidx; $index++) { last if $menu->entrycget($index, -label) > $flow{'id'}; } $tx{'edflows'}->menu->insert($index, 'command', -label => $flow{'id'}, -command => [\&editflow, $flow{'id'}]); $tx{'delflows'}->menu->insert($index, 'command', -label => $flow{'id'}, -command => [\&delflow, $flow{'id'}]); } } )->grid(-row => 0, -column =>0, -padx => 3); $fr->gridColumnconfigure(1, -minsize => 20); $fr->Button(-text => 'Cancel', -command => sub { $efw->destroy } )->grid(-row => 0, -column =>2, -padx => 3); } sub delflow { my $id = $_[0]; delete ${$fled{'flows'}}{$id}; my $index; my $menu = $tx{'edflows'}->menu; my $topidx = $menu->index('last'); for ($index = 0; $index <= $topidx; $index++) { last if $menu->entrycget($index, -label) == $id; } if ($index > $topidx) { print "Flow $id not found in menus.\n" } else { $tx{'edflows'}->menu->delete($index); $tx{'delflows'}->menu->delete($index); } } sub delall { my $option = $mw->Dialog(-title => 'Warning', -text => 'Warning: This will delete ALL flows!', -bitmap => 'warning', -buttons => ['Confirm', 'Cancel'], -default_button => 'Cancel')->Show; return if ($option eq 'Cancel'); foreach $id (keys %{$fled{'flows'}}) { $tx{'edflows'}->menu->delete($id); $tx{'delflows'}->menu->delete($id); } $fled{'flows'} = {}; } sub emitter { foreach $widget ($mw->children) { $widget->destroy } my $fr; $fr = $mw->Frame(-relief => 'raised', -borderwidth => 1); $fr->grid(-row => 0, -column => 0, -sticky => 'ew'); $tx{'mbfile'} = $fr->Menubutton(-text => 'File'); $tx{'mbfile'}->pack(-side => 'left'); $tx{'mbfile'}->configure(-menu => $tx{'mfile'} = $tx{'mbfile'}->Menu(-tearoff => 0)); $tx{'mfile'}->command(-label => 'New', -command => \&reset_txconf); $tx{'mfile'}->command(-label => 'Open', -command => \&open_file); $tx{'mfile'}->command(-label => 'Save', -command => sub { save_file($tx{'file'}) }); $tx{'mfile'}->command(-label => 'Save as', -command => \&save_file); $tx{'mbflows'} = $fr->Menubutton(-text => 'Flows'); $tx{'mbflows'}->pack(-side => 'left'); $tx{'mbflows'}->configure(-menu => $tx{'mflows'} = $tx{'mbflows'}->Menu(-tearoff => 0)); $tx{'mflows'}->command(-label => 'New', -command => \&editflow); $tx{'edflows'} = $tx{'mflows'}->Cascade(-label => 'Edit', -tearoff => 0); $tx{'delflows'} = $tx{'mflows'}->Cascade(-label => 'Delete', -tearoff => 0); $tx{'mflows'}->separator; $tx{'mflows'}->command(-label => 'Delete all', -command => \&delall); $fr = $mw->Frame(); $fr->grid(-row => 1, -column => 0, -padx => 5, -pady => 5); $fr->Label(-text => 'Start')->grid(-row => 0, -column => 0); $fr->Radiobutton(-text => 'Now', -command => sub { $tx{'unsaved'} = 1; }, -variable => \$tx{'start'}, -value => 'NOW')->grid(-row => 0, -column => 1); $fr->Radiobutton(-text => 'At', -command => sub { $tx{'unsaved'} = 1; }, -variable => \$tx{'start'}, -value => 'AT')->grid(-row => 0, -column => 2); $tx{'starth'} = '00'; $tx{'startm'} = '00'; $tx{'starts'} = '00'; $fr->Entry(-textvariable => \$tx{'starth'}, -validate => 'all', -vcmd => sub { if ($_[4] >= 0) { $tx{'unsaved'} = 1; $tx{'start'} = 'AT'; } $_[0] =~ /^\d\d$end/ and $_[0] < 24 or $_[4] == 0 or $_[4] == 1 and $_[0] =~ /^\d$end/ }, -invcmd => sub { if ($_[4] < 0) { $_[0] =~ /(\d?)(\d?)/; if ($1 eq '') { $tx{'starth'} = '00'; } elsif ($2 eq '') { $tx{'starth'} = "0$1"; } } }, -width => 2)->grid(-row => 0, -column => 3); $fr->Label(-text => ':')->grid(-row => 0, -column => 4); $fr->Entry(-textvariable => \$tx{'startm'}, -validate => 'all', -vcmd => sub { if ($_[4] >= 0) { $tx{'unsaved'} = 1; $tx{'start'} = 'AT'; } $_[0] =~ /^\d\d$end/ and $_[0] < 60 or $_[4] == 0 or $_[4] == 1 and $_[0] =~ /^\d$end/ }, -invcmd => sub { if ($_[4] < 0) { $_[0] =~ /(\d?)(\d?)/; if ($1 eq '') { $tx{'startm'} = '00'; } elsif ($2 eq '') { $tx{'startm'} = "0$1"; } } }, -width => 2)->grid(-row => 0, -column => 5); $fr->Label(-text => ':')->grid(-row => 0, -column => 6); $fr->Entry(-textvariable => \$tx{'starts'}, -validate => 'all', -vcmd => sub { if ($_[4] >= 0) { $tx{'unsaved'} = 1; $tx{'start'} = 'AT'; } $_[0] =~ /^\d\d$end/ and $_[0] < 60 or $_[4] == 0 or $_[4] == 1 and $_[0] =~ /^\d$end/ }, -invcmd => sub { if ($_[4] < 0) { $_[0] =~ /(\d?)(\d?)/; if ($1 eq '') { $tx{'starts'} = '00'; } elsif ($2 eq '') { $tx{'starts'} = "0$1"; } } }, -width => 2)->grid(-row => 0, -column => 7); $tx{'start'} = 'NOW'; $fr = $mw->Frame(); $fr->grid(-row => 2, -column => 0, -padx => 5, -pady => 5); $fr->Checkbutton(-text => 'Soft real-time priority', -command => sub { $tx{'unsaved'} = 1; }, -variable => \$tx{'srt'})->grid(-row => 0, -column => 0); $tx{'srtprio'} = 50; $fr->Entry(-width => 3, -validate => 'all', -vcmd => sub { if ($_[4] >= 0) { $tx{'unsaved'} = 1; $tx{'srt'} = 1; } $_[0] =~ /^\d{1,2}$end/ and $_[0] >= 1 and $_[0] <= 60 or $_[4] == 0 or $_[4] == 1 and $_[0] =~ /^\d$end/ }, -invcmd => sub { if ($_[4] < 0) { $tx{'srtprio'} = 50; # default $tx{'srt'} = 0; } }, -textvariable => \$tx{'srtprio'})->grid(-row => 0, -column =>1); $tx{'srt'} = 0; $fr = $mw->Frame(); $fr->grid(-row => 3, -column => 0, -padx => 5, -pady => 5); $fr->Button(-text => 'Start', -command => \&launch_rude )->grid(-row => 0, -column =>0, -padx => 3); $fr->gridColumnconfigure(1, -minsize => 20); $fr->Button(-text => 'Cancel', -command => sub { main() if !unsaved_changes(); } )->grid(-row => 0, -column =>2, -padx => 3); $mw->update; $mw->geometry($mw->reqwidth() . 'x' . $mw->reqheight()); $tx{'unsaved'} = 0; if (defined $argfile) { open_file($argfile); undef $argfile; } } sub collector { foreach $widget ($mw->children) { $widget->destroy } my $fr = $mw->Frame(); $fr->grid(-row => 0, -column => 0, -padx => 5, -pady => 5); $fr->Label(-text => 'Mode')->grid(-row => 0, -column => 0, -rowspan => 3); $coll{'mode'} = 'stats'; $fr->Radiobutton(-text => 'Normal', -variable => \$coll{'mode'}, -value =>'normal', -command => \&collmode )->grid(-row => 0, -column => 1, -sticky => 'w'); $fr->Radiobutton(-text => 'Statistics only', -variable => \$coll{'mode'}, -value =>'stats', -command => \&collmode )->grid(-row => 1, -column => 1, -sticky => 'w'); $fr->Radiobutton(-text => 'Log to file', -variable => \$coll{mode}, -value =>'log', -command => \&collmode )->grid(-row => 2, -column => 1, -sticky => 'w'); $fr->gridColumnconfigure(2, -minsize => 20); $coll{'bstart'} = $fr->Button(-text => 'Start', -command => \&launch_crude); $coll{'bstart'}->grid(-row => 0, -column => 3, -rowspan => 3, -padx => 5, -pady => 5); $coll{'bcancel'} = $fr->Button(-text => 'Cancel', -command => \&main); $coll{'bcancel'}->grid(-row => 0, -column => 4, -rowspan => 3, -padx => 5, -pady => 5); $fr = $mw->Frame(); $fr->grid(-row => 1, -column => 0, -padx => 5, -pady => 5); $coll{'rbip'} = 'any'; $fr->Label(-text => 'IP')->grid(-row => 0, -column => 0, -rowspan => 2); $fr->Radiobutton(-text => 'Any', -variable => \$coll{'rbip'}, -value =>'any' )->grid(-row => 0, -column => 1, -columnspan => 2, -sticky => 'w'); $fr->Radiobutton(-text => 'Use', -variable => \$coll{'rbip'}, -value =>'use' )->grid(-row => 1, -column => 1, -sticky => 'w'); $coll{'ip'} = ''; $fr->Entry(-width => 16, -textvariable => \$coll{'ip'} )->grid(-row => 1, -column => 2, -padx => 5); $fr->gridColumnconfigure(3, -minsize => 15); $fr->Label(-text => 'Port')->grid(-row => 0, -column => 4, -rowspan => 2, -padx => 5, -sticky => 'e'); $coll{'port'} = '10001'; $fr->Entry(-width => 6, -textvariable => \$coll{'port'} )->grid(-row => 0, -column => 5, -rowspan => 2, -sticky => 'w'); $fr = $mw->Frame(); $fr->grid(-row => 2, -column => 0); my $collfr = $fr; $coll{'labtext'} = 'Flows'; my $colllab = $fr->Label(-textvariable => \$coll{'labtext'}); $colllab->grid(-row => 0, -column => 0, -padx => 5, -pady => 5, -sticky => 'e'); $coll{'enttext'} = ''; $coll{'file'} = ''; $coll{'flows'} = ''; $coll{'collent'} = $fr->Entry(-width => 32, -textvariable => \$coll{'flows'}); $coll{'collent'}->grid(-row => 0, -column => 1, -sticky => 'w'); $coll{'collbut'} = $fr->Button(-text => 'Select', -state => 'disabled', -command => sub { $coll{'file'} = $mw->FileSelect(-directory => '.', -title => 'Log file')->Show } ); $coll{'collbut'}->grid(-row => 0, -column => 2, -padx => 5, -pady => 5); $fr = $mw->Frame(); $fr->grid(-row => 3, -column => 0, -padx => 25, -sticky => 'w'); $coll{'srt'} = 0; $fr->Checkbutton(-text => 'Soft real-time priority', -variable => \$coll{'srt'} )->grid(-row => 0, -column => 0, -padx => 5, -pady => 5, -sticky => 'w'); $coll{'srtprio'} = '50'; $fr->Entry(-width => 3, -textvariable => \$coll{'srtprio'} )->grid(-row => 0, -column => 1, -sticky => 'w'); $fr = $mw->Frame(); $fr->grid(-row => 4, -column => 0, -padx => 25, -sticky => 'w'); $coll{'npkts'} = 0; $fr->Checkbutton(-text => 'Capture only', -variable => \$coll{'npkts'} )->grid(-row => 0, -column => 0, -columnspan => 2, -padx => 5, -pady => 5, -sticky => 'w'); $coll{'pkts'} = ''; $fr->Entry(-width => 10, -textvariable => \$coll{'pkts'} )->grid(-row => 0, -column => 2, -sticky => 'w', -columnspan => 2); $fr->Label(-text => 'packets' )->grid(-row => 0, -column => 4, -padx => 5, -sticky => 'w'); $mw->update; $collfr->gridColumnconfigure(0, -minsize => $colllab->width + 10); # 10 = 5 xpad on both sides $mw->update; $mw->geometry($mw->reqwidth() . 'x' . $mw->reqheight()); } sub collmode { if ($coll{'mode'} eq 'normal') { $coll{'labtext'} = '-'; $coll{'collent'}->configure(-textvariable => \$coll{'enttext'}); $coll{'collent'}->configure(-state => 'disabled'); $coll{'collbut'}->configure(-state => 'disabled'); } elsif ($coll{'mode'} eq 'stats') { $coll{'labtext'} = 'Flows'; $coll{'collent'}->configure(-textvariable => \$coll{'flows'}); $coll{'collent'}->configure(-state => 'normal'); $coll{'collbut'}->configure(-state => 'disabled'); } elsif ($coll{'mode'} eq 'log') { $coll{'labtext'} = 'File'; $coll{'collent'}->configure(-textvariable => \$coll{'file'}); $coll{'collent'}->configure(-state => 'normal'); $coll{'collbut'}->configure(-state => 'normal'); } else { print STDERR "Bad option in collmode\n"; } } sub valid_ip { my ($ip) = @_; my @octets = split (/\./, $ip); my $octet; return 0 if @octets != 4; foreach $octet (@octets) { return 0 if ($octet !~ /\d{1,3}/ || $octet > 255); } return 1; } sub launch_crude { my $cmdline; $coll{'bstart'}->configure(-state => 'disabled'); $coll{'bcancel'}->configure(-state => 'disabled'); # Common validation if ($coll{'rbip'} eq 'use' && !valid_ip($coll{'ip'})) { $mw->Dialog(-title => 'Warning', -text => 'Please use a valid IP or choose \'Any\'.', -bitmap => 'warning', -buttons => ['Dismiss'])->Show; goto GOBACK; } if ($coll{'port'} !~ s/^\s*(\d+)\s*$end/$1/ or $coll{'port'} > 65535) { $mw->Dialog(-title => 'Warning', -text => "Port must be a number <= 65535\nDefault port is 10001", -bitmap => 'warning', -buttons => ['Dismiss'])->Show; goto GOBACK; } if ($coll{'srt'} && ($coll{'srtprio'} !~ s/^\s*(\d+)\s*$end/$1/ or $coll{'srtprio'} > 90 or $coll{'srtprio'} == 0)) { $mw->Dialog(-title => 'Warning', -text => "Soft real-time priority must be in range 1-90\nDefault value is 50", -bitmap => 'warning', -buttons => ['Dismiss'])->Show; goto GOBACK; } if ($coll{'npkts'} && ($coll{'pkts'} !~ s/^\s*(\d+)\s*$end/$1/ or $coll{'pkts'} == 0)) { $mw->Dialog(-title => 'Warning', -text => "Please specify a valid number of packets to capture", -bitmap => 'warning', -buttons => ['Dismiss'])->Show; goto GOBACK; } # Mode-specific validation if ($coll{'mode'} eq 'normal') { $cmdline = "crude"; } elsif ($coll{'mode'} eq 'stats') { my $flows = ''; foreach (split (/[,;]/, $coll{'flows'})) { if (! /^\s*(\d+)\s*$end/) { $mw->Dialog(-title => 'Warning', -text => 'Please use a comma-separated list of flows.', -bitmap => 'warning', -buttons => ['Dismiss'])->Show; goto GOBACK; } else { $flows .= "$1\,"; } } chop ($flows); if ("$flows" eq '') { $mw->Dialog(-title => 'Warning', -text => 'List of flows must not be empty!', -bitmap => 'warning', -buttons => ['Dismiss'])->Show; goto GOBACK; } $cmdline = "crude -s $flows"; } elsif ($coll{'mode'} eq 'log') { if (-e $coll{'file'}) { my $option = $mw->Dialog(-title => 'Warning', -text => 'Warning: Log file already exists!', -bitmap => 'warning', -buttons => ['Overwrite', 'Cancel'])->Show; goto GOBACK if ($option eq 'Cancel'); } $cmdline = "crude -l $coll{'file'}"; } else { print STDERR "Bad option in launch_crude\n"; } $cmdline .= " -p $coll{'port'}"; $cmdline .= " -i $coll{'ip'}" if ($coll{'rbip'} eq 'use'); $cmdline .= " -P $coll{'srtprio'}" if ($coll{'srt'}); $cmdline .= " -n $coll{'pkts'}" if ($coll{'npkts'}); launch($cmdline); $coll{'bstart'}->configure(-state => 'normal'); $coll{'bcancel'}->configure(-state => 'normal'); return; GOBACK: # Won't get here unless by a goto $coll{'bstart'}->configure(-state => 'normal'); $coll{'bcancel'}->configure(-state => 'normal'); } sub launch { my $command = $_[0]; my $errw = $mw->Toplevel(-title => 'Output'); $errw->grab; # No problem with grab because we're always called from the main window my $errt = $errw->Scrolled('Text', -scrollbars => 'osoe', -wrap => 'none'); $errt->pack(-expand => 1, -fill => 'both', -padx => 3, -pady => 3); tie(*ERROUT, 'Tk::Text', $errt); my $pid; my $errb = $errw->Button(-text => 'Stop', -command => sub {kill('SIGINT', $pid)}); $errb->pack(-pady => 3); my $fgcolor = $errt->cget('foreground'); $errt->tagConfigure('hilight', -foreground => 'red'); $pid = open3(*IN, *OUT, *ERR, $command); close(IN); $errw->update; # Update only after launching the command my $errors = 0; my $selector = IO::Select->new(); $selector->add(*OUT, *ERR); my $err; my $pos; my @ready; for (;;) { @ready = $selector->can_read(1); foreach $fh (@ready) { if (fileno($fh) == fileno(ERR) && ($err = )) { $pos = $errt->index('insert'); print ERROUT $err; $errt->tagAdd('hilight', $pos, 'insert'); $errors = 1; } else { print ERROUT scalar ; } if (eof($fh)) { $selector->remove($fh); goto OUTAHERE if ($selector->count() == 0); } } $mw->update; } OUTAHERE: close(OUT); close(ERR); close(OUTFILE); waitpid($pid, 0); if (!$errors) { print ERROUT "No errors.\n"; } $errt->configure(-state => 'disabled'); $errb->configure(-text => 'Dismiss', -command => sub {$errw->destroy;}); } sub decoder { foreach $widget ($mw->children) { $widget->destroy } $fchoice = $mw->Frame; $choice = 'plaintext'; $fchoice->Radiobutton(-text => 'Plain text', -variable => \$choice, -value => 'plaintext')->grid(-sticky => 'w'); $fchoice->Radiobutton(-text => 'CSV', -variable => \$choice, -value => 'csv')->grid(-sticky => 'w'); $fchoice->grid(-padx => 20, -pady => 5); $mw->Entry(-textvariable => \$srcfile)->grid(-sticky => 'ew', -padx => 5); $fsb = $mw->Frame; $fsb->Button(-text => 'Select Src', -command => sub {$srcfile = $mw->FileSelect(-directory => '.', -title => 'Source file')->Show} )->grid(-row => 0, -column => 0, -padx => 5, -pady => 5); $fsb->grid(); $mw->Entry(-textvariable => \$decfile)->grid(-sticky => 'ew', -padx => 5); $fb = $mw->Frame; $fb->Button(-text => 'Select Dst', -command => sub {$decfile = $mw->FileSelect(-directory => '.', -title => 'Destination file')->Show} )->grid(-row => 0, -column => 0, -padx => 5, -pady => 5); $fb->Button(-text => 'Start', -command => [\&do_decode, \$choice, \$srcfile, \$decfile])->grid(-row => 0, -column => 1, -padx => 5, -pady => 5); $fb->Button(-text => 'Cancel', -command => \&main)->grid(-row => 0, -column => 2, -padx => 5, -pady => 5); $fb->grid(); $mw->update; $mw->geometry($mw->reqwidth() . 'x' . $mw->reqheight()); } sub do_decode { my $choice = ${$_[0]}; my $src = ${$_[1]}; my $dst = ${$_[2]}; my $process = "process_$choice"; $process = \&$process; if (! $src || ! $dst) { $mw->Dialog(-title => 'Warning', -text => 'Please select source and destination files.', -bitmap => 'warning', -buttons => ['Dismiss'])->Show; return } if (! -e "$src") { $mw->Dialog(-title => 'Warning', -text => 'Warning: source file does not exist!', -bitmap => 'warning', -buttons => ['Dismiss'])->Show; return } if (-e "$dst") { my $option = $mw->Dialog(-title => 'Warning', -text => 'Warning: destination file already exists!', -bitmap => 'warning', -buttons => ['Overwrite', 'Cancel'])->Show; return if ($option eq 'Cancel')} if (! open(OUTFILE, ">$dst")) { $mw->Dialog(-title => 'Error', -text => 'Can\'t open file for writing:' . "\n$!\n", -bitmap => 'warning', -buttons => ['Dismiss'])->Show; return } $errw = $mw->Toplevel(-title => 'Errors'); $errw->grab; my $errt = $errw->Scrolled('Text', -scrollbars => 'osoe', -wrap => 'none'); $errt->pack(-expand => 1, -fill => 'both', -padx => 3, -pady => 3); tie(*ERROUT, 'Tk::Text', $errt); my $errb = $errw->Button(-text => 'Dismiss', -state => 'disabled', -command => sub {$errw->destroy; main()}); $errb->pack(-pady => 3); $errw->update; if ($choice eq "csv") { print OUTFILE "ID,SEQ,SRC_IP,SRC_PRT,DST_IP,DST_PRT,Tx,Rx,SIZE\n"} my $pid = open3(*IN, *OUT, *ERR, "crude -d $src"); close(IN); my $errors = 0; my $selector = IO::Select->new(); $selector->add(*OUT, *ERR); my $err; while (@ready = $selector->can_read) { foreach $fh (@ready) { if (fileno($fh) == fileno(ERR) && ($err = )) { print ERROUT scalar $err; $errors = 1; } else { print OUTFILE &$process(scalar ); } $selector->remove($fh) if eof($fh); } $mw->update; } close(OUT); close(ERR); close(OUTFILE); waitpid($pid, 0); if (!$errors) { print ERROUT "No errors.\n"; } $errt->configure(-state => 'disabled'); $errb->configure(-state => 'normal'); } sub process_plaintext { my $str = shift; $str =~ s/^[^I].*//s; return $str; } sub process_csv { my $str = shift; if ($str =~ s/^ID=(.*) SEQ=(.*) SRC=(.*):(.*) DST=(.*):(.*) Tx=(.*) Rx=(.*) SIZE=(.*)/$1,$2,$3,$4,$5,$6,$7,$8,$9/s) { return $str} return ''; } sub save { my ($confname, $auxname, $traceprefix); if (defined $_[0]) { #print "save: \$_[0] = $_[0]\n"; $confname = $_[0]; $auxname = $_[0].'-aux'; $traceprefix = $_[0]; } else { $confname = 'config'; $auxname = 'aux'; $traceprefix = 'trace'; } if (! open(CONF, ">$confname")) { $mw->Dialog(-title => 'Error', -text => "Can\'t open file $confname for writing:" . "\n$!\n", -bitmap => 'warning', -buttons => ['Dismiss'])->Show; return 0; } print CONF "## This file was generated automatically\n\n"; if ($tx{'start'} eq 'NOW') { print CONF "START NOW\n\n"; } else { printf CONF "START %02d:%02d:%02d\n\n", $tx{'starth'}, $tx{'startm'}, $tx{'starts'}; } my $flows = $fled{'flows'}; my ($flow, $mod, $type); foreach $id (sort { $a <=> $b } keys %{$flows}) { print CONF "## Flow $id\n"; $flow = $$flows{$id}; $mod = ${$$flow{'mods'}}{'initial'}; $type = ${${$$flow{'mods'}}{'initial'}}{'type'}; print CONF "$$flow{'tstart'} $id ON $$flow{'sport'} $$flow{'daddr'}:". "$$flow{'dport'} "; if ($type eq 'CONSTANT') { print CONF "CONSTANT $$mod{'rate'} $$mod{'size'}\n"; } elsif ($type eq 'TRACE') { if (! open(TRACE, ">$traceprefix-$id-initial")) { $mw->Dialog(-title => 'Error', -text => "Can\'t open file $traceprefix-$id-initial for ". "writing:\n$!\n", -bitmap => 'warning', -buttons => ['Dismiss'])->Show; close(CONF); return 0; } my $i = 0; foreach (@{$$mod{'trace'}}) { if ($i % 2) { printf TRACE "%1.06f\n", $_; } else { print TRACE "$_ "; } $i++; } close(TRACE); print CONF "TRACE $traceprefix-$id-initial\n"; } elsif ($type eq 'SILENT') { # Arbitrary size, since no packets will be sent print CONF "CONSTANT 0 64\n"; } else { print STDERR "Bad flow type.\n"; } if ($$flow{'settos'}) { print CONF "TOS $id $$flow{'tos'}\n"; } foreach $modid (sort modsort keys %{$$flow{'mods'}}) { next if $modid eq 'initial'; $mod = ${$$flow{'mods'}}{$modid}; $type = $$mod{'type'}; if ($type eq 'CONSTANT') { print CONF "$modid $id MODIFY CONSTANT $$mod{'rate'} $$mod{'size'}\n"; } elsif ($type eq 'TRACE') { if (! open(TRACE, ">$traceprefix-$id-$modid")) { $mw->Dialog(-title => 'Error', -text => "Can\'t open file $traceprefix-$id-$modid for ". "writing:\n$!\n", -bitmap => 'warning', -buttons => ['Dismiss'])->Show; close CONF; return 0; } my $i = 0; foreach (@{$$mod{'trace'}}) { if ($i % 2) { printf TRACE "%1.06f\n", $_; } else { print TRACE "$_ "; } $i++; } close TRACE; print CONF "$modid $id MODIFY TRACE $traceprefix-$id-$modid\n"; } elsif ($type eq 'SILENT') { # Arbitrary size, since no packets will be sent print CONF "$modid $id MODIFY CONSTANT 0 64\n"; } else { print STDERR "Bad mod type.\n"; } } print CONF "$$flow{'tstop'} $id OFF\n\n"; } close CONF; if (! open(AUX, ">$auxname")) { $mw->Dialog(-title => 'Error', -text => "Can\'t open file $auxname for writing:" . "\n$!\n", -bitmap => 'warning', -buttons => ['Dismiss'])->Show; return 0; } print AUX "VERSION $version\n"; print AUX 'SRTPRIO ' . ($tx{'srt'} ? $tx{'srtprio'} : 'NO') . "\n"; close AUX; return 1; } sub save_file { return 0 if ! valid_txparms(); my $olddir = cwd; my $erro; my $tmpdir = tempdir(CLEANUP => 1); unless (defined $tmpdir) { $erro = 'Can\'t create temporary directory'; goto ERRO; } unless (chdir($tmpdir)) { $erro = 'Can\'t chdir to temporary directory'; goto ERRO; } unless (save) { $erro = 'Can\'t save file components'; goto ERRO; } if (system('tar c * | gzip -c > archive') != 0) { $erro = 'Can\'t create compressed file'; goto ERRO; } chdir($olddir); my $file; if (defined $_[0]) { $file = $_[0]; } else { # This would be better, but it's way too buggy... #$file = $mw->getSaveFile( # -filetypes => [['Grude files','.grd'],['All files','*']], # -defaultextension => '.grd', # -initialdir => '.'); #return 0 if (!defined $file or $file eq ''); # The ugly way: $file = $mw->FileSelect(-directory => '.', -defaultextension => 'grd', -title => 'Save as')->Show; return 0 if (!defined $file or $file eq ''); $file =~ s/(\.grd)?\s*$end/.grd/i; if (-e $file) { my $option = $mw->Dialog(-title => 'Warning', -text => "Warning: $file already exists!", -bitmap => 'warning', -buttons => ['Overwrite', 'Cancel'], -default_button => 'Cancel')->Show; unless ($option eq 'Overwrite') { rmtree($tmpdir); return 0; } } } if (!move("$tmpdir/archive", $file)) { $erro = "Can't save file $file"; goto ERRO; } rmtree($tmpdir); $tx{'file'} = $file; $tx{'unsaved'} = 0; return 1; ERRO: chdir($olddir); rmtree($tmpdir); $mw->Dialog(-title => 'Error', -text => $erro, -bitmap => 'error', -buttons => ['Dismiss'])->Show; return 0; } sub unsaved_changes { return 0 if !$tx{'unsaved'}; my $option = $mw->Dialog(-title => 'Warning', -text => 'There are unsaved changes', -bitmap => 'warning', -buttons => ['Save', 'Don\'t save', 'Cancel'])->Show; return 0 if $option eq 'Don\'t save'; save_file($tx{'file'}) if $option eq 'Save'; return 1; } sub reset_txconf { my $index; $index = $tx{'edflows'}->menu->index('last'); $tx{'edflows'}->menu->delete (0, $index) if $index ne 'none'; $index = $tx{'delflows'}->menu->index('last'); $tx{'delflows'}->menu->delete (0, $index) if $index ne 'none'; return if unsaved_changes(); $tx{'srt'} = 0; $tx{'srtprio'} = 50; $tx{'start'} = 'NOW'; $tx{'starth'} = '00'; $tx{'startm'} = '00'; $tx{'starts'} = '00'; $fled{'flows'} = {}; undef $tx{'file'}; $tx{'unsaved'} = 0; } sub open_file { return if unsaved_changes(); my $file; if (defined $_[0]) { $file = $_[0]; if (!-r $file or -d $file) { $mw->Dialog(-title => 'Error', -text => "File $file does not exist, is not readable or is a directory.", -bitmap => 'error', -buttons => ['Dismiss'])->Show; return 0; } } else { $file = $mw->FileSelect(-directory => '.', -defaultextension => 'grd', -title => 'Open', -verify => [ '-r', '!-d' ])->Show; return 0 if !defined $file or $file eq ''; } my $erro; my $tmpdir = tempdir(CLEANUP => 1); unless (defined $tmpdir) { $erro = 'Can\'t create temporary directory'; goto ERRO; } if (system("tar xfz $file -C $tmpdir") != 0) { $erro = 'Can\'t uncompress file to temporary directory'; goto ERRO; } $tx{'unsaved'} = 0; reset_txconf(); # Do the parsing unless (open(AUX, "$tmpdir/aux")) { $erro = 'Can\'t read aux parameters'; goto ERRO; } while () { chomp; if (/^\s*VERSION\s+(\S+)\s*$end/) { my $filever = $1; if ($filever ne $version) { $erro = "Can't open version $filever files"; goto ERRO; } } elsif (/^\s*SRTPRIO\s+(\S+)\s*$end/) { if ("$1" ne 'NO') { $tx{'srt'} = 1; $tx{'srtprio'} = $1; } } } close(AUX); unless (open(CONFIG, "$tmpdir/config")) { $erro = 'Can\'t read config parameters'; goto ERRO; } my ($flow, $start, $stime, $otime, $id, $sport, $daddr, $dport, $type, $parms, $rate, $size, $tracefile, $mtime); while () { chomp; if (/^\s*(\S+)\s+(\S+)\s+ON\s+(\S+)\s+(\S+):(\S+)\s+(\S+)\s*(.*)/) { $stime = $1; $id = $2; $sport = $3; $daddr = $4; $dport = $5; $type = $6; $parms = $7; # print "$stime | $id | $sport | $daddr | $dport | $type | $parms\n"; $flow = +{ 'tstart' => $stime, 'daddr' => $daddr, 'dport' => $dport, 'sport' => $sport, 'settos' => 0, 'tos' => 0 }; if ($type eq 'CONSTANT') { ($rate, $size) = split(/\s+/, $parms, 2); if ($rate == 0) { $$flow{'mods'} = +{'initial' => {'type' => 'SILENT'}}; } else { $$flow{'mods'} = +{ 'initial' => { 'type' => $type, 'rate' => $rate, 'size' => $size } }; } } elsif ($type eq 'TRACE') { $parms =~ s/\s*$end//; $tracefile = "$tmpdir/$parms"; $$flow{'mods'} = +{ 'initial' => { 'type' => $type, 'trace' => parse_trace($tracefile, $id, 'initial') } }; } ${$fled{'flows'}}{$id} = $flow; } elsif (/^\s*(\S+)\s+(\S+)\s+MODIFY\s+(\S+)\s*(.*)/) { $mtime = $1; $id = $2; $type = $3; $parms = $4; if ($type eq 'CONSTANT') { ($rate, $size) = split(/\s+/, $parms, 2); if ($rate == 0) { ${${${$fled{'flows'}}{$id}}{'mods'}}{$mtime} = {'type' => 'SILENT'} } else { ${${${$fled{'flows'}}{$id}}{'mods'}}{$mtime} = {'type' => $type, 'rate' => $rate, 'size' => $size}; } } elsif ($type eq 'TRACE') { $parms =~ s/\s*$end//; $tracefile = "$tmpdir/$parms"; ${${${$fled{'flows'}}{$id}}{'mods'}}{$mtime} = +{ 'type' => $type, 'trace' => parse_trace($tracefile, $id, $mtime) }; } } elsif (/^\s*(\S+)\s+(\S+)\s+OFF\s*$end/) { $otime = $1; $id = $2; ${${$fled{'flows'}}{$id}}{'tstop'} = $otime; } elsif (/^\s*TOS\s+(\S+)\s+(\S+)\s*$end/) { $id = $1; $tos = $2; ${${$fled{'flows'}}{$id}}{'settos'} = 1; ${${$fled{'flows'}}{$id}}{'tos'} = $tos; } elsif (/^\s*START\s+(\S+)\s*$end/) { if ("$1" ne 'NOW') { $start = $1; $tx{'start'} = 'AT'; ($tx{'starth'}, $tx{'startm'}, $tx{'starts'}) = split (/:/, $start, 3); } } } close(CONFIG); my $index = 0; foreach $id (sort { $a <=> $b } keys %{$fled{'flows'}}) { $tx{'edflows'}->menu->insert($index, 'command', -label => $id, -command => [\&editflow, $id]); $tx{'delflows'}->menu->insert($index, 'command', -label => $id, -command => [\&delflow, $id]); $index++; } $tx{'file'} = $file; $tx{'unsaved'} = 0; rmtree($tmpdir); return 1; ERRO: rmtree($tmpdir); $mw->Dialog(-title => 'Error', -text => $erro, -bitmap => 'error', -buttons => ['Dismiss'])->Show; return 0; } sub parse_trace { my ($tracefile, $id, $mtime) = @_; my @trace = (); unless (open(TRACE, $tracefile)) { $erro = "Can\'t read trace for flow $id at time $mtime"; goto ERRO; } my ($size, $wait); while () { chomp; if (/^\s*(\S+)\s+(\S+)\s*$end/) { $size = $1; $wait = $2; push(@trace, ($size, $wait)); } } close(TRACE); return \@trace; ERRO: $mw->Dialog(-title => 'Error', -text => $erro, -bitmap => 'error', -buttons => ['Dismiss'])->Show; return undef; } sub launch_rude { return if ! valid_txparms(); my $olddir = cwd; my $erro; my $tmpdir = tempdir(CLEANUP => 1); unless (defined $tmpdir) { $erro = 'Can\'t create temporary directory'; goto ERRO; } unless (chdir($tmpdir)) { $erro = 'Can\'t chdir to temporary directory'; goto ERRO; } unless (save) { $erro = 'Can\'t save temporary configuration files'; goto ERRO; } my $command = 'rude -s config'; $command .= " -P $tx{'srtprio'}" if $tx{'srt'}; launch($command); chdir($olddir); rmtree($tmpdir); return; ERRO: chdir($olddir); rmtree($tmpdir); $mw->Dialog(-title => 'Error', -text => $erro, -bitmap => 'error', -buttons => ['Dismiss'])->Show; return; }