Tag Archives: Junos

Route Leaking with Junos

I’ve been working on a few projects recently that have in one way or another required the leaking of routes between different routing tables / routing instances. When I first started working with Junos I did find RIB groups a bit confusing, so here goes with a post about the feature.

In this post I’m going to show you three ways to leak routes between tables – using RIB groups, Instance Import and Logical Tunnels.

Lab topology

In this post I will re-use the topology I created in my last vMX post.

routeleak

The topology consists of 2 x vMX,  PE1 and PE2 will be the main routers on each vMX, and INTGW and CE2 will be Logical Systems.

The link between PE2 and CE2 will be via a VRF routing-instance “red”. All other routes are in table inet.0.

The object of the lab is to leak routes between inet.0 and red.inet.0 on PE2. We will leak a default route between inet.0 and red.inet.0 and leak CE2’s loopback in to inet.0

First let’s setup the topology…

INTGW

This device is simulating an Internet gateway. I will originate a default route in IS-IS to the rest of the topology.

Note: the Loopback address is not advertised in to IS-IS.

root@PE1> show configuration logical-systems INTGW
interfaces {
    ge-0/0/2 {
        unit 0 {
            family inet {
                address 192.168.12.1/24;
            }
            family iso;
        }
    }
    lo0 {
        unit 1 {
            family inet {
                address 1.1.1.1/32;
            }
            family iso {
                address 49.0001.0001.0001.0001.00;
            }
        }
    }
}
protocols {
    isis {
        export DEFAULT;
        interface ge-0/0/2.0 {
            point-to-point;
            level 1 disable;
        }
    }
}
policy-options {
    policy-statement DEFAULT {
        from {
            protocol aggregate;
            route-filter 0.0.0.0/0 exact;
        }
        then accept;
    }
}
routing-options {
    aggregate {
        route 0.0.0.0/0;
    }
}

PE1

This router really isn’t doing much of interest. It is simply running IS-IS on the interfaces between INTGW and PE2

PE2

This router is learning routes from INTGW and PE2 via IS-IS. The interface connection to CE2 is placed in a routing-instance “red”. PE2 and CE2 are exchanging routes using OSPF.

interfaces {
    ge-0/0/1 {
        unit 0 {
            family inet {
                address 192.168.34.3/24;
            }
        }
    }
}
routing-instances {
    red {
        instance-type virtual-router;
        interface ge-0/0/1.0;
        protocols {
            ospf {
                area 0.0.0.0 {
                    interface ge-0/0/1.0;
                }
            }
        }
    }
}

CE2

Nothing special about CE2.

interfaces {
    ge-0/0/2 {
        unit 0 {
            family inet {
                address 192.168.34.4/24;
            }
        }
    }
    lo0 {
        unit 4 {
            family inet {
                address 4.4.4.4/32;
            }
        }
    }
}
protocols {
    ospf {
        area 0.0.0.0 {
            interface ge-0/0/2.0;
            interface lo0.4 {
                passive;
            }
        }
    }
}

Objective

Our objective to to be able to to ping the Loopback address on INTGW from CE2. I am not advertising the address in to IS-IS so reachability is achieved via the default route.

root@PE2> show route 0.0.0.0

inet.0: 7 destinations, 7 routes (7 active, 0 holddown, 0 hidden)
+ = Active Route, - = Last Active, * = Both

0.0.0.0/0          *[IS-IS/165] 01:31:47, metric 30
                    > to 192.168.23.2 via ge-0/0/3.0

root@PE2> show route 1.1.1.1/32

root@PE2> ping rapid count 1 1.1.1.1
PING 1.1.1.1 (1.1.1.1): 56 data bytes
!
--- 1.1.1.1 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max/stddev = 3.370/3.370/3.370/nan ms

root@PE2>

If we take a look at INTGW and CE2, neither will currently have reachability to one another.

INTGW:
root@PE1:INTGW> ping rapid count 1 4.4.4.4
PING 4.4.4.4 (4.4.4.4): 56 data bytes
ping: sendto: No route to host
.
--- 4.4.4.4 ping statistics ---
1 packets transmitted, 0 packets received, 100% packet loss

CE2:
root@PE2:CE2> ping rapid count 1 1.1.1.1
PING 1.1.1.1 (1.1.1.1): 56 data bytes
ping: sendto: No route to host
.
--- 1.1.1.1 ping statistics ---
1 packets transmitted, 0 packets received, 100% packet loss

RIB Groups

First of all I will go though how to accomplish the objective with RIB Groups alone.

Essentially a RIB group will allow you to take a route that would be normally be destined for one table, e.g. inet.0, and place that route in another table also, e.g. red.inet.0. This can be done for static, connected, or dynamic routing.

A rib-group is created as below

routing-options {
    rib-groups {
        INET0_to_RED {
            import-rib [ inet.0 red.inet.0 ];
        }
    }
}

Note: the first entry after import-rib is not where we are pulling the routes from, it is where the route would normally be placed.

This config is simply stating any routes that would normally be placed in inet.0 should also be placed in red.inet.0.

However, creating the rib-group alone will not achieve anything – the rib-group must be applied elsewhere in the configuration. You have several options depending on what you want to do:

  • Interface routes – set routing-options interface-routes rib-group <name>
  • Static routes – set routing-options rib-group <name>
  • Dynamic routes, these are applied per protocol, e.g. set protocols ospf rib-group <name>

For this lab we’ll be leaking the IS-IS routes, so I apply the rib-group to IS-IS. Remember as I am leaking routes from inet.0 to red.inet.0 I must apply the rib-group in the master config, not under the routing instance.

The rib-group is applied in to the table where the routes would normally be placed.

set protocols isis rib-group inet INET0_to_RED

Now let’s take a look in the red.inet.0 table, do we see the routes?

root@PE2> show route table red.inet.0

red.inet.0: 7 destinations, 7 routes (7 active, 0 holddown, 0 hidden)
+ = Active Route, - = Last Active, * = Both

0.0.0.0/0          *[IS-IS/165] 05:05:37, metric 30
                    > to 192.168.23.2 via ge-0/0/3.0
2.2.2.2/32         *[IS-IS/18] 05:05:37, metric 10
                    > to 192.168.23.2 via ge-0/0/3.0
4.4.4.4/32         *[OSPF/10] 06:20:33, metric 1
                    > to 192.168.34.4 via ge-0/0/1.0
192.168.12.0/24    *[IS-IS/18] 05:05:37, metric 20
                    > to 192.168.23.2 via ge-0/0/3.0
192.168.34.0/24    *[Direct/0] 06:20:48
                    > via ge-0/0/1.0
192.168.34.3/32    *[Local/0] 06:20:48
                      Local via ge-0/0/1.0
224.0.0.5/32       *[OSPF/10] 06:20:48, metric 1
                      MultiRecv

Awesome! The IS-IS routes are there – we can see the default route and also the loopback on PE1. But what if we wanted to leak the default only? Junos has that covered with an import policy.

I’ll create a policy to accept the default only and apply that to the rib-group.

Just a quick note on the import policy – as IS-IS has a default import policy of accept, I need to add a final term to reject otherwise I will match everything! See this Juniper doc for a reminder of the default import/export policies for the various routing protocols.

policy-options {
    policy-statement DEFAULT {
        term t1 {
            from {
                route-filter 0.0.0.0/0 exact;
            }
            then accept;
        }
        term t2 {
            then reject;
        }
    }
}
rib-groups {
    INET0_to_RED {
        import-rib [ inet.0 red.inet.0 ];
        import-policy DEFAULT;
    }
}

Now the routing table only has the default route leaked!

root@PE2> show route table red.inet.0

red.inet.0: 5 destinations, 5 routes (5 active, 0 holddown, 0 hidden)
+ = Active Route, - = Last Active, * = Both

0.0.0.0/0          *[IS-IS/165] 00:04:57, metric 30
                    > to 192.168.23.2 via ge-0/0/3.0
4.4.4.4/32         *[OSPF/10] 06:46:46, metric 1
                    > to 192.168.34.4 via ge-0/0/1.0
192.168.34.0/24    *[Direct/0] 06:47:01
                    > via ge-0/0/1.0
192.168.34.3/32    *[Local/0] 06:47:01
                      Local via ge-0/0/1.0
224.0.0.5/32       *[OSPF/10] 06:47:01, metric 1
                      MultiRecv

Cool, so at this point the red routing-instance now has a default, but what about CE2, can that see the default?

root@PE2> show route logical-system CE2

inet.0: 4 destinations, 4 routes (4 active, 0 holddown, 0 hidden)
+ = Active Route, - = Last Active, * = Both

4.4.4.4/32         *[Direct/0] 06:50:16
                    > via lo0.4
192.168.34.0/24    *[Direct/0] 06:53:12
                    > via ge-0/0/2.0
192.168.34.4/32    *[Local/0] 06:53:12
                      Local via ge-0/0/2.0
224.0.0.5/32       *[OSPF/10] 06:51:52, metric 1
                      MultiRecv

No default route there! Well I purposely made my life difficult by running a different protocol between PE2 and CE2. Remember PE1 and PE2 are talking IS-IS, but PE2 and CE2 are talking OSPF. So whilst the IS-IS route is now in the red.inet.0 table, we need to create an export policy to redistribute the IS-IS route over to CE2 via OSPF. The export policy is applied to the routing-instance.

Note this time my policy does not need an explicit reject to be configured as the default export policy for OSPF is reject.

policy-options {
    policy-statement FROM_ISIS {
        from protocol isis;
        then accept;
    }
}

The export policy is applied to the routing-instance.

set routing-instances red protocols ospf export FROM_ISIS

Now we have an OSPF external default route present on CE2.

root@PE2> show route logical-system CE2

inet.0: 5 destinations, 5 routes (5 active, 0 holddown, 0 hidden)
+ = Active Route, - = Last Active, * = Both

0.0.0.0/0          *[OSPF/150] 00:00:02, metric 30, tag 0
                    > to 192.168.34.3 via ge-0/0/2.0
4.4.4.4/32         *[Direct/0] 06:55:43
                    > via lo0.4
192.168.34.0/24    *[Direct/0] 06:58:39
                    > via ge-0/0/2.0
192.168.34.4/32    *[Local/0] 06:58:39
                      Local via ge-0/0/2.0
224.0.0.5/32       *[OSPF/10] 06:57:19, metric 1
                      MultiRecv

Great, so CE2 knows how to route to INTGW via the default, but INTGW will not know how to route back at this point. I could do NAT on PE2 to hide the address of CE2, or repeat the rib-group process but this time to leak routes from red.inet.0 to inet.0 on PE2. We’ll do it with a rib-group.

root@PE2# show | compare
[edit routing-options rib-groups]
+   RED_to_INET0 {
+       import-rib [ red.inet.0 inet.0 ];
+   }
[edit routing-instances red protocols ospf]
+     rib-group RED_to_INET0;
[edit policy-options]
+   policy-statement FROM_OSPF {
+       from {
+           protocol ospf;
+           route-filter 4.4.4.4/32 exact;
+       }
+ 
[edit protocols isis]
+   export [ FROM_OSPF ];

Notice this time that the order of the import-rib has changed. We are copying routes from red.inet.0, as this is where the routes would normally be placed so red.inet.0 is the first entry in the import-rib statement. This also reminds us where to apply the rib-group.

The rib-group is applied to the routing-instance OSPF process, and again we must export the OSPF routes to IS-IS. Note this export is applied to the master IS-IS process, not the routing instance.

root@INTGW> show route table inet.0 0.0.0.0/0 exact

inet.0: 9 destinations, 9 routes (9 active, 0 holddown, 0 hidden)
+ = Active Route, - = Last Active, * = Both

0.0.0.0/0          *[IS-IS/165] 09:12:27, metric 20
                    > to 192.168.12.1 via ge-0/0/1.0

At this point we should have reachability between CE2 and INTGW.

root@PE2> ping 1.1.1.1 source 4.4.4.4 rapid logical-system CE2
PING 1.1.1.1 (1.1.1.1): 56 data bytes
!!!!!
--- 1.1.1.1 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max/stddev = 3.890/4.173/4.364/0.181 ms

Success !

Instance Import

Now to repeat this again, but this time using instance import! I’ll start by clearing out the rib-groups.

delete routing-options rib-groups INET0_to_RED
delete routing-options rib-groups RED_to_INET0
delete protocols isis rib-group inet INET0_to_RED
delete routing-instances red protocols ospf rib-group RED_to_INET0

First of all I’ll create a policy to import routes from inet.0 to red.inet.0

policy-options {
    policy-statement FROM_GLOBAL {
        term t1 {
            from {
                instance master;
                route-filter 0.0.0.0/0 exact;
            }
            then accept;
        }
        term t2 {
            then reject;
        }
    }
}

This policy is simply saying for a default route in the master inet.0 table then accept for import and deny everything else. We then apply this policy to the red routing-instance.

set routing-instances red routing-options instance-import FROM_GLOBAL

Now the policy has been configured, table red.inet.0 has a default route imported from the master instance. Since I left the IS-IS to OSPF export in place from the previous rib-group exercise, CE2 will also have the default route.

root@PE2> show route table red.inet.0 0.0.0.0

red.inet.0: 5 destinations, 5 routes (5 active, 0 holddown, 0 hidden)
+ = Active Route, - = Last Active, * = Both

0.0.0.0/0          *[IS-IS/165] 00:06:22, metric 30
                    > to 192.168.23.2 via ge-0/0/3.0

root@PE2> show route 0.0.0.0 logical-system CE2

inet.0: 5 destinations, 5 routes (5 active, 0 holddown, 0 hidden)
+ = Active Route, - = Last Active, * = Both

0.0.0.0/0          *[OSPF/150] 00:06:29, metric 30, tag 0
                    > to 192.168.34.3 via ge-0/0/2.0

I now need to leak the CE2 loopback IP between the red.inet.0 table and the master inet.0 table. Again this is a simple routing policy that is applied to the master routing-options.

routing-options {
    instance-import FROM_RED;
}
policy-options {
    policy-statement FROM_RED {
        term t1 {
            from {
                instance red;
                route-filter 4.4.4.4/32 exact;
            }
            then accept;
        }
        term t2 {
            then reject;
        }
    }
}

As the OSPF to IS-IS exports were left in place from the rib-group exercise, INTGW should now have the 4.4.4.4/32 route, and it does.

root@PE1> show route 4.4.4.4/32 logical-system INTGW

inet.0: 8 destinations, 8 routes (8 active, 0 holddown, 0 hidden)
+ = Active Route, - = Last Active, * = Both

4.4.4.4/32         *[IS-IS/165] 00:02:54, metric 21
                    > to 192.168.12.2 via ge-0/0/2.0

Verification – can I ping from CE2 to INTGW, yes!

root@PE2> ping 1.1.1.1 rapid source 4.4.4.4 logical-system CE2
PING 1.1.1.1 (1.1.1.1): 56 data bytes
!!!!!
--- 1.1.1.1 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max/stddev = 3.879/8.912/24.706/8.031 ms

Using the instance-import feature is perhaps a little more intuitive than rib-groups, although both can achieve the same end result.

Logical Tunnel

A final way of doing the leaking is to use Logical Tunnel interfaces. The configuration is very simple – an LT is created with one end of the tunnel in the master inet.0 table, and the other end added to red routing instance. We then simply run a routing protocol or static routing via the tunnel interface.

First of all we create the tunnel interfaces and assign one side to the correct routing instance.  This is a vMX so I also need to enable the tunnel services.

chassis {
    fpc 0 {
        pic 0 {
            tunnel-services;
        }
    }
}
interfaces {
    lt-0/0/0 {
        unit 0 {
            encapsulation ethernet;
            peer-unit 1;
            family inet {
                address 10.0.0.1/24;
            }
        }
        unit 1 {
            encapsulation ethernet;
            peer-unit 0;
            family inet {
                address 10.0.0.2/24;
            }
        }
    }
}
routing-instances {
    red {
        interface lt-0/0/0.1;      
        }
    }
}

From here, it’s a simple matter of running a routing protocol via the tunnel. As my red routing-instance is using OSPF routing with CE2, I configure the LT interfaces in OSPF within the master config and the routing-instance.

Note, because I’m running IS-IS between PE1 and PE2, on PE2 I’m also redistributing IS-IS routes to OSPF and OSPF routes to IS-IS to provide reachability.

root@PE2# show | compare
[edit protocols]
+   ospf {
+       export FROM_ISIS;
+       area 0.0.0.0 {
+           interface lt-0/0/0.0;
+       }
+   }
+   isis {
+       export FROM_OSPF;
+   }
[edit routing-instances red protocols ospf area 0.0.0.0]
+       interface lt-0/0/0.1;

root@PE2> show route logical-system CE2 0.0.0.0

inet.0: 8 destinations, 8 routes (8 active, 0 holddown, 0 hidden)
+ = Active Route, - = Last Active, * = Both

0.0.0.0/0          *[OSPF/150] 00:06:42, metric 30, tag 0
                    > to 192.168.34.3 via ge-0/0/2.0

root@PE1> show route table inet.0 4.4.4.4

inet.0: 9 destinations, 9 routes (9 active, 0 holddown, 0 hidden)
+ = Active Route, - = Last Active, * = Both

4.4.4.4/32         *[IS-IS/165] 00:07:08, metric 12
                    > to 192.168.23.3 via ge-0/0/3.0

Can I ping from CE2 to INTGW – yes!

root@PE2# run ping 1.1.1.1 source 4.4.4.4 rapid logical-system CE2
PING 1.1.1.1 (1.1.1.1): 56 data bytes
!!!!!
--- 1.1.1.1 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max/stddev = 3.330/3.609/3.884/0.213 ms

Conclusion

I’ve shown three way to leak routes between routing tables on Junos. There are of course many other ways of doing this – static route with next-table, or if I was running MPLS VPNs in this lab I’d also have route-targets to play with, or the auto-export feature for prefix leaking between local VRFs.

If you would like to see a post about these other methods, please say so in the comments. Thanks for reading this post 🙂

 

 

 

 

Junos – securing the RE (filter order is important – eBGP running slow?)

First of all – the Juniper Day One books are a superb resource for learning Junos. If you’ve not checked out the library already – do it now! 😉

Recently a client of mine found a bit of a gotcha with the framework filters discussed in the Day One – Securing the Routing Engine (and also the O’Reilly MX book which references the same material).

These books are both great references for deriving a set of RE filters to secure your Junos routers, so I won’t go in to the detail here – just check out the books!

Now on the the gotcha… I’m also mailing a link to this post to the Juniper community folks, so hopefully a revised copy can be linked on the website soon 🙂

UPDATE August 2015: there is now a revised version of the day 1 that mentions the issue discussed in this post and I have also reviewed the O’Reilly MX book and suggested an edit.

The books provide a detailed framework set of filters for protecting the routing engine, allowing you to pick and choose and chain filters as necessary. But if you chain the filters exactly as shown in the example material, you might have some problems. The example material is great, just remember to adapt it to your environment and only include what you need.

Below is an example of the filter order

input-list [ accept-common-services accept-ospf accept-rip accept-bfd accept-bgp accept-ldp accept-rsvp discard-all ];

Filter “accept-common-services” is first in the chain, which essentially references a further set of filters to be checked in order. Let’s take a look at what that is doing. Pretty self explanatory 🙂

filter accept-common-services {
   apply-flags omit;
   term accept-icmp {
      filter accept-icmp;
   }
   term accept-traceroute {
      filter accept-traceroute;
   }
   term accept-ssh {
      filter accept-ssh;
   }
   term accept-snmp {
      filter accept-snmp;
   }
   term accept-ntp {
      filter accept-ntp;
   }
   term accept-web {
      filter accept-web;
   }
   term accept-dns {
      filter accept-dns;
   }
}

The filter we need to look is “accept-traceroute”. In the Day 1, there are terms for UDP, ICMP and TCP, but I’ve only shown the TCP term below.

filter accept-traceroute {
/* omitted text - UDP and ICMP terms */ 
  apply-flags omit;
   term accept-traceroute-tcp {
      from {
         destination-prefix-list {
            router-ipv4;
            router-ipv4-logical-systms;
         }
         protocol tcp;
         ttl 1;
      }
      then {
         policer management-1m;
         count accept-traceroute-tcp;
         accept;
      }
   }
}

Let’s take a look at what this is doing – matching TCP traffic to IPv4 interfaces on the router, with a TTL of 1. If the traffic is matches, then we accept the traffic, count the packets and police to 1Mbps. At first glance this sounds good – traceroute traffic (e.g. tcptraceroute) using TCP is rate limited (the accept-traceroute also does the same for UDP and ICMP).

But what about TCP traffic with a legitimate TTL of 1?

Which routing protocol uses TCP, a TTL of 1 and might send a lot of data – eBGP !

So the unintended effect is if the filters are chained in as shown in the examples, i.e. as below:

[ accept-common-services accept-ospf accept-rip accept-bfd accept-bgp accept-ldp accept-rsvp discard-all ]

then the “accept-bgp” filter will not be matched for eBGP trafic – the “accept-common-services” filter will match eBGP, and eBGP traffic will be rate limited to 1Mbps! Clearly you don’t want this if you are synchronising a full routing table.

The quick way to look for this is to run the command “show firewall filter lo0.0-i”, and look for the counters “accept-traceroute-tcp-lo0.0-i” and the policer “management-1m-accept-traceroute-tcp-lo0.0-i”. If either of these are showing high counter values, then either you are experiencing excessive TCP traceroute, or perhaps you might want to look at the filter order! 🙂

There is a simple fix of course – either put the “accept-bgp” filter first in the chain, or if TCP traceroute isn’t of interest then remove the configuration from the “accept-traceroute” filter.

So if you’ve used these filters as a basis for you own RE filters, it might be worth taking a look at the filter chain order.