#!/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);
|