File Coverage

script/ospfview
Criterion Covered Total %
statement 46 150 30.6
branch 1 68 1.4
condition 0 27 0.0
subroutine 15 32 46.8
pod n/a
total 62 277 22.3


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             ##########################################################################
4             # Copyright (c) 2012-2022 Alexander Bluhm
5             #
6             # Permission to use, copy, modify, and distribute this software for any
7             # purpose with or without fee is hereby granted, provided that the above
8             # copyright notice and this permission notice appear in all copies.
9             #
10             # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11             # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12             # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13             # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14             # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15             # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16             # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17             ##########################################################################
18              
19 1     1   4790065 use strict;
  1         5  
  1         74  
20 1     1   20 use warnings;
  1         13  
  1         97  
21 1     1   813 use File::Temp;
  1         17731  
  1         70  
22 1     1   602 use Getopt::Long qw(:config posix_default bundling);
  1         9158  
  1         6  
23 1     1   913 use IPC::Open2;
  1         3030  
  1         59  
24 1     1   520 use POSIX;
  1         6145  
  1         6  
25 1     1   3110 use Time::HiRes qw(time sleep);
  1         1505  
  1         4  
26 1     1   671 use OSPF::LSDB::ospfd;
  1         3  
  1         42  
27 1     1   620 use OSPF::LSDB::ospf6d;
  1         5  
  1         52  
28 1     1   808 use OSPF::LSDB::View;
  1         3  
  1         46  
29 1     1   719 use OSPF::LSDB::View6;
  1         5  
  1         46  
30 1     1   537 use OSPF::LSDB::YAML;
  1         4  
  1         1376  
31              
32             sub usage(@) {
33 1 50   1   42 print STDERR "Error: @_\n" if @_;
34 1         59 print STDERR <
35             Periodically poll OSPF database from routing daemon and display it on X11.
36              
37             Usage: $0 [-46bBcdDeEhlpPsSwWv] [-H user\@host] [-I interval]
38             -4 disable IPv6
39             -6 enable IPv6
40             -b generate other area AS boundary router summary
41             -B aggregate other area AS boundary router summary
42             -c cluster identical networks
43             -d show OSPF database diff between updates
44             -D dump OSPF database after updates as YAML to stdout
45             -e generate AS external networks
46             -E aggregate AS external networks
47             -h help, print usage
48             -H user\@host use ssh to login into user\@host to run ospfctl there
49             -I interval query interval in seconds, default 5
50             -l generate legend
51             -p generate link and intra-area-prefix
52             -P generate intra-area-prefix
53             -s generate other area network summary
54             -S aggregate other area network summary
55             -w show most serious warning in dot graph
56             -W show all warnings and areas in dot graph
57             -v be verbose, print warnings to stdout
58             EOF
59 1         212 exit(2);
60             }
61              
62             sub main() {
63 1     1   4 my $diff;
64             my $dump;
65 1         1 my $interval = 5;
66 1         5 my $ipv6;
67             my $legend;
68 1         0 my $ssh;
69 1         0 my %todo;
70             GetOptions(
71 0     0   0 '4' => sub { $ipv6 = 0 },
72 0     0   0 '6' => sub { $ipv6 = 1 },
73 0     0   0 'b' => sub { $todo{boundary}{generate} = 1 },
74 0     0   0 'B' => sub { $todo{boundary}{aggregate} = 1 },
75 0     0   0 'c' => sub { $todo{cluster} = 1 },
76             'd' => \$diff,
77             'D' => \$dump,
78 0     0   0 'e' => sub { $todo{external}{generate} = 1 },
79 0     0   0 'E' => sub { $todo{external}{aggregate} = 1 },
80 1     1   1090 'h' => sub { usage() },
81             'H=s' => \$ssh,
82             'I=i' => \$interval,
83             'l' => \$legend,
84 0     0     'p' => sub { $todo{prefix}{generate} = 1 },
85 0     0     'P' => sub { $todo{prefix}{aggregate} = 1 },
86 0     0     's' => sub { $todo{summary}{generate} = 1 },
87 0     0     'S' => sub { $todo{summary}{aggregate} = 1 },
88 0     0     'w' => sub { $todo{warning}{single} = 1 },
89 0     0     'W' => sub { $todo{warning}{all} = 1 },
90 0     0     'v' => sub { $todo{verbose} = 1 },
91 1 0       17 ) or usage("Bad option");
92 0 0         usage("No arguments allowed") if @ARGV > 0;
93              
94 0           foreach my $option (qw(boundary external prefix summary warning)) {
95 0 0         if (keys %{$todo{$option} || {}} > 1) {
  0 0          
96 0           my $opt = substr($option, 0, 1);
97 0           usage("Options -$opt and -".uc($opt)." used together");
98             }
99             }
100              
101 0 0         if ($todo{prefix}) {
102 0           $todo{intra}{generate} = 1;
103 0 0         $todo{link}{generate} = 1 if $todo{prefix}{generate};
104             }
105              
106 0           my @cmd = qw(dot -Txlib);
107 0           my($pid, $fh, $gone, $term);
108 0           $term = 0;
109             $SIG{TERM} = sub {
110 0     0     local $!;
111 0 0         kill SIGTERM, $pid if $pid;
112 0           $term = 1;
113 0           };
114             $SIG{INT} = sub {
115 0     0     local $!;
116 0 0         kill SIGTERM, $pid if $pid;
117 0           $SIG{'INT'} = 'DEFAULT';
118 0           $term = 1;
119 0           kill SIGINT, $$;
120 0           };
121             $SIG{CHLD} = sub {
122 0 0   0     if ($pid) {
123 0           local ($!, $?);
124 0 0         if (waitpid($pid, POSIX::WNOHANG) > 0) {
125 0 0 0       die "'@cmd' failed: $?" if $? &&
      0        
126             ((WIFEXITED($?) && WEXITSTATUS($?) != 0) ||
127             (WIFSIGNALED($?) && WTERMSIG($?) != SIGTERM));
128 0           undef $pid;
129 0           $gone = 1;
130             }
131             }
132 0           };
133              
134 0 0         my $class = $ipv6 ? 'OSPF::LSDB::View6' : 'OSPF::LSDB::View';
135 0 0         if ($legend) {
136 0           my $dot = $class->legend();
137 0           $gone = 0;
138 0 0         $pid = open2(undef, $fh, @cmd)
139             or die "Open pipe to '@cmd' failed: $!";
140 0           print $fh $dot, "\n";
141 0 0         close($fh)
142             or die "Close pipe to '@cmd' failed: $!";
143 0           pause();
144 0           exit 0;
145             }
146              
147 0           my($oldtime, $oldyaml);
148 0           until ($term) {
149 0           my $time = time();
150 0 0         if ($oldtime) {
151 0           my $sleeptime = $interval - ($time - $oldtime);
152 0 0         if ($sleeptime > 0) {
153 0           select(undef, undef, undef, $sleeptime);
154             }
155             }
156 0           $oldtime = time();
157              
158 0 0         my $ospfclass = $ipv6 ? 'OSPF::LSDB::ospf6d' : 'OSPF::LSDB::ospfd';
159 0           my $ospf = $ospfclass->new(ssh => $ssh);
160 0           eval { $ospf->parse(); };
  0            
161 0 0         if ($@) {
162 0           warn $@;
163 0 0         kill SIGTERM, $pid if $pid;
164 0           next;
165             }
166              
167 0           my $yamlospf = OSPF::LSDB::YAML->new($ospf);
168 0 0 0       if (defined $ipv6 && $ipv6 != $yamlospf->ipv6()) {
169 0           die "Address family does not match -4 and -6 options.\n";
170             }
171 0           my $yaml = $yamlospf->Dump();
172 0           $yaml =~ s/^\s+(age|sequence): .*$//mg;
173 0 0 0       next if ($oldyaml && $oldyaml eq $yaml && ! $gone) || $term;
      0        
      0        
174 0 0         if ($dump) {
175 0           print $yaml;
176             }
177 0 0 0       if ($diff && $oldyaml) {
178 0           my %args = (
179             SUFFIX => ".yaml",
180             TEMPLATE => "ospfview-XXXXXXXXXX",
181             TMPDIR => 1,
182             UNLINK => 1
183             );
184 0           my $old = File::Temp->new(%args);
185 0           print $old $oldyaml;
186 0           my $new = File::Temp->new(%args);
187 0           print $new $yaml;
188 0           system('diff', '-up', $old->filename, $new->filename);
189             }
190 0           $oldyaml = $yaml;
191              
192 0           my $view = $class->new($ospf);
193 0           my $dot = $view->graph(%todo);
194 0 0         if ($todo{verbose}) {
195 0           my @errors = $view->get_errors;
196 0 0         print map { "$_\n" } @errors, "" if @errors;
  0            
197             }
198              
199 0           my $chldsigset = POSIX::SigSet->new( &POSIX::SIGCHLD );
200 0           my $oldsigset = POSIX::SigSet->new();
201 0 0         sigprocmask(POSIX::SIG_BLOCK, $chldsigset, $oldsigset)
202             or die "Block sigprocmask failed: $!";
203 0 0         if ($pid) {
204 0           kill SIGTERM, $pid;
205 0 0         if (waitpid($pid, 0) > 0) {
206 0 0 0       die "'@cmd' failed: $?" if $? &&
      0        
207             ((WIFEXITED($?) && WEXITSTATUS($?) != 0) ||
208             (WIFSIGNALED($?) && WTERMSIG($?) != SIGTERM));
209 0           undef $pid;
210 0           $gone = 1;
211             }
212             }
213 0 0         sigprocmask(POSIX::SIG_SETMASK, $oldsigset, undef)
214             or die "Setmask sigprocmask failed: $!";
215              
216 0           $gone = 0;
217 0 0         $pid = open2(undef, $fh, @cmd)
218             or die "Open pipe to '@cmd' failed: $!";
219 0           print $fh $dot, "\n";
220 0 0         close($fh)
221             or die "Close pipe to '@cmd' failed: $!";
222             }
223             }
224              
225             main();