extract_l4() validates the ICMP header only for IPPROTO_ICMP over IPv4
and the ICMPv6 header only for IPPROTO_ICMPV6 over IPv6.  Any other
combination, such as protocol 58 in an IPv4 header, is accepted as a
protocol without L4 keys and with no length check.  The handler table
is indexed by the protocol number alone, though, so such a packet gets
the ICMP or ICMPv6 handler, whose valid_new() reads the type from an
L4 header that may not be in the packet.

Add l4_proto_get(), which picks a handler from both the address family
and the protocol in the same way extract_l4() does, and use it for every
handler lookup.  Other combinations now use the generic handler, which
does not look at the L4 header.

Fixes: a27d70a89848 ("conntrack: add generic IP protocol support")
Assisted-by: claude-opus-5-5, Claude Code
Signed-off-by: Alejandro Pacheco <[email protected]>
---
 lib/conntrack.c       | 25 ++++++++++++++++++++-----
 tests/ofproto-dpif.at | 41 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 61 insertions(+), 5 deletions(-)

diff --git a/lib/conntrack.c b/lib/conntrack.c
index d40870c63..a81072f33 100644
--- a/lib/conntrack.c
+++ b/lib/conntrack.c
@@ -157,6 +157,21 @@ expectation_clean(struct conntrack *ct, const
struct conn_key *parent_key);

 static struct ct_l4_proto *l4_protos[UINT8_MAX + 1];

+/* Returns the l4 protocol handler for 'key'.  ICMP and ICMPv6 handlers are
+ * used only with their own address family, since extract_l4() validates the
+ * ICMP headers only in that case. */
+static struct ct_l4_proto *
+l4_proto_get(const struct conn_key *key)
+{
+    if ((key->nw_proto == IPPROTO_ICMP
+         && key->dl_type != htons(ETH_TYPE_IP))
+        || (key->nw_proto == IPPROTO_ICMPV6
+            && key->dl_type != htons(ETH_TYPE_IPV6))) {
+        return &ct_proto_other;
+    }
+    return l4_protos[key->nw_proto];
+}
+
 static void
 handle_ftp_ctl(struct conntrack *ct, const struct conn_lookup_ctx *ctx,
                struct dp_packet *pkt, struct conn *ec, long long now,
@@ -2667,9 +2682,9 @@ conn_update(struct conntrack *ct, struct conn
*conn, struct dp_packet *pkt,
             struct conn_lookup_ctx *ctx, long long now)
 {
     ovs_mutex_lock(&conn->lock);
-    uint8_t nw_proto = conn->key_node[CT_DIR_FWD].key.nw_proto;
     enum ct_update_res update_res =
-        l4_protos[nw_proto]->conn_update(ct, conn, pkt, ctx->reply, now);
+        l4_proto_get(&conn->key_node[CT_DIR_FWD].key)->conn_update(
+            ct, conn, pkt, ctx->reply, now);
     ovs_mutex_unlock(&conn->lock);
     return update_res;
 }
@@ -2703,14 +2718,14 @@ conn_expired(const struct conn *conn, long long now)
 static bool
 valid_new(struct dp_packet *pkt, struct conn_key *key)
 {
-    return l4_protos[key->nw_proto]->valid_new(pkt);
+    return l4_proto_get(key)->valid_new(pkt);
 }

 static struct conn *
 new_conn(struct conntrack *ct, struct dp_packet *pkt, struct conn_key *key,
          long long now, uint32_t tp_id)
 {
-    return l4_protos[key->nw_proto]->new_conn(ct, pkt, now, tp_id);
+    return l4_proto_get(key)->new_conn(ct, pkt, now, tp_id);
 }

 static void
@@ -2837,7 +2852,7 @@ conn_to_ct_dpif_entry(const struct conn *conn,
struct ct_dpif_entry *entry,

     long long expiration = conn_expiration(conn) - now;

-    struct ct_l4_proto *class = l4_protos[key->nw_proto];
+    struct ct_l4_proto *class = l4_proto_get(key);
     if (class->conn_get_protoinfo) {
         class->conn_get_protoinfo(conn, &entry->protoinfo);
     }
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index 8598ffa39..fb2bdcc56 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -12674,6 +12674,47 @@ NXST_FLOW reply:
 OVS_VSWITCHD_STOP
 AT_CLEANUP

+AT_SETUP([ofproto-dpif - conntrack - icmp protocol of the other
address family])
+OVS_VSWITCHD_START
+
+add_of_ports br0 1 2
+
+AT_DATA([flows.txt], [dnl
+table=0,priority=10,ip,ct_state=-trk,action=ct(table=1)
+table=0,priority=10,ipv6,ct_state=-trk,action=ct(table=1)
+table=1,priority=10,ct_state=+trk+inv,action=drop
+table=1,priority=10,ct_state=+trk+new,action=output:2
+])
+
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+
+dnl IPv4 with protocol ICMPv6 and IPv6 with next header ICMP, both
+dnl without L4 payload, are tracked like any other protocol.
+m4_define([IP_ICMP6_PKT], m4_join([],
+dnl eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a,type=0x0800)
+[50540000000a5054000000090800],
+dnl ipv4(src=10.0.0.1,dst=10.0.0.2,proto=58,tot_len=20)
+[4500001400000000403a66ae0a0000010a000002]))
+
+m4_define([IP6_ICMP_PKT], m4_join([],
+dnl eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a,type=0x86dd)
+[50540000000a50540000000986dd],
+dnl ipv6(src=2001:db8::1,dst=2001:db8::2,nh=1,plen=0)
+[600000000000014020010db800000000000000000000000120010db8000000000000000000000002]))
+
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 IP_ICMP6_PKT])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 IP6_ICMP_PKT])
+
+AT_CHECK([ovs-appctl revalidator/purge])
+AT_CHECK([ovs-ofctl dump-flows br0 table=1 | ofctl_strip | sort], [0], [dnl
+ table=1, n_packets=2, n_bytes=88, priority=10,ct_state=+new+trk
actions=output:2
+ table=1, priority=10,ct_state=+inv+trk actions=drop
+NXST_FLOW reply:
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
 AT_SETUP([ofproto-dpif - conntrack - zones])
 OVS_VSWITCHD_START

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

Reply via email to