File Coverage

blib/lib/OSPF/LSDB.pm
Criterion Covered Total %
statement 44 45 97.7
branch 18 24 75.0
condition 2 2 100.0
subroutine 10 10 100.0
pod 5 6 83.3
total 79 87 90.8


line stmt bran cond sub pod time code
1             #########################################################################
2             # Copyright (c) 2010-2021 Alexander Bluhm
3             #
4             # Permission to use, copy, modify, and distribute this software for any
5             # purpose with or without fee is hereby granted, provided that the above
6             # copyright notice and this permission notice appear in all copies.
7             #
8             # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9             # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10             # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11             # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12             # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13             # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14             # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15             ##########################################################################
16              
17 40     40   87013 use strict;
  40         71  
  40         884  
18 40     40   148 use warnings;
  40         53  
  40         1219  
19              
20             =pod
21              
22             =head1 NAME
23              
24             OSPF::LSDB - class containing OSPF link state database
25              
26             =head1 SYNOPSIS
27              
28             use OSPF::LSDB;
29              
30             my $ospf = OSPF::LSDB-Enew();
31              
32             =head1 DESCRIPTION
33              
34             The OSPF::LSDB module serves as base class for all the other
35             OSPF::LSDB::... modules.
36             It contains the link state database, can do versioning and validation.
37             To parse, load, store or display the LSDB, convert it into instances
38             of more specific classes.
39             The new() method implements a copy constructor to share LSDB
40             references with minimal overhead.
41              
42             Most names of the database fields are named after RFC 2328 - OSPF
43             version 2.
44             Keys of RFC 5340 - OSPF for IPv6 with a comparable semantics in
45             version 2, have kept their old names to allow code reuse.
46              
47             =cut
48              
49             package OSPF::LSDB;
50 40     40   16955 use Data::Validate::Struct;
  40         7795305  
  40         4718  
51 40     40   24258 use fields qw(ospf errors ssh);
  40         48064  
  40         190  
52              
53             =pod
54              
55             =over
56              
57             =item $OSPF::LSDB::VERSION
58              
59             The version number of the OSPF::LSDB modules.
60             It is also stored in the database dump and thus can be used to
61             upgrade the file.
62              
63             =cut
64              
65             our $VERSION = '1.16';
66              
67             =pod
68              
69             =item OSPF::LSDB-Enew($other)
70              
71             Construct a OSPF::LSDB object.
72             If another OSPF::LSDB object is passed as argument, the new object
73             is contructed with the other's objects database as reference.
74             This can be used to convert between different OSPF::LSDB::...
75             instances and use their specific features.
76              
77             =cut
78              
79             sub new {
80 401     401 1 614344 my OSPF::LSDB $self = shift;
81 401 100       2280 my $other = shift if UNIVERSAL::isa($_[0], "OSPF::LSDB");
82 401         944 my %args = @_;
83 401 50       2158 $self = fields::new($self) unless (ref $self);
84 401         149909 $self->{ospf}{version} = $VERSION;
85 401 100       1401 $self->{ospf} = $other->{ospf} if $other; # copy constructor
86 401         939 $self->{ssh} = $args{ssh};
87 401         942 return $self;
88             }
89              
90             # error() is an internal method to collect errors
91              
92             sub error {
93 251     251 0 375 my OSPF::LSDB $self = shift;
94 251         301 push @{$self->{errors}}, @_;
  251         684  
95             }
96              
97             =pod
98              
99             =item $self-Eget_errors()
100              
101             Returns a list with all errors occured so far.
102             The internal error array gets reset.
103              
104             =cut
105              
106             sub get_errors {
107 175     175 1 936 my OSPF::LSDB $self = shift;
108 175 100       246 my @errors = @{delete $self->{errors} || []};
  175         697  
109              
110 175         536 return @errors;
111             }
112              
113             =pod
114              
115             =item $self-Eipv6()
116              
117             Returns true if the ospf database is version 3 for ipv6.
118              
119             =cut
120              
121             sub ipv6 {
122 2429     2429 1 2656 my OSPF::LSDB $self = shift;
123              
124 2429         7962 return $self->{ospf}{ipv6};
125             }
126              
127             # %CONVERTER is a hash of methods to do the version upgrade step by step
128              
129             my %CONVERTER = (
130             0.1 => sub {
131             my OSPF::LSDB $self = shift;
132             foreach my $e (@{$self->{ospf}{database}{externals}}) {
133             $e->{forward} ||= "0.0.0.0";
134             }
135             $self->{ospf}{version} = 0.1;
136             },
137             0.2 => sub {
138             my OSPF::LSDB $self = shift;
139             foreach my $r (@{$self->{ospf}{database}{routers}}) {
140             foreach my $b (qw(B E V)) {
141             $r->{bits}{$b} ||= 0;
142             }
143             }
144             $self->{ospf}{version} = 0.2;
145             },
146             0.3 => sub {
147             my OSPF::LSDB $self = shift;
148             foreach my $r (@{$self->{ospf}{database}{routers}}) {
149             my $links = delete($r->{links}) || [];
150             foreach my $l (@$links) {
151             my $type = delete $l->{type}
152             or die "No type in link";
153             push @{$r->{$type.'s'}}, $l;
154             }
155             }
156             $self->{ospf}{version} = 0.3;
157             },
158             0.4 => sub {
159             my OSPF::LSDB $self = shift;
160             foreach my $r (@{$self->{ospf}{database}{routers}}) {
161             $r->{router} ||= $self->{ospf}{ipv6} ? "0.0.0.0" : $r->{routerid};
162             }
163             $self->{ospf}{version} = 0.4;
164             },
165             0.5 => sub {
166             my OSPF::LSDB $self = shift;
167             $self->{ospf}{ipv6} //= 0;
168             if ($self->{ospf}{ipv6}) {
169             foreach my $r (@{$self->{ospf}{database}{routers}}) {
170             $r->{bits}{W} ||= 0;
171             }
172             }
173             $self->{ospf}{version} = 0.5;
174             },
175             1.0 => sub {
176             my OSPF::LSDB $self = shift;
177             $self->{ospf}{version} = 1.0;
178             },
179             1.1 => sub {
180             my OSPF::LSDB $self = shift;
181             $self->{ospf}{version} = 1.1;
182             },
183             );
184              
185             =pod
186              
187             =item $self-Econvert()
188              
189             While the version of the current database is less than the module
190             version, apply the conversion functions step by step.
191             The conversion fails if the final major and minor numbers
192             of the database version do not match the module version.
193             The patchlevel is ignored.
194              
195             =cut
196              
197             sub convert {
198 191     191 1 405 my OSPF::LSDB $self = shift;
199 191   100     1328 $self->{ospf}{version} ||= 0.0;
200 191 100       729 return if $self->{ospf}{version} eq $VERSION;
201 190         1487 foreach my $ver (sort keys %CONVERTER) {
202 1330 100       4358 $CONVERTER{$ver}->($self) if $self->{ospf}{version} < $ver;
203             }
204 190 50       1043 if (int(10 * $self->{ospf}{version}) == int(10 * $VERSION)) {
205 190         495 $self->{ospf}{version} = $VERSION
206             } else {
207 0         0 die "Converison incomplete.\n";
208             }
209             }
210              
211             # $VALIDATOR is a Data::Validate::Struct reference
212             # As Data::Validate::Struct does not support optional fields,
213             # split it into a common part and ipv4 and ipv6 only parts.
214             # The fields age and sequence are not used and not validated.
215              
216             # common for ipv4 and ipv6
217             my $VALIDATOR46 = {
218             database => {
219             boundarys => [ {
220             area => 'ipv4',
221             asbrouter => 'ipv4',
222             metric => 'int',
223             routerid => 'ipv4',
224             }, {}, ],
225             externals => [ {
226             address => 'ipv4',
227             metric => 'int',
228             routerid => 'ipv4',
229             type => 'int',
230             }, {}, ],
231             networks => [ {
232             address => 'ipv4',
233             area => 'ipv4',
234             attachments => [ {
235             routerid => 'ipv4',
236             }, {} ],
237             routerid => 'ipv4',
238             }, {}, ],
239             routers => [ {
240             area => 'ipv4',
241             bits => {
242             B => 'int',
243             E => 'int',
244             V => 'int',
245             },
246             pointtopoints => [ {
247             interface => 'ipv4',
248             metric => 'int',
249             routerid => 'ipv4',
250             }, {} ],
251             transits => [ {
252             address => 'ipv4',
253             interface => 'ipv4',
254             metric => 'int',
255             }, {} ],
256             virtuals => [ {
257             interface => 'ipv4',
258             metric => 'int',
259             routerid => 'ipv4',
260             }, {} ],
261             router => 'ipv4',
262             routerid => 'ipv4',
263             }, {}, ],
264             summarys => [ {
265             address => 'ipv4',
266             area => 'ipv4',
267             metric => 'int',
268             routerid => 'ipv4',
269             }, {}, ],
270             },
271             ipv6 => 'int',
272             self => {
273             areas => [ 'ipv4', '' ],
274             routerid => 'ipv4',
275             },
276             version => 'number',
277             };
278              
279             # ipv4 only
280             my $VALIDATOR = {
281             database => {
282             externals => [ {
283             forward => 'ipv4',
284             netmask => 'ipv4',
285             }, {}, ],
286             networks => [ {
287             netmask => 'ipv4',
288             }, {}, ],
289             routers => [ {
290             stubs => [ {
291             metric => 'int',
292             netmask => 'ipv4',
293             network => 'ipv4',
294             }, {} ],
295             }, {}, ],
296             summarys => [ {
297             netmask => 'ipv4',
298             }, {}, ],
299             },
300             };
301              
302             # ipv6 only
303             my $VALIDATOR6 = {
304             database => {
305             boundarys => [ {
306             address => 'ipv4',
307             }, {}, ],
308             externals => [ {
309             prefixaddress => 'ipv6',
310             prefixlength => 'int',
311             }, {}, ],
312             intranetworks => [ {
313             address => 'ipv4',
314             area => 'ipv4',
315             interface => 'ipv4',
316             prefixes => [ {
317             prefixaddress => 'ipv6',
318             prefixlength => 'int',
319             }, {}, ],
320             router => 'ipv4',
321             routerid => 'ipv4',
322             }, {}, ],
323             intrarouters => [ {
324             address => 'ipv4',
325             area => 'ipv4',
326             interface => 'ipv4',
327             prefixes => [ {
328             prefixaddress => 'ipv6',
329             prefixlength => 'int',
330             }, {}, ],
331             router => 'ipv4',
332             routerid => 'ipv4',
333             }, {}, ],
334             links => [ {
335             area => 'ipv4',
336             interface => 'ipv4',
337             linklocal => 'ipv6',
338             prefixes => [ {
339             prefixaddress => 'ipv6',
340             prefixlength => 'int',
341             }, {}, ],
342             routerid => 'ipv4',
343             }, {}, ],
344             routers => [ {
345             bits => {
346             W => 'int',
347             },
348             pointtopoints => [ {
349             address => 'ipv4',
350             }, {} ],
351             transits => [ {
352             routerid => 'ipv4',
353             }, {} ],
354             virtuals => [ {
355             address => 'ipv4',
356             }, {} ],
357             }, {}, ],
358             summarys => [ {
359             prefixaddress => 'ipv6',
360             prefixlength => 'int',
361             }, {}, ],
362             },
363             };
364              
365             =pod
366              
367             =item $self-Evalidate()
368              
369             Ensure that the internal data structure has the correct version and
370             is valid.
371              
372             =back
373              
374             =cut
375              
376             sub validate {
377 195     195 1 404 my OSPF::LSDB $self = shift;
378 195         369 my $ospf = $self->{ospf};
379              
380 195 50       418 die "Ospf not set.\n" unless $ospf;
381 195 50       479 die "No version.\n" unless $ospf->{version};
382 195 50       621 die "Bad version: $ospf->{version}\n" unless $ospf->{version} eq $VERSION;
383              
384 195 100       590 foreach my $vtor ($VALIDATOR46, $self->ipv6 ? $VALIDATOR6 : $VALIDATOR) {
385 390         1659279 my $dvs = Data::Validate::Struct->new($vtor);
386 390 50       1165558 die "Ospf invalid: ", $dvs->errstr(), "\n" unless $dvs->validate($ospf);
387             }
388             }
389              
390             =pod
391              
392             =head1 ERRORS
393              
394             The methods die if any error occures.
395              
396             =head1 SEE ALSO
397              
398             L,
399             L,
400             L,
401             L,
402             L
403              
404             L
405              
406             RFC 2328 - OSPF Version 2 - April 1998
407              
408             RFC 5340 - OSPF for IPv6 - July 2008
409              
410             =head1 AUTHORS
411              
412             Alexander Bluhm
413              
414             =cut
415              
416             1;