TERMINATING ACTIONS IN JUNOS ROUTING POLICY (Sample Chapter from Juniper’s Ambassadors Cookbook 2019)

A few months ago I had the privilege of being invited to contribute chapters to Juniper’s annual Ambassadors Cookbook: a collection of chapters that each teach you how to fix a whole bunch of specific problems.

This year’s book featured a huge variety of content, from guides on QinQ tunneling, to SLAX scripts, to VRRP, to MC-LAG, and so much more – all bundled with a healthy dose of automation. If it sounds like a fun thing that you’d like to read, you can download the PDF of the book here, FOR FREE! Just sign into the Juniper forums to get it.

I contributed three chapters. Two of them were on the different ways that you can manipulate Juniper routers to resolve next-hops via MPLS label-switched paths. My third chapter was a guide to using “terminating actions” in routing policy. And guess what – this blog post below is exactly that chapter!

Give this post a read, and think of it like a try-before -you-buy for the book. Well, it’s a free book, so I should probably say “try before you download”. But downloading is still a serious commitment. There’s no going back once you’ve downloaded. It’s a seal, a seal to the very end.

Juniper gave me an astonishingly wide pass to fill my chapters with jokes, and it was such a joy to write for them. I’m a strong believer that educational writing should be funny and fun to keep people engaged, and the fact that Juniper let me do that says so much about the character of the company. Having said that, inevitably after a few months of not reading the post, I’ve re-read it and found ways to improve the clarity, and add jokes in, so the post below isn’t precisely the same. If you like smiling, the post below is better. If you like sorrow, the post below is worse. You get to choose! You’re so lucky!!

================================

USING TERMINATING ACTIONS IN
JUNOS ROUTING POLICY

  • vSRX Version Used: 12.1X47-D15.4
  • Juniper Platforms General Applicability: MX, EX, SRX, QFX

Later on in this book, my esteemed colleague Pierre-Yves Maunier will show us how to simplify our Junos BGP routing policies. As a warm-up to that, let’s remind ourselves what the different “actions” in a policy actually do, because there’s much more to the words “accept” and “reject” than meets the eye – and, just like a careful bribe to an international diplomat, when you use them right you’re rewarded with tremendous power.

 

THE PROBLEM

You want to manipulate a prefix in multiple ways, but for some reason, only the first term in your policy is being actioned. You want to understand how to use the “then” statement properly, so that your policy works correctly.

 

THE SOLUTION

You may already be familiar with the two main actions in a routing policy: accept, and reject. They do exactly what they say on the tin: “accept” allows the traffic or prefixes through, and reject… well, it rejects it!

However, a thing that Junos newbies might not intuitively know is that “accept” and “reject” are more than just an action: they’re actually what’s known as a terminating action. If a term in a policy has an action of accept or reject, then if a prefix matches the term, no further checks are done. In other words, the checking of the policy is terminated. You know: like the “termination” that comes from the sweet release of death we all crave. Neat!

 

A TERMINATING POLICY DONE RIGHT

Sometimes, ending the policy checks is what you want. Let’s look at an example of when this behaviour is great for us. In the picture below we have two routers, Router 1 and Router 2, with an eBGP peering between them. Router 1 is sending two prefixes to Router 2: 192.168.100.0/24, and 203.0.113.0/24.

Now, imagine we made a policy on Router 2 containing two terms. The first term rejects private IPs. The second term allows everything else. The policy would look a little something like this:

policy-options {
     policy-statement REJECT-EVIL-PREFIXES {
          term REJECT-RFC1918 {
               from {
                    route-filter 10.0.0.0/8 orlonger;
                    route-filter 172.16.0.0/12 orlonger;
                    route-filter 192.168.0.0/16 orlonger;
               }
               then reject;
          term ACCEPT-ALL-OTHERS {
               then accept;
}}}}

If we apply this as an import policy on Router 2‘s BGP session, we see that Router 2 only accepts the 203.0.113.0/24 prefix. The private range is rejected. Great!

root@Router2> show route protocol bgp

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

203.0.113.0/24      *[BGP/170] 00:00:23, localpref 100
                      AS path: 64512 I
                    > to 10.10.12.1 via ge-0/0/0.0


root@Router2> show route protocol bgp hidden

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

192.168.100.0/24      [BGP ] 00:00:25, localpref 100
                       AS path: 64512 I
                     > to 10.10.12.1 via ge-0/0/0.0

NOTE Not many people know that 203.0.113.0/24 is a range reserved for use in documentation. How handy that we have something that looks like a public address that we can freely use in blogs and books! Hey: wouldn’t it be handy if people actually used this range when they were blogging about VPNs and NAT, instead of using private IPs on both the WAN and LAN, so we could actually picture what was going on more easily? Yes! Yes it would. Consider writing to your local congressperson about this extremely important topic.

Now, let’s think about what’s happening here. When Router 1 advertises 192.168.100.0/24 to Router 2, Router 2 will check the policy and see that the first statement rejects it. Because it’s being rejected, we don’t actually need to check any further down the policy. We know we want to reject it, so the policy checking can safely stop there. Even if there were policies below it that would match and accept, it doesn’t matter. Reject means reject for definite. In that respect, using a “terminating action” in the first term of the policy is perfect. The terminating action of reject says to the router “stop processing! You don’t need to look at any more terms. We’ve got what we needed. Put your feet up and relax now.”

(Note: Juniper routers don’t literally tell you to put your feet up and relax, and I cannot be responsible for anyone who is fired in work for putting their feet on their desk.)

 

A TERMINATING ACTION DONE WRONG

Sometimes though, you do want to continue checking further policies. For example, your first term might say “If a prefix matches some conditions, then manipulate the prefix in some way – but then carry on checking other terms to manipulate it further.”

For example, let’s imagine that we wanted to achieve this: “if a prefix comes from Customer A’s VRF, then add community 64512:111 to the prefix, and carry on checking the policy. Next, if the prefix comes from our ISP’s own management range, add community 64512:222 to the prefix, and again carry on checking. Finally, accept everything.”

Well, I’ve got news for you: you don’t need to imagine it, because I wrote it out for you! You can thank me later. Except, don’t thank me, – because the policy below is broken. I haven’t highlighted anything in red below, so give the policy a read, and see if you can work out why this policy won’t work. (Remember, if a routing policy doesn’t have a “from” statement, then the default “from” action is to match everything.)

policy-options {

     community target-CUSTOMER-A-COMMUNITY members target:64512:111;
     community target-MPLS-MANAGEMENT-COMMUNITY members target:64512:222

     prefix-list ISP-MANAGEMENT-PREFIXES {
          172.16.0.0/16;
          172.31.0.0/16;
     }

     policy-statement CUSTOMER-A-EXPORT {
          term CUSTOMER-LANS {
               then {
                    community add target-CUSTOMER-A-COMMUNITY;
                    accept;
                    }
          }
          term MANAGEMENT {
               from {
                    prefix-list-filter ISP-MANAGEMENT-PREFIXES orlonger;
               }
               then {
                    community add target-MPLS-MANAGEMENT-COMMUNITY;
                    accept;
               }
          }
          term ACCEPT-ALL {
               then accept;
}}}

The first term in this policy checks whether the prefix came from the customer’s VRF. If it does, the policy adds the customer community (target:64512:111) to the prefix. However, because the action in this first term is “accept”, Junos doesn’t carry on checking whether the prefix also needs to be given our Management community.

The result of this is that any prefix coming out of the customer’s VRF will certainly be accepted by the first term, and advertised to the Provider Edge router’s peers. It will even have the correct customer community attached to it. But, if the prefix happened to also be within our management range, our router will have advertised the prefix without the Management community – because the second term was never checked. As such, the prefix will never be imported into our management VRF around the rest of the network, and our ISP will have no access to monitor or log onto the customer’s router. Gosh – it’s just about the saddest story anyone’s ever told in all of human history! No-one told me this cookbook was going to be such a rollercoaster of emotions!

Dry your eyes though, because we’re going to make it all better. In fact the solution to this is extremely simple.

You see, if we don’t explicitly add an action, our Junos router has a default action: carry on checking! In fact, there’s one of three one of three default behaviours, depending on what comes next in the policy: “next-term” if there are more terms to check; “next-policy” if you’re at the final term in a policy; or, if there’s no policies left to check, each routing protocol has its own built-in default action. We’ll talk about that in a moment.

Without an explicit terminating action, you’re telling the router to use this default action: “manipulate the route characteristics like the policy term states, then carry on checking further policies”. The prefix hasn’t strictly been accepted yet; rather, it’s been manipulated as necessary, and then passed on for further checking.

Even though this is the default behaviour, some people like to configure the next-term and next-policy actions explicitly, so that customers and junior engineers can read through configurations more easily. One reason I personally love Junos is that the configuration is extremely readable. It’s true that the configs are a little bit longer compared to other vendors, but for me personally, a few extra words in the config leads to an exponential increase in how simple it is to understand what’s going on, even for people who aren’t familiar with Junos. Having said that, explicitly configuring the defaults almost always helps make things even clearer still.

 

FIXING THE BROKEN POLICY

In that last example we didn’t want the policy checks to stop, so using an action of “accept” was the wrong choice. So, let’s fix it by simply deleting the first two “accept” statements. Now, our Juniper routers will use the default action of “next-term”:

delete policy-options policy-statement CUSTOMER-A-EXPORT term CUSTOMER-LANS then accept
delete policy-options policy-statement CUSTOMER-A-EXPORT term MANAGEMENT then accept

Our newly-fixed and working policy now looks like this. I’ve removed the community and prefix-list definitions, to keep it shorter:

policy-options {
     policy-statement CUSTOMER-A-EXPORT {
          term CUSTOMER-LANS {
               then {
                    community add target-CUSTOMER-A-COMMUNITY;
                    }
          }
          term MANAGEMENT {
               from {
                    prefix-list-filter ISP-MANAGEMENT-PREFIXES orlonger;
               }
               then {
                    community add target-MPLS-MANAGEMENT-COMMUNITY;
               }
          }
          term ACCEPT-ALL {
               then accept;
}}}

Notice that we didn’t delete the very final “accept”. It’s the last term in the policy, so at the moment there’s no harm in keeping it there. But remember: Junos allows you to chain as many policies together as you like, so if in the future you add a second policy after this one, you’ll want to think about whether or not to keep this final “accept” in place!

So, as we saw, in this particular case using an “accept” part-way through the policy was wrong. In our very first example, it was the right choice. Let’s look at another example of a time that it’s the correct thing to do.

Imagine if we wanted to achieve something like this: “If a prefixes matches a prefix-list called PRIORITY-TRAFFIC, then I want to add a certain community to the prefix, and I want to allow it. However, I don’t want anything else to happen to the prefix. I’ve already marked it as priority traffic, so even if there are more policies it could be checked against, I want the policy checks to end here.”

In this particular example, a policy with an action of “accept” in the first term would be exactly what we want.

 

THE DEFAULT BEHAVIOUR OF BGP IN JUNOS

Now, you may be wondering: “what’s all this about a each protocol having its own default behavior? What happens if we get to the very last term of the very last policy, and we still haven’t configured a terminating action? Is the prefix accepted, or rejected?” Good question! Let’s talk about it.

PROTIP: Before we look at some examples, remember: when you’re thinking about Junos routing policy, imagine yourself standing inside the inet.0 or inet6.0 table. “Import” means importing a prefix from the protocol, into the routing table. “Export” means pushing prefixes from the routing table, into the protocol.

PROTIP: Are you still imagining yourself standing inside the routing table? I don’t blame you. It’s a beautiful place. Feel free to stay there for a few minutes if you like. Treat yourself to some quality “me” time.

Let’s start with BGP. If you don’t put a policy on a BGP peering, here’s what happens:

IMPORT: All prefixes received from a BGP peer are imported into inet.0 or inet6.0.

EXPORT: All prefixes in the routing table that have been learnt by BGP are exported to the router’s peer – that is, so long as advertising the prefix doesn’t break the rules of BGP, such as iBGP’s rule of not advertising iBGP-learned prefixes to other iBGP peers.

An example, you say? Why, certainly!

Once again we have two routers in our diagram. Router 4 is advertising two prefixes to Router 3. Let’s take a look at what Router 3 sees:

root@Router3> show route 203.0.113.0/24

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

203.0.113.0/25        *[BGP/170] 00:00:01, localpref 100
                        AS path: 64513 I
                      > to 10.10.36.6 via ge-0/0/3.0
203.0.113.128/25      *[BGP/170] 00:00:01, localpref 100
                        AS path: 64513 64513 64513 64513 I
                      > to 10.10.36.6 via ge-0/0/3.0

Notice in the output above that Router 4 manipulated 203.0.113.128/25 before it exported it to Router 3, to prepend its AS-PATH 3 times.

For “fun”, let’s put a policy on Router 3, applied to inbound BGP traffic, saying that we want to reject any prefixes that our neighbour has AS-PATH prepended.

Take a look at the config below, but pay attention to something crucial: notice that this policy contains only one term, rejecting prepended prefixes. There isn’t a second “then accept”-style statement at the end, to accept everything else. Literally the only term is the one rejecting prepended prefixes.

policy-options {

     as-path REJECT_64513_PREPEND "64513 64513 .*";

     policy-statement REJECT_PREPEND {
          from {
               as-path REJECT_64513_PREPEND;
         }
           then reject;
}}

And yet, even though we didn’t configure a term to accept everything else, when we look in the routing table now we see that we’re indeed rejecting the prepended prefix – but we’re still accepting the other route!

root@Router3> show route 203.0.113.0/24

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

203.0.113.0/25         *[BGP/170] 01:34:39, localpref 100
                         AS path: 64513 I
                       > to 10.10.36.6 via ge-0/0/3.0

So, there we have it: BGP has a default import action of “accept”, because it accepts prefixes even if we don’t configure an “accept” action.

 

THE DEFAULT BEHAVIOUR OF OSPF & IS-IS IN JUNOS

OSPF and IS-IS also have a default import action of “accept”. In other words, any prefixes learnt by OSPF/IS-IS will be imported into the routing table.

The default export policy often confuses people though, because for OSPF and IS-IS, the default action is to “reject”. This might seem a little counter-intuitive. Surely if a router learns a prefix by OSPF/IS-IS, it should then advertise that prefix to its other OSPF/IS-IS neighbors? Isn’t that the whole point of a link-state routing protocol?

Well, as it happens, the router does advertise these prefixes on to its neighbors, but – and here’s the twist – our beautiful router doesn’t do it by taking the prefixes out of the routing table, and then advertising them on. Instead, it takes the LSAs (OSPF) and LSPs (IS-IS) learnt from a neighbor, and passes those same LSAs/LSPs on to its own neighbor.

To be clear, the prefixes are still fully being passed on – they’re just not being taken from inet.0 or inet6.0. Remember what we said about how “export” policies take prefixes from those routing tables, and export them into the routing protocol. In other words, if a route is learned by OSPF/IS-IS, and it’s imported into the routing table, then it isn’t being exported back out of the routing table again like it is with BGP. Instead the LSAs/LSPs take care of the job of prefix and link advertisment.

This leads to an interesting situation: if a Router A advertises a prefix via OSPF or IS-IS to a Router B, it isn’t actually possible for Router B to filter that prefix out of its advertisements to Router C. Even if you configure Router B with an export policy to filter a prefix out, the LSAs/LSPs will still be advertised, because the entire network (or more specifically, the area that you’re in) has to have exactly the same link-state database. That’s just the law! You can still manipulate the forwarding table on an individual router, but the advertisements themselves must be consistent across the entire domain. The story is a bit different when you’re at an area border router, but within an area, it’s a no-no.

PROTIP: Let’s talk about RIP in exactly as much detail as it deserves. Here we go: stop using RIP. Why are you still using RIP? It’s 2019. Stop it.

LDP, RSVP, PIM – there’s plenty more protocols, and each one has its own defaults. This handy link tells you all the defaults for all the protocols you can put policies on: https://www.juniper.net/documentation/en_US/junos/topics/concept/policy-routing-policies-actions-defaults.html

My grandmother always used to say to me that “honesty is the best policy”. Well, maybe if she’d used Junos, she’d apologise for being completely and utterly wrong, and say instead that simplicity is the best policy. Don’t worry Nan: I forgive you.

What do I mean when I say that simplicity is the best policy? A policy with too many terms, too many chains, has more chance of going wrong, and it can be confusing to read. Just because you *can* chain policies, and just because you *can* have a hundred terms, doesn’t mean you should. Always remember, complexity is the enemy of understanding. Nevertheless, the ability to make many granular decisions all in one policy has made Juniper routing policy stand out from many other vendors.

Use routing policy well, and you’ll be an internet superhero. Use it badly, and you’ll cause half of the internet to go down. Have fun!

===============================

Did you enjoy my free chapter? If you did, be sure to download the Ambassadors Cookbook for even more chapters from me, and from my Ambassador colleagues, who are far more intelligent and qualified to write about this stuff than me.

And as always, if you enjoyed this post, I’d love it if you shared it on your social media of choice. The more people read my posts, the more I’m inspired to write even more shiny blog posts for you to learn even more cool stuff.

Speaking of social media, follow me on Twitter if you never want to miss a future post! Or follow me on LinkedIn, aka the worst website on the internet, if you want to miss 90% of my posts. The choice is yours. Don’t let me sway you either way!

Leave a Reply

Your email address will not be published. Required fields are marked *