Linux, FreeBSD, Juniper, Cisco / Network security articles and troubleshooting guides

It is currently Tue Jun 06, 2023 8:10 am

Author Message
Post  Post subject: BGP Blackhole (RTBH) with Juniper SRX firewall  |  Posted: Thu May 29, 2014 6:45 am

Joined: Tue Aug 04, 2009 9:16 am
Posts: 250


BGP Blackhole (RTBH) with Juniper SRX firewall

How to use your Juniper SRX firewall and BGP RTBH to fight some of the spam/bad traffic

I own a small (free) web/mail hosting solution for personal and close friends' websites. It is unbelievable how much junk even hosting ~10 domains can attract... about 2000 spam emails / week.

Up until recently, big IP blocks (/24) that were used to deliver spam were added to firewall's blacklist security policy. Due to big amount of spam and new IPs, the configuration started to grow and this method passed the scallability point.

Luckily latest version of SpamAssassin with a few tweaks, ClamAV with Sanesecurity Spam signatures + few custom Clamav signatures filter ~99% of the spam for all domains. Based on this result, Dovecot IMAP server will copy them to the administrator (me :D) in one big and welcoming mail directory (like a quarantine).

I still believe that most inteligent and expensive method is to allow all traffic and perform layer 7 inspection ( Intrussion prevention, anti-virus scanning, NG firewalls and so on), but SA/ClamAV/SaneSecurity are very efficient (and free) guards.

But what if you think this way: "Most of the IP addresses / subnets that are used to deliver spam are also probably used to flood, scan (VoIP, SSH, Web, ftp) vulnerabilities, bruteforcing, host phishing (fishing) domains and other types of malware traffic ? What if I want to block them dynamically from reaching my network altogether ?"

Since my network is single homed and no BGP peering is required with my ISP, the BGP blackhole community and FlowSpec solutions to stop the traffic in the ISP network is not something that I am looking for.

Blackhole communities are used to export a /32 route (an IP inside your network that is under attack) to the ISP via eBGP with the ISP's blackhole community to avoid paying for DDoS traffic and to decongestion the line. This does not block based on source IP/subnets, but rather on destination.

Flowspec is newer technology. Juniper and other vendors have been supporting it for some time (folks at CloudFlare use it very efficiently) but still is not scalable enough for my needs.

A hybrid BGP Remote Triggered Blackhole solution between the above two is something that I need. RTBH on my network's edge - bad traffic still reaches my network, but I can inject dynamicallly tens of thousands of offending IPs and block them.

This example is based on RFC5635 (, but uses slightly different approach than Unicast Reverse Path Filtering as the SRX firewall has anti-spoofing IP screening feature that can be enabled in a security zone (Checkout "Understanding Juniper SRX security zones":

Using IP Spoofing screen over Unicast Reverse Path Filtering has the advantage that it is easier to syslog the offending IPs and see details of the blocked traffic (timestamp, destination IP, source IP). This can also be achieved with uRPF using fail policy, but it is just a little more complex.

Hoow does it work ?
- Extract IP addresses used to deliver spam, sort and prepare.
- Edge SRX firewall is configured for a BGP peering connection not to the ISP, but to an internal PERL script. The SRX firewall is configured to set the next hop of all routes to a Reject (send ICMP Dest NET unreachable) or Discard (drop) next hop. It is also configured to reject internal RFC1918 prefixes and to accept only prefix lengths in the range between /24 and /32.
- The perl script uses Perl's NET::BGP::Update module to inject routes into the SRX. Based on bgpsimple, but altered to inject prefixes dynamically (on reciving SIGINT signal). It is also configured to daemonize.
- When new prefixes are added, they are added into a file and a SIGINT signal is sent to the script's daemon PID (kill -2 <PID of script child/daemon).

This guide starts of with extracting the IP addresses used to deliver spam to the network.

In the Maildir (IMAP mail directory format that, contrary to MBOX, keeps each individual email in a separate file. Advantages and disadvantages...), it is easy to extract the offending IPs using a simple command line (works on Linux and BSD).

nobody@mail-server:/var/home/vmail/domain/user/Maildir/.Junk/cur# grep -l '^Subject: \*\*\*\*\*SPAM' * | xargs grep '^Received: from .* by' | awk '{print $3}' | sort -n | uniq -c | sort -n
      2 94.242.203
      3 115.68.23
      3 5.9.76
      3 89.40.214
      4 50.31.35.
      4 82.208.162.
      5 31.14.8.
      8 31.14.8.
      8 31.14.8.
      8 31.14.8.
     11 31.14.8.

Output is masked (last octet removed).

This can be very well altered for different analysis of the offending IPaddresses.

Next part: BGP configuration on edge SRX firewall to peer with the perl script.
# show routing-instances ISP protocols bgp group iBGP-block-Spam
type internal;
multihop {
    ttl 2;
local-address;   --> Loopback IP address
import iBGP-block-Spam-import;
family inet {
    unicast {
        prefix-limit {
            maximum 10000;
export iBGP-block-Spam-export;
neighbor; --> Perl script is running on this IP address
# show policy-options policy-statement iBGP-block-Spam-export
then reject;

# show policy-options policy-statement iBGP-block-Spam-import   
term reject-internal {
    from {
        route-filter orlonger reject;
        route-filter orlonger reject;
        route-filter orlonger reject;
term 1 {
    from {
        route-filter prefix-length-range /24-/32;
    then {
        community add no-export;
term 2 {
    then reject;
# show routing-instances ISP routing-options static route

# show security screen ids-option internet | display set | match spoof
set security screen ids-option internet ip spoofing
# show security zones security-zone internet screen
screen internet;
# show security log | display set
set security log mode stream
set security log rate-cap 2
set security log source-address
set security log stream homeserv format sd-syslog
set security log stream homeserv category all
set security log stream homeserv host
set security log stream homeserv host port 514

Some failsafe measures in the srx firewall:
- accepts only prefix ranges between /24 and /32
- accepts a maximum of 10k prefixes and tears down the BGP session with the script in case this limit is reached ( in Junos this limit is applied to received prefixes, not accepted prefixes)
- authentication would also be good.

The perl script is structured this way:
- trap SIGINT signal and execute a subroutine named "check". When this subroutine is executed it triggers a chain reaction that involves checking a file on disk for new IPs, constructs the BGP update object, moves these new IP addresses to another file (that is loaded at execute time) and empties the old one.

The script is very basic, without prefix validation - which is probably done in Net::BGP::Update module of perl - and it's intended for proof of concept. It is not supported by me, nor my employer.

Defining some of the interesting variables...

use strict;
use warnings;
use Getopt::Long;

use Net::BGP;
use Net::BGP::Process;

#$SIG{INT} = sub { my $check = 1; }
my $check = 0;
my $end_loop = 5;
my $cur = 1;
my $newupdate;
my $pid = fork();
$SIG{INT} = \&check;

Some interesting subroutines
sub check() {
   $check = 1;
sub on_the_fly() {
   if ($check == 1) {
   my $peer = shift(@_);
   my $input_file = 'bad_ips.txt';
   my $input_size = -s $input_file;
   my $update;
   my $line;
   if($input_size == 0 ) {
      print "WTF ?!\r\n";
      $check = 0;

   # Open file with badroutes
   open (BADROUTES, '>>bad_routes.txt');
   # Open file with new IPs
   open BADIPS, '<',$input_file;
   while (<BADIPS>) {

      if ($_ !~ /\/(24)|(25)|(26)|(27)|(32)$/) {
         $line = $_.'/32';

      } else {
         $line = $_;

      if ($line =~ s/^-(.*)/\1/g) {
         $update = Net::BGP::Update->new(
            Withdraw    => [ $line ],
            NextHop     => '',
            Origin      => 'INCOMPLETE',
            AsPath      => '' ,
            LocalPref   => 100,
            MED => 200
      } else {
         $update = Net::BGP::Update->new(
            NLRI => [ $line ],
            NextHop     => '',
            Origin      => 'INCOMPLETE',
            AsPath      => '' ,
            LocalPref   => 100,
            MED => 200
         print BADROUTES "$line\r\n";
   open BADIPS, '+>', $input_file;
   close BADIPS;
   $check = 0;

Now, the daemonizing part (it has to run in background)
   # Parent exiting
} else {
   ## CHILD

   if ($dry) {
        die "Prefix file (-f) required for dry run!\n" if not ($infile);
        sub_debug ("m", "Starting dry run.\n");
        sub_debug ("m", "Dry run done, exiting.\n");
   $peer->add_timer(\&load_on_execute, 5);
   $peer->add_timer(\&on_the_fly, 1);

The script uses fork() to create a child process that does all the work. The parent dies and child is adopted by INIT (basic Unix).

Next, it opens up the BGP connection to the peer (SRX edge firewall) and creates two callback methods that are executed inside the bgp event loop.

First callback, "load_on_execute" injects all the routes from the "bad_routes.txt" file into BGP when the script is initialized (not covered).

The second callback, "on_the_fly", is executed only when $check variable is 1. This variable is enabled when the "check" subroutine is called. The "check" subroutine is called when SIGINT is received.

Next, the callback method checks if the "bad_ips.txt" file size is not zero ( no point in going further, right ?).

It opens the "bad_ips.txt" file for reading, strips new line characters and if the line starts with a dash, it creates a withdraw event. Otherwise, it creates an update one.

At the end, it creates the bgp update object cleans the input file, remembers all new IPs and disables the $check variable. All these are done inside the bgp event loop.

Once the script has been started, let's do some Verification:
> show bgp summary
Groups: 2 Peers: 3 Down peers: 0
Table          Tot Paths  Act Paths Suppressed    History Damp State    Pending
inet.0                 0          0          0          0          0          0
Peer                     AS      InPkt     OutPkt    OutQ   Flaps Last Up/Dwn State|#Active/Received/Accepted/Damped...             64819       3285       2487       0       8    12:26:21 Establ
  ISP.inet.0: 1031/1032/1031/0

Good. I inject another prefix:
user@server$ echo>>bad_ips.txt
user@server$ kill -2 97855
user@server$ tcpdump <...> > Flags [P.], cksum 0xa0bd (correct), seq 1:57, ack 19, win 514, options [nop,nop,TS val 4066994726 ecr 3436391629], length 56: BGP, length: 56
        Update Message (2), length: 56
          Origin (1), length: 1, Flags [T]: IGP
          AS Path (2), length: 0, Flags [T]: empty
          Next Hop (3), length: 4, Flags [T]:
          Multi Exit Discriminator (4), length: 4, Flags [O]: 200
          Local Preference (5), length: 4, Flags [T]: 100
          Updated routes:

> show bgp summary   
Groups: 2 Peers: 3 Down peers: 0
Table          Tot Paths  Act Paths Suppressed    History Damp State    Pending
inet.0                 0          0          0          0          0          0
Peer                     AS      InPkt     OutPkt    OutQ   Flaps Last Up/Dwn State|#Active/Received/Accepted/Damped...             64819       3290       2492       0       8    12:27:44 Establ
  ISP.inet.0: 1032/1033/1032/0

> show route table ISP.inet.0 terse

ISP.inet.0: 4094 destinations, 7134 routes (4092 active, 0 holddown, 3 hidden)
+ = Active Route, - = Last Active, * = Both

A Destination        P Prf   Metric 1   Metric 2  Next hop         AS path
*   B 170        100        200  Reject           I

> show route table ISP.inet.0 extensive | match age
                Age: 38       Metric: 200     Metric2: 0

Route was added 38 seconds ago.

user@server$ ping
PING ( 56 data bytes
36 bytes from Destination Net Unreachable
Vr HL TOS  Len   ID Flg  off TTL Pro  cks      Src      Dst
4  5  00 5400 d409   0 0000  40  01 8649

user@attacker$ telnet 80               
telnet: Unable to connect to remote host: Connection timed out

So for packets coming from the internet, packet is dropped by the anti-spoofing feature. Packets coming from the LAN side towards the blocked IP, are rejected (ICMP type 3) as spoofing is not performed on this side.

Here is a screen syslog message generated by the srx:
RT_IDS - RT_SCREEN_IP [junos@2636. attack-name="IP spoofing!" source-address="" destination-address="" protocol-id="6" source-zone-name="internet" interface-name="ae1.5" action="drop"]

On Juniper routing platforms without multi services cards, the anti-spoof feature is not present (it's a firewall feature), but instead the uRPF feature can be used (described in RFC 5635).

Note: Both anti-spoof and unicast Reverse Path Filtering features work on the same routing table as the ingress interface and, to my knowledge, it cannot be changed.

There are beter tools out there (like exaBGP) that are ready to do this job much easier, but for me, this is better learning experience.

Display posts from previous:  Sort by  
E-mail friendPrint view

Topics related to - "BGP Blackhole (RTBH) with Juniper SRX firewall"
 Topics   Author   Replies   Views   Last post 
There are no new unread posts for this topic. Juniper SRX firewall debug: packet dropped: for self but not interested




Mon Jun 23, 2014 3:52 am

admin View the latest post

There are no new unread posts for this topic. ROUTING INSTANCE is not working on firewall srx210




Thu Jun 23, 2016 9:50 am

admin View the latest post

There are no new unread posts for this topic. Juniper SRX NAT64 static-nat inet impacts non-nat IPv4 traffic




Wed May 11, 2016 9:15 pm

admin View the latest post

There are no new unread posts for this topic. Configuring and verifying unicast reverse path filter (uRPF) on Juniper SRX




Fri Feb 01, 2013 12:09 pm

admin View the latest post

There are no new unread posts for this topic. Juniper SRX NAT64 behavior in relation to DF (Don’t Fragment) bit on incoming IPv4 packets




Thu Mar 10, 2016 11:31 am

admin View the latest post

There are no new unread posts for this topic. Juniper SRX testcase - How to block TCP SYN packets with data/segment bytes (strict-syn-check)




Tue Jun 19, 2012 8:38 am

admin View the latest post

There are no new unread posts for this topic. Juniper SRX packet mode switch back to flow mode (verification)




Tue May 28, 2013 11:10 am

mandrei99 View the latest post

There are no new unread posts for this topic. Juniper SRX - How to perform source nat on Junos self originated packets - Junos 11.4




Sun Jun 03, 2012 3:46 pm

debuser View the latest post


Who is online
Users browsing this forum: No registered users and 0 guests
You can post new topics in this forum
You can reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum
Jump to:  
cronNews News Site map Site map SitemapIndex SitemapIndex RSS Feed RSS Feed Channel list Channel list

Delete all board cookies | The team | All times are UTC - 5 hours [ DST ]