Windows tracert is fairly straight forward and uses pure ICMP with incrementing TTL values. Linux traceroute with the -I switch works the same way.
The firewall is required to allow the following:
Outbound
- Echo Request
Inbound
- Echo Reply
- Time-Exceeded (needed for TTL=0 responses)
Cisco and Linux traceroute by default uses incrementing UDP ports (from 33434) and incrementing TTL values.
The firewall is required to allow the following:
Outbound
- UDP ports 33434 - 33464
Inbound
- Time-Exceeded (needed for TTL=0 responses)
- Destination Unreachable (needed for the final hop port-not-found response)
Putting it all together we get a rule set that looks something like this:
object-group icmp-type ICMP-returns
description Legit ICMP responses
icmp-object echo-reply
icmp-object time-exceeded
icmp-object unreachable
object-group service Cisco_Traceroute_udp udp
port-object range 33434 33464
access-list outside_access_in extended permit icmp any object-group External_nets object-group ICMP-returns log disable
access-list inside_access_in remark Permit outbound pings
access-list inside_access_in extended permit icmp object-group Internal_nets any echo log disable
access-list inside_access_in remark Permit traceroute from Cisco devices
access-list inside_access_in extended permit udp object-group Internal_nets any object-group Cisco_Traceroute_udp log disable
Obviously, this assumes sensible values for Internal_nets (e.g. 192.168.0.0/16) and External_nets (i.e. public IP ranges assigned to your external interface)
As an addendum, the Firewall is not (strictly speaking) a router, and therefore in many cases will not decrement the TTL. I have found this unnecessary in most cases, but if needed, can be enabled as follows:
policy-map global_policy
class class-default
set connection decrement-ttl