On Wed, Aug 26, 2026 at 4:44 AM Ales Musil via dev <[email protected]>
wrote:

> When an external router sends an ICMP "Redirect to Host"
> (IPv4 type 5) or ICMPv6 Redirect (type 137) to an OVN
> Logical Router Port IP, the packet is not matched by any
> specific handler in lr_in_ip_input.  On gateway and
> distributed-gateway routers this causes the packet to fall
> through to conntrack stages where ct() fails, spamming OVS
> logs.  On plain routers the priority-80 ICMP-unreachable
> catch-all handles them, but that is guarded by
> !is_gw_router so gateway routers are left unprotected.
>
> Per RFC 1812 (IPv4) and RFC 4861 section 8 (IPv6), a router
> must not act on ICMP Redirect messages addressed to it.
> Linux enforces this by defaulting accept_redirects to off
> when forwarding is enabled.
>
> Add priority-95 flows in lr_in_ip_input that silently drop
> ICMP Redirect packets (IPv4 type 5 and ICMPv6 type 137)
> destined to any router-owned IP, before the conntrack
> stages.  This gives uniform behavior across all router
> types and eliminates the ct failure log spam.
>
> Reported-at: https://issues.redhat.com/browse/FDP-1936
> Assisted-by: Claude Opus 4.6, OpenCode
> Signed-off-by: Ales Musil <[email protected]>
> ---
>  Documentation/ref/ovn-logical-flows.7.rst |  9 +++++++++
>  northd/northd.c                           | 24 +++++++++++++++++++++++
>  tests/ovn-northd.at                       | 22 +++++++++++++++++++++
>  3 files changed, 55 insertions(+)
>
> diff --git a/Documentation/ref/ovn-logical-flows.7.rst
> b/Documentation/ref/ovn-logical-flows.7.rst
> index 1a9168ac8..5c8894ae0 100644
> --- a/Documentation/ref/ovn-logical-flows.7.rst
> +++ b/Documentation/ref/ovn-logical-flows.7.rst
> @@ -2377,6 +2377,15 @@ contains the following flows to implement very
> basic IP host functionality.
>        flags.loopback = 1;
>        next;
>
> +- ICMP Redirect drop.  A router must not act on ICMP Redirect messages
> +  addressed to it (RFC 1812 for IPv4; RFC 4861, section 8 for IPv6).  For
> +  each IP address *A* owned by a router port, a priority-95 flow silently
> +  drops ICMP Redirect packets destined to *A*.  For IPv4, the flow matches
> +  ``ip4.dst == A && icmp4 && icmp4.type == 5``.  For IPv6, the flow
> matches
> +  ``ip6.dst == A && icmp6 && icmp6.type == 137``.  These flows are
> installed
> +  before the conntrack stages, so Redirect packets are neither run through
> +  ``ct()`` (which would fail and log errors) nor forwarded.
> +
>  - Reply to ARP requests.
>
>    These flows reply to ARP requests for the router's own IP address. The
> ARP
> diff --git a/northd/northd.c b/northd/northd.c
> index 88e3ece88..b5f5c46ad 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -17507,6 +17507,16 @@ build_ipv6_input_flows_for_lrouter_port(
>          ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
>                        ds_cstr(match), lrp_actions, lflow_ref,
>                        WITH_HINT(&op->nbrp->header_));
> +
> +        /* ICMPv6 Redirect (type 137) addressed to the router's own IP.
> +         * Same rationale as IPv4 (RFC 4861 s8). */
> +        ds_clear(match);
> +        ds_put_cstr(match, "ip6.dst == ");
> +        op_put_v6_networks(match, op);
> +        ds_put_cstr(match, " && icmp6 && icmp6.type == 137");
>

is `icmp6 && icmp6.type == 137` redundent? I know we do similar checks
elsewhere but the icmpv6 echo flows just above do not include the check for
icmp6 I would think that the two should match. Outside the scope of this
change should we decide as a project if one way is more readable than the
other?


> +        ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 95,
> +                      ds_cstr(match), debug_drop_action(), lflow_ref,
> +                      WITH_HINT(&op->nbrp->header_));
>      }
>
>      /* ND reply.  These flows reply to ND solicitations for the
> @@ -17710,6 +17720,20 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
>                        "next; ";
>          ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
> ds_cstr(match),
>                        icmp_actions, lflow_ref,
> WITH_HINT(&op->nbrp->header_));
> +
> +        /* ICMP Redirect (type 5) addressed to the router's own IP.
> +         * A router must not act on Redirects sent to it (RFC 1812;
> +         * Linux accept_redirects defaults off when forwarding).  Drop
> +         * here, before conntrack, so they are neither run through ct
> +         * in the unsnat/dnat stages (which fails and spams the log)
> +         * nor forwarded. */
> +        ds_clear(match);
> +        ds_put_cstr(match, "ip4.dst == ");
> +        op_put_v4_networks(match, op, false);
> +        ds_put_cstr(match, " && icmp4 && icmp4.type == 5");
>

same observation as icmp6


> +        ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 95,
> +                      ds_cstr(match), debug_drop_action(), lflow_ref,
> +                      WITH_HINT(&op->nbrp->header_));
>      }
>
>      /* BFD msg handling */
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index 6d191c1a0..a97a317cc 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -1899,6 +1899,8 @@ AT_CHECK([grep "lr_in_ip_input" sbflows | grep
> 'ip.\.dst == {' | grep drop | ovn
>    table=??(lr_in_ip_input     ), priority=60   , match=(ip4.dst ==
> {192.168.0.1}), action=(drop;)
>    table=??(lr_in_ip_input     ), priority=60   , match=(ip6.dst ==
> {2000::1, fe80::200:ff:fe00:10}), action=(drop;)
>    table=??(lr_in_ip_input     ), priority=60   , match=(ip6.dst ==
> {fe80::200:ff:fe00:20}), action=(drop;)
> +  table=??(lr_in_ip_input     ), priority=95   , match=(ip4.dst ==
> {10.10.0.1, 192.168.1.1} && icmp4 && icmp4.type == 5), action=(drop;)
> +  table=??(lr_in_ip_input     ), priority=95   , match=(ip6.dst ==
> {2000::1, fe80::200:ff:fe00:10} && icmp6 && icmp6.type == 137),
> action=(drop;)
>  ])
>
>  # create SNAT with external IP equal to LRP's IP
> @@ -1912,6 +1914,8 @@ AT_CHECK([grep "lr_in_ip_input" sbflows | grep
> 'ip.\.dst == {' | grep drop | ovn
>    table=??(lr_in_ip_input     ), priority=60   , match=(ip4.dst ==
> {10.10.0.1, 192.168.1.1}), action=(drop;)
>    table=??(lr_in_ip_input     ), priority=60   , match=(ip6.dst ==
> {2000::1, fe80::200:ff:fe00:10}), action=(drop;)
>    table=??(lr_in_ip_input     ), priority=60   , match=(ip6.dst ==
> {fe80::200:ff:fe00:20}), action=(drop;)
> +  table=??(lr_in_ip_input     ), priority=95   , match=(ip4.dst ==
> {10.10.0.1, 192.168.1.1} && icmp4 && icmp4.type == 5), action=(drop;)
> +  table=??(lr_in_ip_input     ), priority=95   , match=(ip6.dst ==
> {2000::1, fe80::200:ff:fe00:10} && icmp6 && icmp6.type == 137),
> action=(drop;)
>  ])
>
>  check ovn-nbctl lr-nat-del lr0
> @@ -1929,6 +1933,8 @@ AT_CHECK([grep "lr_in_ip_input" sbflows | grep
> 'ip.\.dst == {' | grep drop | ovn
>    table=??(lr_in_ip_input     ), priority=60   , match=(ip4.dst ==
> {192.168.0.1}), action=(drop;)
>    table=??(lr_in_ip_input     ), priority=60   , match=(ip6.dst ==
> {fe80::200:ff:fe00:10}), action=(drop;)
>    table=??(lr_in_ip_input     ), priority=60   , match=(ip6.dst ==
> {fe80::200:ff:fe00:20}), action=(drop;)
> +  table=??(lr_in_ip_input     ), priority=95   , match=(ip4.dst ==
> {10.10.0.1, 192.168.1.1} && icmp4 && icmp4.type == 5), action=(drop;)
> +  table=??(lr_in_ip_input     ), priority=95   , match=(ip6.dst ==
> {2000::1, fe80::200:ff:fe00:10} && icmp6 && icmp6.type == 137),
> action=(drop;)
>  ])
>
>  check ovn-nbctl lr-nat-del lr0
> @@ -1946,6 +1952,8 @@ AT_CHECK([grep "lr_in_ip_input" sbflows | grep
> 'ip.\.dst == {' | grep drop | ovn
>    table=??(lr_in_ip_input     ), priority=60   , match=(ip4.dst ==
> {192.168.0.1}), action=(drop;)
>    table=??(lr_in_ip_input     ), priority=60   , match=(ip6.dst ==
> {2000::1, fe80::200:ff:fe00:10}), action=(drop;)
>    table=??(lr_in_ip_input     ), priority=60   , match=(ip6.dst ==
> {fe80::200:ff:fe00:20}), action=(drop;)
> +  table=??(lr_in_ip_input     ), priority=95   , match=(ip4.dst ==
> {10.10.0.1, 192.168.1.1} && icmp4 && icmp4.type == 5), action=(drop;)
> +  table=??(lr_in_ip_input     ), priority=95   , match=(ip6.dst ==
> {2000::1, fe80::200:ff:fe00:10} && icmp6 && icmp6.type == 137),
> action=(drop;)
>  ])
>
>  check ovn-nbctl lb-del lb1
> @@ -1963,6 +1971,8 @@ AT_CHECK([grep "lr_in_ip_input" sbflows | grep
> 'ip.\.dst == {' | grep drop | ovn
>    table=??(lr_in_ip_input     ), priority=60   , match=(ip4.dst ==
> {192.168.0.1}), action=(drop;)
>    table=??(lr_in_ip_input     ), priority=60   , match=(ip6.dst ==
> {fe80::200:ff:fe00:10}), action=(drop;)
>    table=??(lr_in_ip_input     ), priority=60   , match=(ip6.dst ==
> {fe80::200:ff:fe00:20}), action=(drop;)
> +  table=??(lr_in_ip_input     ), priority=95   , match=(ip4.dst ==
> {10.10.0.1, 192.168.1.1} && icmp4 && icmp4.type == 5), action=(drop;)
> +  table=??(lr_in_ip_input     ), priority=95   , match=(ip6.dst ==
> {2000::1, fe80::200:ff:fe00:10} && icmp6 && icmp6.type == 137),
> action=(drop;)
>  ])
>
>  OVN_CLEANUP_NORTHD
> @@ -14839,6 +14849,12 @@ AT_CHECK([grep "lr_in_ip_input" lr0flows |
> ovn_strip_lflows], [0], [dnl
>    table=??(lr_in_ip_input     ), priority=92   , match=(inport ==
> "lr0-public" && arp.op == 1 && arp.tpa == 172.168.0.100 &&
> is_chassis_resident("cr-lr0-public")), action=(eth.dst = eth.src; eth.src =
> xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha =
> xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1;
> output;)
>    table=??(lr_in_ip_input     ), priority=92   , match=(inport ==
> "lr0-public" && arp.op == 1 && arp.tpa == 172.168.0.110 &&
> is_chassis_resident("sw0-port1")), action=(eth.dst = eth.src; eth.src =
> 30:54:00:00:00:03; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha =
> 30:54:00:00:00:03; arp.tpa <-> arp.spa; outport = inport; flags.loopback =
> 1; output;)
>    table=??(lr_in_ip_input     ), priority=92   , match=(inport ==
> "lr0-public" && arp.op == 1 && arp.tpa == 172.168.0.120 &&
> is_chassis_resident("cr-lr0-public")), action=(eth.dst = eth.src; eth.src =
> xreg0[[0..47]]; arp.op = 2; /* ARP reply */ arp.tha = arp.sha; arp.sha =
> xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1;
> output;)
> +  table=??(lr_in_ip_input     ), priority=95   , match=(ip4.dst ==
> 10.0.0.1 && icmp4 && icmp4.type == 5), action=(drop;)
> +  table=??(lr_in_ip_input     ), priority=95   , match=(ip4.dst ==
> 172.168.0.10 && icmp4 && icmp4.type == 5), action=(drop;)
> +  table=??(lr_in_ip_input     ), priority=95   , match=(ip4.dst ==
> 20.0.0.1 && icmp4 && icmp4.type == 5), action=(drop;)
> +  table=??(lr_in_ip_input     ), priority=95   , match=(ip6.dst ==
> fe80::200:ff:fe00:ff01 && icmp6 && icmp6.type == 137), action=(drop;)
> +  table=??(lr_in_ip_input     ), priority=95   , match=(ip6.dst ==
> fe80::200:ff:fe00:ff02 && icmp6 && icmp6.type == 137), action=(drop;)
> +  table=??(lr_in_ip_input     ), priority=95   , match=(ip6.dst ==
> fe80::200:ff:fe00:ff03 && icmp6 && icmp6.type == 137), action=(drop;)
>  ])
>
>  AT_CHECK([grep "lr_in_unsnat" lr0flows | ovn_strip_lflows], [0], [dnl
> @@ -15021,6 +15037,12 @@ AT_CHECK([grep "lr_in_ip_input" lr0flows |
> ovn_strip_lflows], [0], [dnl
>    table=??(lr_in_ip_input     ), priority=90   , match=(ip6.dst ==
> fe80::200:ff:fe00:ff01 && icmp6.type == 128 && icmp6.code == 0),
> action=(ip6.dst <-> ip6.src; ip.ttl = 255; icmp6.type = 129; flags.loopback
> = 1; next; )
>    table=??(lr_in_ip_input     ), priority=90   , match=(ip6.dst ==
> fe80::200:ff:fe00:ff02 && icmp6.type == 128 && icmp6.code == 0),
> action=(ip6.dst <-> ip6.src; ip.ttl = 255; icmp6.type = 129; flags.loopback
> = 1; next; )
>    table=??(lr_in_ip_input     ), priority=90   , match=(ip6.dst ==
> fe80::200:ff:fe00:ff03 && icmp6.type == 128 && icmp6.code == 0),
> action=(ip6.dst <-> ip6.src; ip.ttl = 255; icmp6.type = 129; flags.loopback
> = 1; next; )
> +  table=??(lr_in_ip_input     ), priority=95   , match=(ip4.dst ==
> 10.0.0.1 && icmp4 && icmp4.type == 5), action=(drop;)
> +  table=??(lr_in_ip_input     ), priority=95   , match=(ip4.dst ==
> 172.168.0.10 && icmp4 && icmp4.type == 5), action=(drop;)
> +  table=??(lr_in_ip_input     ), priority=95   , match=(ip4.dst ==
> 20.0.0.1 && icmp4 && icmp4.type == 5), action=(drop;)
> +  table=??(lr_in_ip_input     ), priority=95   , match=(ip6.dst ==
> fe80::200:ff:fe00:ff01 && icmp6 && icmp6.type == 137), action=(drop;)
> +  table=??(lr_in_ip_input     ), priority=95   , match=(ip6.dst ==
> fe80::200:ff:fe00:ff02 && icmp6 && icmp6.type == 137), action=(drop;)
> +  table=??(lr_in_ip_input     ), priority=95   , match=(ip6.dst ==
> fe80::200:ff:fe00:ff03 && icmp6 && icmp6.type == 137), action=(drop;)
>  ])
>
>  AT_CHECK([grep "lr_in_unsnat" lr0flows | ovn_strip_lflows], [0], [dnl
> --
> 2.55.0
>
> _______________________________________________
> dev mailing list
> [email protected]
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
>

Since this is a change that a user can see, should it also include an entry
in NEWS? something to the effect of: removes log spam from ct failures on
gateway routers receiving ICMP Redirect packets?

Jacob
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to