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   4046 use strict;
  7         29  
  7         241  
6 7         666 use vars qw(
7             $VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS @PATHTYPES
8             @BGP_PATH_ATTR_COUNTS
9 7     7   35 );
  7         17  
10              
11             ## Inheritance and Versioning ##
12              
13             @ISA = qw( Exporter );
14             $VERSION = '0.17';
15              
16             ## Module Imports ##
17              
18 7     7   50 use Carp;
  7         12  
  7         455  
19 7     7   2069 use IO::Socket;
  7         77565  
  7         44  
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         157 '+=' => \&prepend,
29             'eq' => \&equal,
30             'ne' => \¬equal,
31             '@{}' => \&asarray,
32 7     7   4082 'fallback' => 1;
  7         17  
33              
34 7     7   4632 use Net::BGP::ASPath::AS;
  7         18  
  7         230  
35 7     7   3023 use Net::BGP::ASPath::AS_CONFED_SEQUENCE;
  7         18  
  7         205  
36 7     7   3002 use Net::BGP::ASPath::AS_CONFED_SET;
  7         18  
  7         202  
37 7     7   44 use Net::BGP::ASPath::AS_SEQUENCE;
  7         13  
  7         128  
38 7     7   30 use Net::BGP::ASPath::AS_SET;
  7         19  
  7         19925  
39              
40             ## Public Class Methods ##
41              
42             sub new {
43 111     111 1 6046 my $class = shift;
44 111         165 my $value = shift;
45 111         162 my $options = shift;
46              
47 111 50       233 if (!defined($options)) { $options = {}; }
  111         176  
48 111   50     483 $options->{as4} ||= 0;
49              
50 111 100       255 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         286 };
56              
57 110         221 bless($this, $class);
58              
59 110 100       251 if (defined($value)) {
60 57 100       104 if (ref $value) {
61 1 50       5 if (ref $value eq 'ARRAY') {
62 1         5 $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         99 $this->_setfromstring($value);
69             }
70             }
71              
72 110         410 return ($this);
73             }
74              
75             sub _setfromstring {
76 203     203   383 my ($this, $value) = @_;
77 203         797 $this->{_as_path} = [];
78              
79             # Normalize string
80 203         1896 $value =~ s/\s+/ /g;
81 203         453 $value =~ s/^\s//;
82 203         342 $value =~ s/\s$//;
83 203         565 $value =~ s/\s?,\s?/,/g;
84              
85 203         451 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     3171 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     1194 $value = $3 || '';
104 355         849 my $segment = Net::BGP::ASPath::AS->new($1);
105              
106 355         486 push(@{ $this->{_as_path} }, $segment);
  355         1006  
107             }
108 203         496 return $this;
109             }
110              
111             sub clone {
112 162     162 1 554 my $proto = shift;
113 162   66     404 my $class = ref $proto || $proto;
114 162 100       317 $proto = shift unless ref $proto;
115              
116 162         359 my $clone = { _as_path => [] };
117              
118 162         232 foreach my $p (@{ $proto->{_as_path} }) {
  162         353  
119 253         345 push(@{ $clone->{_as_path} }, $p->clone);
  253         650  
120             }
121              
122 162         384 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   85 my ($class, $buffer, $buffer2, $options) = @_;
133 31         78 my $this = $class->new;
134              
135 31 100       84 if (!defined($options)) { $options = {}; }
  14         23  
136 31   100     120 $options->{as4} ||= 0;
137              
138 31 100       76 my $size = $options->{as4} ? 4 : 2;
139              
140 31 100       66 if (!defined($buffer2)) { $buffer2 = ''; }
  13         22  
141              
142 31         45 my $segment;
143 31         73 while ($buffer ne '') {
144              
145 39         125 ($segment, $buffer)
146             = Net::BGP::ASPath::AS->_new_from_msg($buffer, $options);
147              
148             # Error handling
149 39 50       111 if ( !(defined $segment) ) {
150 0         0 return undef;
151             }
152 39 50 66     123 if ( length($buffer) && ( ( length($buffer) - 2 ) % $size) ) {
153 0         0 return undef;
154             }
155              
156 39         58 push(@{ $this->{_as_path} }, $segment);
  39         210  
157             }
158              
159             # We ignore AS4_PATHs on native AS4 speaker sessions
160             # So we stop here.
161 31 100       80 if ($options->{as4}) {
162 4         13 return $this;
163             }
164              
165 27         45 my @as4_path;
166              
167 27         57 while ($buffer2 ne '') {
168              
169 5         19 ($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       18 if ( !(defined $segment) ) {
178 0         0 return undef;
179             }
180 5 50 66     18 if ( length($buffer2) && ( ( length($buffer2) - 2 ) % 4) ) {
181 0         0 return undef;
182             }
183              
184 5         11 push (@as4_path, $segment);
185             }
186              
187 27         70 my $as_count = $this->_length_helper( $this->{_as_path} );
188 27         60 my $as4_count = $this->_length_helper( \@as4_path );
189              
190 27 50       57 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         66 while ($remove > 0) {
198 5         8 my $ele = pop @{ $this->{_as_path} };
  5         10  
199 5 100       23 if ($ele->length <= $remove) {
200 2         6 $remove -= $ele->length;
201             } else {
202 3         5 push @{ $this->{_as_path} }, $ele->remove_tail($remove);
  3         8  
203 3         10 $remove = 0;
204             }
205             }
206              
207 27         43 push @{ $this->{_as_path} }, @as4_path;
  27         53  
208              
209 27         80 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   52 my ($this, $args) = @_;
221              
222 25 100       61 if (!defined($args)) { $args = {}; }
  14         26  
223 25   100     125 $args->{as4} ||= 0;
224              
225 25         37 my $has_as4;
226 25         40 my $msg = '';
227 25         38 foreach my $segment (@{ $this->{_as_path} }) {
  25         72  
228 33         136 $msg .= $segment->_encode($args);
229              
230 33 100       106 if ($segment->_has_as4()) { $has_as4 = 1; }
  6         14  
231             }
232              
233 25         44 my $as4;
234 25 100 100     94 if ( ( !($args->{as4} ) ) && ($has_as4) ) {
235 1         4 $as4 = '';
236              
237 1         2 foreach my $segment (@{ $this->{_as_path} }) {
  1         3  
238 2 50       9 if ( !(ref($segment) =~ /_CONFED_/) ) {
239 2         6 $as4 .= $segment->_encode( { as4 => 1 } );
240             }
241             }
242             }
243              
244 25         100 return ($msg, $as4);
245             }
246              
247             sub prepend {
248 46     46 1 165 my $this = shift;
249 46         69 my $value = shift;
250 46 100       138 return $this->prepend_confed($value) if ($value =~ /^\(/);
251 31         75 $this->strip;
252              
253 31         62 my @list = ($value);
254 31 100       68 @list = @{$value} if (ref $value eq 'ARRAY');
  1         4  
255 31 100       66 @list = split(' ', $list[0]) if $list[0] =~ / /;
256              
257             # Ugly - slow - but simple! Should be improved later!
258 31         113 return $this->_setfromstring(join(' ', @list) . ' ' . $this)->cleanup;
259             }
260              
261             sub prepend_confed {
262 33     33 1 116 my $this = shift;
263              
264 33         50 my $value = shift;
265 33 100       136 $value =~ s/^\((.*)\)$/$1/ unless ref $value;
266              
267 33         81 my @list = ($value);
268 33 100       67 @list = @{$value} if (ref $value eq 'ARRAY');
  1         3  
269 33 100       83 @list = split(' ', $list[0]) if $list[0] =~ / /;
270              
271             # Ugly - slow - but simple! Should be improved later!
272 33         116 return $this->_setfromstring('(' . join(' ', @list) . ') ' . $this)
273             ->cleanup;
274             }
275              
276             sub cleanup {
277 82     82 1 136 my $this = shift;
278              
279             # Ugly - slow - but simple! Should be improved later!
280 82         162 my $str = $this->as_string;
281 82         226 $str =~ s/\{\}//g;
282 82         133 $str =~ s/\(\)//g;
283 82         211 $str =~ s/(\d)\) +\((\d)/$1 $2/g;
284 82         169 return $this->_setfromstring($str);
285             }
286              
287             sub _confed {
288 12     12   22 my $this = shift->clone;
289 12         29 @{ $this->{_as_path} } =
290 12         17 grep { (ref $_) =~ /_CONFED_/ } @{ $this->{_as_path} };
  25         64  
  12         21  
291 12         26 return $this;
292             }
293              
294             sub strip {
295 71     71 1 128 my $this = shift;
296 71         174 @{ $this->{_as_path} } =
297 71         89 grep { (ref $_) !~ /_CONFED_/ } @{ $this->{_as_path} };
  117         336  
  71         151  
298 71         123 return $this;
299             }
300              
301             sub striped {
302 26     26 1 53 return shift->clone->strip(@_);
303             }
304              
305             sub aggregate {
306 4     4 1 358 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         10 my $nset = Net::BGP::ASPath::AS_SET->new;
312              
313             # Lists of confed / normal part of paths
314 4         9 my @clist = map { $_->_confed } @olist;
  12         19  
315 4         9 my @nlist = map { $_->striped } @olist;
  12         21  
316              
317 4         9 my $res = '';
318 4         10 foreach my $pair ([ \@clist, $cset ], [ \@nlist, $nset ]) {
319 8         12 my ($list, $set) = @{$pair};
  8         80  
320              
321             # Find common head
322 8         22 my $head = $list->[0]->_head;
323 8         17 foreach my $obj (@{$list}[ 1 .. @{$list} - 1 ]) {
  8         17  
  8         15  
324 16         30 my $s = $obj->_head;
325 16         27 $head = _longest_common_head($head, $s);
326             }
327              
328             # Find tail set
329 8         16 foreach my $obj (@{$list}) {
  8         13  
330 24         46 my $tail = $obj->_tail($head);
331 24 100       70 $tail = '(' . $tail if $tail =~ /^[^\(]*\).*$/; # Fix tail
332 24         55 $obj = Net::BGP::ASPath->new($tail);
333 24         68 $set->merge($obj);
334             }
335 8 100       25 $head .= ')' if $head =~ /^\([^\)]+$/; # Fix head
336 8         26 $res .= "$head $set ";
337             }
338              
339             # Construct result
340 4         13 return Net::BGP::ASPath->new($res)->cleanup;
341             }
342              
343             ## Utility functions (not methods!) ##
344             sub _longest_common_head {
345 16     16   32 my ($s1, $s2) = @_;
346 16         52 my $pos = 0;
347 16         31 $s1 .= ' ';
348 16         36 $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       144 $pos = $i if substr($s1, $i, 1) eq ' ';
352             }
353 16         56 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   43 my $this = shift->clone;
361 24         34 my $ok = 1;
362             $this->{_as_path} =
363 25 100 66     121 [ grep { $ok &&= (ref $_) =~ /_SEQUENCE$/; $_ = undef unless $ok; }
  25         84  
364 24         28 @{ $this->{_as_path} } ];
  24         47  
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   41 my $thisstr = shift() . " ";
374 24         45 my $head = shift() . " ";
375 24         58 $head =~ s/\(/\\(/g;
376 24         34 $head =~ s/\)/\\)/g;
377 24 50       199 return undef unless $thisstr =~ s/^$head//;
378 24         63 $thisstr =~ s/ $//;
379 24         49 return $thisstr;
380             }
381              
382             # For compatability
383             sub asstring {
384 7     7 0 22 my $this = shift;
385 7         18 return $this->as_string(@_);
386             }
387              
388             sub as_string {
389 517     517 1 1039 my $this = shift;
390              
391 517         974 return $this->_as_string_helper($this->{_as_path});
392             }
393              
394             sub _as_string_helper {
395 517     517   798 my ($this, $path) = @_;
396              
397 517         665 return join(' ', map { $_->as_string; } @{ $path });
  818         1661  
  517         921  
398             }
399              
400              
401             sub asarray {
402 26     26 0 331 my $this = shift;
403 26         33 my @res;
404 26         34 foreach my $s (@{ $this->{_as_path} }) {
  26         48  
405 26         33 push(@res, @{ $s->asarray });
  26         53  
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 5 my ($this, $other) = @_;
424 1 50       5 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 407 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 10     10 0 32 my ($this, $other) = @_;
436 10 50       17 return 1 unless defined($other);
437 10         22 return $this->length <=> $other->length;
438             }
439              
440             sub equal {
441 21     21 0 406 my ($this, $other) = @_;
442 21 50       45 return 0 unless defined($other);
443 21 50       48 confess "Cannot compare " . (ref $this) . " with a " . (ref $other) . "\n"
444             unless ref $other eq ref $this;
445 21 100       47 return $this->as_string eq $other->as_string ? 1 : 0;
446             }
447              
448             sub notequal {
449 1     1 0 4 my ($this, $other) = @_;
450 1 50       4 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 47     47 1 164 my ($this) = @_;
456              
457 47         96 return $this->_length_helper($this->{_as_path});
458             }
459              
460             sub _length_helper {
461 101     101   157 my ($this, $path) = @_;
462              
463 101         132 my $res = 0;
464 101         133 foreach my $p (@{ $path }) {
  101         179  
465 140         301 $res += $p->length;
466             }
467 101         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 ##