FortiGate
FortiGate Next Generation Firewall utilizes purpose-built security processors and threat intelligence security services from FortiGuard labs to deliver top-rated protection and high performance, including encrypted traffic.
cgustave
Staff
Staff
Purpose
The purpose for this article is to clarify how Reverse Path Forwarding (RPF) is implemented on the FortiGate.
It also explains how the vdom specific cli setting "config system settings -> set strict-src-check" modifies the RPF behaviour.
Behaviour is highlighted with example.

Reverse Path Filtering is defined in RFC 3704.
Scope



Diagram
The following setup is used in the test scenario

cgustave_33955_diagram.png



Expectations, Requirements
Reverse Path Filter (aka RPF) is a security enforcement allowing to drop an ingressing packet based on its source ip address.
The packet source IP address is checked against the routing table for reverse path (ie: route to the source IP address of the packet).
Depending on Reverse Path Filter configuration, packet may be dropped or forwarded.

FortiGate implements only 2 types of Reverse Patch Filters referenced in RFC 3704 as "Strict Reverse Path Forwarding (section 2.2)" and "Feasible Path Reverse Path Forwarding (section 2.3)". It does not implement "Loose Reverse Path Forwarding" (section 2.4), nor "Loose Reverse Path Forwarding Ignoring Default Routes" (section 2.5)

VDOM CLI option "strict-src-check enable|disable (default: disable)" in "config system settings" section allows to choose between "strict" and "feasible path".

  • set strict-src-check disable  : (default option)  selects "feasible path" behaviour
  • set strict-src-check enable    : selects the "strict" behaviour

Difference between "strict" and "feasible path" :

  • 'strict' : a routing lookup (with best match) is made for the packet source IP. Packet is dropped if its ingressing interface does not match the interface selected by the routing lookup.

  • 'feasible path' : not only the best match route is considered. Other routes pointing to packet ingressing interfaces are also checked. If one of them includes the packet source IP address (even if not the best match route), packet is accepted.


Neutralizing the RPF filter:

There are scenario where it is required to neutralize RPF.
As of today, there is no available option to disable RPF at the interface level however there are other possibilities:

  • "asymetric routing enable" :
Configuring the vdom in asymetric mode (set asymroute enable) is one of them but also it also disables the packet state inspection which may not be wanted.

  • "strict-src-check disable" + adding a supernet route as "feasible patch"
A route with a larger prefix can be added pointing to interface where packet egresses. Since best match applies, the most specific route will be used to route packets. This "non-priority" route is added to provide a "feasible path". 'strict-src-check' should be set to 'disable'.

  • "strict-src-check disable" + adding the same route as the best matching one (same subnet, same prefix, same distance) but having a higher priority value than the best match one. This will force the route to be injected in the routing table as a second choice.
          Note : the lower priority, the best . If not defined, priority is set to '0' per default



Configuration
FortiGate FG-3810A configuration used for the demonstration are attached

Verification
Examples:

The following examples are provided to highlight the "strict-src-check" setting.
These examples use several vdoms of the fortigate. Port1 and Port3 are connected with a cross-over cable for the inter-vdom communication.

Test traffic :
A telnet is issued from vdom client to vdom server ip address (192.168.3.1).
The flow is diverted by a policy route on vdom 'traffic' toward vdom 'snat' where packet is source-natted with an IP pool (192.168.5.1-10).
Packet is re-injected in 'traffic' vdom with a source ip address of 192.168.5.x

Flow :
- packet leaves client vdom as "192.168.0.1 -> 192.168.3.1"
- packet flows in vdom 'traffic' from interface (p3v84) to (p3v85) and reached vdom 'snat'
- packet is source-natted in vdom 'snat' and re-injected to vdom 'traffic'. Packet is now like  192.168.5.X -> 192.168.3.1
- RPF takes place in vdom 'traffic'.


Different cases are shown below:

A> vdom traffic configured with "strict-src-check disable"
with a feasible path
RPF is neutralized by a "feasible path" route  192.168.0.0/16 and packet is expected to flow.

Telnet from client vdom is working :

FG3K8A-4 (client) # execute telnet 192.168.3.1
FG3K8A-4 login:

'traffic vdom' routing table:

FG3K8A-4 (traffic) # get router info routing-table all
Codes: K - kernel, C - connected, S - static, R - RIP, B - BGP
       O - OSPF, IA - OSPF inter area
       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
       E1 - OSPF external type 1, E2 - OSPF external type 2
       i - IS-IS, L1 - IS-IS level-1, L2 - IS-IS level-2, ia - IS-IS inter area
       * - candidate default

S       192.168.0.0/16 [10/0] via 192.168.2.1, p3v86
C       192.168.0.0/24 is directly connected, p3v84
C       192.168.2.0/24 is directly connected, p3v86
C       192.168.3.0/24 is directly connected, p3v87
S       192.168.4.0/24 [10/0] via 192.168.0.1, p3v84
C       192.168.5.0/24 is directly connected, p3v85



Debug flow captured in traffic vdom sho
ws the packet path up to server vdom :

FG3K8A-4 (traffic) #
id=36871 trace_id=99 func=resolve_ip_tuple_fast line=3785 msg="vd-client received a packet(proto=6, 192.168.0.1:1111->192.168.3.1:23) from local."
id=36871 trace_id=99 func=resolve_ip_tuple line=3925 msg="allocate a new session-0000045b"
id=36871 trace_id=100 func=resolve_ip_tuple_fast line=3785 msg="vd-traffic received a packet(proto=6, 192.168.0.1:1111->192.168.3.1:23) from p3v84."
id=36871 trace_id=100 func=resolve_ip_tuple line=3925 msg="allocate a new session-0000045c"
id=36871 trace_id=100 func=vf_ip4_route_input line=1591 msg="Match policy routing: to 192.168.5.1 via ifindex-31"
id=36871 trace_id=100 func=vf_ip4_route_input line=1599 msg="find a route: gw-192.168.5.1 via p3v85"
id=36871 trace_id=100 func=fw_forward_handler line=555 msg="Allowed by Policy-1:"
id=36871 trace_id=101 func=resolve_ip_tuple_fast line=3785 msg="vd-snat received a packet(proto=6, 192.168.0.1:1111->192.168.3.1:23) from p1v85."
id=36871 trace_id=101 func=resolve_ip_tuple line=3925 msg="allocate a new session-0000045d"
id=36871 trace_id=101 func=vf_ip4_route_input line=1599 msg="find a route: gw-192.168.2.2 via p1v86"
id=36871 trace_id=101 func=get_new_addr line=1948 msg="find SNAT: IP-192.168.4.2(from IPPOOL), port-0(fixed port)"
id=36871 trace_id=101 func=fw_forward_handler line=555 msg="Allowed by Policy-1: SNAT"
id=36871 trace_id=101 func=__ip_session_run_tuple line=2116 msg="SNAT 192.168.0.1->192.168.4.2:1111"
id=36871 trace_id=102 func=resolve_ip_tuple_fast line=3785 msg="vd-traffic received a packet(proto=6, 192.168.4.2:1111->192.168.3.1:23) from p3v86."
id=36871 trace_id=102 func=resolve_ip_tuple line=3925 msg="allocate a new session-0000045e"
id=36871 trace_id=102 func=vf_ip4_route_input line=1599 msg="find a route: gw-192.168.3.1 via p3v87"
id=36871 trace_id=102 func=fw_forward_handler line=555 msg="Allowed by Policy-2:"
id=36871 trace_id=103 func=resolve_ip_tuple_fast line=3785 msg="vd-server received a packet(proto=6, 192.168.4.2:1111->192.168.3.1:23) from p1v87."


B> vdom traffic configured with "strict-src-check enable".
Strict RPF is expected to drop the packets.

configuration is now changed:

FG3K8A-4 (traffic) # config system settings
FG3K8A-4 (settings) # set strict-src-check enable
FG3K8A-4 (settings) # end



Telnet from client vdom fails:

FG3K8A-4 (client) # execute telnet 192.168.3.1
Timeout!

Debug flow captured in traffic vdom shows the packet dropped by RPF filter.

FG3K8A-4 (traffic) #
id=36871 trace_id=91 func=resolve_ip_tuple_fast line=3785 msg="vd-client received a packet(proto=6, 192.168.0.1:1108->192.168.3.1:23) from local."
id=36871 trace_id=91 func=resolve_ip_tuple_fast line=3825 msg="Find an existing session, id-00000391, original direction"
id=36871 trace_id=92 func=resolve_ip_tuple_fast line=3785 msg="vd-traffic received a packet(proto=6, 192.168.0.1:1108->192.168.3.1:23) from p3v84."
id=36871 trace_id=92 func=resolve_ip_tuple_fast line=3825 msg="Find an existing session, id-00000392, original direction"
id=36871 trace_id=92 func=ipv4_fast_cb line=50 msg="enter fast path"
id=36871 trace_id=93 func=resolve_ip_tuple_fast line=3785 msg="vd-snat received a packet(proto=6, 192.168.0.1:1108->192.168.3.1:23) from p1v85."
id=36871 trace_id=93 func=resolve_ip_tuple_fast line=3825 msg="Find an existing session, id-00000393, original direction"
id=36871 trace_id=93 func=ipv4_fast_cb line=50 msg="enter fast path"
id=36871 trace_id=93 func=ip_session_run_all_tuple line=4819 msg="SNAT 192.168.0.1->192.168.4.2:1108"
id=36871 trace_id=94 func=resolve_ip_tuple_fast line=3785 msg="vd-traffic received a packet(proto=6, 192.168.4.2:1108->192.168.3.1:23) from p3v86."
id=36871 trace_id=94 func=resolve_ip_tuple line=3925 msg="allocate a new session-0000039e"
id=36871 trace_id=94 func=ip_route_input_slow line=1287 msg="reverse path check fail(by strict-src-check),drop"



C> vdom traffic configured with "strict-src-check disable" without a feasible path
strict-src-check is disabled and feasible path is removed. Packet is expected to be dropped by RPF because no feasible path exists.

Configuration change (feasible route deleted):

FG3K8A-4 (traffic) # config system settings
FG3K8A-4 (settings) # set strict-src-check disable
FG3K8A-4 (settings) # end
FG3K8A-4 (traffic) # config router static
FG3K8A-4 (static) # show
config router static
    edit 3
        set device "p3v86"
        set dst 192.168.0.0 255.255.0.0
        set gateway 192.168.2.1
    next
    edit 2
        set device "p3v84"
        set dst 192.168.4.0 255.255.255.0
        set gateway 192.168.0.1
    next
end
FG3K8A-4 (static) # delete 3
FG3K8A-4 (static) # end

Routing table:

FG3K8A-4 (traffic) # get router info routing-table all
Codes: K - kernel, C - connected, S - static, R - RIP, B - BGP
       O - OSPF, IA - OSPF inter area
       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
       E1 - OSPF external type 1, E2 - OSPF external type 2
       i - IS-IS, L1 - IS-IS level-1, L2 - IS-IS level-2, ia - IS-IS inter area
       * - candidate default

C       192.168.0.0/24 is directly connected, p3v84
C       192.168.2.0/24 is directly connected, p3v86
C       192.168.3.0/24 is directly connected, p3v87
S       192.168.4.0/24 [10/0] via 192.168.0.1, p3v84
C       192.168.5.0/24 is directly connected, p3v85



Telnet from client vdom fails:

FG3K8A-4 (client) # execute telnet 192.168.3.1
Timeout!


Debug flow shows syn packet dropped by RPF because of no feasible path :

FG3K8A-4 (traffic) #
id=36871 trace_id=129 func=resolve_ip_tuple_fast line=3785 msg="vd-client received a packet(proto=6, 192.168.0.1:1113->192.168.3.1:23) from local."
id=36871 trace_id=129 func=resolve_ip_tuple line=3925 msg="allocate a new session-000005b7"
id=36871 trace_id=130 func=resolve_ip_tuple_fast line=3785 msg="vd-traffic received a packet(proto=6, 192.168.0.1:1113->192.168.3.1:23) from p3v84."
id=36871 trace_id=130 func=resolve_ip_tuple line=3925 msg="allocate a new session-000005b8"
id=36871 trace_id=130 func=vf_ip4_route_input line=1591 msg="Match policy routing: to 192.168.5.1 via ifindex-31"
id=36871 trace_id=130 func=vf_ip4_route_input line=1599 msg="find a route: gw-192.168.5.1 via p3v85"
id=36871 trace_id=130 func=fw_forward_handler line=555 msg="Allowed by Policy-1:"
id=36871 trace_id=131 func=resolve_ip_tuple_fast line=3785 msg="vd-snat received a packet(proto=6, 192.168.0.1:1113->192.168.3.1:23) from p1v85."
id=36871 trace_id=131 func=resolve_ip_tuple line=3925 msg="allocate a new session-000005b9"
id=36871 trace_id=131 func=vf_ip4_route_input line=1599 msg="find a route: gw-192.168.2.2 via p1v86"
id=36871 trace_id=131 func=get_new_addr line=1948 msg="find SNAT: IP-192.168.4.2(from IPPOOL), port-0(fixed port)"
id=36871 trace_id=131 func=fw_forward_handler line=555 msg="Allowed by Policy-1: SNAT"
id=36871 trace_id=131 func=__ip_session_run_tuple line=2116 msg="SNAT 192.168.0.1->192.168.4.2:1113"
id=36871 trace_id=132 func=resolve_ip_tuple_fast line=3785 msg="vd-traffic received a packet(proto=6, 192.168.4.2:1113->192.168.3.1:23) from p3v86."
id=36871 trace_id=132 func=resolve_ip_tuple line=3925 msg="allocate a new session-000005ba"
id=36871 trace_id=132 func=ip_route_input_slow line=1276 msg="reverse path check fail, drop"


D> vdom traffic configured with "strict-src-check disable" with a second non priority route
In this scenario, 2 routes for 192.168.4.0/24 exist :
- The preferred one has priority 0 (default). This is the one used for routing and points to a different direction than the one the packet ingress from.
- The second one has priority 10 (less preferred), not used for routing because a similar route with lower priority number exists. It points to the interface where our packet comes from. This is the one that neutralizes the RPF filter for the source natted packet.

Configuration :

config router static
    edit 2
        set device "p3v84"
        set dst 192.168.4.0 255.255.255.0
        set gateway 192.168.0.1
    next
    edit 3
        set comment "neutralize RPF for 192.168.4.0/24"
        set device "p3v86"
        set dst 192.168.4.0 255.255.255.0
        set gateway 192.168.2.1
        set priority 10
    next

end


Routing table:

FG3K8A-4 (static) # get router info routing-table all
Codes: K - kernel, C - connected, S - static, R - RIP, B - BGP
       O - OSPF, IA - OSPF inter area
       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
       E1 - OSPF external type 1, E2 - OSPF external type 2
       i - IS-IS, L1 - IS-IS level-1, L2 - IS-IS level-2, ia - IS-IS inter area
       * - candidate default

C       192.168.0.0/24 is directly connected, p3v84
C       192.168.2.0/24 is directly connected, p3v86
C       192.168.3.0/24 is directly connected, p3v87
S       192.168.4.0/24 [10/0] via 192.168.0.1, p3v84
                       [10/0] via 192.168.2.1, p3v86, [10/0]
C       192.168.5.0/24 is directly connected, p3v85


Connection is OK:

FG3K8A-4 (client) # execute telnet 192.168.3.1
FG3K8A-4 login:


Flow shows packets transmitted :

FG3K8A-4 (traffic) # id=36871 trace_id=145 func=resolve_ip_tuple_fast line=3785 msg="vd-client received a packet(proto=6, 192.168.0.1:1117->192.168.3.1:23) from local."
id=36871 trace_id=145 func=resolve_ip_tuple line=3925 msg="allocate a new session-00001d04"
id=36871 trace_id=146 func=resolve_ip_tuple_fast line=3785 msg="vd-traffic received a packet(proto=6, 192.168.0.1:1117->192.168.3.1:23) from p3v84."
id=36871 trace_id=146 func=resolve_ip_tuple line=3925 msg="allocate a new session-00001d05"
id=36871 trace_id=146 func=vf_ip4_route_input line=1591 msg="Match policy routing: to 192.168.5.1 via ifindex-31"
id=36871 trace_id=146 func=vf_ip4_route_input line=1599 msg="find a route: gw-192.168.5.1 via p3v85"
id=36871 trace_id=146 func=fw_forward_handler line=555 msg="Allowed by Policy-1:"
id=36871 trace_id=147 func=resolve_ip_tuple_fast line=3785 msg="vd-snat received a packet(proto=6, 192.168.0.1:1117->192.168.3.1:23) from p1v85."
id=36871 trace_id=147 func=resolve_ip_tuple line=3925 msg="allocate a new session-00001d06"
id=36871 trace_id=147 func=vf_ip4_route_input line=1599 msg="find a route: gw-192.168.2.2 via p1v86"
id=36871 trace_id=147 func=get_new_addr line=1948 msg="find SNAT: IP-192.168.4.2(from IPPOOL), port-0(fixed port)"
id=36871 trace_id=147 func=fw_forward_handler line=555 msg="Allowed by Policy-1: SNAT"
id=36871 trace_id=147 func=__ip_session_run_tuple line=2116 msg="SNAT 192.168.0.1->192.168.4.2:1117"
id=36871 trace_id=148 func=resolve_ip_tuple_fast line=3785 msg="vd-traffic received a packet(proto=6, 192.168.4.2:1117->192.168.3.1:23) from p3v86."
id=36871 trace_id=148 func=resolve_ip_tuple line=3925 msg="allocate a new session-00001d07"
id=36871 trace_id=148 func=vf_ip4_route_input line=1599 msg="find a route: gw-192.168.3.1 via p3v87"
id=36871 trace_id=148 func=fw_forward_handler line=555 msg="Allowed by Policy-2:"
id=36871 trace_id=149 func=resolve_ip_tuple_fast line=3785 msg="vd-server received a packet(proto=6, 192.168.4.2:1117->192.168.3.1:23) from p1v87."


Now, if we enable strict-src-check, RPF drops the packet :

configuration :

FG3K8A-4 (traffic) # config system settings
FG3K8A-4 (settings) # set strict-src-check enable
FG3K8A-4 (settings) # end


Flow showing packet is dropped:

FG3K8A-4 (traffic) #
 id=36871 trace_id=175 func=resolve_ip_tuple_fast line=3785 msg="vd-client received a packet(proto=6, 192.168.0.1:1119->192.168.3.1:23) from local."
id=36871 trace_id=175 func=resolve_ip_tuple line=3925 msg="allocate a new session-00001dd3"
id=36871 trace_id=176 func=resolve_ip_tuple_fast line=3785 msg="vd-traffic received a packet(proto=6, 192.168.0.1:1119->192.168.3.1:23) from p3v84."
id=36871 trace_id=176 func=resolve_ip_tuple line=3925 msg="allocate a new session-00001dd4"
id=36871 trace_id=176 func=vf_ip4_route_input line=1591 msg="Match policy routing: to 192.168.5.1 via ifindex-31"
id=36871 trace_id=176 func=vf_ip4_route_input line=1599 msg="find a route: gw-192.168.5.1 via p3v85"
id=36871 trace_id=176 func=fw_forward_handler line=555 msg="Allowed by Policy-1:"
id=36871 trace_id=177 func=resolve_ip_tuple_fast line=3785 msg="vd-snat received a packet(proto=6, 192.168.0.1:1119->192.168.3.1:23) from p1v85."
id=36871 trace_id=177 func=resolve_ip_tuple line=3925 msg="allocate a new session-00001dd5"
id=36871 trace_id=177 func=vf_ip4_route_input line=1599 msg="find a route: gw-192.168.2.2 via p1v86"
id=36871 trace_id=177 func=get_new_addr line=1948 msg="find SNAT: IP-192.168.4.2(from IPPOOL), port-0(fixed port)"
id=36871 trace_id=177 func=fw_forward_handler line=555 msg="Allowed by Policy-1: SNAT"
id=36871 trace_id=177 func=__ip_session_run_tuple line=2116 msg="SNAT 192.168.0.1->192.168.4.2:1119"
id=36871 trace_id=178 func=resolve_ip_tuple_fast line=3785 msg="vd-traffic received a packet(proto=6, 192.168.4.2:1119->192.168.3.1:23) from p3v86."
id=36871 trace_id=178 func=resolve_ip_tuple line=3925 msg="allocate a new session-00001dd6"
id=36871 trace_id=178 func=ip_route_input_slow line=1287 msg="reverse path check fail(by strict-src-check),drop"


Contributors