BGP script for Blacked Hole Routing app with sql route table

Blacked hole routing uygulalmaları için, bir veri tabanından routeları alıcak, periyodik olarak değişiklikleri takip edicek ve bu prefix listesini BGP ile anons edecek bir uygulama incelemelerim;

John Kristoff, Nanog sunumlarında yer alan routeinjector.pl scriptini kullamaya çalışdım.

Alternatif kaynaklar;

http://www.nongnu.org/quagga/
http://bird.network.cz/
https://code.google.com/p/exabgp/

Uygulama sırasında yaşadığım sorunlar.

1 – IBGP connection kuruyorsanız, BGP updatelerinde local preflerin set edilmesi gerek.

$update = Net::BGP::Update->new(
NLRI => $nlri,
AsPath => $local_asn,
NextHop => $next_hop,
LocalPref => 200,
Origin => 2, # INCOMPLETE, see RFC 4271
);

2 – Veri tabanından çok fazla route alınıp anons edilmek istenmesi durumunda BGP session düşüyor. BGP Update uzunluğundan daha fazla data olabiliyor. Bu sorun alt seviyelerden mi (packet packing, IO::Socket veya NET::BGP modullerinden) kaynaklanıyor anlıyamadım. Fakrlı bir yaklaşım ile, update pakedini uygun uzunlukda parçalara ayırıp, birden fazla update oluşturarak aşdım. send_update parçacığına ekleme yapdım.

3 – Scripti daemonize hale getirildi. Bu şekilde arka planda çalışır durumda.

4 – Script static hale getirdim. BGP ve veri tabanı bilgileri statik olarak girilmek durumunda.

5 – Log kısmını, uzak sunucuya gönderecek şekilde değiştirdim.

6 – Ayrıca IGP yanlışlık ile defualt route ve supernet anons edilmesine karşı uyarı ve önlem koydum.

7 – Cisco ile BGP kurma sırasında, sessionlar zaman zaman düşüyordu. NET:BGP varsayılan holdtime ve keepalive sürelerini değiştirdim. Daha istikrarlı bir duruma geçdi.

#!/usr/bin/perl
use strict;
use warnings;
use Proc::Daemon;

 

# $Id: routeinjector.pl,v 1.2 2012/02/03 17:11:10 jtk Exp $

use Array::Diff; # WARNING: Algorithm::Diff::XS is not taint mode compatible
use DBI;
use Regexp::IPv6 qw($IPv6_re);
use Net::BGP::Process;
use Net::BGP::Peer;
use POSIX qw(strftime);
use Sys::Syslog qw(:standard :macros setlogsock);

use Net::Netmask;

 

# the period, in seconds, to run the utility callback functions
use constant CHECK_PEER_INTERVAL => 5;
use constant CHECK_DB_INTERVAL => 5;

# adapted from Regexp::IPv6 0.03 (yes, oddly enough)
use constant IPV4_RE => qr{ \A (
(?: 25[0-5] | 2[0-4][0-9] | [0-1]? [0-9]{1,2} )
(?:
[.]
(?: 25[0-5] | 2[0-4][0-9] | [0-1]? [0-9]{1,2} )
){3}
) \Z
}xms;

# BGP ERROR codes and subcodes adapted from bgpsimple.pl
# XXX: put this into a separate module file
my %BGP_ERR = ( # this hash doesn’t work well as a constant, use Readonly?
0 => {
__NAME__ => ‘Reserved’,
0 => ‘Reserved’,
},
1 => {
__NAME__ => ‘Message Header Error’,
0 => ‘Reserved’,
1 => ‘Connection Not Synchronized’,
2 => ‘Bad Message Length’,
3 => ‘Bad Message Type’,
},
2 => {
__NAME__ => ‘OPEN Message Error’,
0 => ‘Reserved’,
1 => ‘Unsupported Version Number’,
2 => ‘Bad Peer AS’,
3 => ‘Bad BGP Identifier’,
4 => ‘Unsupported Optional Parameter’,
5 => ‘[Deprecated], see RFC4271’,
6 => ‘Unacceptable Hold Time’,
},
3 => {
__NAME__ => ‘UPDATE Message Error’,
0 => ‘Reserved’,
1 => ‘Malformed Attribute List’,
2 => ‘Unrecognized Well-known Attribute’,
3 => ‘Missing Well-known Attribute’,
4 => ‘Attribute Flags Error’,
5 => ‘Attribute Length Error’,
6 => ‘Invalid ORIGIN Attribute’,
7 => ‘[Deprecated], see RFC4271’,
8 => ‘Invalid NEXT_HOP Attribute’,
9 => ‘Optional Attribute Error’,
10 => ‘Invalid Network Field’,
11 => ‘Malformed AS_PATH’,
},
4 => {
__NAME__ => ‘Hold Timer Expired’,
0 => ‘Reserved’,
},
5 => {
__NAME__ => ‘Finite State Machine Error’,
0 => ‘Reserved’,
},
6 => {
__NAME__ => ‘Cease’,
0 => ‘Reserved’,
1 => ‘Maximum Number of Prefixes Reached’,
2 => ‘Administrative Shutdown’,
3 => ‘Peer De-configured’,
4 => ‘Administrative Reset’,
5 => ‘Connection Rejected’,
6 => ‘Other Configuration Change’,
7 => ‘Connection Collision Resolution’,
9 => ‘Out of Resources’,
},
);

 

# required arguments need untainting for Socket.pm
my $local_addr = untaint_addr( ‘10.2.1.1’ );
my $local_asn = untaint_asn( ‘65001’ );
my $peer_addr = untaint_addr( ‘10.2.1.2’ );
my $peer_asn = untaint_asn( ‘65001’ );
my $next_hop = untaint_addr( ‘10.2.1.1’ );
# setup logging if requested
# NOTE: we don’t know the load so just keep the log device open
openlog(“givename”, ‘noeol,nonul’);
setlogsock({ type => “udp”, host => “10.2.1.1”, port => 514 });
# setup persistent database connection if applicable
my $dbh_ref = db_connect(
{
dbhost => ‘10.2.1.35’,
dbtype => ‘ODBC’,
dbport => ‘1433’,
dbname => ‘name’,
dbuser => ‘user’,
dbpass => ‘pass’,
}
);

# announce array reference used to advertise routes
my $announce_ref;

# our peering configuration hash to pass to object creator
my %SESSION_CONFIG = (
Start => 1,
ThisID => $local_addr,
ThisAS => $local_asn,
PeerID => $peer_addr,
PeerAS => $peer_asn,
HoldTime => 180,
KeepAliveTime => 30,

Listen => 0, # non-standard, but we avoid needing root privs
);

# create necessary routing and peer objects
my $bgp = Net::BGP::Process->new;
my $peer = Net::BGP::Peer->new(%SESSION_CONFIG);

# apply peer configuration
$bgp->add_peer($peer);

# setup handlers
$peer->set_open_callback( \&callback_open );
$peer->set_update_callback( \&callback_update );
$peer->set_notification_callback( \&callback_notification );
$peer->set_refresh_callback( \&callback_refresh );
$peer->set_reset_callback( \&callback_reset );
$peer->set_error_callback( \&callback_error );
$peer->add_timer( \&callback_check_peer, CHECK_PEER_INTERVAL );
$peer->add_timer( \&callback_check_db, CHECK_DB_INTERVAL );
$SIG{INT} = \&sig_int;

# run as long as long as we’re not interrupted
$bgp->event_loop();

# tear down database connection
db_disconnect($dbh_ref);

# closing the log file should be the last thing we do
closelog();
# bye bye
exit;

# given a valid looking IP address, return it, else return undef
sub untaint_addr {
my $addr = shift || return;

$addr = lc($addr); # IPv6 hex chars must be lc per RFC 5952

# IPv6 addresses will always have a colon, IPv4 won’t
if ( $addr =~ /:/ ) {
($addr) = $addr =~ /\A($IPv6_re)\Z/;
}
else {
($addr) = $addr =~ IPV4_RE;
}

return $addr; # an IPv4 address, IPv6 address or undef
}

# given a valid looking 16-bit ASN, return it, else return undef
sub untaint_asn {
my $asn = shift || return;

return if $asn !~ m{ \A \d{1,5} \Z }xms; # simple digit check
return if $asn < 0 || $asn > 65535; # Net::BGP only supports 16-bit ASNs

($asn) = $asn =~ m{ \A (\d+) \Z }xms;

return $asn; # will be a 16-bit ASN
}

# used for log messages and database record timestamps
sub current_timestamp {
return strftime “%Y-%m-%d %H:%M:%S UTC”, gmtime();
}

# connect to the database
sub db_connect {
my ($arg_ref) = @_;
my $db_type = $arg_ref->{dbtype};
my $db_host = $arg_ref->{dbhost};
my $db_port = $arg_ref->{dbport};
my $db_user = $arg_ref->{dbuser};
my $db_pass = $arg_ref->{dbpass};
my $db_name = $arg_ref->{dbname};

#TODO: MySQL does this different, try to do the right thing
my $db_dsn = “DBI:$db_type:$db_name”; #Added for ODBC, look odbc.ini and freetds.conf for database config
my $dbh;
eval { $dbh = DBI->connect( $db_dsn, $db_user, $db_pass ); };
if ($@) {
logit( LOG_ERR, __LINE__, $@ );
}
return $dbh ? \$dbh : undef;
}

# disconnect from the database
sub db_disconnect {
my ($dbh_ref) = @_;

eval { $$dbh_ref->disconnect; };
if ($@) {
logit( LOG_ERR, __LINE__, $@ );
}

return;
}

# send log messages to stdout, stderr or syslog as appropriate
sub logit {
my ( $level, $line_num, $message ) = @_;

my $stamp = current_timestamp;
$level ||= LOG_WARNING; # just in case we got nothing
$line_num ||= 0; # just in case we got nothing
$message ||= ‘unspecified event’; # just in case we got nothing
##STDOUT
printf “%s [%s:%d] %s\n”, $stamp, $level, $line_num, $message;
if ( $level == LOG_WARNING || $level == LOG_ERR ) {
# syslog( $level, “[$line_num] $message” );
}
return;
}

# get the current active route list
sub get_routes {
my $sql = “SELECT DISTINCT prefix FROM ROUTETABLE”;
# get an array reference to the current announce prefixes
my $routes_ref = $$dbh_ref->selectcol_arrayref($sql);
if ( !$routes_ref ) {
logit( LOG_ERR, __LINE__, DBI->errstr );
return;
}

if (scalar @$routes_ref < 1) {
my $message =
‘Total number of Prefixes:@$routes_ref error’ . scalar @$routes_ref;
logit( LOG_ERR, __LINE__, $message );
return;
}

return $routes_ref;
}

# what to do when receiving a BGP open message
sub callback_open {
my ($peer) = @_;
my $message =
‘BGP session open from ‘ . $peer->peer_id . ‘ AS’ . $peer->peer_as;

logit( LOG_INFO, __LINE__, $message );

# NOTE: we can’t do a $peer->update from here for some reason, too soon?
# we’ll get them sent out on timer, but do init/clear array ref
$announce_ref = undef;
return;
}

# what to do when receiving a BGP update message
sub callback_update {

return;
}

# what to do when receiving a BGP notification message
sub callback_notification {
my ( $peer, $notify ) = @_;

# we don’t really care what we see here

return;
}

# what to do when receiving a route refresh message
sub callback_refresh {
my ( $peer, $refresh ) = @_;

# NOTE: It seems Net::BGP calls us when refresh is an option in
# an OPEN message but there won’t be any refresh object for us.
# Furthermore, with Net::BGP 0.14 we don’t seem to be called
# when we receive an actual refresh message. Thus, this
# subroutine is a placeholder.

return;
}

# what to do when BGP session is reset
sub callback_reset {
my ($peer) = @_;
my $message = sprintf “BGP session reset with %s AS%s”,
$peer->peer_id, $peer->peer_as;

logit( LOG_ERR, __LINE__, $message );

return;
}

# what to do when Net::BGP application detects an error
sub callback_error {
my ( $peer, $error ) = @_;
my $message = sprintf “BGP failure with %s AS%s type %s subcode %s”,
$peer->peer_id, $peer->peer_as,
$BGP_ERR{ $error->error_code }{__NAME__},
$BGP_ERR{ $error->error_code }{ $error->error_subcode };

if ( $error->error_data ) {
$message .= ‘, data ‘ . unpack( ‘H*’, $error->error_data );
}

logit( LOG_ERR, __LINE__, $message );

return;
}

# what to do every CHECK_PEER_INTERVAL
sub callback_check_peer {
my ($peer) = @_;
my $message = sprintf “Trying to reestablish peering with %s AS%s”,
$peer->peer_id, $peer->peer_as;

# try to get session reestablished if broken
if ( !$peer->is_established ) {
logit( LOG_NOTICE, __LINE__, $message );
$peer->start();
return;
}

# if we have routes we’re announcing, see if we need to send an update
if ($announce_ref) {

# get current route announcement list
my $candidate_ref = get_routes();

# Array::Diff doesn’t gracefully handle undef parameters
if ($candidate_ref) {
my $diff = Array::Diff->diff( $announce_ref, $candidate_ref );
send_update( $diff->added, $diff->deleted );
}
else {

# withdraw everything
send_update( undef, $announce_ref );
}
$announce_ref = $candidate_ref;
}
else {

# init announce list and announce if necessary
$announce_ref = get_routes();
send_update($announce_ref);
}

return;
}

# what to do every CHECK_DB_INTERVAL
sub callback_check_db {
my ($peer) = @_;
my $message = sprintf “Trying to reestablish the database connection”;

if ( !$$dbh_ref->ping ) {
logit( LOG_NOTICE, __LINE__, $message );
db_connect($dbh_ref);
}

return;
}

# what to do when interrupted
sub sig_int {
my $message = sprintf “Shutting down peering with %s AS%s”,
$peer->peer_id, $peer->peer_as;

logit( LOG_WARNING, __LINE__, $message );

# this calls $peer->stop and sends a cease NOTIFICATION message
$bgp->remove_peer($peer);
return;
}

# send a list of announcements and withdrawals
sub send_update {
my ( $nlri_ref, $withdraw_ref ) = @_;
my $update;
my %prefixes;
my @new_nlri;
my @new_withdraw;
my $maxupdatesize=800;
return if !$nlri_ref && !$withdraw_ref;
logit( LOG_DEBUG, __LINE__, “inside update” );

# both an announce and withdrawal
if ( $nlri_ref && $withdraw_ref ) {
logit( LOG_INFO, __LINE__, ‘Update with NLRI & WITHDRAW Prefixes’ );
for (@$nlri_ref) {
my $nlri_row = $_;
my $block = new Net::Netmask ($nlri_row);
logit( LOG_INFO, __LINE__, “normalized prefix $block” );
logit( LOG_ERR, __LINE__, “Prefix:$block shorther than 24” ) if ($block->bits() <= 24);
logit( LOG_ERR, __LINE__, “Skiping Prefix:$nlri_row normalized as $block Default Route” ),next if ($block->bits() == 0);
push (@new_nlri,$block);
}
for (@$withdraw_ref) {
my $withdraw_row = $_;
logit( LOG_INFO, __LINE__, “Prefix $withdraw_row” );
$withdraw_row=~s/( null0)//;
logit( LOG_INFO, __LINE__, “Prefix $withdraw_row” );
$withdraw_row=~s/(\s)+/\:/;
logit( LOG_INFO, __LINE__, “Prefix $withdraw_row” );
my $block = new Net::Netmask ($withdraw_row);
logit( LOG_INFO, __LINE__, “Prefix $withdraw_row” );
push (@new_withdraw,$block);
logit( LOG_INFO, __LINE__, “normalized prefix $block” );
}

my $l=scalar @new_nlri;
if (scalar @new_withdraw >= scalar @new_nlri) {
$l=scalar @new_withdraw;
}
my $i=0;
my $z=0;
my @tmp_nlriupdate=();
my @tmp_withdrawupdate=();
while ($i < $l) {
push(@tmp_nlriupdate,$new_nlri[$i]) if (defined($new_nlri[$i]));
push(@tmp_withdrawupdate,$new_withdraw[$i]) if (defined($new_withdraw[$i]));
if ($z!=int($i/$maxupdatesize)) {
$update = Net::BGP::Update->new(
NLRI => \@tmp_nlriupdate,
Withdraw => \@tmp_withdrawupdate,
AsPath => $local_asn,
NextHop => $next_hop,
LocalPref => 200,
Origin => 2, # INCOMPLETE, see RFC 4271
);
$peer->update($update);
@tmp_nlriupdate=();
@tmp_withdrawupdate=();
} elsif ($i==$l-1) {
$update = Net::BGP::Update->new(
NLRI => \@tmp_nlriupdate,
Withdraw => \@tmp_withdrawupdate,
AsPath => $local_asn,
NextHop => $next_hop,
LocalPref => 200,
Origin => 2, # INCOMPLETE, see RFC 4271
);
$peer->update($update);
@tmp_nlriupdate=();
@tmp_withdrawupdate=();
}
$z=int($i/$maxupdatesize);
$i++;
}
}
# just an announce
elsif ($nlri_ref) {
logit( LOG_INFO, __LINE__, ‘Just NLRI update’ );
for (@$nlri_ref) {
my $nlri_row = $_;
my $block = new Net::Netmask ($nlri_row);
logit( LOG_INFO, __LINE__, “normalized prefix $block” );
logit( LOG_ERR, __LINE__, “Prefix:$block shorther than 24” ) if ($block->bits() <= 24);
logit( LOG_ERR, __LINE__, “Skiping Prefix:$nlri_row normalized as $block Default Route” ),next if ($block->bits() == 0);
push (@new_nlri,$block);
}
my $l=scalar @new_nlri;
my $i=0;
my $z=0;
my @tmp_update;
while ($i < $l) {
push(@tmp_update,$new_nlri[$i]);
if ($z!=int($i/$maxupdatesize)) {
$update = Net::BGP::Update->new(
NLRI => \@tmp_update,
AsPath => $local_asn,
NextHop => $next_hop,
LocalPref => 200,
Origin => 2, # INCOMPLETE, see RFC 4271
);
$peer->update($update);
@tmp_update=();
} elsif ($i==$l-1) {
$update = Net::BGP::Update->new(
NLRI => \@tmp_update,
AsPath => $local_asn,
NextHop => $next_hop,
LocalPref => 200,
Origin => 2, # INCOMPLETE, see RFC 4271
);
$peer->update($update);
@tmp_update=();
}
$z=int($i/$maxupdatesize);
$i++;
}

}
# just a withdrawal
else {
logit( LOG_INFO, __LINE__, ‘Just withdraw’ );
for (@$withdraw_ref) {
my $withdraw_row = $_;
my $block = new Net::Netmask ($withdraw_row);
logit( LOG_INFO, __LINE__, “Prefix $withdraw_row” );
push (@new_withdraw,$block);
logit( LOG_INFO, __LINE__, “normalized prefix $block” );
push (@new_withdraw,$block);
}
my $l=scalar @new_withdraw;
my $i=0;
my $z=0;
my @tmp_update;
while ($i < $l) {
push(@tmp_update,$new_withdraw[$i]);
if ($z!=int($i/$maxupdatesize)) {
$update = Net::BGP::Update->new( Withdraw => \@tmp_update, );
$peer->update($update);
@tmp_update=();
} elsif ($i==$l-1) {
$update = Net::BGP::Update->new( Withdraw => \@tmp_update, );
$peer->update($update);
@tmp_update=();
}
$z=int($i/$maxupdatesize);
$i++;
}

}
return;
}