File Coverage

blib/lib/Net/BGP/ASPath.pm
Criterion Covered Total %
statement 251 262 95.8
branch 71 96 73.9
condition 23 29 79.3
subroutine 37 39 94.8
pod 10 19 52.6
total 392 445 88.0


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package Net::BGP::ASPath;
4              
5 7     7   4102 use strict;
  7         14  
  7         254  
6 7         678 use vars qw(
7             $VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS @PATHTYPES
8             @BGP_PATH_ATTR_COUNTS
9 7     7   38 );
  7         13  
10              
11             ## Inheritance and Versioning ##
12              
13             @ISA = qw( Exporter );
14             $VERSION = '0.18';
15              
16             ## Module Imports ##
17              
18 7     7   42 use Carp;
  7         12  
  7         421  
19 7     7   2052 use IO::Socket;
  7         81347  
  7         37  
20             use overload
21             '<=>' => \&len_compare,
22             '<' => \&len_lessthen,
23             '>' => \&len_greaterthen,
24             '==' => \&len_equal,
25             '!=' => \&len_notequal,
26             '""' => \&as_string,
27 0     0   0 '+' => sub { my $x = shift->clone; $x->prepend(shift); },
  0         0  
28 7         148 '+=' => \&prepend,
29             'eq' => \&equal,
30             'ne' => \¬equal,
31             '@{}' => \&asarray,
32 7     7   4260 'fallback' => 1;
  7         15  
33              
34 7     7   4954 use Net::BGP::ASPath::AS;
  7         20  
  7         262  
35 7     7   3456 use Net::BGP::ASPath::AS_CONFED_SEQUENCE;
  7         23  
  7         220  
36 7     7   3099 use Net::BGP::ASPath::AS_CONFED_SET;
  7         17  
  7         220  
37 7     7   43 use Net::BGP::ASPath::AS_SEQUENCE;
  7         16  
  7         127  
38 7     7   33 use Net::BGP::ASPath::AS_SET;
  7         14  
  7         21035  
39              
40             ## Public Class Methods ##
41              
42             sub new {
43 111     111 1 6219 my $class = shift;
44 111         170 my $value = shift;
45 111         144 my $options = shift;
46              
47 111 50       244 if (!defined($options)) { $options = {}; }
  111         185  
48 111   50     488 $options->{as4} ||= 0;
49              
50 111 100       229 return clone Net::BGP::ASPath($value) if (ref $value eq 'Net::BGP::ASPath');
51              
52             my $this = {
53             _as_path => [],
54             _as4 => $options->{as4}
55 110         340 };
56              
57 110         202 bless($this, $class);
58              
59 110 100       212 if (defined($value)) {
60 57 100       100 if (ref $value) {
61 1 50       4 if (ref $value eq 'ARRAY') {
62 1         7 $this->_setfromstring(join ' ', @$value);
63             } else {
64 0         0 croak "Unknown ASPath constructor argument type: " . ref $value
65             }
66             } else {
67             # Scalar/string
68 56         106 $this->_setfromstring($value);
69             }
70             }
71              
72 110         381 return ($this);
73             }
74              
75             sub _setfromstring {
76 203     203   393 my ($this, $value) = @_;
77 203         779 $this->{_as_path} = [];
78              
79             # Normalize string
80 203         1863 $value =~ s/\s+/ /g;
81 203         411 $value =~ s/^\s//;
82 203         379 $value =~ s/\s$//;
83 203         545 $value =~ s/\s?,\s?/,/g;
84              
85 203         447 while ($value ne '') {
86              
87             # Note that the AS_SEQUENCE can't be > 255 path elements. The entire len
88             # of the AS_PATH can be > 255 octets, true, but not an individual AS_SET
89             # segment.
90             # TODO: We should do the same for other path types and also take care to
91             # not allow ourselves to overflow the 65535 byte length limit if this is
92             # converted back to a usable path.
93             # TODO: It would be better to put the short AS PATH at end of the path,
94             # not the beginning of the path, so that it is easier for other routers
95             # to process.
96 355 50 100     3196 confess 'Invalid path segments for path object: >>' . $value . '<<'
      66        
97             unless (
98             ($value =~ /^(\([^\)]*\))( (.*))?$/) || # AS_CONFED_* segment
99             ($value =~ /^(\{[^\}]*\})( (.*))?$/) || # AS_SET segment
100             ($value =~ /^(([0-9]+\s*){1,255})(.*)?$/)
101             ); # AS_SEQUENCE seqment
102              
103 355   100     1186 $value = $3 || '';
104 355         955 my $segment = Net::BGP::ASPath::AS->new($1);
105              
106 355         475 push(@{ $this->{_as_path} }, $segment);
  355         1041  
107             }
108 203         505 return $this;
109             }
110              
111             sub clone {
112 162     162 1 588 my $proto = shift;
113 162   66     379 my $class = ref $proto || $proto;
114 162 100       295 $proto = shift unless ref $proto;
115              
116 162         373 my $clone = { _as_path => [] };
117              
118 162         230 foreach my $p (@{ $proto->{_as_path} }) {
  162         398  
119 253         402 push(@{ $clone->{_as_path} }, $p->clone);
  253         682  
120             }
121              
122 162         387 return (bless($clone, $class));
123             }
124              
125             # This takes two buffers. The first buffer is the standard AS_PATH buffer and
126             # should always be defined.
127             #
128             # The second buffer is the AS4_PATH buffer.
129             #
130             # The third parameter is true if AS4 is natively supported, false if AS4 is not
131             sub _new_from_msg {
132 31     31   73 my ($class, $buffer, $buffer2, $options) = @_;
133 31         67 my $this = $class->new;
134              
135 31 100       79 if (!defined($options)) { $options = {}; }
  14         22  
136 31   100     169 $options->{as4} ||= 0;
137              
138 31 100       79 my $size = $options->{as4} ? 4 : 2;
139              
140 31 100       60 if (!defined($buffer2)) { $buffer2 = ''; }
  13         18  
141              
142 31         43 my $segment;
143 31         80 while ($buffer ne '') {
144              
145 39         138 ($segment, $buffer)
146             = Net::BGP::ASPath::AS->_new_from_msg($buffer, $options);
147              
148             # Error handling
149 39 50       110 if ( !(defined $segment) ) {
150 0         0 return undef;
151             }
152 39 50 66     110 if ( length($buffer) && ( ( length($buffer) - 2 ) % $size) ) {
153 0         0 return undef;
154             }
155              
156 39         59 push(@{ $this->{_as_path} }, $segment);
  39         238  
157             }
158              
159             # We ignore AS4_PATHs on native AS4 speaker sessions
160             # So we stop here.
161 31 100       87 if ($options->{as4}) {
162 4         17 return $this;
163             }
164              
165 27         39 my @as4_path;
166              
167 27         54 while ($buffer2 ne '') {
168              
169 5         33 ($segment, $buffer2)
170             = Net::BGP::ASPath::AS->_new_from_msg(
171             $buffer2,
172             { as4 => 1 }
173             );
174              
175             # TODO: Should make sure type is only AS_SEQUENCE or AS_SET!
176              
177 5 50       32 if ( !(defined $segment) ) {
178 0         0 return undef;
179             }
180 5 50 66     20 if ( length($buffer2) && ( ( length($buffer2) - 2 ) % 4) ) {
181 0         0 return undef;
182             }
183              
184 5         13 push (@as4_path, $segment);
185             }
186              
187 27         74 my $as_count = $this->_length_helper( $this->{_as_path} );
188 27         61 my $as4_count = $this->_length_helper( \@as4_path );
189              
190 27 50       59 if ($as_count < $as4_count) {
191             # We ignroe the AS4 stuff per RFC4893 in this case
192 0         0 return $this;
193             }
194              
195 27         39 my $remove = $as4_count;
196              
197 27         55 while ($remove > 0) {
198 5         8 my $ele = pop @{ $this->{_as_path} };
  5         10  
199 5 100       12 if ($ele->length <= $remove) {
200 2         8 $remove -= $ele->length;
201             } else {
202 3         13 push @{ $this->{_as_path} }, $ele->remove_tail($remove);
  3         13  
203 3         8 $remove = 0;
204             }
205             }
206              
207 27         34 push @{ $this->{_as_path} }, @as4_path;
  27         53  
208              
209 27         82 return $this;
210             }
211              
212             ## Public Object Methods ##
213              
214             # This encodes the AS_PATH and AS4_PATH elements (both are returned)
215             #
216             # If the AS4_PATH element is undef, that indicates an AS4_PATH is not
217             # needed - either we're encoding in 32-bit clear format, or all
218             # elements have only 16 bit ASNs.
219             sub _encode {
220 25     25   57 my ($this, $args) = @_;
221              
222 25 100       63 if (!defined($args)) { $args = {}; }
  14         22  
223 25   100     122 $args->{as4} ||= 0;
224              
225 25         45 my $has_as4;
226 25         40 my $msg = '';
227 25         58 foreach my $segment (@{ $this->{_as_path} }) {
  25         91  
228 33         152 $msg .= $segment->_encode($args);
229              
230 33 100       118 if ($segment->_has_as4()) { $has_as4 = 1; }
  6         14  
231             }
232              
233 25         53 my $as4;
234 25 100 100     133 if ( ( !($args->{as4} ) ) && ($has_as4) ) {
235 1         2 $as4 = '';
236              
237 1         2 foreach my $segment (@{ $this->{_as_path} }) {
  1         4  
238 2 50       7 if ( !(ref($segment) =~ /_CONFED_/) ) {
239 2         10 $as4 .= $segment->_encode( { as4 => 1 } );
240             }
241             }
242             }
243              
244 25         100 return ($msg, $as4);
245             }
246              
247             sub prepend {
248 46     46 1 167 my $this = shift;
249 46         71 my $value = shift;
250 46 100       142 return $this->prepend_confed($value) if ($value =~ /^\(/);
251 31         88 $this->strip;
252              
253 31         65 my @list = ($value);
254 31 100       91 @list = @{$value} if (ref $value eq 'ARRAY');
  1         4  
255 31 100       73 @list = split(' ', $list[0]) if $list[0] =~ / /;
256              
257             # Ugly - slow - but simple! Should be improved later!
258 31         118 return $this->_setfromstring(join(' ', @list) . ' ' . $this)->cleanup;
259             }
260              
261             sub prepend_confed {
262 33     33 1 86 my $this = shift;
263              
264 33         46 my $value = shift;
265 33 100       143 $value =~ s/^\((.*)\)$/$1/ unless ref $value;
266              
267 33         80 my @list = ($value);
268 33 100       71 @list = @{$value} if (ref $value eq 'ARRAY');
  1         3  
269 33 100       84 @list = split(' ', $list[0]) if $list[0] =~ / /;
270              
271             # Ugly - slow - but simple! Should be improved later!
272 33         127 return $this->_setfromstring('(' . join(' ', @list) . ') ' . $this)
273             ->cleanup;
274             }
275              
276             sub cleanup {
277 82     82 1 143 my $this = shift;
278              
279             # Ugly - slow - but simple! Should be improved later!
280 82         177 my $str = $this->as_string;
281 82         185 $str =~ s/\{\}//g;
282 82         135 $str =~ s/\(\)//g;
283 82         199 $str =~ s/(\d)\) +\((\d)/$1 $2/g;
284 82         181 return $this->_setfromstring($str);
285             }
286              
287             sub _confed {
288 12     12   21 my $this = shift->clone;
289 12         35 @{ $this->{_as_path} } =
290 12         20 grep { (ref $_) =~ /_CONFED_/ } @{ $this->{_as_path} };
  25         62  
  12         25  
291 12         26 return $this;
292             }
293              
294             sub strip {
295 71     71 1 122 my $this = shift;
296 71         189 @{ $this->{_as_path} } =
297 71         89 grep { (ref $_) !~ /_CONFED_/ } @{ $this->{_as_path} };
  117         343  
  71         155  
298 71         159 return $this;
299             }
300              
301             sub striped {
302 26     26 1 70 return shift->clone->strip(@_);
303             }
304              
305             sub aggregate {
306 4     4 1 345 my @olist = @_;
307 4 100       11 shift(@olist) unless ref $olist[0];
308              
309             # Sets
310 4         13 my $cset = Net::BGP::ASPath::AS_CONFED_SET->new;
311 4         12 my $nset = Net::BGP::ASPath::AS_SET->new;
312              
313             # Lists of confed / normal part of paths
314 4         10 my @clist = map { $_->_confed } @olist;
  12         24  
315 4         7 my @nlist = map { $_->striped } @olist;
  12         21  
316              
317 4         10 my $res = '';
318 4         12 foreach my $pair ([ \@clist, $cset ], [ \@nlist, $nset ]) {
319 8         90 my ($list, $set) = @{$pair};
  8         21  
320              
321             # Find common head
322 8         22 my $head = $list->[0]->_head;
323 8         13 foreach my $obj (@{$list}[ 1 .. @{$list} - 1 ]) {
  8         16  
  8         17  
324 16         28 my $s = $obj->_head;
325 16         31 $head = _longest_common_head($head, $s);
326             }
327              
328             # Find tail set
329 8         14 foreach my $obj (@{$list}) {
  8         14  
330 24         46 my $tail = $obj->_tail($head);
331 24 100       64 $tail = '(' . $tail if $tail =~ /^[^\(]*\).*$/; # Fix tail
332 24         54 $obj = Net::BGP::ASPath->new($tail);
333 24         65 $set->merge($obj);
334             }
335 8 100       25 $head .= ')' if $head =~ /^\([^\)]+$/; # Fix head
336 8         28 $res .= "$head $set ";
337             }
338              
339             # Construct result
340 4         14 return Net::BGP::ASPath->new($res)->cleanup;
341             }
342              
343             ## Utility functions (not methods!) ##
344             sub _longest_common_head {
345 16     16   30 my ($s1, $s2) = @_;
346 16         22 my $pos = 0;
347 16         28 $s1 .= ' ';
348 16         34 $s2 .= ' ';
349 16         44 for my $i (0 .. length($s1) - 1) {
350 80 100       150 last unless substr($s1, $i, 1) eq substr($s2, $i, 1);
351 76 100       137 $pos = $i if substr($s1, $i, 1) eq ' ';
352             }
353 16         59 return substr($s1, 0, $pos);
354             }
355              
356             sub _head
357              
358             # Head means the leading non-set part of the path
359             {
360 24     24   42 my $this = shift->clone;
361 24         33 my $ok = 1;
362             $this->{_as_path} =
363 25 100 66     114 [ grep { $ok &&= (ref $_) =~ /_SEQUENCE$/; $_ = undef unless $ok; }
  25         84  
364 24         27 @{ $this->{_as_path} } ];
  24         50  
365 24         46 return $this;
366             }
367              
368             sub _tail
369              
370             # Tail means everything after the "head" given as argument.
371             # The tail is returned as a string. Returns undef if "head" is invalid.
372             {
373 24     24   43 my $thisstr = shift() . " ";
374 24         45 my $head = shift() . " ";
375 24         53 $head =~ s/\(/\\(/g;
376 24         38 $head =~ s/\)/\\)/g;
377 24 50       199 return undef unless $thisstr =~ s/^$head//;
378 24         65 $thisstr =~ s/ $//;
379 24         48 return $thisstr;
380             }
381              
382             # For compatability
383             sub asstring {
384 7     7 0 13 my $this = shift;
385 7         20 return $this->as_string(@_);
386             }
387              
388             sub as_string {
389 519     519 1 1004 my $this = shift;
390              
391 519         953 return $this->_as_string_helper($this->{_as_path});
392             }
393              
394             sub _as_string_helper {
395 519     519   816 my ($this, $path) = @_;
396              
397 519         640 return join(' ', map { $_->as_string; } @{ $path });
  820         1711  
  519         1029  
398             }
399              
400              
401             sub asarray {
402 26     26 0 339 my $this = shift;
403 26         38 my @res;
404 26         31 foreach my $s (@{ $this->{_as_path} }) {
  26         45  
405 26         30 push(@res, @{ $s->asarray });
  26         56  
406             }
407 26         73 return \@res;
408             }
409              
410             sub len_equal {
411 1     1 0 4 my ($this, $other) = @_;
412 1 50       4 return 0 unless defined($other);
413 1 50       4 return ($this->length == $other->length) ? 1 : 0;
414             }
415              
416             sub len_notequal {
417 0     0 0 0 my ($this, $other) = @_;
418 0 0       0 return 1 unless defined($other);
419 0 0       0 return ($this->length != $other->length) ? 1 : 0;
420             }
421              
422             sub len_lessthen {
423 1     1 0 3 my ($this, $other) = @_;
424 1 50       4 return 0 unless defined($other);
425 1 50       4 return ($this->length < $other->length) ? 1 : 0;
426             }
427              
428             sub len_greaterthen {
429 1     1 0 410 my ($this, $other) = @_;
430 1 50       5 return 1 unless defined($other);
431 1 50       4 return ($this->length > $other->length) ? 1 : 0;
432             }
433              
434             sub len_compare {
435 12     12 0 37 my ($this, $other) = @_;
436 12 50       25 return 1 unless defined($other);
437 12         23 return $this->length <=> $other->length;
438             }
439              
440             sub equal {
441 22     22 0 417 my ($this, $other) = @_;
442 22 50       44 return 0 unless defined($other);
443 22 50       51 confess "Cannot compare " . (ref $this) . " with a " . (ref $other) . "\n"
444             unless ref $other eq ref $this;
445 22 100       59 return $this->as_string eq $other->as_string ? 1 : 0;
446             }
447              
448             sub notequal {
449 1     1 0 3 my ($this, $other) = @_;
450 1 50       5 return 1 unless defined($other);
451 1 50       3 return $this->as_string ne $other->as_string ? 1 : 0;
452             }
453              
454             sub length {
455 51     51 1 207 my ($this) = @_;
456              
457 51         99 return $this->_length_helper($this->{_as_path});
458             }
459              
460             sub _length_helper {
461 105     105   159 my ($this, $path) = @_;
462              
463 105         150 my $res = 0;
464 105         135 foreach my $p (@{ $path }) {
  105         172  
465 148         326 $res += $p->length;
466             }
467 105         246 return $res;
468             }
469              
470             ## POD ##
471              
472             =pod
473              
474             =head1 NAME
475              
476             C - Class encapsulating BGP-4 AS Path information
477              
478             =head1 SYNOPSIS
479              
480             use Net::BGP::ASPath;
481              
482             # Constructor
483             $aspath = Net::BGP::ASPath->new(undef, { as4 => 1 });
484             $aspath2 = Net::BGP::ASPath->new([65001,65002]);
485             $aspath3 = Net::BGP::ASPath->new("(65001 65002) 65010");
486             $aspath4 = Net::BGP::ASPath->new("65001 {65011,65010}");
487              
488             # Object Copy
489             $clone = $aspath->clone();
490              
491             # Modifiers;
492             $aspath = $aspath->prepend(64999);
493             $aspath = $aspath->prepend("64999 65998");
494             $aspath = $aspath->prepend([64999,65998]);
495              
496             $aspath = $aspath->prepend("(64999 65998)");
497             $aspath = $aspath->prepend_confed("64999 65998");
498              
499             $aspath += "65001 65002"; # Same as $aspath->prepend("65001 65002")
500              
501             $aspath5 = $aspath->striped; # New object
502             $aspath = $aspath->strip; # Same modified
503              
504             $aspath = $aspath->cleanup # Same modified
505              
506             # Aggregation
507             $aspath = $aspath1->aggregate($aspath2,$aspath3);
508             $aspath = Net::BGP::ASPath->aggregate($aspath1,$aspath2,$aspath3);
509              
510              
511             # Accessor Methods
512             $length = $aspath->length;
513             $string = $aspath->as_string;
514             $array_ref = $aspath->asarray
515              
516             # In context
517             $string = "The AS path is: " . $aspath;
518             $firstas = $aspath[0];
519              
520             # Length comparisons
521             if ($aspath < $aspath2) { ... };
522             if ($aspath > $aspath2) { ... };
523             if ($aspath == $aspath2) { ... };
524             if ($aspath != $aspath2) { ... };
525             @sorted = sort { $a <=> $b } ($aspath, $aspath2, $aspath3, $aspath4);
526              
527             # Path comparisons
528             if ($aspath eq $aspath2) { ... };
529             if ($aspath ne $aspath2) { ... };
530              
531             =head1 DESCRIPTION
532              
533             This module encapsulates the data contained in a BGP-4 AS_PATH, including
534             confederation extensions.
535              
536             =head1 CONSTRUCTOR
537              
538             =over 4
539              
540             =item new() - create a new C object
541              
542             $aspath = Net::BGP::ASPath->new( PATHDATA, OPTIONS );
543              
544             This is the constructor for C objects. It returns a
545             reference to the newly created object. The first parameter may be either:
546              
547             =over 4
548              
549             =item ARRAY_REF
550              
551             An array ref containing AS numbers interpreted as an AS_PATH_SEQUENCE.
552              
553             =item SCALAR
554              
555             A string with AS numbers separated by spaces (AS_PATH_SEQUANCE).
556             AS_PATH_SETs are written using "{}" with "," to separate AS numbers.
557             AS_PATH_CONFED_* is written similarly, but encapsulated in "()".
558              
559             =item C
560              
561             Another ASPath object, in which case a clone is constructed.
562              
563             =item C
564              
565             This will create the ASPath object with empty contents.
566              
567             =back
568              
569             Following the PATHDATA, the OPTIONS may be specified. Currently the
570             only valid option is c, which, if true, builds ASPath objects
571             usable for talking to a peer that supports 32 bit ASNs. False, or
572             the default value, assumes that the peer does not support 32 bit ASNs,
573             which affects the decode routines. Note that the encode routines
574             are not dependent upon this option.
575              
576             Basically, if as4 is true, AS_PATH is populated from messages assuming
577             4 byte ASNs and AS4_PATH is not used. Encoded AS_PATH attributes also
578             assume a 4 byte ASN.
579              
580             If as4 is false, AS_PATH is populated from messages assuming 2 byte ASNs,
581             and, if available, AS4_PATH is used to replace occurences of 23456
582             when possible when outputing to user-readable formats. Encoding routines
583             will also allow output of AS4_PATH objects when appropriate.
584              
585             =back
586              
587             =head1 OBJECT COPY
588              
589             =over 4
590              
591             =item clone() - clone a C object
592              
593             $clone = $aspath->clone();
594              
595             This method creates an exact copy of the C object.
596              
597             =back
598              
599             =head1 ACCESSOR METHODS
600              
601             =over 4
602              
603             =item length()
604              
605             Return the path-length used in BGP path selection. This is the sum
606             of the lengths of all AS_PATH elements. This does however not include
607             AS_PATH_CONFED_* elements and AS_SEGMENTS which count as one BGP hop.
608              
609             =item as_string()
610              
611             Returns the path as a string in same notation that the constructor accepts.
612              
613             =item cleanup()
614              
615             Reduce the path by removing meaningless AS_PATH elements (empty sets or
616             sequences) and joining neighbor elements of the same _SET type.
617              
618             =item strip()
619              
620             Strips AS_CONFED_* segments from the path.
621              
622             =item striped()
623              
624             Returns a strip() 'ed clone() of the path.
625              
626             =item prepend(ARRAY)
627              
628             =item prepend(SCALAR)
629              
630             Strips AS_CONFED_* segments from the path and prepends one or more AS numbers
631             to the path as given as arguments, either as an array of AS numbers or as a
632             string with space separated AS numbers. If the string has "()" surrounding it,
633             prepend_confed will be used instead.
634              
635             =item prepend_confed(ARRAY)
636              
637             =item prepend_confed(SCALAR)
638              
639             Prepends one or more confederation AS numbers to the path as given as
640             arguments, either as an array of AS numbers or as a string with space
641             separated AS numbers. "()" around the string is ignored.
642              
643             =item aggregate(ASPath)
644              
645             =item aggregate(ARRAY)
646              
647             Aggregates the current ASPath with the ASPath(s) given as argument.
648             If invoked as a class method, aggregate all ASPaths given as argument.
649              
650             To aggregate means to find the longest common substring (of the paths of all
651             objects that should be aggregated) and keep them, but
652             replacing the non-common substrings with AS_SET segments. Currently only
653             the longest common normal and confederation head will be found and the remaining
654             will be left as an AS_SET and AS_CONFED_SET.
655              
656             Returns the aggregated object. The objects themselves are not modified.
657              
658             =back
659              
660             =head1 SEE ALSO
661              
662             =over
663              
664             =item L
665              
666             =item L
667              
668             =item L
669              
670             =item L
671              
672             =item L
673              
674             =item L
675              
676             =item L
677              
678             =back
679              
680             =head1 AUTHOR
681              
682             Martin Lorensen
683              
684             =cut
685              
686             ## End Package Net::BGP::ASPath ##
687              
688             1;