#!/usr/bin/perl # # $Id: expand-as-macro,v 1.18 2011/09/29 11:45:15 he Exp $ # # Copyright (c) 2011 # UNINETT A/S. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY UNINETT AND ``AS IS'' AND ANY # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL UNINETT OR NORDUnet OR # THEIR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # Expand an as-macro in the IRR into an as-origin list, and format the # result either for IOS, IOS-XR or JunOS. # # Prefers to use peval from the IRRtoolset package, resorts to using # whois with either whois.ripe.net or whois.radb.net. The whois # program is expected to honor POSIX argument parsing rules, in that # "--" ends the parsing of the program option list. use Getopt::Std; use strict; sub parse_rpsl { my($fh) = @_; my($inm) = 0; my(@f); our($opt_v); while(<$fh>) { chop; if ($opt_v) { printf("%s\n", $_); } if ($inm) { if (/^\s/) { $_ =~ s/,/ /g; @f = split; &add_ases(@f); } else { $inm = 0; } } if (!$inm) { if (/^members:/) { $inm = 1; $_ =~ s/,/ /g; @f = split; shift @f; &add_ases(@f); } } } if ($opt_v) { print "\n"; } } sub parse_ripe181 { my($fh) = @_; our($notripe, $opt_v); my(@f); while(<IN>) { chop; if ($opt_v) { printf("%s\n", $_); } if (/^% No entries found/) { $notripe = 1; } if (/^as-list:/) { @f = split; shift @f; &add_ases(@f); } } } sub getmacro_ripe { my($mn) = @_; our(%seen, $opt_a); my($IN); $mn = uc($mn); $seen{$mn} = 1; open($IN, "whois -h whois.ripe.net -- \"$opt_a -r $mn\"|"); &parse_rpsl($IN); close($IN); } sub getmacro_radb { my($mn) = @_; my($inm) = 0; our(%seen); my($IN); $mn = uc($mn); $seen{$mn} = 1; open($IN, "whois -h whois.radb.net -- \"-r $mn\"|"); &parse_rpsl($IN); close($IN); } sub add_ases { my(@a) = @_; our(%seen); our(@expand, @ases); foreach my $as (@a) { if ($as =~ /AS-[A-Z0-9]*/) { if (! defined($seen{$as})) { push(@expand, $as); } } if ($as =~ /AS([0-9]+)/) { push(@ases, $1); } } } sub getmacro_ratools { my($mn) = @_; $_ = `peval -h whois.ripe.net -protocol ripe -no-as $mn`; if ($? != 0) { return undef; } $_ =~ s/\(//g; $_ =~ s/\)//g; my(@a) = split; &add_ases(@a); return 1; } sub getmacro { my($mn) = @_; our($notripe); if (&getmacro_ratools($mn)) { return; } else { if ($notripe) { &getmacro_radb($mn); } else { &getmacro_ripe($mn); if ($notripe) { &getmacro_radb($mn); } } } } sub getmacros { my(@start) = @_; our($notripe); our(@expand); $notripe = 0; push(@expand, @start); while($#expand != -1) { &getmacro(shift(@expand)); } } sub print_j { my($apgn, @ases) = @_; my($n, $s, $pfx); printf("policy-options {\n"); printf("replace:\n"); printf(" as-path-group %s {\n", $apgn); $n = 0; $s = ""; foreach my $as (@ases) { if ($s eq "" || length($s) >= 65) { if ($s ne "") { $s .= ")\";"; printf("%s\n", $s); } $n++; $pfx = " as-path " . sprintf("%d \".*(", $n); $s = $pfx; } else { $s .= "|"; } $s .= sprintf("%s", $as); } if ($s ne $pfx) { printf("%s)\";\n", $s); } printf(" }\n}\n"); } sub print_x { my($apgn, @ases) = @_; my($n, $s, $pfx); printf("as-path-set %s\n", $apgn); $n = 0; $s = ""; foreach my $as (@ases) { if ($s eq "" || length($s) >= 65) { if ($s ne "") { $s .= ")\$',"; printf("%s\n", $s); } $n++; $pfx = " ios-regex '_("; $s = $pfx; } else { $s .= "|"; } $s .= sprintf("%s", $as); } printf("%s)\$'\n", $s); printf("end-set\n"); } # Plain printing; just the numbers sub print_p { my(@ases) = @_; foreach my $as (@ases) { printf("%d\n", $as); } } sub printases { my($num, @ases) = @_; my(%as); our($opt_p, $opt_j, $opt_x, $opt_1); foreach my $as (@ases) { $as{$as} = 1; } undef @ases; foreach my $as (keys %as) { push(@ases, $as); } my @sortas = sort {$a <=> $b} @ases; if ($opt_p) { print_p(@sortas); return; } if ($opt_j) { print_j($num, @sortas); return; } if ($opt_x) { print_x($num, @sortas); return; } my($s, $pfx, $sol); printf("!\nno ip as-path access-list $num\n!\n"); $pfx = "ip as-path access-list $num permit "; if (defined($opt_1)) { foreach my $as (@sortas) { printf("%s_%d\$\n", $pfx, $as); } } else { $sol = $pfx . "_("; # start of line $s = ""; foreach my $as (@sortas) { if ($s eq "" || length($s) >= 65) { if ($s ne "") { $s .= ")\$"; printf("%s\n", $s); } $s = $sol; } else { $s .= "|"; } $s .= sprintf("%s", $as); } if ($s ne $sol) { printf("%s)\$\n", $s); } } printf("!\n"); } getopts('1ajn:pvx'); our($opt_a, $opt_n, $opt_p); our(@ases); if (!defined($opt_n) && !defined($opt_p)) { printf(STDERR "usage: $0 [-a] [-p] [-v] [-j] [-x] [-1] " . "-n aclnum/name as-macro ...\n"); printf(STDERR "\n"); printf(STDERR "-a pass through to whois to RIPE (all sources)\n"); printf(SDTERR "-p plain -- just the AS numbers, one per line\n"); printf(STDERR "-v verbose -- print whois output to stdout\n"); printf(STDERR "-j JunOS -- format as-path-group for JunOS\n"); printf(STDERR "-x IOS-XR -- format as-path-set for IOS-XR\n"); printf(STDERR "(none) IOS -- default is to print IOS ACL\n"); printf(STDERR "-1 with IOS output, only one AS per line\n"); printf(STDERR "-n name or number of IOS ACL / as-path-{set|group}\n"); exit(1); } if (defined($opt_a)) { $opt_a = "-a"; } &getmacros(@ARGV); &printases($opt_n, @ases);