O'Reilly logo

Juniper MX Series by Harry Reynolds, Douglas Richard Hanks Jr.

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

RE Protection Case Study

This case study presents a current best practice example of a stateless filter to protect an MX router’s IPv4 and IPv6 control plane. In addition, the recent DDoS detection feature, available on Trio-based MX routers starting with release v11.2, are examined and then combined with RE filtering to harden the router against unauthorized access and resource depletion.

As networks become more critical, security and high availability become ever more crucial. The need for secure access to network infrastructure, both in terms of user-level authentication and authorization and all the way to the configuration and use of secure access protocols like SSH, is a given. So much so, that These topics have been covered in many recent books. So as to not rehash the same information, readers interested in these topics are directed to Junos Enterprise Routing, Second Edition, by O’Reilly Media.

The goal of this section is to provide an up-to-date example of a strong RE protection filter for both IPv4 and IPv6, and to address the topic of why basic filters may not guard against resource depletion, which, if allowed to go unchecked, can halt a router’s operation just as effectively as any “hacker” who gains unauthorized access to the system with nefarious intent.

The topic of router security is complex and widespread. So much so that informational RFC 6192 was produced to outline IPv4 and IPv6 filtering best practices, along with example filters for both IOS- and Junos OS-based products. There is much overlap between the examples in this section and the RFC’s suggestions, which is a good sign, as you can never have too many smart people thinking about security. and It’s good to see different approaches and techniques as well as a confirmation that many complex problems have common solutions that have been well tested.

IPv4 RE Protection Filter

This section provides the reader with a current best practice example of an RE protection filter for IPv4 traffic. Protection filters are applied in the input direction to filter traffic arriving on PFE or management ports before it’s processed by the RE. Output filters are generally used for CoS marking of locally generated control plane traffic, as opposed to security-related reason as you generally trust your own routers and the traffic they originate. Figure 4-1 provides the topology details that surround this case study.

DDoS Protection Lab Topology.

Figure 4-1. DDoS Protection Lab Topology.

The example, used with permission from Juniper Networks Books, is taken from Day One: Securing the Routing Engine by Douglas Hanks, also the coauthor of this book.

Warning

Note: Router security is no small matter. The reader is encouraged to examine the filter carefully before adapting it for use in his or her own network.

The principles behind the filter’s operation and the specific rationale behind its design framework are explained in the Day One book, and so are not repeated here in the interest of brevity. The filter is included here as a case study example for several reasons:

  • RE protection is important and needed, and this is a really good filter. There’s no point in recreating an already perfectly round wheel, and the Day One book is freely available as a PDF.

  • The example makes great use of some important Junos features that are not necessarily MX-specific, and so have not been covered in this chapter, including filter nesting (a filter calling another filter), apply-path, and prefix-list. All are powerful tools that can make managing and understanding a complex filter much simpler. The examples also make use of the apply-flags omit statement. This flag results in the related configuration block not being displayed in a show configuration command, unless you pipe the results to display omit. Again, while not a filter-specific feature, this is another cool Junos capability that can be utilized to make living with long filters that much easier.

  • It’s a good test of this chapter and the reader’s comprehension of the same. This is a real-world example of a complex filter that solves a real issue. While specific protocol nuances, such as the specific multicast addresses used by OSPF, may not be known, having arrived here, the reader should be able to follow the filter’s operation and use of policing with little guidance.

  • The example is comprehensive, providing support for virtually all known legitimate routing protocols and services; be sure to remove support for any protocols or services that are not currently used, either by deleting the filter in question or by simply not including that filter in the list of filters that you ultimately apply to the lo0 interface. For example, as IS-IS is used in the current lab, there is currently no need for any OSPF-specific filter. Also, be sure to confirm that the prefix lists contain all addresses that should be able to reach the related service or protocol.

When first applying the filter list, you should replace the final discard-all term with one that matches all with an accept and log action. This is done as a safeguard to prevent service disruption in the event that a valid service or protocol has not been accommodated by a previous term. After applying the filter, pay special attention to any log hits indicating traffic has made it to the final catch-all term, as this may indicate you have more filter work to do.

Warning

Before applying any RE filter, you should carefully evaluate both the filters/terms and their application order to confirm that all valid services and remote access methods are allowed. In addition, you must also edit the sample prefix list to ensure they accurately reflect all internal and external addresses from where the related services should be reachable. Whenever making this type of change, console access should be available in the event that recovery is needed, and you should strongly consider the use of commit confirmed command.

When your filter is correctly matched to the particulars of your network, the only traffic that should fall through to the final term should be that which is unsupported and therefore unneeded, and safe to drop. Once it is so confirmed, you should make the discard-all filter the last in the chain—its ongoing count and logging actions simplify future troubleshooting when a new service is added and no one can figure out why it’s not working. Yes, true security is a pain, but far less so in the long run then the lack of, or worse yet, a false sense of security!

Let’s begin with the policy-related configuration where prefix lists are defined in such a way that they automatically populate with addresses assigned to the system itself, as well as well-known addresses associated with common protocols. This small bit of upfront work makes later address-based matches a snap and makes ongoing address and peer definition changes painless, as the filter automatically keeps up. Note that the sample expressions catch all addresses assigned, including those on the management network and GRE tunnels, etc. The sample presumes some use of logical systems (a feature previously known as logical routers). Where not applicable you can safely omit the related prefix list.

{master}[edit]
regress@R1-RE0# show policy-options | no-more
prefix-list router-ipv4 {
    apply-path "interfaces <*> unit <*> family inet address <*>";
}
prefix-list bgp-neighbors {
    apply-path "protocols bgp group <*> neighbor <*>";
}
prefix-list ospf {
    224.0.0.5/32;
    224.0.0.6/32;
}
prefix-list rfc1918 {
    10.0.0.0/8;
    172.16.0.0/12;
    192.168.0.0/16;
}
prefix-list rip {
    224.0.0.9/32;
}
prefix-list vrrp {
    224.0.0.18/32;
}
prefix-list multicast-all-routers {
    224.0.0.2/32;
}
prefix-list router-ipv4-logical-systms {
    apply-path "logical-systems <*> interfaces <*> unit <*> family inet address <*>";
}
prefix-list bgp-neighbors-logical-systems {
    apply-path "logical-systems <*> protocols bgp group <*> neighbor <*>";
}
prefix-list radius-servers {
    apply-path "system radius-server <*>";
}
prefix-list tacas-servers {
    apply-path "system tacplus-server <*>";
}
prefix-list ntp-server {
    apply-path "system ntp server <*>";
}
prefix-list snmp-client-lists {
    apply-path "snmp client-list <*> <*>";
}
prefix-list snmp-community-clients {
    apply-path "snmp community <*> clients <*>";
}
prefix-list localhost {
    127.0.0.1/32;
}
prefix-list ntp-server-peers {
    apply-path "system ntp peer <*>";
}
prefix-list dns-servers {
    apply-path "system name-server <*>";
}

You can confirm your apply-path and prefix lists are doing what you expect by showing the list and piping the output to display inheritance. Again, it’s critical that your prefix lists contain all expected addresses from where a service should be reachable, so spending some time here to confirm the regular expressions work as expected is time well spent. Here, the results of the router-ipv4 apply-path regular expression are examined.

{master}[edit]
jnpr@R1-RE0# show policy-options prefix-list router-ipv4
apply-path "interfaces <*> unit <*> family inet address <*>";

{master}[edit]
jnpr@R1-RE0# show policy-options prefix-list router-ipv4 | display inheritance
##
## apply-path was expanded to:
##     192.168.0.0/30;
##     10.8.0.0/31;
##     192.0.2.0/26;
##     192.0.2.64/26;
##     10.3.255.1/32;
##     172.19.90.0/23;
##
apply-path "interfaces <*> unit <*> family inet address <*>";

If you do not see one or more commented prefixes, as in this example, then either the related configuration does not exist or there is a problem in your path statement. As additional confirmation, consider the sample BGP stanza added to R1, consisting of three BGP peer groups: two IPv6 and one IPv4:

{master}[edit]
jnpr@R1-RE0# show protocols bgp
group int_v4 {
    type internal;
    local-address 10.3.255.1;
    neighbor 10.3.255.2;
}
group ebgp_v6 {
    type external;
    peer-as 65010;
    neighbor fd1e:63ba:e9dc:1::1;
}
group int_v6 {
    type internal;
    local-address 2001:db8:1::ff:1;
    neighbor 2001:db8:1::ff:2;
}

Once again, the related prefix lists are confirmed to contain all expected entries:

{master}[edit]
jnpr@R1-RE0# show policy-options prefix-list bgp-neighbors_v4 | display inheritance
##
## apply-path was expanded to:
##     10.3.255.2/32;
##
apply-path "protocols bgp group <*_v4> neighbor <*>";

{master}[edit]
jnpr@R1-RE0# show policy-options prefix-list bgp-neighbors_v6 | display inheritance
##
## apply-path was expanded to:
##     fd1e:63ba:e9dc:1::1/128;
##     2001:db8:1::ff:2/128;
##
apply-path "protocols bgp group <*_v6> neighbor <*>";

And now, the actual filter. It’s a long one, but security is never easy and is more an ongoing process than a one-point solution anyway. At least the comprehensive nature of the filter means it’s easy to grow into new services or protocols as you simply have to apply the related filters when the new service is turned up:

{master}[edit]
jnpr@R1-RE0# show firewall family inet | no-more
prefix-action management-police-set { /* OMITTED */ };
prefix-action management-high-police-set { /* OMITTED */ };
filter accept-bgp { /* OMITTED */ };
filter accept-ospf { /* OMITTED */ };
filter accept-rip { /* OMITTED */ };
filter accept-vrrp { /* OMITTED */ };
filter accept-ssh { /* OMITTED */ };
filter accept-snmp { /* OMITTED */ };
filter accept-ntp { /* OMITTED */ };
filter accept-web { /* OMITTED */ };
filter discard-all { /* OMITTED */ };
filter accept-traceroute { /* OMITTED */ };
filter accept-igp { /* OMITTED */ };
filter accept-common-services { /* OMITTED */ };
filter accept-sh-bfd { /* OMITTED */ };
filter accept-ldp { /* OMITTED */ };
filter accept-ftp { /* OMITTED */ };
filter accept-rsvp { /* OMITTED */ };
filter accept-radius { /* OMITTED */ };
filter accept-tacas { /* OMITTED */ };
filter accept-remote-auth { /* OMITTED */ };
filter accept-telnet { /* OMITTED */ };
filter accept-dns { /* OMITTED */ };
filter accept-ldp-rsvp { /* OMITTED */ };
filter accept-established { /* OMITTED */ };
filter accept-all { /* OMITTED */ };
filter accept-icmp { /* OMITTED */ };
filter discard-frags { /* OMITTED */ };

Not much to see, given the omit flag is in play. Easy enough to fix:

{master}[edit]
jnpr@R1-RE0# show firewall family inet | no-more | display omit
prefix-action management-police-set {
    apply-flags omit;
    policer management-1m;
    count;
    filter-specific;
    subnet-prefix-length 24;
    destination-prefix-length 32;
}
prefix-action management-high-police-set {
    apply-flags omit;
    policer management-5m;
    count;
    filter-specific;
    subnet-prefix-length 24;
    destination-prefix-length 32;
}
filter accept-bgp {
    apply-flags omit;
    term accept-bgp {
        from {
            source-prefix-list {
                bgp-neighbors_v4;
                bgp-neighbors-logical-systems_v4;
            }
            protocol tcp;
            port bgp;
        }
        then {
            count accept-bgp;
            accept;
        }
    }
}
filter accept-ospf {
    apply-flags omit;
    term accept-ospf {
        from {
            source-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            destination-prefix-list {
                router-ipv4;
                ospf;
                router-ipv4-logical-systms;
            }
            protocol ospf;
        }
        then {
            count accept-ospf;
            accept;
        }
    }
}
filter accept-rip {
    apply-flags omit;
    term accept-rip {
        from {
            source-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            destination-prefix-list {
                rip;
            }
            protocol udp;
            destination-port rip;
        }
        then {
            count accept-rip;
            accept;
        }
    }
    term accept-rip-igmp {
        from {
            source-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            destination-prefix-list {
                rip;
            }
            protocol igmp;
        }
        then {
            count accept-rip-igmp;
            accept;
        }
    }
}
filter accept-vrrp {
    apply-flags omit;
    term accept-vrrp {
        from {
            source-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            destination-prefix-list {
                vrrp;
            }
            protocol [ vrrp ah ];
        }
        then {
            count accept-vrrp;
            accept;
        }
    }
}
filter accept-ssh {
    apply-flags omit;
    term accept-ssh {
        from {
            source-prefix-list {
                rfc1918;
            }
            destination-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            protocol tcp;
            destination-port ssh;
        }
        then {
            policer management-5m;
            count accept-ssh;
            accept;
        }
    }
}
filter accept-snmp {
    apply-flags omit;
    term accept-snmp {
        from {
            source-prefix-list {
                snmp-client-lists;
                snmp-community-clients;
            }
            destination-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            protocol udp;
            destination-port snmp;
        }
        then {
            policer management-5m;
            count accept-snmp;
            accept;
        }
    }
}
filter accept-ntp {
    apply-flags omit;
    term accept-ntp {
        from {
            source-prefix-list {
                ntp-server;
            }
            destination-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            protocol udp;
            port ntp;
        }
        then {
            policer management-1m;
            count accept-ntp;
            accept;
        }
    }
    term accept-ntp-peer {
        from {
            source-prefix-list {
                ntp-server-peers;
            }
            destination-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            protocol udp;
            destination-port ntp;
        }
        then {
            policer management-1m;
            count accept-ntp-peer;
            accept;
        }
    }
    term accept-ntp-server {
        from {
            source-prefix-list {
                rfc1918;
            }
            destination-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            protocol udp;
            destination-port ntp;
        }
        then {
            policer management-1m;
            count accept-ntp-server;
            accept;
        }
    }
}
filter accept-web {
    apply-flags omit;
    term accept-web {
        from {
            source-prefix-list {
                rfc1918;
            }
            destination-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            protocol tcp;
            destination-port [ http https ];
        }
        then {
            policer management-5m;
            count accept-web;
            accept;
        }
    }
}
filter discard-all {
    apply-flags omit;
    term discard-ip-options {
        from {
            ip-options any;
        }
        then {
            count discard-ip-options;
            log;
            syslog;
            discard;
        }
    }
    term discard-TTL_1-unknown {
        from {
            ttl 1;
        }
        then {
            count discard-all-TTL_1-unknown;
            log;
            syslog;
            discard;
        }
    }
    term discard-tcp {
        from {
            protocol tcp;
        }
        then {
            count discard-tcp;
            log;
            syslog;
            discard;
        }
    }
    term discard-netbios {
        from {
            protocol udp;
            destination-port 137;
        }
        then {
            count discard-netbios;
            log;
            syslog;
            discard;
        }
    }
    term discard-udp {
        from {
            protocol udp;
        }
        then {
            count discard-udp;
            log;
            syslog;
            discard;
        }
    }
    term discard-icmp {
        from {
            protocol icmp;
        }
        then {
            count discard-icmp;
            log;
            syslog;
            discard;
        }
    }
    term discard-unknown {
        then {
            count discard-unknown;
            log;
            syslog;
            discard;
        }
    }
}
filter accept-traceroute {
    apply-flags omit;
    term accept-traceroute-udp {
        from {
            destination-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            protocol udp;
            ttl 1;
            destination-port 33435-33450;
        }
        then {
            policer management-1m;
            count accept-traceroute-udp;
            accept;
        }
    }
    term accept-traceroute-icmp {
        from {
            destination-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            protocol icmp;
            ttl 1;
            icmp-type [ echo-request timestamp time-exceeded ];
        }
        then {
            policer management-1m;
            count accept-traceroute-icmp;
            accept;
        }
    }
    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;
        }
    }
}
filter accept-igp {
    apply-flags omit;
    term accept-ospf {
        filter accept-ospf;
    }
    term accept-rip {
        filter accept-rip;
    }
}
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;
    }
}
filter accept-sh-bfd {
    apply-flags omit;
    term accept-sh-bfd {
        from {
            source-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            destination-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            protocol udp;
            source-port 49152-65535;
            destination-port 3784-3785;
        }
        then {
            count accept-sh-bfd;
            accept;
        }
    }
}
filter accept-ldp {
    apply-flags omit;
    term accept-ldp-discover {
        from {
            source-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            destination-prefix-list {
                multicast-all-routers;
            }
            protocol udp;
            destination-port ldp;
        }
        then {
            count accept-ldp-discover;
            accept;
        }
    }
    term accept-ldp-unicast {
        from {
            source-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            destination-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            protocol tcp;
            port ldp;
        }
        then {
            count accept-ldp-unicast;
            accept;
        }
    }
    term accept-tldp-discover {
        from {
            destination-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            protocol udp;
            destination-port ldp;
        }
        then {
            count accept-tldp-discover;
            accept;
        }
    }
    term accept-ldp-igmp {
        from {
            source-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            destination-prefix-list {
                multicast-all-routers;
            }
            protocol igmp;
        }
        then {
            count accept-ldp-igmp;
            accept;
        }
    }
}
filter accept-ftp {
    apply-flags omit;
    term accept-ftp {
        from {
            source-prefix-list {
                rfc1918;
            }
            destination-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            protocol tcp;
            port [ ftp ftp-data ];
        }
        then {
            policer management-5m;
            count accept-ftp;
            accept;
        }
    }
}
filter accept-rsvp {
    apply-flags omit;
    term accept-rsvp {
        from {
            destination-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            protocol rsvp;
        }
        then {
            count accept-rsvp;
            accept;
        }
    }
}
filter accept-radius {
    apply-flags omit;
    term accept-radius {
        from {
            source-prefix-list {
                radius-servers;
            }
            destination-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            protocol udp;
            source-port [ radacct radius ];
            tcp-established;
        }
        then {
            policer management-1m;
            count accept-radius;
            accept;
        }
    }
}
filter accept-tacas {
    apply-flags omit;
    term accept-tacas {
        from {
            source-prefix-list {
                tacas-servers;
            }
            destination-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            protocol [ tcp udp ];
            source-port [ tacacs tacacs-ds ];
            tcp-established;
        }
        then {
            policer management-1m;
            count accept-tacas;
            accept;
        }
    }
}
filter accept-remote-auth {
    apply-flags omit;
    term accept-radius {
        filter accept-radius;
    }
    term accept-tacas {
        filter accept-tacas;
    }
}
filter accept-telnet {
    apply-flags omit;
    term accept-telnet {
        from {
            source-prefix-list {
                rfc1918;
            }
            destination-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            protocol tcp;
            destination-port telnet;
        }
        then {
            policer management-1m;
            count accept-telnet;
            accept;
        }
    }
}
filter accept-dns {
    apply-flags omit;
    term accept-dns {
        from {
            source-prefix-list {
                dns-servers;
            }
            destination-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            protocol [ udp tcp ];
            source-port 53;
        }
        then {
            policer management-1m;
            count accept-dns;
            accept;
        }
    }
}
filter accept-ldp-rsvp {
    apply-flags omit;
    term accept-ldp {
        filter accept-ldp;
    }
    term accept-rsvp {
        filter accept-rsvp;
    }
}
filter accept-established {
    apply-flags omit;
    term accept-established-tcp-ssh {
        from {
            destination-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            source-port ssh;
            tcp-established;
        }
        then {
            policer management-5m;
            count accept-established-tcp-ssh;
            accept;
        }
    }
    term accept-established-tcp-ftp {
        from {
            destination-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            source-port ftp;
            tcp-established;
        }
        then {
            policer management-5m;
            count accept-established-tcp-ftp;
            accept;
        }
    }
    term accept-established-tcp-ftp-data-syn {
        from {
            destination-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            source-port ftp-data;
            tcp-initial;
        }
        then {
            policer management-5m;
            count accept-established-tcp-ftp-data-syn;
            accept;
        }
    }
    term accept-established-tcp-ftp-data {
        from {
            destination-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            source-port ftp-data;
            tcp-established;
        }
        then {
            policer management-5m;
            count accept-established-tcp-ftp-data;
            accept;
        }
    }
    term accept-established-tcp-telnet {
        from {
            destination-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            source-port telnet;
            tcp-established;
        }
        then {
            policer management-5m;
            count accept-established-tcp-telnet;
            accept;
        }
    }
    term accept-established-tcp-fetch {
        from {
            destination-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            source-port [ http https ];
            tcp-established;
        }
        then {
            policer management-5m;
            count accept-established-tcp-fetch;
            accept;
        }
    }
    term accept-established-udp-ephemeral {
        from {
            destination-prefix-list {
                router-ipv4;
                router-ipv4-logical-systms;
            }
            protocol udp;
            destination-port 49152-65535;
        }
        then {
            policer management-5m;
            count accept-established-udp-ephemeral;
            accept;
        }
    }
}
filter accept-all {
    apply-flags omit;
    term accept-all-tcp {
        from {
            protocol tcp;
        }
        then {
            count accept-all-tcp;
            log;
            syslog;
            accept;
        }
    }
    term accept-all-udp {
        from {
            protocol udp;
        }
        then {
            count accept-all-udp;
            log;
            syslog;
            accept;
        }
    }
    term accept-all-igmp {
        from {
            protocol igmp;
        }
        then {
            count accept-all-igmp;
            log;
            syslog;
            accept;
        }
    }
    term accept-icmp {
        from {
            protocol icmp;
        }
        then {
            count accept-all-icmp;
            log;
            syslog;
            accept;
        }
    }
    term accept-all-unknown {
        then {
            count accept-all-unknown;
            log;
            syslog;
            accept;
        }
    }
}
filter accept-icmp {
    apply-flags omit;
    term no-icmp-fragments {
        from {
            is-fragment;
            protocol icmp;
        }
        then {
            count no-icmp-fragments;
            log;
            discard;
        }
    }
    term accept-icmp {
        from {
            protocol icmp;
            ttl-except 1;
            icmp-type [ echo-reply echo-request time-exceeded unreachable source-quench router-advertisement parameter-problem ];
        }
        then {
            policer management-5m;
            count accept-icmp;
            accept;
        }
    }
}
filter discard-frags {
    term 1 {
        from {
            first-fragment;
        }
        then {
            count deny-first-frags;
            discard;
        }
    }
    term 2 {
        from {
            is-fragment;
        }
        then {
            count deny-other-frags;
            discard;
        }
    }
}

After all that work, don’t forget to actually apply all applicable filters as an input-list under family inet on the lo0 interface. Before making any changes, please carefully consider the following suggestions, however:

Before actually activating the lo0 application of the IPv4 protection filter, you should:

  1. Confirm that all prefix lists are accurate for your networks and that they encompass the necessary address ranges.

  2. Confirm that all valid services and remote access protocols are accepted in a filter, and that the filter is included in the input list; for example, in Day One: Securing The Routing Engine, the accept-telnet filter is not actually applied because Telnet is a nonsecure protocol, and frankly should never be used in a production network. While Telnet is used to access the testbed needed to develop this material, making the absence of the accept-telnet filter pretty obvious at time of commit . . . don’t ask me how I know this.

  3. Make sure the filter initially ends in a match-all term with accept and log actions to make sure no valid services are denied.

  4. Consider using commit confirmed for this type of change. Again, don’t ask me how I know, but there is a hint in the preceding paragraphs.

The final RE protection filters used in this case study were modified from the example used in the Day One book in the following ways:

  • The accept-telnet filter is applied in the list; as a lab, Telnet is deemed acceptable. The OSPF and RIP filters are omitted as not in use or planned in the near future.

  • The accept-icmp filter is modified to no longer match on fragments; this function is replaced with a global deny fragments filter that’s applied at the front of the filter list. See the related sidebar.

The list of filters applied to the lo0 interface of R1 for this example is shown; note that the list now begins with the discard-frags filter, the inclusion of the accept-telnet filter, and that the final discard-all filter is in effect. Again, for initial application in a production network, consider using a final match-all filter with accept and log actions to first confirm that no valid services are falling through to the final term before switching over to a final discard action.

The filter does not include the allow-ospf or allow-rip filters as the current test bed is using IS-IS, which cannot be affected by an inet family filter anyway. It’s worth noting that the accept-sh-bfd filter is so named as the port range specified allows single-hop BFD sessions only. According to draft-ietf-bfd-multihop-09.txt (now RFC 5883), multihop BFD sessions must use UDP destination port 4784.

{master}[edit]
regress@R1-RE0# show interfaces lo0
unit 0 {
    family inet {
        filter {
            input-list [ discard-frags accept-common-services accept-sh-bfd accept-bgp
              accept-ldp accept-rsvp accept-telnet discard-all ];
        }
        address 10.3.255.1/32;
    }
    family iso {
        address 49.0001.0100.0325.5001.00;
    }
    family inet6 {
        address 2001:db8:1::ff:1/128;
    }
}

A syslog is added to catch and consolidate any filter-related syslog actions for easy debug later. Remember, the log action writes to kernel cache that is overwritten and lost in a reboot, while syslog can support file archiving and remote logging. Here, the local syslog is configured:

jnpr@R1-RE0# show system syslog
file re_filter {
    firewall any;
    archive size 10m;
}

After committing the filter, and breathing a sigh of relief as you confirm that remote access is still working (this time), let’s quickly look for any issues. To begin with, filter application is confirmed:

{master}[edit]
jnpr@R1-RE0# run show interfaces filters lo0
Interface       Admin Link Proto Input Filter         Output Filter
lo0             up    up
lo0.0           up    up   inet  lo0.0-i
                           iso
                           inet6
lo0.16384       up    up   inet
lo0.16385       up    up   inet

Next, examine the syslog to see what traffic is falling through unmatched to be discarded:

{master}[edit]
jnpr@R1-RE0# run show log re_filter
Dec 12 12:58:09  R1-RE0 fpc2 PFE_FW_SYSLOG_IP: FW: irb.200
                   D vrrp 192.0.2.67 224.0.0.18   0  0  (1 packets)
Dec 12 12:58:15  R1-RE0 last message repeated 7 times
Dec 12 12:58:16  R1-RE0 fpc2 PFE_FW_SYSLOG_IP: FW: irb.200
                   D vrrp 192.0.2.67 224.0.0.18   0  0  (2 packets)
Dec 12 12:58:17  R1-RE0 fpc2 PFE_FW_SYSLOG_IP: FW: irb.200
                   D vrrp 192.0.2.67 224.0.0.18   0  0  (1 packets)
Dec 12 12:58:21  R1-RE0 last message repeated 4 times
Dec 12 12:58:22  R1-RE0 fpc2 PFE_FW_SYSLOG_IP: FW: irb.200
                   D vrrp 192.0.2.67 224.0.0.18   0  0  (2 packets)
Dec 12 12:58:23  R1-RE0 fpc2 PFE_FW_SYSLOG_IP: FW: irb.200
                   D vrrp 192.0.2.67 224.0.0.18   0  0  (1 packets)
Dec 12 12:58:26  R1-RE0 last message repeated 3 times
Dec 12 12:58:27  R1-RE0 fpc2 PFE_FW_SYSLOG_IP: FW: irb.200
                   D vrrp 192.0.2.67 224.0.0.18   0  0  (2 packets)
Dec 12 12:58:28  R1-RE0 fpc2 PFE_FW_SYSLOG_IP: FW: irb.200
                   D vrrp 192.0.2.67 224.0.0.18   0  0  (1 packets)

Do’h! What was that warning about confirming the applied filter has support for all supported services, and about using an accept-all in the final term until proper operating is confirmed, again? The syslog action in the final discard-all filter has quickly shown that VRRP is being denied by the filter, which readily explains why VRRP is down, and the phones are starting to ring. The applied filter list is modified by adding the accept-vrrp filter; note the use of the insert function to ensure the correct ordering of filters by making sure that the discard-all filter remains at the end of the list:

{master}[edit interfaces lo0 unit 0 family inet]
jnpr@R1-RE0# set filter input-list accept-vrrp

{master}[edit interfaces lo0 unit 0 family inet]
jnpr@R1-RE0# show
filter {
    input-list [ discard-frags accept-common-services accept-sh-bfd accept-bgp
      accept-ldp accept-rsvp accept-telnet discard-all accept-vrrp ];
}
address 10.3.255.1/32;

{master}[edit interfaces lo0 unit 0 family inet]
jnpr@R1-RE0# insert filter input-list accept-vrrp before discard-all

{master}[edit interfaces lo0 unit 0 family inet]
jnpr@R1-RE0# show
filter {
    input-list [ discard-frags accept-common-services accept-ospf accept-rip
      accept-sh-bfd accept-bgp accept-ldp accept-rsvp accept-telnet accept-vrrp
        discard-all ];
}
address 10.3.255.1/32;

After the change the log file is cleared, and after a few moments redisplayed:

{master}[edit interfaces lo0 unit 0 family inet]
jnpr@R1-RE0# run clear log re_filter

. . .
{master}[edit interfaces lo0 unit 0 family inet]
jnpr@R1-RE0# run show log re_filter
Dec 12 13:09:59 R1-RE0 clear-log[21857]: logfile cleared

{master}[edit interfaces lo0 unit 0 family inet]
jnpr@R1-RE0#

Perfect—the lack of syslog entry and continued operation of existing services confirms proper operation of the IPv4 RE protection filter.

IPv6 RE Protection Filter

While we have IPv4 running, many networks are only now beginning to deploy IPv6. Given the lack of ubiquity, IPv6 control planes have not been the target of many attacks; many operators have not felt the need to deploy IPv6 RE protection, leading to a general lack of experience in IPv6 filtering best practices.

Next-Header Nesting, the Bane of Stateless Filters

A significant issue with any IPv6 filtering scheme is IPv6’s use of next-header nesting, which makes some stateless filtering tasks tricky, if not downright impossible. IPv6, as defined in RFC 2460, states: “In IPv6, optional internet-layer information is encoded in separate headers that may be placed between the IPv6 header and the upper-layer header in a packet. . . . an IPv6 packet may carry zero, one, or more extension headers, each identified by the Next Header field of the preceding header.”

The net result is that there can be multiple extension headers placed between the IPv6 header and the upper layer protocol that you might want to match on (TCP, UDP, OSPF3, ICMP6, etc.). Stateless filters are designed to extract keys for matching packet fields using bit positions within a packet that are assumed to be found in the same location. Stateless IPv6 filters on Trio are able to match on the first protocol (next header) that is identified in the IPv6 packet's next-header field, and/or on bits within the actual payload, i.e., the transport protocol (TCP or UDP) ports. There is no flittering capability based on the actual contents of any extension header, and that in the 11.4 release you cannot match on the payload, for example to match a TCP port, when any extension header is present. The ability to match both the first extension header and a payload port is expected in a future release.

Note

However, regardless of how many extension headers are present, Trio ASICs have the ability to extract the first 32 bits following the last extension header to facilitate Layer 4 (TCP or UDP) port-based matches, even when one or more extension headers are present. On a supported release, the ability to match on a payload protocol when extension headers are present is enabled by specifying the payload-protocol keyword in your match criteria.

The presence of extension headers leads to unpredictable filter operation when using a next-header match condition. For example, consider a user who wants to filter out Multicast Listener Discovery messages (MLD does for IPv6 what IGMP does for IPv4: it allows multicast hosts to express interest in listening to a multicast group). The user knows that that MLD is an extension of ICMP6, and happily proceeds to create (and commit) the filter shown, only to find MLD messages are not matched, and therefore still allowed to pass through the filter:

{master}[edit firewall family inet6]
jnpr@R1-RE0# show
filter count_mld {
    term 1 {
        from {
            next-header icmp;
            icmp-type [ membership-query membership-report membership-termination ];
        }
        then {
            count mld_traffic;
            discard;
        }
    }
    term 2 {
        then accept;
    }
}

In this case, a quick look at RFC for MLD (RFC 3810) and the previous restriction on being able to match on a single next-header makes the reason for the filter’s failure clear. MLD requires the inclusion of the hop-by-hop extension header, which must precede the ICMP6 header that the filter seeks to match. This means if you want to filter MLD using a stateless filter you must, in fact, set the filter to match on the presence of the hop-by-hop header rather the header you really wanted. The obvious issue here is that other protocols, like RSVP, can also make use of a hop-by-hop header (though Junos does not currently support IPv6-based MPLS signaling), so wholesale filtering based on hop-by-hop (or other extension) headers can lead to unexpected filtering actions.

The Sample IPv6 Filter

As with the IPv4 filter example, it’s assumed that the reader is familiar with Junos firewall filter syntax and operation, as well basic IPv6 protocol operation, header fields, and option extension headers. As always, when it comes to filters, no one size fits all, and the reader is encouraged to carefully consider the effects of the sample filter along with careful testing of its operation against the specific IPv6 protocols supported in their networks so that any necessary adjustments can be made before being placed into use on a production network.

Additional details on IPv6 protocol filtering specific to the broad range of possible ICMPv6 message types can be found in RFC 4890, “Recommendations for Filtering ICMPv6 Messages in Firewalls.”

To begin, the IPv6 prefix list definitions are displayed; the previous lists used for IPv4 remain in place, with the exception noted in the following:

jnpr@R1-RE0# show policy-options
prefix-list router-ipv4 {
. . .
prefix-list bgp-neighbors_v4 {
    apply-path "protocols bgp group <*_v4> neighbor <*>";
}
prefix-list router-ipv6 {
    apply-path "interfaces <*> unit <*> family inet6 address <*>";
}
prefix-list bgp-neighbors_v6 {
    apply-path "protocols bgp group <*_v6> neighbor <*>";
}
prefix-list link_local {
    fe80::/64;
}
prefix-list rfc3849 {
    2001:db8::/32;
}

The IPv6-based prefix list performs the same function as their V4 counterparts. IPv6’s use of Link Local addressing for many routing protocols means you need to include support for them, as well as your global IPv6 interface routes. Note that the previous bgp-neighbors prefix list, as originally used for IPv4, has been renamed and the apply-path regular expression modified, so as to not conflict with the same function in IPv6. This approach assumes that you place IPv4 and IPv6 peers in separate groups with a group name that ends in either _v4 or _v6. The IPv6 RE protection filters are displayed:

{master}[edit firewall family inet6]
jnpr@R1-RE0#
filter discard-extension-headers {
    apply-flags omit;
    term discard-extension-headers {
        from {
            # Beware - VRRPv3 with authentication or OSPFv3 with Authentication
                enabled may use AH/ESP!
            next-header [ ah dstopts egp esp fragment gre icmp igmp ipip ipv6 
                no-next-header routing rsvp sctp ];
        }
        then {
            count discard-ipv6-extension-headers;
            log;
            syslog;
            discard;
        }
    }
}
filter deny-icmp6-undefined {
    apply-flags omit;
    term icmp6-unassigned-discard {
        from {
            next-header icmpv6;
            icmp-type [ 102-106 155-199 202-254 ];
        }
        then discard;
    }
    term rfc4443-discard {
        from {
            next-header icmpv6;
            icmp-type [ 100-101 200-201 ];
        }
        then discard;
    }
}
filter accept-icmp6-misc {
    apply-flags omit;
    term neigbor-discovery-accept {
        from {
            next-header icmpv6;
            icmp-type 133-136;
        }
        then accept;
    }
    term inverse-neigbor-discovery-accept {
        from {
            next-header icmpv6;
            icmp-type 141-142;
        }
        then accept;
    }
    term icmp6-echo-request {
        from {
            next-header icmpv6;
            icmp-type echo-request;
        }
        then accept;
    }
    term icmp6-echo-reply {
        from {
            next-header icmpv6;
            icmp-type echo-reply;
        }
        then accept;
    }
    term icmp6-dest-unreachable-accept {
        from {
            next-header icmpv6;
            icmp-type destination-unreachable;
        }
        then accept;
    }
    term icmp6-packet-too-big-accept {
        from {
            next-header icmpv6;
            icmp-type packet-too-big;
        }
        then accept;
    }
    term icmp6-time-exceeded-accept {
        from {
            next-header icmpv6;
            icmp-type time-exceeded;
            icmp-code 0;
        }
        then accept;
    }
    term icmp6-parameter-problem-accept {
        from {
            next-header icmpv6;
            icmp-type parameter-problem;
            icmp-code [ 1 2 ];
        }
        then accept;
    }
}
filter accept-shsh-bfd-v6 {
    apply-flags omit;
    term accept-sh-bfd-v6 {
        from {
            source-prefix-list {
                router-ipv6;
            }
            destination-prefix-list {
                router-ipv6;
            }
            source-port 49152-65535;
            destination-port 3784-3785;
        }
        then accept;
    }
}
filter accept-MLD-hop-by-hop_v6 {
    apply-flags omit;
    term bgp_v6 {
        from {
            next-header hop-by-hop;
        }
        then {
            count hop-by-hop-extension-packets;
            accept;
        }
    }
}
filter accept-bgp-v6 {
    apply-flags omit;
    term bgp_v6 {
        from {
            prefix-list {
                rfc3849;
                bgp-neighbors_v6;
            }
            next-header tcp;
            destination-port bgp;
        }
        then accept;
    }
}
filter accept-ospf3 {
    apply-flags omit;
    term ospfv3 {
        from {
            source-prefix-list {
                link_local;
            }
            next-header ospf;
        }
        then accept;
    }
}
filter accept-dns-v6 {
    apply-flags omit;
    term dnsv6 {
        from {
            source-prefix-list {
                rfc3849;
            }
            next-header [ udp tcp ];
            port domain;
        }
        then accept;
    }
}
filter accept-ntp-v6 {
    apply-flags omit;
    term ntpv6 {
        from {
            source-prefix-list {
                rfc3849;
            }
            next-header udp;
            destination-port ntp;
        }
        then accept;
    }
}
filter accept-ssh-v6 {
    apply-flags omit;
    term sshv6 {
        from {
            source-prefix-list {
                rfc3849;
            }
            next-header tcp;
            destination-port ssh;
        }
        then {
            policer management-5m;
            count accept-ssh;
            accept;
        }
    }
}
filter accept-snmp-v6 {
    apply-flags omit;
    term snmpv6 {
        from {
            source-prefix-list {
                rfc3849;
            }
            next-header udp;
            destination-port snmp;
        }
        then accept;
    }
}
filter accept-radius-v6 {
    apply-flags omit;
    term radiusv6 {
        from {
            source-prefix-list {
                rfc3849;
            }
            next-header udp;
            port [ 1812 1813 ];
        }
        then accept;
    }
}
filter accept-telnet-v6 {
    apply-flags omit;
    term telnetv6 {
        from {
            source-prefix-list {
                rfc3849;
            }
            next-header tcp;
            port telnet;
        }
        then {
            policer management-5m;
            count accept-ssh;
            accept;
        }
    }
}
filter accept-common-services-v6 {
    apply-flags omit;
    term accept-icmp6 {
        filter accept-icmp6-misc;
    }
    term accept-traceroute-v6 {
        filter accept-traceroute-v6;
    }
    term accept-ssh-v6 {
        filter accept-ssh-v6;
    }
    term accept-snmp-v6 {
        filter accept-snmp-v6;
    }
    term accept-ntp-v6 {
        filter accept-ntp-v6;
    }
    term accept-dns-v6 {
        filter accept-dns-v6;
    }
}
filter accept-traceroute-v6 {
    apply-flags omit;
    term accept-traceroute-udp {
        from {
            destination-prefix-list {
                router-ipv6;
            }
            next-header udp;
            destination-port 33435-33450;
            hop-limit 1;
        }
        then {
            policer management-1m;
            count accept-traceroute-udp-v6;
            accept;
        }
    }
    term accept-traceroute-icmp6 {
        from {
            destination-prefix-list {
                router-ipv6;
            }
            next-header icmp;
            icmp-type [ echo-request time-exceeded ];
            hop-limit 1;
        }
        then {
            policer management-1m;
            count accept-traceroute-icmp6;
            accept;
        }
    }
    term accept-traceroute-tcp-v6 {
        from {
            destination-prefix-list {
                router-ipv6;
            }
            next-header tcp;
            hop-limit 1;
        }
        then {
            policer management-1m;
            count accept-traceroute-tcp-v6;
            accept;
        }
    }
}
filter discard-all-v6 {
    apply-flags omit;
    term discard-HOPLIMIT_1-unknown {
        from {
            hop-limit 1;
        }
        then {
            count discard-all-HOPLIMIT_1-unknown;
            log;
            syslog;
            discard;
        }
    }
    term discard-tcp-v6 {
        from {
            next-header tcp;
        }
        then {
            count discard-tcp-v6;
            log;
            syslog;
            discard;
        }
    }
    term discard-netbios-v6 {
        from {
            next-header udp;
            destination-port 137;
        }
        then {
            count discard-netbios-v6;
            log;
            syslog;
            discard;
        }
    }
    term discard-udp {
        from {
            next-header udp;
        }
        then {
            count discard-udp-v6;
            log;
            syslog;
            discard;
        }
    }
    term discard-icmp6 {
        from {
            next-header icmp;
        }
        then {
            count discard-icmp;
            log;
            syslog;
            discard;
        }
    }
    term discard-unknown {
        then {
            count discard-unknown;
            log;
            syslog;
            discard;
        }
    }
}

The IPv6 filters makes use of the same policers defined previously for IPv4, and follows the same general modular approach, albeit with less counting actions in terms that accept traffic, their use already being demonstrated for IPv4. In this case, the discard-extension-headers filter discards all unused extension headers, including the fragmentation header, which ensures fragments are not subjected to any additional term processing where unpredictable results could occur given a fragment’s lack of a transport header. As per the filter’s comment, the discard action includes traffic with either the AH and/or EH authentications headers, which can be used for legitimate traffic like OSPF3. As always, you need to carefully gauge the needs of each network against any sample filter and make adjustments accordingly.

As before, the relevant list of IPv6 filters are again applied as an input list to the lo0 interface. Now under family inet6:

{master}[edit]
jnpr@R1-RE0# show interfaces lo0 unit 0
family inet {
    filter {
        input-list [ discard-frags accept-common-services accept-sh-bfd accept-bgp
          accept-ldp accept-rsvp accept-telnet accept-vrrp discard-all ];
    }
    address 10.3.255.1/32;
}
family iso {
    address 49.0001.0100.0325.5001.00;
}
family inet6 {
    filter {
        input-list [ discard-extension-headers accept-MLD-hop-by-hop_v6 
          deny-icmp6-undefined accept-common-services-v6 accept-sh-bfd-v6 accept-bgp-v6
          accept-telnet-v6 accept-ospf3 accept-radius-v6 discard-all-v6 ];
    }
    address 2001:db8:1::ff:1/128;
}

After applying the IPv6 filter, the syslog is cleared; after a few moments, it’s possible to display any new matches. Recall that at this stage only unauthorized traffic should be reaching the final discard-all action for both the IPv4 and IPv6 filter lists:

{master}[edit]
jnpr@R1-RE0# run show log re_filter
Dec 13 10:26:51 R1-RE0 clear-log[27090]: logfile cleared
Dec 13 10:26:52  R1-RE0 /kernel: FW: fxp0.0  D  tcp 172.17.13.146 172.19.90.172
  34788  21
Dec 13 10:26:55  R1-RE0 /kernel: FW: fxp0.0  D  tcp 172.17.13.146 172.19.90.172
  34788  21
Dec 13 10:26:55  R1-RE0 /kernel: FW: fxp0.0  D igmp 172.19.91.95 224.0.0.1  0  0
Dec 13 10:27:01  R1-RE0 /kernel: FW: fxp0.0  D  tcp 172.17.13.146 172.19.90.172
  34788  21
Dec 13 10:27:55  R1-RE0 /kernel: FW: fxp0.0  D igmp 172.19.91.95 224.0.0.1  0  0
. . .
Dec 13 10:34:41  R1-RE0 /kernel: FW: fxp0.0  D  udp 172.19.91.43 172.19.91.255
  138  138
Dec 13 10:34:55  R1-RE0 /kernel: FW: fxp0.0  D igmp 172.19.91.95 224.0.0.1  0  0
Dec 13 10:35:55  R1-RE0 /kernel: FW: fxp0.0  D igmp 172.19.91.95 224.0.0.1  0  0
Dec 13 10:36:55  R1-RE0 /kernel: FW: fxp0.0  D igmp 172.19.91.95 224.0.0.1  0  0

The result shown here is good. The only traffic not being accepted by other terms is coming from unauthorized hosts at 172.17.13.0/24, an address not included in the official lab topology, which shows the filter is having the desired effect. All the discarded traffic arrives on the shared OoB management network via fxp0, and appears to be a mix of IGMP, FTP, and NetBIOS. As a final confirmation, you confirm BGP and BFD session status at R1:

{master}[edit]
jnpr@R1-RE0# run show bgp summary
Groups: 3 Peers: 3 Down peers: 1
Table          Tot Paths  Act Paths Suppressed    History Damp State    Pending
inet.0                 0          0          0          0          0          0
inet6.0                0          0          0          0          0          0
Peer                     AS      InPkt     OutPkt    OutQ   Flaps Last Up/Dwn State|
  #Active/Received/Accepted/Damped...
10.3.255.2            65000       2010       2009       0       0    15:09:23 0/0/0/0
2001:db8:1::ff:2      65000        298        296       0       2     2:13:16 Establ
  inet6.0: 0/0/0/0
fd1e:63ba:e9dc:1::1   65010          0          0       0       0    17:52:23 Active

At this point, the EBGP session to the external BGP P1 device is expected to be down, but both the IPv6 and IPv4 IBGP sessions are established, as is the BFD session between R1 and R2. This BFD session is IPv4-based and runs over the ae0.1 interface to provide the IS-IS protocol with rapid fault detection capabilities:

{master}[edit]
jnpr@R1-RE0# show protocols isis
reference-bandwidth 100g;
level 1 disable;
interface ae0.1 {
    point-to-point;
    bfd-liveness-detection {
        minimum-interval 1000;
        multiplier 3;
    }
}
interface lo0.0 {
    passive;
}

{master}[edit]
jnpr@R1-RE0# run show bfd session
                                                  Detect   Transmit
Address                  State     Interface      Time     Interval  Multiplier
10.8.0.1                 Up        ae0.1          3.000     1.000        3

1 sessions, 1 clients
Cumulative transmit rate 1.0 pps, cumulative receive rate 1.0 pps

The continued operation of permitted services coupled with the lack of unexpected log entries from the discard-all action of both RE protection filters confirms they are working as designed and concludes the RE protection case study.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required