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(); |