From the blog.

Managing Digital Racket
The more I tune out, the less I miss it. But that has presented me with some complex choices for a nuanced approach to curb
Complexity – My Friend, My Enemy
Over my years of network engineering, I've learned that the fewer features you can implement while still achieving a business goal, the better. Why? Fewer
junos-logo

How To: Simple Juniper SRX Rate-Limiting via Policer

1,026 Words. Plan about 6 minute(s) to read this.

I recently needed to configure rate-limiting on a Juniper SRX650 firewall’s 1GbE interface facing an ISP. The scenario was that the ISP allowed line rate traffic and billed at the 95th percentile of utilization. As long as 95th percentile was under the number of bps we were paying for, there was no excess charge. But that possibility of an excess charge was sort of annoying. How to provide ease of mind? Rate limit all traffic going through the 1GbE interfaces to the ISP.

In Juniper-land, there are several different ways to accomplish QoS tasks like this. I opted to run a simple policer in the end, but I took the following notes along the way.

  • Some features & functions vary by hardware interface. IOW, not all QoS capability is necessarily going to work everywhere across every Junos device or interface. This isn’t shocking. To perform QoS functions at scale (i.e. a useful bits-per-second rate), there is a reliance on silicon. Not all Juniper devices or line cards in those devices have the same ASICs. Therefore, not all QoS functionality will be identical. This is exactly what you find with other vendors.
  • Similar to Cisco hierarchical QoS, it’s possible to build complex Junos QoS policies using the “class-of-service” command hierarchy. Digging through this documentation, a network engineer creates forwarding-classes & schedulers that are called by scheduler-maps. Scheduler-maps are applied to interfaces. This is similar to Cisco MQC behavior where class-maps are called by policy-maps, and policy-maps are applied to interfaces.
  • I didn’t dig into class-of-service too deeply, but it appears to forward based on the CoS bits in 802.1q tagged frames. I need to dig a bit more to see if “class-of-service” also copes with DSCP values, which is the 6-bit value contained in the IP ToS byte. Most network-wide QoS schemes, expect to make queuing decisions on DSCP values at some junctions, and not merely CoS.
  • I’m still struggling to navigate Juniper’s documentation hierarchy as effectively as I would like. I’ve found the greatest success not with Google, but in Juniper’s Configuration Guide PDFs. I’ve downloaded a pile of them, and they are more likely to teach me what I need. The more I refer to these PDFs, the more familiar I’m getting with the Juniper way of looking at the world.

Here’s the solution I ended up with on a SRX650 cluster running 11.4 family code. This was verified to work via a simple bandwidth speed testing site, where the results went from 1Gbps line rate down to the policed rate in both directions. Sadly. ;-)

Commented Code

# Create the policer. In this case, we’re at 80Mbps, and
# allowing a data burst of 625KB (that’s a byte count, not
# a data rate). Why 625KB? That’s the amount of data that
# can flow through a 1GbE interface in 5ms, the burst
# minimum recommended by Juniper here. We can make the burst
# value bigger if we choose.
#
# Creating the policer by itself does nothing. The policer
# must be called elsewhere in the policy to be effective.
#
set firewall policer GLOBAL-POLICER if-exceeding bandwidth-limit 80m
set firewall policer GLOBAL-POLICER if-exceeding burst-size-limit 625k
set firewall policer GLOBAL-POLICER then discard
#
# Create the outbound filter. We could create just one filter
# and apply it both inbound and outbound, but this gives us a
# little more flexibility, and makes certain that reporting
# via “show firewall filter” is not ambiguous.
#
# The filter says that traffic matching anything is subject to
# the policer we created above. For completeness, I added a
# “END-POLICY” term that allows any traffic that falls through
# from the “SOURCE-ANY” to pass through the filter. Technically,
# this shouldn't be required, but it’s a fail-safe as firewall
# filters *will* drop traffic if not configured correctly. This
# is similar to putting a “permit ip any any” at the end of a
# Cisco ACL.
#
set firewall filter OUTBOUND-FILTER term SOURCE-ANY from source-address 0.0.0.0/0
set firewall filter OUTBOUND-FILTER term SOURCE-ANY then policer GLOBAL-POLICER
set firewall filter OUTBOUND-FILTER term SOURCE-ANY then accept
set firewall filter OUTBOUND-FILTER term END-POLICY then accept
#
# Create inbound filter.
#
set firewall filter INBOUND-FILTER term SOURCE-ANY from source-address 0.0.0.0/0
set firewall filter INBOUND-FILTER term SOURCE-ANY then policer GLOBAL-POLICER
set firewall filter INBOUND-FILTER term SOURCE-ANY then accept
set firewall filter INBOUND-FILTER term END-POLICY then accept
#
# Apply the inbound and outbound filter to the appropriate
# interface. In the case of a clustered SRX, we’re applying
# the filter to the redundant Ethernet interface uplinking to
# the ISP.
#
set interfaces reth-0 unit 0 family inet filter input INBOUND-FILTER
set interfaces reth-0 unit 0 family inet filter output OUTBOUND-FILTER

To see what’s happening with the policy, try…

# Display what filters are applied.
show interfaces reth0 detail | match "(interface|filter)”
#
# I believe this displays the number of packets dropped
# as a result of the filter? Docs are vague.
show firewall filter INBOUND-FILTER
show firewall filter OUTBOUND-FILTER

Resulting Configuration

interfaces {
    reth0 {
        redundant-ether-options {
            redundancy-group 1;
        }
        unit 0 {
            family inet {
                filter {
                    input INBOUND-FILTER;
                    output OUTBOUND-FILTER;
                }
                address 10.20.30.40/24;
            }
        }
    }
firewall {
    policer GLOBAL-POLICER {
        if-exceeding {
            bandwidth-limit 80m;
            burst-size-limit 625k;
        }
        then discard;
    }
    filter OUTBOUND-FILTER {
        term SOURCE-ANY {
            from {
                source-address {
                    0.0.0.0/0;
                }
            }
            then {
                policer GLOBAL-POLICER;
                accept;
            }
        }
        term END-POLICY {
            then accept;
        }
    }
    filter INBOUND-FILTER {
        term SOURCE-ANY {
            from {
                source-address {
                    0.0.0.0/0;
                }
            }
            then {
                policer GLOBAL-POLICER;
                accept;
            }
        }
        term END-POLICY {
            then accept;
        }
    }
}

I might test some of the other techniques that I think would work for this, but need to get Firefly running in my lab first. Why bother? The downside of policing is that it’s not especially elegant. Global policers are a pretty large hammer. While this was straightforward to implement, posed (in my estimation) the least amount of implementation risk to my production firewalls, and addressed the issue head on, a more elegant QoS approach is a modular one.

It’s worth pointing out that the firewall filters above can have additional terms added to them to allow for more granular treatment of which IP addresses are getting policed. Even so, a true QoS policy would break down traffic classes in a hierarchical manner that would allow for maximum policy flexibility without having to rewrite the entire policy each time a significant change was required. This is something I need to evaluate anyway, as using SRX devices at the edge of my far-flung network is a topic of interest.