File Coverage

script/ospfview
Criterion Covered Total %
statement 46 146 31.5
branch 1 66 1.5
condition 0 27 0.0
subroutine 15 31 48.3
pod n/a
total 62 270 22.9


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   4211855 use strict;
  1         10  
  1         61  
20 1     1   11 use warnings;
  1         1  
  1         86  
21 1     1   640 use File::Temp;
  1         14254  
  1         67  
22 1     1   516 use Getopt::Long qw(:config posix_default bundling);
  1         7585  
  1         4  
23 1     1   581 use IPC::Open2;
  1         2465  
  1         40  
24 1     1   358 use POSIX;
  1         4930  
  1         4  
25 1     1   2359 use Time::HiRes qw(time sleep);
  1         1147  
  1         3  
26 1     1   518 use OSPF::LSDB::ospfd;
  1         3  
  1         29  
27 1     1   377 use OSPF::LSDB::ospf6d;
  1         3  
  1         32  
28 1     1   551 use OSPF::LSDB::View;
  1         2  
  1         29  
29 1     1   499 use OSPF::LSDB::View6;
  1         3  
  1         28  
30 1     1   358 use OSPF::LSDB::YAML;
  1         3  
  1         1148  
31              
32             sub usage(@) {
33 1 50   1   37 print STDERR "Error: @_\n" if @_;
34 1         49 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         150 exit(2);
60             }
61              
62             sub main() {
63 1     1   2 my $diff;
64             my $dump;
65 1         2 my $interval = 5;
66 1         3 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   920 '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       15 ) 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{INT} = sub {
110 0     0     local $!;
111 0 0         kill SIGTERM, $pid if $pid;
112 0           $SIG{'INT'} = 'DEFAULT';
113 0           $term = 1;
114 0           kill SIGINT, $$;
115 0           };
116             $SIG{CHLD} = sub {
117 0 0   0     if ($pid) {
118 0           local ($!, $?);
119 0 0         if (waitpid($pid, POSIX::WNOHANG) > 0) {
120 0 0 0       die "'@cmd' failed: $?" if $? &&
      0        
121             ((WIFEXITED($?) && WEXITSTATUS($?) != 0) ||
122             (WIFSIGNALED($?) && WTERMSIG($?) != SIGTERM));
123 0           undef $pid;
124 0           $gone = 1;
125             }
126             }
127 0           };
128              
129 0 0         my $class = $ipv6 ? 'OSPF::LSDB::View6' : 'OSPF::LSDB::View';
130 0 0         if ($legend) {
131 0           my $dot = $class->legend();
132 0           $gone = 0;
133 0 0         $pid = open2(undef, $fh, @cmd)
134             or die "Open pipe to '@cmd' failed: $!";
135 0           print $fh $dot, "\n";
136 0 0         close($fh)
137             or die "Close pipe to '@cmd' failed: $!";
138 0           pause();
139 0           exit 0;
140             }
141              
142 0           my($oldtime, $oldyaml);
143 0           for (;;) {
144 0           my $time = time();
145 0 0         if ($oldtime) {
146 0           my $sleeptime = $interval - ($time - $oldtime);
147 0 0         if ($sleeptime > 0) {
148 0           select(undef, undef, undef, $sleeptime);
149             }
150             }
151 0           $oldtime = time();
152              
153 0 0         my $ospfclass = $ipv6 ? 'OSPF::LSDB::ospf6d' : 'OSPF::LSDB::ospfd';
154 0           my $ospf = $ospfclass->new(ssh => $ssh);
155 0           eval { $ospf->parse(); };
  0            
156 0 0         if ($@) {
157 0           warn $@;
158 0 0         kill SIGTERM, $pid if $pid;
159 0           next;
160             }
161              
162 0           my $yamlospf = OSPF::LSDB::YAML->new($ospf);
163 0 0 0       if (defined $ipv6 && $ipv6 != $yamlospf->ipv6()) {
164 0           die "Address family does not match -4 and -6 options.\n";
165             }
166 0           my $yaml = $yamlospf->Dump();
167 0           $yaml =~ s/^\s+(age|sequence): .*$//mg;
168 0 0 0       next if ($oldyaml && $oldyaml eq $yaml && ! $gone) || $term;
      0        
      0        
169 0 0         if ($dump) {
170 0           print $yaml;
171             }
172 0 0 0       if ($diff && $oldyaml) {
173 0           my %args = (
174             SUFFIX => ".yaml",
175             TEMPLATE => "ospfview-XXXXXXXXXX",
176             TMPDIR => 1,
177             UNLINK => 1
178             );
179 0           my $old = File::Temp->new(%args);
180 0           print $old $oldyaml;
181 0           my $new = File::Temp->new(%args);
182 0           print $new $yaml;
183 0           system('diff', '-up', $old->filename, $new->filename);
184             }
185 0           $oldyaml = $yaml;
186              
187 0           my $view = $class->new($ospf);
188 0           my $dot = $view->graph(%todo);
189 0 0         if ($todo{verbose}) {
190 0           my @errors = $view->get_errors;
191 0 0         print map { "$_\n" } @errors, "" if @errors;
  0            
192             }
193              
194 0           my $chldsigset = POSIX::SigSet->new( &POSIX::SIGCHLD );
195 0           my $oldsigset = POSIX::SigSet->new();
196 0 0         sigprocmask(POSIX::SIG_BLOCK, $chldsigset, $oldsigset)
197             or die "Block sigprocmask failed: $!";
198 0 0         if ($pid) {
199 0           kill SIGTERM, $pid;
200 0 0         if (waitpid($pid, 0) > 0) {
201 0 0 0       die "'@cmd' failed: $?" if $? &&
      0        
202             ((WIFEXITED($?) && WEXITSTATUS($?) != 0) ||
203             (WIFSIGNALED($?) && WTERMSIG($?) != SIGTERM));
204 0           undef $pid;
205 0           $gone = 1;
206             }
207             }
208 0 0         sigprocmask(POSIX::SIG_SETMASK, $oldsigset, undef)
209             or die "Setmask sigprocmask failed: $!";
210              
211 0           $gone = 0;
212 0 0         $pid = open2(undef, $fh, @cmd)
213             or die "Open pipe to '@cmd' failed: $!";
214 0           print $fh $dot, "\n";
215 0 0         close($fh)
216             or die "Close pipe to '@cmd' failed: $!";
217             }
218             }
219              
220             main();