{"id":162,"date":"2020-10-24T17:26:49","date_gmt":"2020-10-24T09:26:49","guid":{"rendered":"https:\/\/blog.swineson.me\/en\/?p=162"},"modified":"2020-12-05T16:13:15","modified_gmt":"2020-12-05T08:13:15","slug":"reverse-route-modes-on-juniper-srx","status":"publish","type":"post","link":"https:\/\/blog.swineson.me\/en\/reverse-route-modes-on-juniper-srx\/","title":{"rendered":"What the Flow: Reverse Route Modes on Juniper SRX"},"content":{"rendered":"<p>A SRX is a &#8220;security device&#8221;, or as we call it conventionally, a firewall. Modern layer-3 firewalls route packets just like a router, but unlike a router, a firewall can organize packets into connections (flows) and run ACLs on the entire flow. This unique functionality is the fundamental building block of every &#8220;advanced&#8221; security feature offered by a firewall: dynamic NAT (PAT\/NPT), zone-based firewall (ZBFW), ACLs for in or out connections only, L7 filtering, etc. For the connection (flow) tracking to work, all the packets in a connection must go through the same device, and the 5-tuple of all the packets in a connection must be of expected values, which usually means:<\/p>\n<ul>\n<li>The packets from A to B and the packets from B to A must all go through the firewall at some point<\/li>\n<li>There shouldn&#8217;t be single-sided stateless NAT happening on the route<\/li>\n<\/ul>\n<p>This was never an issue when everyone was single-homed and all the routers had only one routing table. But not today. SRXs now have built-in support for virtual routers which can create an asymmetric flow easily. Let&#8217;s look at this simplified topology:<\/p>\n<p><!--more--><\/p>\n<p><a href=\"https:\/\/blog.swineson.me\/en\/reverse-route-packet-mode-on-juniper-srx\/juniper-srx-vr-conntrack-demo\/\" rel=\"attachment wp-att-163\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-163\" src=\"https:\/\/blog.swineson.me\/en\/wp-content\/uploads\/sites\/2\/2020\/10\/juniper-srx-vr-conntrack-demo.png\" alt=\"\" width=\"1323\" height=\"313\" srcset=\"https:\/\/blog.swineson.me\/en\/wp-content\/uploads\/sites\/2\/2020\/10\/juniper-srx-vr-conntrack-demo.png 1323w, https:\/\/blog.swineson.me\/en\/wp-content\/uploads\/sites\/2\/2020\/10\/juniper-srx-vr-conntrack-demo-300x71.png 300w, https:\/\/blog.swineson.me\/en\/wp-content\/uploads\/sites\/2\/2020\/10\/juniper-srx-vr-conntrack-demo-1024x242.png 1024w, https:\/\/blog.swineson.me\/en\/wp-content\/uploads\/sites\/2\/2020\/10\/juniper-srx-vr-conntrack-demo-768x182.png 768w, https:\/\/blog.swineson.me\/en\/wp-content\/uploads\/sites\/2\/2020\/10\/juniper-srx-vr-conntrack-demo-624x148.png 624w\" sizes=\"(max-width: 1323px) 100vw, 1323px\" \/><\/a><\/p>\n<p>First of all, we need to make sure the SRX works in flow mode. If it is in the packet mode, then it acts like a traditional packet-forwarding router and all the things we are talking about it today does not apply at all.<\/p>\n<pre class=\"lang:default highlight:0 decode:true\">root@lan-gw&gt; show security flow status\r\n  Flow forwarding mode:\r\n    Inet forwarding mode: flow based\r\n    Inet6 forwarding mode: flow based<\/pre>\n<p>For flexibility (i.e. we will have multiple LAN tenants and we want to isolate them later), we put the ISP and the LAN in different virtual routers:<\/p>\n<pre class=\"lang:default highlight:0 decode:true \"># VR at the ISP side\r\nset routing-instances ISP instance-type virtual-router\r\nset routing-instances ISP interface ge-0\/0\/1.0\r\nset routing-instances ISP routing-options rib ISP.inet.0 static defaults install\r\nset routing-instances ISP routing-options rib ISP.inet.0 static defaults resolve\r\nset routing-instances ISP routing-options rib ISP.inet.0 static route 0.0.0.0\/0 next-hop 192.0.2.1\r\nset routing-instances ISP routing-options auto-export\r\nset interfaces ge-0\/0\/1.0 family inet address 192.0.2.2\/24\r\n\r\n# VR at the LAN side\r\nset routing-instances LAN instance-type virtual-router\r\nset routing-instances LAN interface ge-0\/0\/2.0\r\nset routing-instances LAN routing-options auto-export\r\nset interfaces ge-0\/0\/2.0 family inet address 192.168.1.1\/24\r\n\r\n# import all ISP VR routes into LAN VR\r\nset routing-instances LAN routing-options instance-import VR_ISP_TO_LAN\r\nset policy-options policy-statement VR_ISP_TO_LAN term 1 from instance ISP\r\nset policy-options policy-statement VR_ISP_TO_LAN term 1 then accept\r\nset policy-options policy-statement VR_ISP_TO_LAN term FINAL then reject<\/pre>\n<p>This created 2 IPv4 routing tables: <span class=\"lang:default highlight:0 decode:true crayon-inline \">ISP.inet.0<\/span> and <span class=\"lang:default highlight:0 decode:true crayon-inline\">LAN.inet.0<\/span>, then assigned the two interfaces to their corresponding routing tables. Then we also imported all the routers in the <span class=\"lang:default highlight:0 decode:true crayon-inline \">ISP.inet.0<\/span> table into the <span class=\"lang:default highlight:0 decode:true crayon-inline \">LAN.inet.0<\/span> table. Let&#8217;s see if the routing tables contain the routes we expected:<\/p>\n<pre class=\"lang:default highlight:0 decode:true\">root@lan-gw&gt; show route\r\n\r\nISP.inet.0: 3 destinations, 3 routes (3 active, 0 holddown, 0 hidden)\r\n+ = Active Route, - = Last Active, * = Both\r\n\r\n0.0.0.0\/0          *[Static\/5] 00:16:39\r\n                    &gt;  to 192.0.2.1 via ge-0\/0\/1.0\r\n192.0.2.0\/24       *[Direct\/0] 00:16:39\r\n                    &gt;  via ge-0\/0\/1.0\r\n192.0.2.2\/32       *[Local\/0] 00:16:39\r\n                       Local via ge-0\/0\/1.0\r\n\r\nLAN.inet.0: 5 destinations, 5 routes (5 active, 0 holddown, 0 hidden)\r\n+ = Active Route, - = Last Active, * = Both\r\n\r\n0.0.0.0\/0          *[Static\/5] 00:14:43\r\n                    &gt;  to 192.0.2.1 via ge-0\/0\/1.0\r\n192.0.2.0\/24       *[Direct\/0] 00:14:43\r\n                    &gt;  via ge-0\/0\/1.0\r\n192.0.2.2\/32       *[Local\/0] 00:14:43\r\n                       Local via ge-0\/0\/1.0\r\n192.168.1.0\/24     *[Direct\/0] 00:18:48\r\n                    &gt;  via ge-0\/0\/2.0\r\n192.168.1.1\/32     *[Local\/0] 00:18:48\r\n                       Local via ge-0\/0\/2.0\r\n\r\n<\/pre>\n<p>So, if we ping <span class=\"lang:default highlight:0 decode:true crayon-inline\">192.168.1.2<\/span> from a device on the ISP side (e.g. <span class=\"lang:default highlight:0 decode:true crayon-inline\">192.0.2.1<\/span>), it should not go through the SRX. This is because if we send a packet destinated to <span class=\"lang:default highlight:0 decode:true crayon-inline \">192.168.1.2<\/span> into SRX&#8217;s interface <span class=\"lang:default highlight:0 decode:true crayon-inline\">ge-0\/0\/1.0<\/span>, and since <span class=\"lang:default highlight:0 decode:true crayon-inline\">ge-0\/0\/1.0<\/span> is associated to the routing table <span class=\"lang:default highlight:0 decode:true crayon-inline\">ISP.inet.0<\/span>, there are no detailed route to <span class=\"lang:default highlight:0 decode:true crayon-inline\">192.168.1.2<\/span>, thus the packet is routed back to <span class=\"lang:default highlight:0 decode:true crayon-inline\">ge-0\/0\/1.0<\/span> thanks to the default route.<\/p>\n<p>Let&#8217;s put on the router hat and think another question: what will happen if we ping <span class=\"lang:default highlight:0 decode:true crayon-inline \">192.0.2.1<\/span> from the LAN side (e.g. <span class=\"lang:default highlight:0 decode:true crayon-inline\">192.168.1.2<\/span>)? Obviously the ICMP echo request packet will go through the SRX since we do have routes to <span class=\"lang:default highlight:0 decode:true crayon-inline\">192.0.2.0\/24<\/span> in routing table <span class=\"lang:default highlight:0 decode:true crayon-inline \">LAN.inet.0<\/span> pointing to the correct interface, but the ICMP echo reply packet would not go through just like the first case, right? Well, let&#8217;s try it out.<\/p>\n<pre class=\"lang:default highlight:0 decode:true \">root@enduser:~# ping -c 1 192.0.2.1\r\nPING 192.0.2.1 (192.0.2.1) 56(84) bytes of data.\r\n64 bytes from 192.0.2.1: icmp_seq=1 ttl=63 time=1.62 ms\r\n\r\n--- 192.0.2.1 ping statistics ---\r\n1 packets transmitted, 1 received, 0% packet loss, time 0ms\r\nrtt min\/avg\/max\/mdev = 1.616\/1.616\/1.616\/0.000 ms\r\n<\/pre>\n<p>Well, that&#8217;s a little unexpected. But why? It&#8217;s because the SRX&#8217;s flow based forwarding process is a little different from a router&#8217;s packet based forwarding process. Let&#8217;s look at our ICMP flow:<\/p>\n<pre class=\"lang:default decode:true\">root@lan-gw&gt; show security flow session extensive protocol icmp\r\nSession ID: 1842, Status: Normal\r\nFlags: 0x80000040\/0x0\/0x0\/0x800003\r\nPolicy name: default-policy-logical-system-00\/2\r\nSource NAT pool: Null\r\nDynamic application: junos:UNKNOWN,\r\nEncryption:  Unknown\r\nApplication traffic control rule-set: INVALID, Rule: INVALID\r\nMaximum timeout: 4, Current timeout: 2\r\nSession State: Valid\r\nStart time: 2681, Duration: 2\r\n   In: 192.168.1.2\/1 --&gt; 192.0.2.1\/744;icmp,\r\n  Conn Tag: 0x0, Interface: ge-0\/0\/2.0,\r\n    Session token: 0x6008, Flag: 0x21\r\n    Route: 0xb0010, Gateway: 192.168.1.2, Tunnel: 0\r\n    Port sequence: 0, FIN sequence: 0,\r\n    FIN state: 0,\r\n    Pkts: 1, Bytes: 84\r\n   Out: 192.0.2.1\/744 --&gt; 192.168.1.2\/1;icmp,\r\n  Conn Tag: 0x0, Interface: ge-0\/0\/1.0,\r\n    Session token: 0x5007, Flag: 0x20\r\n    Route: 0xa0010, Gateway: 192.0.2.1, Tunnel: 0\r\n    Port sequence: 0, FIN sequence: 0,\r\n    FIN state: 0,\r\n    Pkts: 1, Bytes: 84\r\nTotal sessions: 1\r\n<\/pre>\n<p>A flow consists of two match conditions for two packet directions. The &#8220;In&#8221; condition is taken from the first packet of a flow received by SRX, and the &#8220;Out&#8221; condition is derived from all the packet transformation rules. (NOTE: Everything displayed here with a direction assumes the current device is receiving the traffic. e.g. If you need to see the gateway of the &#8220;Out&#8221; direction, you need to look at the line after &#8220;In&#8221;.) This is how connection tracking works under the hood. In Junos, either direction will be associated with a route in the active routing table. By default, the reverse route lookup mode is &#8220;flow mode&#8221;, which causes the route associated with the &#8220;Out&#8221; direction is also calculated from the routing table of the &#8220;In&#8221; interface. This mode breaks the assumption that a packet should be forwarded according to the routing table the receiving interface is associated to, but flow symmetry is easily achieved.<\/p>\n<p>Now let&#8217;s try set the reverse route lookup mode to &#8220;packet mode&#8221;:<\/p>\n<pre class=\"lang:default highlight:0 decode:true \"> set security flow advanced-options reverse-route-packet-mode-vr<\/pre>\n<p>You can verify yourself the routing table is the same, but the LAN device would not be able to ping the ISP device anymore. If you look at the flow session table again, you should see the associated route for the &#8220;Out&#8221; direction is now <span class=\"lang:default highlight:0 decode:true crayon-inline\">0x0<\/span>, denoting a route lookup failure.<\/p>\n<pre class=\"lang:default decode:true\">root@lan-gw&gt; show security flow session extensive protocol icmp\r\nSession ID: 2365, Status: Normal\r\nFlags: 0x40\/0x0\/0x0\/0x3\r\nPolicy name: default-policy-logical-system-00\/2\r\nSource NAT pool: Null\r\nDynamic application: junos:UNKNOWN,\r\nEncryption:  Unknown\r\nApplication traffic control rule-set: INVALID, Rule: INVALID\r\nMaximum timeout: 60, Current timeout: 6\r\nSession State: Valid\r\nStart time: 3477, Duration: 1\r\n   In: 192.168.1.2\/1 --&gt; 192.0.2.1\/754;icmp,\r\n  Conn Tag: 0x0, Interface: ge-0\/0\/2.0,\r\n    Session token: 0x6008, Flag: 0x21\r\n    Route: 0x0, Gateway: 192.168.1.2, Tunnel: 0\r\n    Port sequence: 0, FIN sequence: 0,\r\n    FIN state: 0,\r\n    Pkts: 1, Bytes: 84\r\n   Out: 192.0.2.1\/754 --&gt; 192.168.1.2\/1;icmp,\r\n  Conn Tag: 0x0, Interface: ge-0\/0\/1.0,\r\n    Session token: 0x5007, Flag: 0x20\r\n    Route: 0xa0010, Gateway: 192.0.2.1, Tunnel: 0\r\n    Port sequence: 0, FIN sequence: 0,\r\n    FIN state: 0,\r\n    Pkts: 0, Bytes: 0\r\nTotal sessions: 1<\/pre>\n<p>If we do a packet capture on the ISP router, we should see it successfully receiving the echo request packet and sending out the echo reply packet. The reply packet will be discarded by the SRX (because of the route lookup failure) and never reach its intended destination, though.<\/p>\n<pre class=\"lang:default highlight:0 decode:true\">root@ISP:~# tcpdump -ni ens3 icmp\r\n[ 4174.166928] device ens3 entered promiscuous mode\r\ntcpdump: verbose output suppressed, use -v or -vv for full protocol decode\r\nlistening on ens3, link-type EN10MB (Ethernet), capture size 262144 bytes\r\n09:14:46.237810 IP 192.168.1.2 &gt; 192.0.2.1: ICMP echo request, id 757, seq 130, length 64\r\n09:14:46.237852 IP 192.0.2.1 &gt; 192.168.1.2: ICMP echo reply, id 757, seq 130, length 64\r\n<\/pre>\n<p>In this configuration, you can still ping the SRX&#8217;s ISP-facing interface IP from the LAN device:<\/p>\n<pre class=\"lang:default highlight:0 decode:true\">root@enduser:~# ping -c 1 192.0.2.2\r\nPING 192.0.2.2 (192.0.2.2) 56(84) bytes of data.\r\n64 bytes from 192.0.2.2: icmp_seq=1 ttl=64 time=0.985 ms\r\n\r\n--- 192.0.2.2 ping statistics ---\r\n1 packets transmitted, 1 received, 0% packet loss, time 0ms\r\nrtt min\/avg\/max\/mdev = 0.985\/0.985\/0.985\/0.000 ms\r\n<\/pre>\n<p>Why this is possible is trivial and left as an exercise to the reader.<\/p>\n<h1>Conclusion<\/h1>\n<p>One that is used to the VRFs on the routers might be confused by the default reverse route lookup mode when using SRXs for the first time. But the ability to change how returning packet is routed is particularly useful when familiarized. It allows us to easily achieve flow symmetry in a multi-VR environment without adding a lot complexity on route import\/export policies, and it can simulate simple ZBFW functionality with pure routing table designs.<\/p>\n<h1>Appendix<\/h1>\n<p>The lab topology is verified under Junos OS 18.4R3.3 (vSRX3) and 20.1R1-S1.2 (SRX300).<\/p>\n<p>For simplicity, the configuration above is not complete. If you are trying to replicate the lab topology, you also need the following configuration:<\/p>\n<pre class=\"lang:default highlight:0 decode:true\">set system root-authentication plain-text-password\r\nset system host-name lan-gw\r\nset system no-redirects\r\nset security policies default-policy permit-all\r\nset security zones security-zone ISP host-inbound-traffic system-services any-service\r\nset security zones security-zone ISP host-inbound-traffic protocols all\r\nset security zones security-zone ISP interfaces ge-0\/0\/1.0\r\nset security zones security-zone LAN host-inbound-traffic system-services any-service\r\nset security zones security-zone LAN host-inbound-traffic protocols all\r\nset security zones security-zone LAN interfaces ge-0\/0\/2.0\r\n<\/pre>\n<p>References:<\/p>\n<ul>\n<li><a href=\"https:\/\/www.juniper.net\/documentation\/en_US\/junos\/topics\/topic-map\/security-flow-packet-mode-reverse-route.html\" target=\"_blank\" rel=\"noopener noreferrer\">Reverse Route Packet Mode using Virtual Router<\/a><\/li>\n<li><a href=\"https:\/\/rtodto.net\/how-to-avoid-flow-asymmetry-on-srx\/\" target=\"_blank\" rel=\"noopener noreferrer\">How to avoid flow asymmetry on SRX<\/a><\/li>\n<li><a href=\"https:\/\/www.juniper.net\/documentation\/en_US\/junos\/topics\/reference\/command-summary\/show-security-flow-session-extensive-node.html\" target=\"_blank\" rel=\"noopener noreferrer\">show security flow session extensive node<\/a><\/li>\n<li><a href=\"https:\/\/www.juniper.net\/documentation\/en_US\/junos\/topics\/reference\/configuration-statement\/no-redirects-edit-system.html\" target=\"_blank\" rel=\"noopener noreferrer\">no-redirects (IPv4 Traffic)<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>A SRX is a &#8220;security device&#8221;, or as we call it conventionally, a firewall. Modern layer-3 firewalls route packets just like a router, but unlike a router, a firewall can organize packets into connections (flows) and run ACLs on the entire flow. This unique functionality is the fundamental building block of every &#8220;advanced&#8221; security feature [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[13,14],"tags":[],"class_list":["post-162","post","type-post","status-publish","format-standard","hentry","category-juniper","category-junos-os"],"acf":[],"_links":{"self":[{"href":"https:\/\/blog.swineson.me\/en\/wp-json\/wp\/v2\/posts\/162"}],"collection":[{"href":"https:\/\/blog.swineson.me\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.swineson.me\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.swineson.me\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.swineson.me\/en\/wp-json\/wp\/v2\/comments?post=162"}],"version-history":[{"count":13,"href":"https:\/\/blog.swineson.me\/en\/wp-json\/wp\/v2\/posts\/162\/revisions"}],"predecessor-version":[{"id":236,"href":"https:\/\/blog.swineson.me\/en\/wp-json\/wp\/v2\/posts\/162\/revisions\/236"}],"wp:attachment":[{"href":"https:\/\/blog.swineson.me\/en\/wp-json\/wp\/v2\/media?parent=162"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.swineson.me\/en\/wp-json\/wp\/v2\/categories?post=162"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.swineson.me\/en\/wp-json\/wp\/v2\/tags?post=162"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}