File Coverage

blib/lib/OSPF/LSDB/ospf6d.pm
Criterion Covered Total %
statement 245 247 99.1
branch 230 236 97.4
condition 9 9 100.0
subroutine 17 17 100.0
pod 2 10 20.0
total 503 519 96.9


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 11     11   517766 use strict;
  11         79  
  11         301  
18 11     11   54 use warnings;
  11         21  
  11         481  
19              
20             =pod
21              
22             =head1 NAME
23              
24             OSPF::LSDB::ospf6d - parse OpenBSD B link state database
25              
26             =head1 SYNOPSIS
27              
28             use OSPF::LSDB::ospf6d;
29              
30             my $ospf6d = OSPF::LSDB::ospf6d-Enew();
31              
32             my $ospf6d = OSPF::LSDB::ospf6d-Enew(ssh => "user@host");
33              
34             $ospf6d-Eparse(%files);
35              
36             =head1 DESCRIPTION
37              
38             The OSPF::LSDB::ospf6d module parses the output of OpenBSD
39             B and fills the L base object.
40             The output of
41             C,
42             C,
43             C,
44             C,
45             C,
46             C
47             C
48             C
49             is needed.
50             It can be given as separate files or obtained dynamically.
51             In the latter case B is invoked if permissions are not
52             sufficient to run B.
53             If the object has been created with the C argument, the specified
54             user and host are used to login and run B there.
55              
56             There is only one public method:
57              
58             =cut
59              
60             package OSPF::LSDB::ospf6d;
61 11     11   55 use base 'OSPF::LSDB::ospfd';
  11         20  
  11         3529  
62 11     11   276 use File::Slurp;
  11         22  
  11         802  
63 11     11   70 use Regexp::Common;
  11         21  
  11         75  
64 11     11   11638 use Regexp::IPv6;
  11         8972  
  11         653  
65 11         163 use fields qw(
66             link intra
67 11     11   69 );
  11         24  
68              
69             sub new {
70 8     8 1 800 my OSPF::LSDB::ospf6d $self = OSPF::LSDB::ospfd::new(@_);
71 8         24 $self->{ospfd} = "ospf6d";
72 8         21 $self->{ospfctl} = "ospf6ctl";
73             # XXX hard coded routing domain 0
74 8         24 $self->{ospfsock} = "/var/run/ospf6d.sock.0";
75 8         15 %{$self->{showdb}} = (%{$self->{showdb}}, (
  8         49  
  8         37  
76             link => "link",
77             intra => "intra",
78             ));
79 8         33 return $self;
80             }
81              
82             # shortcut
83             my $IP = qr/$RE{net}{IPv4}{-keep}/;
84             my $IP6 = qr/($Regexp::IPv6::IPv6_re|::)/;
85             my $PREFIX = qr,($Regexp::IPv6::IPv6_re|::)/$RE{num}{int}{-keep},;
86              
87             sub parse_router {
88 16     16 0 13769 my OSPF::LSDB::ospf6d $self = shift;
89 16         40 my($area, $router, $link) = ("", "", "");
90 16         24 my(@routers, $lnum, $type, $r, $l);
91 16         19 foreach (@{$self->{router}}) {
  16         33  
92 327 100 100     24279 if (/^ +Router Link States \(Area $IP\)$/) {
    100 100        
    100          
    100          
93 18 100       45 die "$_ Link $link of router $router in area $area not finished.\n"
94             if $l;
95 17 100       38 die "$_ Too few links at router $router in area $area.\n" if $lnum;
96 16 100       32 die "$_ Router $router in area $area not finished.\n" if $r;
97 15         28 $area = $1;
98 15         25 next;
99             } elsif (/^$/) {
100 62 100       145 if ($l) {
101 14 100       38 die "Link $link of router $router in area $area without type.\n"
102             if ! $type;
103 13         16 push @{$r->{$type.'s'}}, $l;
  13         37  
104 13         21 undef $type;
105 13         14 undef $l;
106             }
107 61 100       104 if (! $lnum) {
108 41         50 undef $r;
109             }
110 61         107 next;
111             } elsif (! $r && /^\w/) {
112 21 100       45 die "$_ No area for router defined.\n" if ! $area;
113 20         26 $router = "";
114 20         39 $r = { area => $area };
115 20         38 push @routers, $r;
116             } elsif (! $l && /^ {4}\w/) {
117 19 100       47 die "$_ Too many links at router $router in area $area.\n"
118             if ! $lnum;
119 18         60 $lnum--;
120 18         26 $link = "";
121 18         26 $l = {};
122             }
123 245 100       855 if (/^LS age: $RE{num}{int}{-keep}$/) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
124 20         2696 $r->{age} = $1;
125             } elsif (/^LS Type: ([\w ()]+)$/) {
126 20 100       2519 die "$_ Type of router-LSA is $1 and not Router in area $area.\n"
127             if $1 ne "Router";
128             } elsif (/^Link State ID: $IP$/) {
129 19         2386 $r->{router} = $1;
130             } elsif (/^Advertising Router: $IP$/) {
131 19         2337 $r->{routerid} = $1;
132 19         113 $router = $1;
133             } elsif (/^LS Seq Number: (0x$RE{num}{hex})$/) {
134 19         4413 $r->{sequence} = $1;
135             } elsif (/^Flags: ([-*|\w]*)$/) {
136 16         3372 my $flags = $1;
137 16         27 foreach (qw(W V E B)) {
138 64 100       557 $r->{bits}{$_} = $flags =~ /\b$_\b/ ? 1 : 0;
139             }
140             } elsif (/^Number of Links: $RE{num}{int}{-keep}$/) {
141 16         5060 $lnum = $1;
142             } elsif (/^ Link \([ .\w]*\) connected to: ([\w -]+)$/) {
143 17 50       5768 if ($1 eq "Point-to-Point") {
    100          
    50          
144 0         0 $type = "pointtopoint";
145             } elsif ($1 eq "Transit Network") {
146 16         25 $type = "transit";
147             } elsif ($1 eq "Virtual Link") {
148 0         0 $type = "virtual";
149             } else {
150 1         12 die "$_ Unknown link type $1 at router $router ".
151             "in area $area.\n";
152             }
153 16 50       279 if (/\(Interface ID $IP\)/) {
154 16         189 $l->{interface} = $1;
155             }
156             } elsif (/^ (?:Designated )?Router ID: $IP$/) {
157 17         5456 $l->{routerid} = $1;
158 17         175 $link = $1;
159             } elsif (/^ (?:DR )?Interface ID: $IP$/) {
160 17         5381 $l->{address} = $1;
161 17         182 $link = "$1\@$link";
162             } elsif (/^ Metric: $RE{num}{int}{-keep}$/) {
163 14         5968 $l->{metric} = $1;
164             } elsif (/^ {4}\w/) {
165 1         423 die "$_ Unknown line at link $link of router $router ".
166             "in area $area.\n";
167             } elsif (! /^(Options|Checksum|Length):/) {
168 1         435 die "$_ Unknown line at router $router in area $area.\n";
169             }
170             }
171 6 100       22 die "Link $link of router $router in area $area not finished.\n" if $l;
172 5 100       18 die "Too few links at router $router in area $area.\n" if $lnum;
173 4 100       17 die "Router $router in area $area not finished.\n" if $r;
174 3         48 $self->{ospf}{database}{routers} = \@routers;
175             }
176              
177             sub parse_network {
178 11     11 0 8709 my OSPF::LSDB::ospf6d $self = shift;
179 11         30 my($area, $network) = ("", "");
180 11         30 my(@networks, $attachments, $rnum, $n);
181 11         19 foreach (@{$self->{network}}) {
  11         31  
182 122 100       7547 if (/^ +Net Link States \(Area $IP\)$/) {
    100          
    100          
183 12 100       48 die "$_ Attached routers of network $network in area $area ".
184             "not finished.\n" if $attachments;
185 11 100       36 die "$_ Network $network in area $area not finished.\n" if $n;
186 10         46 $area = $1;
187 10         21 next;
188             } elsif (/^$/) {
189 26 100       76 die "$_ Too few attached routers at network $network ".
190             "in area $area.\n" if $rnum;
191 25         93 undef $attachments;
192 25         31 undef $n;
193 25         60 next;
194             } elsif (! $n) {
195 11 100       36 die "$_ No area for network defined.\n" if ! $area;
196 10         23 $network = "";
197 10         26 $n = { area => $area };
198 10         75 push @networks, $n;
199             }
200 83 100       338 if (/^LS age: $RE{num}{int}{-keep}$/) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
201 10         1622 $n->{age} = $1;
202             } elsif (/^LS Type: ([\w ()]+)$/) {
203 10 100       1161 die "$_ Type of network-LSA is $1 and not Network in area $area.\n"
204             if $1 ne "Network";
205             } elsif (/^Link State ID: $IP \(Interface ID of Designated Router\)$/) {
206 9         1224 $n->{address} = $1;
207 9         61 $network = $1;
208             } elsif (/^Advertising Router: $IP$/) {
209 9         1237 $n->{routerid} = $1;
210 9         71 $network .= "\@$1";
211             } elsif (/^LS Seq Number: (0x$RE{num}{hex})$/) {
212 9         2458 $n->{sequence} = $1;
213             } elsif (/^Number of Routers: $RE{num}{int}{-keep}$/) {
214 6         1899 $rnum = $1;
215             } elsif (/^ Attached Router: $IP$/) {
216 10 100       3629 if (! $attachments) {
217 6         16 $attachments = [];
218 6         18 $n->{attachments} = $attachments;
219             }
220 10 50       32 $rnum-- if defined $rnum ;
221 10         136 push @$attachments, { routerid => $1 };
222             } elsif (! /^(Options|Checksum|Length):/) {
223 1         319 die "$_ Unknown line at network $network in area $area.\n";
224             }
225             }
226 5 100       31 die "Attached routers of network $network in area $area not finished.\n"
227             if $attachments;
228 4 100       22 die "Network $network in area $area not finished.\n" if $n;
229 3         18 $self->{ospf}{database}{networks} = \@networks;
230             }
231              
232             sub parse_summary {
233 2     2 0 5 my OSPF::LSDB::ospf6d $self = shift;
234             # XXX not yet
235 2         6 $self->{ospf}{database}{summarys} = [];
236             }
237              
238             sub parse_boundary {
239 2     2 0 3 my OSPF::LSDB::ospf6d $self = shift;
240             # XXX not yet
241 2         5 $self->{ospf}{database}{boundarys} = [];
242             }
243              
244             sub parse_external {
245 8     8 0 7042 my OSPF::LSDB::ospf6d $self = shift;
246 8         19 my $external = "";
247 8         18 my(@externals, $e);
248 8         14 foreach (@{$self->{external}}) {
  8         24  
249 331 100       27963 if (/^ +Type-5 AS External Link States$/) {
    100          
    100          
250 10 100       39 die "$_ External $external not finished.\n" if $e;
251 9 100       39 die "$_ Too many external sections.\n", if @externals;
252 8         26 next;
253             } elsif (/^$/) {
254 42         62 undef $e;
255 42         68 next;
256             } elsif (! $e) {
257 30         44 $external = "";
258 30         43 $e = {};
259 30         59 push @externals, $e;
260             }
261 279 100       931 if (/^LS age: $RE{num}{int}{-keep}$/) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
262 30         3776 $e->{age} = $1;
263             } elsif (/^LS Type: ([\w ()]+)$/) {
264 30 100       3397 die "$_ Type of external-LSA is $1 and not AS External.\n"
265             if $1 ne "AS External";
266             } elsif (/^Link State ID: $IP$/) {
267 29         3354 $e->{address} = $1;
268 29         159 $external = $1;
269             } elsif (/^Advertising Router: $IP$/) {
270 29         3364 $e->{routerid} = $1;
271 29         175 $external .= "\@$1";
272             } elsif (/^LS Seq Number: (0x$RE{num}{hex})$/) {
273 29         6701 $e->{sequence} = $1;
274             } elsif (/^ Metric: $RE{num}{int}{-keep} Type: ([1-2])$/) {
275 26         8226 $e->{metric} = $1;
276 26         286 $e->{type} = $4;
277             } elsif (/^ Prefix: $PREFIX$/) {
278 26         8363 $e->{prefixaddress} = $1;
279 26         309 $e->{prefixlength} = $2;
280             } elsif (! /^(Checksum|Length| Flags):/) {
281 1         328 die "$_ Unknown line at external $external.\n";
282             }
283             }
284 4 100       25 die "External $external not finished.\n" if $e;
285 3         18 $self->{ospf}{database}{externals} = \@externals;
286             }
287              
288             sub parse_link {
289 11     11 0 9909 my OSPF::LSDB::ospf6d $self = shift;
290 11         34 my($area, $link) = ("", "");
291 11         26 my(@links, $prefixes, $pnum, $l);
292 11         21 foreach (@{$self->{link}}) {
  11         29  
293 397 100       30973 if (/^ +Link \(Type-8\) Link States \(Area $IP Interface \w+\)$/) {
    100          
    100          
294 19 100       68 die "$_ Prefixes of link $link in area $area not finished.\n"
295             if $prefixes;
296 18 100       54 die "$_ Link $link in area $area not finished.\n" if $l;
297 17         56 $area = $1;
298 17         38 next;
299             } elsif (/^$/) {
300 59 100       200 die "$_ Too few prefixes at link $link in area $area.\n" if $pnum;
301 58         91 undef $prefixes;
302 58         72 undef $l;
303 58         133 next;
304             } elsif (! $l) {
305 30 100       98 die "$_ No area for link defined.\n" if ! $area;
306 29         55 $link = "";
307 29         77 $l = { area => $area };
308 29         67 push @links, $l;
309             }
310 318 100       1160 if (/^LS age: $RE{num}{int}{-keep}$/) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
311 29         3785 $l->{age} = $1;
312             } elsif (/^LS Type: ([\w ()]+)$/) {
313 29 100       3294 die "$_ Type of link-LSA is $1 and not Link in area $area.\n"
314             if $1 ne "Link";
315             } elsif (/^Link State ID: $IP \(Interface ID of Advertising Router\)$/) {
316 28         3582 $l->{interface} = $1;
317 28         191 $link = $1;
318             } elsif (/^Advertising Router: $IP$/) {
319 28         3455 $l->{routerid} = $1;
320 28         189 $link .= "\@$1";
321             } elsif (/^LS Seq Number: (0x$RE{num}{hex})$/) {
322 28         7008 $l->{sequence} = $1;
323             } elsif (/^Link Local Address: ($IP6)$/) {
324 25         5694 $l->{linklocal} = $1;
325             } elsif (/^Number of Prefixes: $RE{num}{int}{-keep}$/) {
326 25         8600 $pnum = $1;
327             } elsif (/^ Prefix: $PREFIX$/) {
328 49 100       15726 if (! $prefixes) {
329 10         24 $prefixes = [];
330 10         24 $l->{prefixes} = $prefixes;
331             }
332 49 50       101 $pnum-- if defined $pnum ;
333 49         636 push @$prefixes, { prefixaddress => $1, prefixlength => $2 };
334             } elsif (! /^(Options|Checksum|Length):/) {
335 1         329 die "$_ Unknown line at link $link in area $area.\n";
336             }
337             }
338 5 100       38 die "Prefixes of link $link in area $area not finished.\n" if $prefixes;
339 4 100       27 die "Link $link in area $area not finished.\n" if $l;
340 3         20 $self->{ospf}{database}{links} = \@links;
341             }
342              
343             sub parse_intra {
344 14     14 0 15109 my OSPF::LSDB::ospf6d $self = shift;
345 14         30 my($area, $intra) = ("", "");
346 14         23 my(@intranetworks, @intrarouters, $type, $prefixes, $pnum, $i);
347 14         19 foreach (@{$self->{intra}}) {
  14         27  
348 435 100       26851 if (/^ +Intra Area Prefix Link States \(Area $IP\)$/) {
    100          
    100          
349 15 100       42 die "$_ Prefixes of intra-area-prefix $intra in area $area ".
350             "not finished.\n" if $prefixes;
351 14 100       32 die "$_ Intra-area-prefix $intra in area $area not finished.\n"
352             if $i;
353 13         29 $area = $1;
354 13         23 next;
355             } elsif (/^$/) {
356 50 100       108 die "$_ Too few prefixes at intra-area-prefix $intra ".
357             "in area $area.\n" if $pnum;
358 49 100 100     126 die "$_ Intra-area-prefix $intra in area $area has no ".
359             "referenced LS type.\n" if $i && ! $type;
360 48         59 undef $type;
361 48         49 undef $prefixes;
362 48         46 undef $i;
363 48         96 next;
364             } elsif (! $i) {
365 31 100       64 die "$_ No area for intra-area-prefix defined.\n" if ! $area;
366 30         41 $intra = "";
367 30         77 $i = { area => $area };
368             }
369 369 100       1237 if (/^LS age: $RE{num}{int}{-keep}$/) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
370 30         3691 $i->{age} = $1;
371             } elsif (/^LS Type: ([\w ()]+)$/) {
372 30 100       3298 die "$_ Type of intra-area-prefix-LSA is $1 and not Intra Area ".
373             "(Prefix) in area $area.\n" if $1 ne "Intra Area (Prefix)";
374             } elsif (/^Link State ID: $IP$/) {
375 29         3368 $i->{address} = $1;
376 29         167 $intra = $1;
377             } elsif (/^Advertising Router: $IP$/) {
378 29         3337 $i->{routerid} = $1;
379 29         182 $intra .= "\@$1";
380             } elsif (/^LS Seq Number: (0x$RE{num}{hex})$/) {
381 29         6577 $i->{sequence} = $1;
382             } elsif (/^Referenced LS Type: (\w+)$/) {
383 26 100       5444 die "$_ Referenced LS type given more than once ".
384             "at intra-area-prefix $intra in area $area.\n" if $type;
385 25         52 $type = $1;
386 25 100       62 if ($type eq "Router") {
    100          
387 13         101 push @intrarouters, $i
388             } elsif ($type eq "Network") {
389 11         84 push @intranetworks, $i
390             } else {
391 1         10 die "$_ Unknown referenced LS type $type ".
392             "at intra-area-prefix $intra in area $area.\n";
393             }
394             } elsif (/^Referenced Link State ID: $IP$/) {
395 23         4941 $i->{interface} = $1;
396             } elsif (/^Referenced Advertising Router: $IP$/) {
397 23         5066 $i->{router} = $1;
398             } elsif (/^Number of Prefixes: $RE{num}{int}{-keep}$/) {
399 23         7249 $pnum = $1;
400             } elsif (/^ Prefix: $PREFIX(?: Options: \S+)?(?: Metric: $RE{num}{int}{-keep})?$/) {
401 73 100       31134 if (! $prefixes) {
402 20         33 $prefixes = [];
403 20         57 $i->{prefixes} = $prefixes;
404             }
405 73 50       144 $pnum-- if defined $pnum ;
406 73         243 my %pfx = (prefixaddress => $1, prefixlength => $2);
407 73 100       150 $pfx{metric} = $5 if defined $5;
408 73         998 push @$prefixes, \%pfx;
409             } elsif (! /^(Checksum|Length):/) {
410 1         471 die "$_ Unknown line at intra-area-prefix $intra in area $area.\n";
411             }
412             }
413 5 100       30 die "Prefixes of intra-area-prefix $intra in area $area not finished.\n"
414             if $prefixes;
415 4 100       23 die "Intra-area-prefix $intra in area $area not finished.\n" if $i;
416 3         11 $self->{ospf}{database}{intranetworks} = \@intranetworks;
417 3         14 $self->{ospf}{database}{intrarouters} = \@intrarouters;
418             }
419              
420             sub parse_lsdb {
421 2     2 0 6 my OSPF::LSDB::ospf6d $self = shift;
422 2         12 $self->SUPER::parse_lsdb(@_);
423 2         10 $self->parse_link();
424 2         6 $self->parse_intra();
425             }
426              
427             =pod
428              
429             =over 4
430              
431             =item $self-Eparse(%files)
432              
433             This function takes a hash with file names as value containing the
434             B output data.
435             The hash keys are named C, C, C, C,
436             C, C, C, C.
437             If a hash entry is missing, B is run instead to obtain the
438             information dynamically.
439              
440             The complete OSPF link state database is stored in the B field
441             of the base class.
442              
443             =back
444              
445             =cut
446              
447             sub parse {
448 2     2 1 9 my OSPF::LSDB::ospf6d $self = shift;
449 2         13 $self->SUPER::parse(@_);
450 2         6 $self->{ospf}{ipv6} = 1;
451             }
452              
453             =pod
454              
455             This module has been tested with OpenBSD 5.1.
456             If it works with other versions is unknown.
457              
458             =head1 ERRORS
459              
460             The methods die if any error occurs.
461              
462             =head1 SEE ALSO
463              
464             L,
465             L
466              
467             L
468              
469             =head1 AUTHORS
470              
471             Alexander Bluhm
472              
473             =cut
474              
475             1;