diff mbox series

[ovs-dev,RFC] Add a new OVS action check_pkt_larger

Message ID 20181121181112.6275-1-nusiddiq@redhat.com
State Superseded
Headers show
Series [ovs-dev,RFC] Add a new OVS action check_pkt_larger | expand

Commit Message

Numan Siddique Nov. 21, 2018, 6:11 p.m. UTC
From: Numan Siddique <nusiddiq@redhat.com>

This patch adds a new action 'check_pkt_larger' which checks if the
packet is larger than the given size and stores the result in the
destination register.

Usage: check_pkt_larger:len->REGISTER
Eg. match=...,actions=check_pkt_larger:1442->NXM_NX_REG0[0],next;

This patch makes use of the new datapath action - 'check_pkt_len'
which is still WIP. At the start of ovs-vswitchd, datapath is probed
for this action. If the datapath action is present, then 'check_pkt_larger'
makes use of this datapath action.

Datapath action 'check_pkt_len' takes 3 nlattr's - pkt_len, userspace condition and
nested userspace attr - OVS_ACTION_ATTR_USERSPACE. Datapath when executing this
action sends the packet to the userspace if -

      * userspace condition is 1 (which means greater) and the actual
        length of the packet is greater than the specified pkt_len.

      * userspace condition is 2 (which means lesser or equal) and
        the actual length of the packet is lesser than or equal to
        the specified pkt_len.

If the packet is sent to the userspace, remaining actions after 'check_pkt_len' are
not executed.

Let's say we have these flows added to an OVS bridge br-int

table=0, priority=100 in_port=1,ip,actions=check_pkt_larger:100->NXM_NX_REG0[0],resubmit(,1)
table=1, priority=200,in_port=1,ip,reg0=0x1/0x1 actions=output:3
table=1, priority=100,in_port=1,ip,actions=output:4

Suppose if a packet is received from in_port=1 of length 150, the packet is sent
to userspace (MISS_UPCALL). The action 'check_pkt_larger' will be translated as
  - check_pkt_len( <= 100 ? userspace(pid=<PID>,slow_path(check_pkt_len))
And ovs-vswitchd will add a dp flow as
  - check_pkt_len( <= 100 ? userspace(pid=<PID>,slow_path(check_pkt_len)), output: 3

What it means is that, datapath will check the packet length and if the packet length
is less than 100, it will upcall the packet, else it will continue with the actions. In
this case, the packet will be output to port 3. Any subsequent packets with length
greater than 100 will be handled in datapath itself until the dp flow expires.

If in case, a packet with length lesser or equal to 100 is received, datapath will
upcall the packet. ovs-vswitchd will now replace the old flow with a new dp flow
 - check_pkt_len( > 100 ? userspace(pid=<PID>,slow_path(check_pkt_len)), output: 4

In case, datapath doesn't support 'check_pkt_len' action, the OVS action
'check_pkt_larger' sets SLOW_ACTION so that datapath flow is not added.

This OVS action is intended to be used by OVN to check the packet length
and generate an ICMP packet with type 3, code 4 and next hop mtu
in the logical router pipeline if the MTU of the physical interface
is lesser than the packet length. More information can be found here [1]

TODO:
 - Add test case.
 - Change the action format from check_pkt_larger:len to check_pkt_larger(len)

Concerns: I have concerns regarding the approach I have to taken to replace the
old dp flow with the new dp flow if there is a change in packet length. I am
not sure if it's a good idea. Other approach could be to not replace the dp flow.
ovs-vswitchd can add dp flow like
 - check_pkt_len( > pkt_len ? userspace(pid=<PID>,slow_path(check_pkt_len)), <other actions>
<other actions> will always gets executed if the packet len is <= pkt_len. Datapath will
upcall the packet only if the packet length is greater than pkt_len.

Request to suggest a better name for the action in case 'check_pkt_larger'
seems odd.

[1] - https://mail.openvswitch.org/pipermail/ovs-discuss/2018-July/047039.html

Reported-at:
https://mail.openvswitch.org/pipermail/ovs-discuss/2018-July/047039.html
Suggested-by: Ben Pfaff <blp@ovn.org>
Signed-off-by: Numan Siddique <nusiddiq@redhat.com>
CC: Ben Pfaff <blp@ovn.org>
---
 .../linux/compat/include/linux/openvswitch.h  | 31 ++++++
 include/openvswitch/ofp-actions.h             | 11 +++
 lib/dpif-netdev.c                             |  1 +
 lib/dpif.c                                    |  1 +
 lib/odp-execute.c                             |  4 +
 lib/odp-util.c                                | 40 ++++++++
 lib/odp-util.h                                |  4 +-
 lib/ofp-actions.c                             | 95 ++++++++++++++++++-
 ofproto/ofproto-dpif-ipfix.c                  |  1 +
 ofproto/ofproto-dpif-sflow.c                  |  1 +
 ofproto/ofproto-dpif-upcall.c                 | 48 ++++++----
 ofproto/ofproto-dpif-xlate.c                  | 52 ++++++++++
 ofproto/ofproto-dpif.c                        | 46 +++++++++
 ofproto/ofproto-dpif.h                        |  5 +-
 14 files changed, 320 insertions(+), 20 deletions(-)
diff mbox series

Patch

diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h
index aaeb0341a..5c3702384 100644
--- a/datapath/linux/compat/include/linux/openvswitch.h
+++ b/datapath/linux/compat/include/linux/openvswitch.h
@@ -855,6 +855,36 @@  enum ovs_nat_attr {
 
 #define OVS_NAT_ATTR_MAX (__OVS_NAT_ATTR_MAX - 1)
 
+/*
+ * enum ovs_check_pkt_len_attr - Attributes for %OVS_ACTION_ATTR_CHECK_PKT_LEN.
+ *
+ * @OVS_CHECK_PKT_LEN_ATTR_PKT_LEN: u16 Packet length to check for.
+ * @OVS_CHECK_PKT_LEN_ATTR_USERSPACE_COND: u8 comparison condition to send
+ * the packet to userspace. One of OVS_CHECK_PKT_LEN_COND_*.
+ * @OVS_CHECK_PKT_LEN_ATTR_USERPACE - Nested OVS_USERSPACE_ATTR_* actions.
+ */
+enum ovs_check_pkt_len_attr {
+    OVS_CHECK_PKT_LEN_ATTR_UNSPEC,
+    OVS_CHECK_PKT_LEN_ATTR_PKT_LEN,
+    OVS_CHECK_PKT_LEN_ATTR_USERSPACE_COND,
+    OVS_CHECK_PKT_LEN_ATTR_USERPACE,
+    __OVS_CHECK_PKT_LEN_ATTR_MAX,
+};
+
+/*
+ * Send the packet to userspace if the packet length is greater than the
+ * specified value in OVS_CHECK_PKT_LEN_ATTR_PKT_LEN attr.
+ */
+#define OVS_CHECK_PKT_LEN_COND_GREATER 1
+
+/*
+ * Send the packet to userspace if the packet length is lesser than
+ * or equal to the specified value in OVS_CHECK_PKT_LEN_ATTR_PKT_LEN attr.
+ */
+#define OVS_CHECK_PKT_LEN_COND_LESSER_EQ 2
+
+#define OVS_CHECK_PKT_LEN_ATTR_MAX (__OVS_CHECK_PKT_LEN_ATTR_MAX - 1)
+
 /**
  * enum ovs_action_attr - Action types.
  *
@@ -935,6 +965,7 @@  enum ovs_action_attr {
 	OVS_ACTION_ATTR_PUSH_NSH,     /* Nested OVS_NSH_KEY_ATTR_*. */
 	OVS_ACTION_ATTR_POP_NSH,      /* No argument. */
 	OVS_ACTION_ATTR_METER,         /* u32 meter number. */
+	OVS_ACTION_ATTR_CHECK_PKT_LEN, /* Nested OVS_CHECK_PKT_LEN_ATTR_*. */
 
 #ifndef __KERNEL__
 	OVS_ACTION_ATTR_TUNNEL_PUSH,   /* struct ovs_action_push_tnl*/
diff --git a/include/openvswitch/ofp-actions.h b/include/openvswitch/ofp-actions.h
index e0588afad..2ec045fe5 100644
--- a/include/openvswitch/ofp-actions.h
+++ b/include/openvswitch/ofp-actions.h
@@ -123,6 +123,8 @@  struct vl_mff_map;
     OFPACT(NAT,             ofpact_nat,         ofpact, "nat")          \
     OFPACT(OUTPUT_TRUNC,    ofpact_output_trunc,ofpact, "output_trunc") \
     OFPACT(CLONE,           ofpact_nest,        actions, "clone")       \
+    OFPACT(CHECK_PKT_LARGER, ofpact_check_pkt_larger, ofpact,           \
+           "check_pkt_larger")                                          \
                                                                         \
     /* Debugging actions.                                               \
      *                                                                  \
@@ -572,6 +574,15 @@  struct ofpact_meter {
     uint32_t provider_meter_id;
 };
 
+/* OFPACT_CHECK_PKT_LARGER.
+ *
+ * Used for NXAST_CHECK_PKT_LARGER. */
+struct ofpact_check_pkt_larger {
+    struct ofpact ofpact;
+    uint16_t pkt_len;
+    struct mf_subfield dst;
+};
+
 /* OFPACT_WRITE_ACTIONS, OFPACT_CLONE.
  *
  * Used for OFPIT11_WRITE_ACTIONS, NXAST_CLONE. */
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index c44c417d3..ee874b4ee 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -6813,6 +6813,7 @@  dp_execute_cb(void *aux_, struct dp_packet_batch *packets_,
     case OVS_ACTION_ATTR_PUSH_NSH:
     case OVS_ACTION_ATTR_POP_NSH:
     case OVS_ACTION_ATTR_CT_CLEAR:
+    case OVS_ACTION_ATTR_CHECK_PKT_LEN:
     case __OVS_ACTION_ATTR_MAX:
         OVS_NOT_REACHED();
     }
diff --git a/lib/dpif.c b/lib/dpif.c
index 59aa1dc7c..9c887b6b4 100644
--- a/lib/dpif.c
+++ b/lib/dpif.c
@@ -1274,6 +1274,7 @@  dpif_execute_helper_cb(void *aux_, struct dp_packet_batch *packets_,
     case OVS_ACTION_ATTR_POP_NSH:
     case OVS_ACTION_ATTR_CT_CLEAR:
     case OVS_ACTION_ATTR_UNSPEC:
+    case OVS_ACTION_ATTR_CHECK_PKT_LEN:
     case __OVS_ACTION_ATTR_MAX:
         OVS_NOT_REACHED();
     }
diff --git a/lib/odp-execute.c b/lib/odp-execute.c
index 5831d1fdb..0fb5f9b02 100644
--- a/lib/odp-execute.c
+++ b/lib/odp-execute.c
@@ -673,6 +673,7 @@  requires_datapath_assistance(const struct nlattr *a)
     case OVS_ACTION_ATTR_PUSH_NSH:
     case OVS_ACTION_ATTR_POP_NSH:
     case OVS_ACTION_ATTR_CT_CLEAR:
+    case OVS_ACTION_ATTR_CHECK_PKT_LEN:
         return false;
 
     case OVS_ACTION_ATTR_UNSPEC:
@@ -900,6 +901,9 @@  odp_execute_actions(void *dp, struct dp_packet_batch *batch, bool steal,
             }
             break;
 
+        case OVS_ACTION_ATTR_CHECK_PKT_LEN: {
+            break;
+        }
         case OVS_ACTION_ATTR_OUTPUT:
         case OVS_ACTION_ATTR_TUNNEL_PUSH:
         case OVS_ACTION_ATTR_TUNNEL_POP:
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 626a03b76..041dc76a1 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -124,6 +124,7 @@  odp_action_len(uint16_t type)
     case OVS_ACTION_ATTR_CLONE: return ATTR_LEN_VARIABLE;
     case OVS_ACTION_ATTR_PUSH_NSH: return ATTR_LEN_VARIABLE;
     case OVS_ACTION_ATTR_POP_NSH: return 0;
+    case OVS_ACTION_ATTR_CHECK_PKT_LEN: return ATTR_LEN_VARIABLE;
 
     case OVS_ACTION_ATTR_UNSPEC:
     case __OVS_ACTION_ATTR_MAX:
@@ -1035,6 +1036,42 @@  format_odp_set_nsh(struct ds *ds, const struct nlattr *attr)
     ds_put_cstr(ds, "))");
 }
 
+static void
+format_odp_check_pkt_len_action(struct ds *ds, const struct nlattr *attr,
+                                const struct hmap *portno_names)
+{
+    static const struct nl_policy ovs_cpl_policy[] = {
+            [OVS_CHECK_PKT_LEN_ATTR_PKT_LEN] = { .type = NL_A_U16 },
+            [OVS_CHECK_PKT_LEN_ATTR_USERSPACE_COND] = { .type = NL_A_U8 },
+            [OVS_CHECK_PKT_LEN_ATTR_USERPACE] = { .type = NL_A_NESTED }
+    };
+    struct nlattr *a[ARRAY_SIZE(ovs_cpl_policy)];
+    const struct nlattr *userspace;
+
+    ds_put_cstr(ds, "check_pkt_len");
+    if (!nl_parse_nested(attr, ovs_cpl_policy, a, ARRAY_SIZE(a))) {
+        ds_put_cstr(ds, "(error)");
+        return;
+    }
+
+    uint8_t cond = nl_attr_get_u8(a[OVS_CHECK_PKT_LEN_ATTR_USERSPACE_COND]);
+    if (cond != OVS_CHECK_PKT_LEN_COND_GREATER &&
+        cond != OVS_CHECK_PKT_LEN_COND_LESSER_EQ) {
+        ds_put_cstr(ds, "(error)");
+        return;
+    }
+
+    uint16_t pkt_len = nl_attr_get_u8(a[OVS_CHECK_PKT_LEN_ATTR_PKT_LEN]);
+    if (cond == OVS_CHECK_PKT_LEN_COND_GREATER) {
+        ds_put_format(ds, "(> %u ? ", pkt_len);
+    } else {
+        ds_put_format(ds, "(<= %u ? ", pkt_len);
+    }
+
+    userspace = nl_attr_get(a[OVS_CHECK_PKT_LEN_ATTR_USERPACE]);
+    format_odp_userspace_action(ds, userspace, portno_names);
+    ds_put_cstr(ds, ")");
+}
 
 static void
 format_odp_action(struct ds *ds, const struct nlattr *a,
@@ -1174,6 +1211,9 @@  format_odp_action(struct ds *ds, const struct nlattr *a,
     case OVS_ACTION_ATTR_POP_NSH:
         ds_put_cstr(ds, "pop_nsh()");
         break;
+    case OVS_ACTION_ATTR_CHECK_PKT_LEN:
+        format_odp_check_pkt_len_action(ds, a, portno_names);
+        break;
     case OVS_ACTION_ATTR_UNSPEC:
     case __OVS_ACTION_ATTR_MAX:
     default:
diff --git a/lib/odp-util.h b/lib/odp-util.h
index 6e684f8e6..133f67d95 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -45,7 +45,9 @@  struct pkt_metadata;
     SPR(SLOW_ACTION,     "action",                                      \
         "Uses action(s) not supported by datapath")                     \
     SPR(SLOW_MATCH,      "match",                                       \
-        "Datapath can't match specifically enough")
+        "Datapath can't match specifically enough")                     \
+    SPR(CHECK_PKT_LEN,   "check_pkt_len",                               \
+        "Check packet length condition matched")
 
 /* Indexes for slow-path reasons.  Client code uses "enum slow_path_reason"
  * values instead of these, these are just a way to construct those. */
diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
index a80a4a308..5fd61de8a 100644
--- a/lib/ofp-actions.c
+++ b/lib/ofp-actions.c
@@ -355,6 +355,8 @@  enum ofp_raw_action_type {
     /* NX1.3+(48): void. */
     NXAST_RAW_DEC_NSH_TTL,
 
+    /* NX1.0+(49): struct nx_action_check_pkt_larger, ... */
+    OFPAT_RAW_CHECK_PKT_LARGER,
 /* ## ------------------ ## */
 /* ## Debugging actions. ## */
 /* ## ------------------ ## */
@@ -492,6 +494,7 @@  ofpact_next_flattened(const struct ofpact *ofpact)
     case OFPACT_ENCAP:
     case OFPACT_DECAP:
     case OFPACT_DEC_NSH_TTL:
+    case OFPACT_CHECK_PKT_LARGER:
         return ofpact_next(ofpact);
 
     case OFPACT_CLONE:
@@ -7399,6 +7402,94 @@  check_WRITE_METADATA(const struct ofpact_metadata *a OVS_UNUSED,
     return 0;
 }
 
+/* Check packet length action. */
+
+struct nx_action_check_pkt_larger {
+    ovs_be16 type;              /* OFPAT_VENDOR. */
+    ovs_be16 len;               /* 24. */
+    ovs_be32 vendor;            /* NX_VENDOR_ID. */
+    ovs_be16 subtype;           /* NXAST_OUTPUT_REG. */
+    uint8_t pad[6];
+    ovs_be16 pkt_len;           /* Length of the packet to check. */
+    ovs_be16 ofs_nbits;         /* (ofs << 6) | (n_bits - 1). */
+    ovs_be32 dst;               /* Destination register. */
+};
+
+OFP_ASSERT(sizeof(struct nx_action_check_pkt_larger) == 24);
+
+static enum ofperr
+decode_OFPAT_RAW_CHECK_PKT_LARGER(
+    const struct nx_action_check_pkt_larger *ncpl,
+    enum ofp_version ofp_version OVS_UNUSED, struct ofpbuf *out)
+{
+    struct ofpact_check_pkt_larger *check_pkt_larger =
+        ofpact_put_CHECK_PKT_LARGER(out);
+    check_pkt_larger->pkt_len = ntohs(ncpl->pkt_len);
+    const struct mf_field *mf = mf_from_nxm_header(ntohl(ncpl->dst), NULL);
+    check_pkt_larger->dst.field = mf;
+    check_pkt_larger->dst.ofs = nxm_decode_ofs(ncpl->ofs_nbits);
+    check_pkt_larger->dst.n_bits = nxm_decode_n_bits(ncpl->ofs_nbits);;
+    return 0;
+}
+
+static void
+encode_CHECK_PKT_LARGER(const struct ofpact_check_pkt_larger *check_pkt_larger,
+                 enum ofp_version ofp_version OVS_UNUSED,
+                 struct ofpbuf *out OVS_UNUSED)
+{
+    struct nx_action_check_pkt_larger *ncpl = put_OFPAT_CHECK_PKT_LARGER(out);
+    ncpl->pkt_len = htons(check_pkt_larger->pkt_len);
+    ncpl->ofs_nbits = nxm_encode_ofs_nbits(
+        check_pkt_larger->dst.ofs, check_pkt_larger->dst.n_bits);
+    if (check_pkt_larger->dst.field) {
+        ncpl->dst = htonl(nxm_header_from_mff(check_pkt_larger->dst.field));
+    }
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_CHECK_PKT_LARGER(char *arg, const struct ofpact_parse_params *pp)
+{
+    char *value;
+    char *delim;
+    char *key;
+    char *error = set_field_split_str(arg, &key, &value, &delim);
+    if (error) {
+        return error;
+    }
+    delim[0] = '\0';
+    struct mf_subfield dst;
+    error = mf_parse_subfield(&dst, key);
+    if (error) {
+        return error;
+    }
+
+    struct ofpact_check_pkt_larger *check_pkt_larger =
+        ofpact_put_CHECK_PKT_LARGER(pp->ofpacts);
+    error = str_to_u16(value, NULL, &check_pkt_larger->pkt_len);
+    if (error) {
+        return error;
+    }
+    check_pkt_larger->dst = dst;
+    return NULL;
+}
+
+static void
+format_CHECK_PKT_LARGER(const struct ofpact_check_pkt_larger *a,
+                        const struct ofpact_format_params *fp)
+{
+    ds_put_format(fp->s, "%scheck_pkt_larger:%s%"PRIu32"->",
+                  colors.param, colors.end, a->pkt_len);
+    mf_format_subfield(&a->dst, fp->s);
+}
+
+static enum ofperr
+check_CHECK_PKT_LARGER(const struct ofpact_check_pkt_larger *a OVS_UNUSED,
+                       const struct ofpact_check_params *cp OVS_UNUSED)
+{
+    return 0;
+}
+
+
 /* Goto-Table instruction. */
 
 static void
@@ -7685,6 +7776,7 @@  action_set_classify(const struct ofpact *a)
     case OFPACT_WRITE_METADATA:
     case OFPACT_DEBUG_RECIRC:
     case OFPACT_DEBUG_SLOW:
+    case OFPACT_CHECK_PKT_LARGER:
         return ACTION_SLOT_INVALID;
 
     default:
@@ -7884,6 +7976,7 @@  ovs_instruction_type_from_ofpact_type(enum ofpact_type type)
     case OFPACT_ENCAP:
     case OFPACT_DECAP:
     case OFPACT_DEC_NSH_TTL:
+    case OFPACT_CHECK_PKT_LARGER:
     default:
         return OVSINST_OFPIT11_APPLY_ACTIONS;
     }
@@ -8754,6 +8847,7 @@  ofpact_outputs_to_port(const struct ofpact *ofpact, ofp_port_t port)
     case OFPACT_ENCAP:
     case OFPACT_DECAP:
     case OFPACT_DEC_NSH_TTL:
+    case OFPACT_CHECK_PKT_LARGER:
     default:
         return false;
     }
@@ -8990,7 +9084,6 @@  ofpacts_parse__(char *str, const struct ofpact_parse_params *pp,
         enum ofpact_type type;
         char *error = NULL;
         ofp_port_t port;
-
         if (ofpact_type_from_name(key, &type)) {
             error = ofpact_parse(type, value, pp);
             inst = ovs_instruction_type_from_ofpact_type(type);
diff --git a/ofproto/ofproto-dpif-ipfix.c b/ofproto/ofproto-dpif-ipfix.c
index 402980678..4b44a773a 100644
--- a/ofproto/ofproto-dpif-ipfix.c
+++ b/ofproto/ofproto-dpif-ipfix.c
@@ -3014,6 +3014,7 @@  dpif_ipfix_read_actions(const struct flow *flow,
         case OVS_ACTION_ATTR_POP_ETH:
         case OVS_ACTION_ATTR_PUSH_NSH:
         case OVS_ACTION_ATTR_POP_NSH:
+        case OVS_ACTION_ATTR_CHECK_PKT_LEN:
         case OVS_ACTION_ATTR_UNSPEC:
         case __OVS_ACTION_ATTR_MAX:
         default:
diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c
index 62a09b5d1..236755387 100644
--- a/ofproto/ofproto-dpif-sflow.c
+++ b/ofproto/ofproto-dpif-sflow.c
@@ -1222,6 +1222,7 @@  dpif_sflow_read_actions(const struct flow *flow,
         case OVS_ACTION_ATTR_PUSH_NSH:
         case OVS_ACTION_ATTR_POP_NSH:
         case OVS_ACTION_ATTR_UNSPEC:
+        case OVS_ACTION_ATTR_CHECK_PKT_LEN:
         case __OVS_ACTION_ATTR_MAX:
         default:
             break;
diff --git a/ofproto/ofproto-dpif-upcall.c b/ofproto/ofproto-dpif-upcall.c
index e36cfa0ee..5d052d1d3 100644
--- a/ofproto/ofproto-dpif-upcall.c
+++ b/ofproto/ofproto-dpif-upcall.c
@@ -193,7 +193,8 @@  enum upcall_type {
     SFLOW_UPCALL,               /* sFlow sample. */
     FLOW_SAMPLE_UPCALL,         /* Per-flow sampling. */
     IPFIX_UPCALL,               /* Per-bridge sampling. */
-    CONTROLLER_UPCALL           /* Destined for the controller. */
+    CONTROLLER_UPCALL,           /* Destined for the controller. */
+    CHECK_PKT_LEN_UPCALL        /* check packet length condition upcall. */
 };
 
 enum reval_result {
@@ -368,9 +369,11 @@  static int ukey_create_from_dpif_flow(const struct udpif *,
                                       struct udpif_key **);
 static void ukey_get_actions(struct udpif_key *, const struct nlattr **actions,
                              size_t *size);
-static bool ukey_install__(struct udpif *, struct udpif_key *ukey)
+static bool ukey_install__(struct udpif *, struct udpif_key *ukey,
+                           bool force_replace_old_ukey)
     OVS_TRY_LOCK(true, ukey->mutex);
-static bool ukey_install(struct udpif *udpif, struct udpif_key *ukey);
+static bool ukey_install(struct udpif *udpif, struct udpif_key *ukey,
+                         bool force_replace_old_ukey);
 static void transition_ukey_at(struct udpif_key *ukey, enum ukey_state dst,
                                const char *where)
     OVS_REQUIRES(ukey->mutex);
@@ -1045,6 +1048,9 @@  classify_upcall(enum dpif_upcall_type type, const struct nlattr *userdata,
     if (cookie->type == USER_ACTION_COOKIE_SFLOW) {
         return SFLOW_UPCALL;
     } else if (cookie->type == USER_ACTION_COOKIE_SLOW_PATH) {
+        if (cookie->slow_path.reason == CHECK_PKT_LEN) {
+            return CHECK_PKT_LEN_UPCALL;
+        }
         return SLOW_PATH_UPCALL;
     } else if (cookie->type == USER_ACTION_COOKIE_FLOW_SAMPLE) {
         return FLOW_SAMPLE_UPCALL;
@@ -1117,7 +1123,8 @@  upcall_receive(struct upcall *upcall, const struct dpif_backer *backer,
     upcall->type = classify_upcall(type, userdata, &upcall->cookie);
     if (upcall->type == BAD_UPCALL) {
         return EAGAIN;
-    } else if (upcall->type == MISS_UPCALL) {
+    } else if (upcall->type == MISS_UPCALL ||
+               upcall->type == CHECK_PKT_LEN_UPCALL) {
         error = xlate_lookup(backer, flow, &upcall->ofproto, &upcall->ipfix,
                              &upcall->sflow, NULL, &upcall->ofp_in_port);
         if (error) {
@@ -1247,7 +1254,7 @@  upcall_xlate(struct udpif *udpif, struct upcall *upcall,
     /* This function is also called for slow-pathed flows.  As we are only
      * going to create new datapath flows for actual datapath misses, there is
      * no point in creating a ukey otherwise. */
-    if (upcall->type == MISS_UPCALL) {
+    if (upcall->type == MISS_UPCALL || upcall->type == CHECK_PKT_LEN_UPCALL) {
         upcall->ukey = ukey_create_from_upcall(upcall, wc);
     }
 }
@@ -1272,7 +1279,8 @@  upcall_uninit(struct upcall *upcall)
     }
 }
 
-/* If there are less flows than the limit, and this is a miss upcall which
+/* If there are less flows than the limit, and this is a miss upcall or
+ * check pkt len upcall which
  *
  *      - Has no recirc_id, OR
  *      - Has a recirc_id and we can get a reference on the recirc ctx,
@@ -1283,7 +1291,7 @@  should_install_flow(struct udpif *udpif, struct upcall *upcall)
 {
     unsigned int flow_limit;
 
-    if (upcall->type != MISS_UPCALL) {
+    if (upcall->type != MISS_UPCALL && upcall->type != CHECK_PKT_LEN_UPCALL) {
         return false;
     } else if (upcall->recirc && !upcall->have_recirc_ref) {
         VLOG_DBG_RL(&rl, "upcall: no reference for recirc flow");
@@ -1338,7 +1346,7 @@  upcall_cb(const struct dp_packet *packet, const struct flow *flow, ovs_u128 *ufi
         goto out;
     }
 
-    if (upcall.ukey && !ukey_install(udpif, upcall.ukey)) {
+    if (upcall.ukey && !ukey_install(udpif, upcall.ukey, false)) {
         static struct vlog_rate_limit rll = VLOG_RATE_LIMIT_INIT(1, 1);
         VLOG_WARN_RL(&rll, "upcall_cb failure: ukey installation fails");
         error = ENOSPC;
@@ -1399,6 +1407,7 @@  dpif_read_actions(struct udpif *udpif, struct upcall *upcall,
     case MISS_UPCALL:
     case SLOW_PATH_UPCALL:
     case CONTROLLER_UPCALL:
+    case CHECK_PKT_LEN_UPCALL:
     default:
         break;
     }
@@ -1417,6 +1426,7 @@  process_upcall(struct udpif *udpif, struct upcall *upcall,
     switch (upcall->type) {
     case MISS_UPCALL:
     case SLOW_PATH_UPCALL:
+    case CHECK_PKT_LEN_UPCALL:
         upcall_xlate(udpif, upcall, odp_actions, wc);
         return 0;
 
@@ -1583,8 +1593,9 @@  handle_upcalls(struct udpif *udpif, struct upcall *upcalls,
 
         if (should_install_flow(udpif, upcall)) {
             struct udpif_key *ukey = upcall->ukey;
-
-            if (ukey_install(udpif, ukey)) {
+            bool force_replace_old_ukey =
+                upcall->type == CHECK_PKT_LEN_UPCALL ? true : false;
+            if (ukey_install(udpif, ukey, force_replace_old_ukey)) {
                 upcall->ukey_persists = true;
                 put_op_init(&ops[n_ops++], ukey, DPIF_FP_CREATE);
             }
@@ -1813,14 +1824,14 @@  ukey_create_from_dpif_flow(const struct udpif *udpif,
 
 static bool
 try_ukey_replace(struct umap *umap, struct udpif_key *old_ukey,
-                 struct udpif_key *new_ukey)
+                 struct udpif_key *new_ukey, bool force_replace_old_ukey)
     OVS_REQUIRES(umap->mutex)
     OVS_TRY_LOCK(true, new_ukey->mutex)
 {
     bool replaced = false;
 
     if (!ovs_mutex_trylock(&old_ukey->mutex)) {
-        if (old_ukey->state == UKEY_EVICTED) {
+        if (old_ukey->state == UKEY_EVICTED || force_replace_old_ukey) {
             /* The flow was deleted during the current revalidator dump,
              * but its ukey won't be fully cleaned up until the sweep phase.
              * In the mean time, we are receiving upcalls for this traffic.
@@ -1849,7 +1860,8 @@  try_ukey_replace(struct umap *umap, struct udpif_key *old_ukey,
  * On success, returns true, installs the ukey and returns it in a locked
  * state. Otherwise, returns false. */
 static bool
-ukey_install__(struct udpif *udpif, struct udpif_key *new_ukey)
+ukey_install__(struct udpif *udpif, struct udpif_key *new_ukey,
+               bool force_replace_old_ukey)
     OVS_TRY_LOCK(true, new_ukey->mutex)
 {
     struct umap *umap;
@@ -1865,7 +1877,8 @@  ukey_install__(struct udpif *udpif, struct udpif_key *new_ukey)
         /* Uncommon case: A ukey is already installed with the same UFID. */
         if (old_ukey->key_len == new_ukey->key_len
             && !memcmp(old_ukey->key, new_ukey->key, new_ukey->key_len)) {
-            locked = try_ukey_replace(umap, old_ukey, new_ukey);
+            locked = try_ukey_replace(umap, old_ukey, new_ukey,
+                                      force_replace_old_ukey);
         } else {
             struct ds ds = DS_EMPTY_INITIALIZER;
 
@@ -1940,11 +1953,12 @@  transition_ukey_at(struct udpif_key *ukey, enum ukey_state dst,
 }
 
 static bool
-ukey_install(struct udpif *udpif, struct udpif_key *ukey)
+ukey_install(struct udpif *udpif, struct udpif_key *ukey,
+             bool force_replace_old_ukey)
 {
     bool installed;
 
-    installed = ukey_install__(udpif, ukey);
+    installed = ukey_install__(udpif, ukey, force_replace_old_ukey);
     if (installed) {
         ovs_mutex_unlock(&ukey->mutex);
     }
@@ -1985,7 +1999,7 @@  ukey_acquire(struct udpif *udpif, const struct dpif_flow *flow,
         if (retval) {
             goto done;
         }
-        install = ukey_install__(udpif, ukey);
+        install = ukey_install__(udpif, ukey, false);
         if (install) {
             retval = 0;
         } else {
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 839fddd99..f9b733d4d 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -5565,6 +5565,7 @@  reversible_actions(const struct ofpact *ofpacts, size_t ofpacts_len)
         case OFPACT_UNROLL_XLATE:
         case OFPACT_WRITE_ACTIONS:
         case OFPACT_WRITE_METADATA:
+        case OFPACT_CHECK_PKT_LARGER:
             break;
 
         case OFPACT_CT:
@@ -5873,6 +5874,7 @@  freeze_unroll_actions(const struct ofpact *a, const struct ofpact *end,
         case OFPACT_CT:
         case OFPACT_CT_CLEAR:
         case OFPACT_NAT:
+        case OFPACT_CHECK_PKT_LARGER:
             /* These may not generate PACKET INs. */
             break;
 
@@ -6065,6 +6067,51 @@  compose_ct_clear_action(struct xlate_ctx *ctx)
     }
 }
 
+static void
+xlate_check_pkt_larger(struct xlate_ctx *ctx,
+                       struct ofpact_check_pkt_larger *check_pkt_larger)
+{
+    if (!ctx->xin->packet || !check_pkt_larger->dst.field) {
+        return;
+    }
+
+    union mf_subvalue value;
+    memset(&value, 0, sizeof value);
+    value.u8_val =
+        dp_packet_size(ctx->xin->packet) > check_pkt_larger->pkt_len;
+    mf_write_subfield_flow(&check_pkt_larger->dst, &value, &ctx->xin->flow);
+    if (ctx->xbridge->support.check_pkt_len) {
+        size_t offset = nl_msg_start_nested(ctx->odp_actions,
+                                            OVS_ACTION_ATTR_CHECK_PKT_LEN);
+        nl_msg_put_u16(ctx->odp_actions, OVS_CHECK_PKT_LEN_ATTR_PKT_LEN,
+                       check_pkt_larger->pkt_len);
+        uint8_t condition = OVS_CHECK_PKT_LEN_COND_GREATER;
+        if (dp_packet_size(ctx->xin->packet) > check_pkt_larger->pkt_len) {
+            condition = OVS_CHECK_PKT_LEN_COND_LESSER_EQ;
+        }
+        nl_msg_put_u8(ctx->odp_actions, OVS_CHECK_PKT_LEN_ATTR_USERSPACE_COND,
+                       condition);
+
+        struct user_action_cookie cookie = {
+            .type = USER_ACTION_COOKIE_SLOW_PATH,
+            .ofp_in_port = ctx->xin->flow.in_port.ofp_port,
+            .ofproto_uuid = ctx->xbridge->ofproto->uuid,
+            .slow_path.reason = CHECK_PKT_LEN,
+        };
+        odp_port_t odp_port = ofp_port_to_odp_port(
+            ctx->xbridge, ctx->xin->flow.in_port.ofp_port);
+        uint32_t pid = dpif_port_get_pid(ctx->xbridge->dpif, odp_port);
+        size_t userspace_offset = nl_msg_start_nested(
+            ctx->odp_actions, OVS_CHECK_PKT_LEN_ATTR_USERPACE);
+        odp_put_userspace_action(pid, &cookie, sizeof cookie, ODPP_NONE,
+                                 false, ctx->odp_actions);
+        nl_msg_end_nested(ctx->odp_actions, userspace_offset);
+        nl_msg_end_nested(ctx->odp_actions, offset);
+    } else {
+        ctx->xout->slow |= SLOW_ACTION;
+    }
+}
+
 static void
 rewrite_flow_encap_ethernet(struct xlate_ctx *ctx,
                             struct flow *flow,
@@ -6387,6 +6434,7 @@  recirc_for_mpls(const struct ofpact *a, struct xlate_ctx *ctx)
     case OFPACT_WRITE_ACTIONS:
     case OFPACT_WRITE_METADATA:
     case OFPACT_GOTO_TABLE:
+    case OFPACT_CHECK_PKT_LARGER:
     default:
         break;
     }
@@ -6842,6 +6890,10 @@  do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
         case OFPACT_DEBUG_SLOW:
             ctx->xout->slow |= SLOW_ACTION;
             break;
+
+        case OFPACT_CHECK_PKT_LARGER:
+            xlate_check_pkt_larger(ctx, ofpact_get_CHECK_PKT_LARGER(a));
+            break;
         }
 
         /* Check if need to store this and the remaining actions for later
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index b7acfa246..edfe2c061 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -1290,6 +1290,51 @@  check_ct_clear(struct dpif_backer *backer)
     return supported;
 }
 
+
+/* Tests whether 'backer''s datapath supports the
+ * OVS_ACTION_ATTR_CHECK_PKT_LEN action. */
+static bool
+check_check_pkt_len(struct dpif_backer *backer)
+{
+    struct odputil_keybuf keybuf;
+    struct ofpbuf actions;
+    struct ofpbuf key;
+    struct flow flow;
+    bool supported;
+
+    struct odp_flow_key_parms odp_parms = {
+        .flow = &flow,
+        .probe = true,
+    };
+
+    memset(&flow, 0, sizeof flow);
+    ofpbuf_use_stack(&key, &keybuf, sizeof keybuf);
+    odp_flow_key_from_flow(&odp_parms, &key);
+    ofpbuf_init(&actions, 64);
+    size_t cpl_start;
+    cpl_start = nl_msg_start_nested(&actions, OVS_ACTION_ATTR_CHECK_PKT_LEN);
+    nl_msg_put_u16(&actions, OVS_CHECK_PKT_LEN_ATTR_PKT_LEN, 0);
+    nl_msg_put_u8(&actions, OVS_CHECK_PKT_LEN_ATTR_USERSPACE_COND, 1);
+    size_t cpl_userspace_offset = nl_msg_start_nested(
+        &actions, OVS_CHECK_PKT_LEN_ATTR_USERPACE);
+    struct user_action_cookie cookie = {
+        .type = USER_ACTION_COOKIE_SLOW_PATH,
+        .ofp_in_port = 0,
+    };
+    odp_put_userspace_action(1, &cookie, sizeof cookie, ODPP_NONE,
+                             false, &actions);
+    nl_msg_end_nested(&actions, cpl_userspace_offset);
+    nl_msg_end_nested(&actions, cpl_start);
+
+    supported = dpif_probe_feature(backer->dpif, "check_pkt_len", &key,
+                                   &actions, NULL);
+    ofpbuf_uninit(&actions);
+    VLOG_INFO("%s: Datapath %s check_pkt_len action",
+              dpif_name(backer->dpif), (supported) ? "supports"
+                                                   : "does not support");
+    return supported;
+}
+
 /* Probe the highest dp_hash algorithm supported by the datapath. */
 static size_t
 check_max_dp_hash_alg(struct dpif_backer *backer)
@@ -1397,6 +1442,7 @@  check_support(struct dpif_backer *backer)
     backer->rt_support.ct_eventmask = check_ct_eventmask(backer);
     backer->rt_support.ct_clear = check_ct_clear(backer);
     backer->rt_support.max_hash_alg = check_max_dp_hash_alg(backer);
+    backer->rt_support.check_pkt_len = check_check_pkt_len(backer);
 
     /* Flow fields. */
     backer->rt_support.odp.ct_state = check_ct_state(backer);
diff --git a/ofproto/ofproto-dpif.h b/ofproto/ofproto-dpif.h
index 1a404c82f..cd5321eb9 100644
--- a/ofproto/ofproto-dpif.h
+++ b/ofproto/ofproto-dpif.h
@@ -192,7 +192,10 @@  struct group_dpif *group_dpif_lookup(struct ofproto_dpif *,
     DPIF_SUPPORT_FIELD(bool, ct_clear, "Conntrack clear")                   \
                                                                             \
     /* Highest supported dp_hash algorithm. */                              \
-    DPIF_SUPPORT_FIELD(size_t, max_hash_alg, "Max dp_hash algorithm")
+    DPIF_SUPPORT_FIELD(size_t, max_hash_alg, "Max dp_hash algorithm")       \
+                                                                            \
+    /* True if the datapath supports OVS_ACTION_ATTR_CHECK_PKT_LEN. */   \
+    DPIF_SUPPORT_FIELD(bool, check_pkt_len, "Check pkt length action")
 
 /* Stores the various features which the corresponding backer supports. */
 struct dpif_backer_support {