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   3497 use strict;
  7         10  
  7         271  
6 7         692 use vars qw(
7             $VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS @PATHTYPES
8             @BGP_PATH_ATTR_COUNTS
9 7     7   30 );
  7         7  
10              
11             ## Inheritance and Versioning ##
12              
13             @ISA = qw( Exporter );
14             $VERSION = '0.16';
15              
16             ## Module Imports ##
17              
18 7     7   37 use Carp;
  7         9  
  7         429  
19 7     7   2322 use IO::Socket;
  7         70905  
  7         34  
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         97 '+=' => \&prepend,
29             'eq' => \&equal,
30             'ne' => \¬equal,
31             '@{}' => \&asarray,
32 7     7   3908 'fallback' => 1;
  7         12  
33              
34 7     7   4095 use Net::BGP::ASPath::AS;
  7         11  
  7         210  
35 7     7   2929 use Net::BGP::ASPath::AS_CONFED_SEQUENCE;
  7         14  
  7         187  
36 7     7   2646 use Net::BGP::ASPath::AS_CONFED_SET;
  7         11  
  7         172  
37 7     7   35 use Net::BGP::ASPath::AS_SEQUENCE;
  7         8  
  7         103  
38 7     7   23 use Net::BGP::ASPath::AS_SET;
  7         7  
  7         16040  
39              
40             ## Public Class Methods ##
41              
42             sub new {
43 111     111 1 4955 my $class = shift;
44 111         117 my $value = shift;
45 111         106 my $options = shift;
46              
47 111 50       224 if (!defined($options)) { $options = {}; }
  111         158  
48 111   50     437 $options->{as4} ||= 0;
49              
50 111 100       247 return clone Net::BGP::ASPath($value) if (ref $value eq 'Net::BGP::ASPath');
51              
52 110         269 my $this = {
53             _as_path => [],
54             _as4 => $options->{as4}
55             };
56              
57 110         228 bless($this, $class);
58              
59 110 100       207 if (defined($value)) {
60 57 100       88 if (ref $value) {
61 1 50       4 if (ref $value eq 'ARRAY') {
62 1         4 $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         80 $this->_setfromstring($value);
69             }
70             }
71              
72 110         413 return ($this);
73             }
74              
75             sub _setfromstring {
76 203     203   242 my ($this, $value) = @_;
77 203         470 $this->{_as_path} = [];
78              
79             # Normalize string
80 203         1939 $value =~ s/\s+/ /g;
81 203         301 $value =~ s/^\s//;
82 203         274 $value =~ s/\s$//;
83 203         486 $value =~ s/\s?,\s?/,/g;
84              
85 203         406 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     3009 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     1070 $value = $3 || '';
104 355         828 my $segment = Net::BGP::ASPath::AS->new($1);
105              
106 355         317 push(@{ $this->{_as_path} }, $segment);
  355         1036  
107             }
108 203         466 return $this;
109             }
110              
111             sub clone {
112 162     162 1 503 my $proto = shift;
113 162   66     413 my $class = ref $proto || $proto;
114 162 100       266 $proto = shift unless ref $proto;
115              
116 162         341 my $clone = { _as_path => [] };
117              
118 162         146 foreach my $p (@{ $proto->{_as_path} }) {
  162         389  
119 253         201 push(@{ $clone->{_as_path} }, $p->clone);
  253         665  
120             }
121              
122 162         406 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   53 my ($class, $buffer, $buffer2, $options) = @_;
133 31         70 my $this = $class->new;
134              
135 31 100       76 if (!defined($options)) { $options = {}; }
  14         19  
136 31   100     101 $options->{as4} ||= 0;
137              
138 31 100       67 my $size = $options->{as4} ? 4 : 2;
139              
140 31 100       61 if (!defined($buffer2)) { $buffer2 = ''; }
  13         15  
141              
142 31         44 my $segment;
143 31         75 while ($buffer ne '') {
144              
145 39         145 ($segment, $buffer)
146             = Net::BGP::ASPath::AS->_new_from_msg($buffer, $options);
147              
148             # Error handling
149 39 50       112 if ( !(defined $segment) ) {
150 0         0 return undef;
151             }
152 39 50 66     128 if ( length($buffer) && ( ( length($buffer) - 2 ) % $size) ) {
153 0         0 return undef;
154             }
155              
156 39         38 push(@{ $this->{_as_path} }, $segment);
  39         270  
157             }
158              
159             # We ignore AS4_PATHs on native AS4 speaker sessions
160             # So we stop here.
161 31 100       82 if ($options->{as4}) {
162 4         15 return $this;
163             }
164              
165 27         23 my @as4_path;
166              
167 27         57 while ($buffer2 ne '') {
168              
169 5         16 ($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       13 if ( !(defined $segment) ) {
178 0         0 return undef;
179             }
180 5 50 66     22 if ( length($buffer2) && ( ( length($buffer2) - 2 ) % 4) ) {
181 0         0 return undef;
182             }
183              
184 5         12 push (@as4_path, $segment);
185             }
186              
187 27         71 my $as_count = $this->_length_helper( $this->{_as_path} );
188 27         48 my $as4_count = $this->_length_helper( \@as4_path );
189              
190 27 50       58 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         31 my $remove = $as4_count;
196              
197 27         64 while ($remove > 0) {
198 5         7 my $ele = pop @{ $this->{_as_path} };
  5         6  
199 5 100       12 if ($ele->length <= $remove) {
200 2         5 $remove -= $ele->length;
201             } else {
202 3         3 push @{ $this->{_as_path} }, $ele->remove_tail($remove);
  3         8  
203 3         13 $remove = 0;
204             }
205             }
206              
207 27         26 push @{ $this->{_as_path} }, @as4_path;
  27         51  
208              
209 27         78 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   46 my ($this, $args) = @_;
221              
222 25 100       64 if (!defined($args)) { $args = {}; }
  14         20  
223 25   100     103 $args->{as4} ||= 0;
224              
225 25         27 my $has_as4;
226 25         31 my $msg = '';
227 25         30 foreach my $segment (@{ $this->{_as_path} }) {
  25         88  
228 33         171 $msg .= $segment->_encode($args);
229              
230 33 100       137 if ($segment->_has_as4()) { $has_as4 = 1; }
  6         20  
231             }
232              
233 25         32 my $as4;
234 25 100 100     117 if ( ( !($args->{as4} ) ) && ($has_as4) ) {
235 1         2 $as4 = '';
236              
237 1         1 foreach my $segment (@{ $this->{_as_path} }) {
  1         4  
238 2 50       6 if ( !(ref($segment) =~ /_CONFED_/) ) {
239 2         6 $as4 .= $segment->_encode( { as4 => 1 } );
240             }
241             }
242             }
243              
244 25         97 return ($msg, $as4);
245             }
246              
247             sub prepend {
248 46     46 1 130 my $this = shift;
249 46         46 my $value = shift;
250 46 100       123 return $this->prepend_confed($value) if ($value =~ /^\(/);
251 31         51 $this->strip;
252              
253 31         41 my @list = ($value);
254 31 100       65 @list = @{$value} if (ref $value eq 'ARRAY');
  1         2  
255 31 100       65 @list = split(' ', $list[0]) if $list[0] =~ / /;
256              
257             # Ugly - slow - but simple! Should be improved later!
258 31         87 return $this->_setfromstring(join(' ', @list) . ' ' . $this)->cleanup;
259             }
260              
261             sub prepend_confed {
262 33     33 1 156 my $this = shift;
263              
264 33         35 my $value = shift;
265 33 100       136 $value =~ s/^\((.*)\)$/$1/ unless ref $value;
266              
267 33         59 my @list = ($value);
268 33 100       59 @list = @{$value} if (ref $value eq 'ARRAY');
  1         3  
269 33 100       80 @list = split(' ', $list[0]) if $list[0] =~ / /;
270              
271             # Ugly - slow - but simple! Should be improved later!
272 33         104 return $this->_setfromstring('(' . join(' ', @list) . ') ' . $this)
273             ->cleanup;
274             }
275              
276             sub cleanup {
277 82     82 1 99 my $this = shift;
278              
279             # Ugly - slow - but simple! Should be improved later!
280 82         171 my $str = $this->as_string;
281 82         148 $str =~ s/\{\}//g;
282 82         108 $str =~ s/\(\)//g;
283 82         161 $str =~ s/(\d)\) +\((\d)/$1 $2/g;
284 82         130 return $this->_setfromstring($str);
285             }
286              
287             sub _confed {
288 12     12   15 my $this = shift->clone;
289 12         27 @{ $this->{_as_path} } =
  25         42  
290 12         13 grep { (ref $_) =~ /_CONFED_/ } @{ $this->{_as_path} };
  12         14  
291 12         19 return $this;
292             }
293              
294             sub strip {
295 71     71 1 96 my $this = shift;
296 71         158 @{ $this->{_as_path} } =
  117         247  
297 71         64 grep { (ref $_) !~ /_CONFED_/ } @{ $this->{_as_path} };
  71         129  
298 71         104 return $this;
299             }
300              
301             sub striped {
302 26     26 1 45 return shift->clone->strip(@_);
303             }
304              
305             sub aggregate {
306 4     4 1 262 my @olist = @_;
307 4 100       9 shift(@olist) unless ref $olist[0];
308              
309             # Sets
310 4         10 my $cset = Net::BGP::ASPath::AS_CONFED_SET->new;
311 4         9 my $nset = Net::BGP::ASPath::AS_SET->new;
312              
313             # Lists of confed / normal part of paths
314 4         7 my @clist = map { $_->_confed } @olist;
  12         18  
315 4         6 my @nlist = map { $_->striped } @olist;
  12         17  
316              
317 4         6 my $res = '';
318 4         8 foreach my $pair ([ \@clist, $cset ], [ \@nlist, $nset ]) {
319 8         8 my ($list, $set) = @{$pair};
  8         12  
320              
321             # Find common head
322 8         17 my $head = $list->[0]->_head;
323 8         12 foreach my $obj (@{$list}[ 1 .. @{$list} - 1 ]) {
  8         15  
  8         15  
324 16         23 my $s = $obj->_head;
325 16         29 $head = _longest_common_head($head, $s);
326             }
327              
328             # Find tail set
329 8         8 foreach my $obj (@{$list}) {
  8         63  
330 24         44 my $tail = $obj->_tail($head);
331 24 100       58 $tail = '(' . $tail if $tail =~ /^[^\(]*\).*$/; # Fix tail
332 24         42 $obj = Net::BGP::ASPath->new($tail);
333 24         86 $set->merge($obj);
334             }
335 8 100       26 $head .= ')' if $head =~ /^\([^\)]+$/; # Fix head
336 8         21 $res .= "$head $set ";
337             }
338              
339             # Construct result
340 4         10 return Net::BGP::ASPath->new($res)->cleanup;
341             }
342              
343             ## Utility functions (not methods!) ##
344             sub _longest_common_head {
345 16     16   19 my ($s1, $s2) = @_;
346 16         15 my $pos = 0;
347 16         18 $s1 .= ' ';
348 16         25 $s2 .= ' ';
349 16         36 for my $i (0 .. length($s1) - 1) {
350 80 100       123 last unless substr($s1, $i, 1) eq substr($s2, $i, 1);
351 76 100       142 $pos = $i if substr($s1, $i, 1) eq ' ';
352             }
353 16         51 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   38 my $this = shift->clone;
361 24         23 my $ok = 1;
362 25   66     91 $this->{_as_path} =
363 25 100       76 [ grep { $ok &&= (ref $_) =~ /_SEQUENCE$/; $_ = undef unless $ok; }
  24         40  
364 24         19 @{ $this->{_as_path} } ];
365 24         34 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   33 my $thisstr = shift() . " ";
374 24         31 my $head = shift() . " ";
375 24         37 $head =~ s/\(/\\(/g;
376 24         28 $head =~ s/\)/\\)/g;
377 24 50       190 return undef unless $thisstr =~ s/^$head//;
378 24         42 $thisstr =~ s/ $//;
379 24         40 return $thisstr;
380             }
381              
382             # For compatability
383             sub asstring {
384 7     7 0 7 my $this = shift;
385 7         17 return $this->as_string(@_);
386             }
387              
388             sub as_string {
389 517     517 1 888 my $this = shift;
390              
391 517         866 return $this->_as_string_helper($this->{_as_path});
392             }
393              
394             sub _as_string_helper {
395 517     517   502 my ($this, $path) = @_;
396              
397 517         450 return join(' ', map { $_->as_string; } @{ $path });
  818         1581  
  517         787  
398             }
399              
400              
401             sub asarray {
402 26     26 0 258 my $this = shift;
403 26         20 my @res;
404 26         19 foreach my $s (@{ $this->{_as_path} }) {
  26         43  
405 26         18 push(@res, @{ $s->asarray });
  26         54  
406             }
407 26         76 return \@res;
408             }
409              
410             sub len_equal {
411 1     1 0 3 my ($this, $other) = @_;
412 1 50       4 return 0 unless defined($other);
413 1 50       2 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       3 return 0 unless defined($other);
425 1 50       3 return ($this->length < $other->length) ? 1 : 0;
426             }
427              
428             sub len_greaterthen {
429 1     1 0 378 my ($this, $other) = @_;
430 1 50       3 return 1 unless defined($other);
431 1 50       4 return ($this->length > $other->length) ? 1 : 0;
432             }
433              
434             sub len_compare {
435 11     11 0 23 my ($this, $other) = @_;
436 11 50       16 return 1 unless defined($other);
437 11         14 return $this->length <=> $other->length;
438             }
439              
440             sub equal {
441 21     21 0 366 my ($this, $other) = @_;
442 21 50       40 return 0 unless defined($other);
443 21 50       45 confess "Cannot compare " . (ref $this) . " with a " . (ref $other) . "\n"
444             unless ref $other eq ref $this;
445 21 100       38 return $this->as_string eq $other->as_string ? 1 : 0;
446             }
447              
448             sub notequal {
449 1     1 0 2 my ($this, $other) = @_;
450 1 50       4 return 1 unless defined($other);
451 1 50       2 return $this->as_string ne $other->as_string ? 1 : 0;
452             }
453              
454             sub length {
455 49     49 1 129 my ($this) = @_;
456              
457 49         83 return $this->_length_helper($this->{_as_path});
458             }
459              
460             sub _length_helper {
461 103     103   104 my ($this, $path) = @_;
462              
463 103         90 my $res = 0;
464 103         82 foreach my $p (@{ $path }) {
  103         154  
465 146         300 $res += $p->length;
466             }
467 103         243 return $res;
468             }
469              
470             ## POD ##
471              
472             =pod
473              
474             =head1 NAME
475              
476             Net::BGP::ASPath - 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, inluding
534             confederation extentions.
535              
536             =head1 CONSTRUCTOR
537              
538             =over 4
539              
540             =item new() - create a new Net::BGP::ASPath object
541              
542             $aspath = Net::BGP::ASPath->new( PATHDATA, OPTIONS );
543              
544             This is the constructor for Net::BGP::ASPath 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 inteperted as an AS_PATH_SEQUENCE.
552              
553             =item SCALAR
554              
555             A string with AS numbers seperated by spaces (AS_PATH_SEQUANCE).
556             AS_PATH_SETs is written using "{}" with "," to seperate AS numbers.
557             AS_PATH_CONFED_* is writen equally, but encapsulated in "()".
558              
559             =item Net::BGP::ASPath
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 an 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 Net::BGP::ASPath object
592              
593             $clone = $aspath->clone();
594              
595             This method creates an exact copy of the Net::BGP::ASPath 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 count as one BGP hop.
608              
609             =item as_string()
610              
611             Returns the path as a string in same notation as the constructor accept.
612              
613             =item cleanup()
614              
615             Reduce the path by removing meaningless AS_PATH elements (empty sets or
616             sequences) and joining neighbour elements of 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 seperated AS numbers. If string has "()" arround, prepend_confed
633             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             seperated AS numbers. "()" arround 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 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 remaing
654             will be left as an AS_SET and AS_CONFED_SET.
655              
656             Returns the aggregated object. The objects self are not modified.
657              
658             =back
659              
660             =head1 SEE ALSO
661              
662             B, B, Net::BGP, Net::BGP::Process, Net::BGP::Peer,
663             Net::BGP::Notification, Net::BGP::NLRI, Net::BGP::Update
664              
665             =head1 AUTHOR
666              
667             Martin Lorensen
668              
669             =cut
670              
671             ## End Package Net::BGP::ASPath ##