@@ -798,6 +798,36 @@ struct ovs_action_push_eth {
struct ovs_key_ethernet addresses;
};
+/*
+ * 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.
*
@@ -842,7 +872,8 @@ struct ovs_action_push_eth {
* packet, or modify the packet (e.g., change the DSCP field).
* @OVS_ACTION_ATTR_CLONE: make a copy of the packet and execute a list of
* actions without affecting the original packet and key.
- *
+ * @OVS_ACTION_ATTR_CHECK_PKT_LEN: Check the length of the packet and
+ * send it to userspace if the condition matches.
* Only a single header can be set with a single %OVS_ACTION_ATTR_SET. Not all
* fields within a header are modifiable, e.g. the IPv4 protocol and fragment
* type may not be changed.
@@ -875,6 +906,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 ID. */
+ OVS_ACTION_ATTR_CHECK_PKT_LEN, /* Nested OVS_CHECK_PKT_LEN_ATTR_*. */
OVS_ACTION_ATTR_CLONE, /* Nested OVS_CLONE_ATTR_*. */
__OVS_ACTION_ATTR_MAX, /* Nothing past this will be accepted
@@ -1208,6 +1208,58 @@ static int execute_recirc(struct datapath *dp, struct sk_buff *skb,
return clone_execute(dp, skb, key, recirc_id, NULL, 0, last, true);
}
+static int execute_check_pkt_len(struct datapath *dp, struct sk_buff *skb,
+ struct sw_flow_key *key,
+ const struct nlattr *attr)
+{
+ struct nlattr *a, *userspace_attr = NULL;
+ u16 actual_pkt_len;
+ u16 pkt_len = 0;
+ u8 userspace_cond = 0;
+ bool is_pkt_len_greater;
+ bool send_to_userspace = false;
+ int err = 0;
+ int rem;
+
+ nla_for_each_nested(a, attr, rem) {
+ switch (nla_type(a)) {
+ case OVS_CHECK_PKT_LEN_ATTR_PKT_LEN:
+ pkt_len = nla_get_u16(a);
+ break;
+ case OVS_CHECK_PKT_LEN_ATTR_USERSPACE_COND:
+ userspace_cond = nla_get_u8(a);
+ break;
+ case OVS_CHECK_PKT_LEN_ATTR_USERPACE:
+ userspace_attr = nla_data(a);
+ break;
+ }
+ }
+
+ actual_pkt_len = skb->len + (skb_vlan_tag_present(skb) ? VLAN_HLEN : 0);
+ is_pkt_len_greater = actual_pkt_len > pkt_len;
+
+ if (userspace_cond == OVS_CHECK_PKT_LEN_COND_GREATER &&
+ is_pkt_len_greater)
+ send_to_userspace = true;
+
+ if (userspace_cond == OVS_CHECK_PKT_LEN_COND_LESSER_EQ &&
+ !is_pkt_len_greater)
+ send_to_userspace = true;
+
+ if (send_to_userspace && userspace_attr) {
+ err = output_userspace(dp, skb, key, userspace_attr,
+ attr, nla_len(attr),
+ OVS_CB(skb)->cutlen);
+ OVS_CB(skb)->cutlen = 0;
+ /* If the packet is sent to userspace, we don't want to
+ * execute further actions.
+ */
+ err = 1;
+ }
+
+ return err;
+}
+
/* Execute a list of actions against 'skb'. */
static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
struct sw_flow_key *key,
@@ -1369,8 +1421,11 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
break;
}
- }
+ case OVS_ACTION_ATTR_CHECK_PKT_LEN:
+ err = execute_check_pkt_len(dp, skb, key, a);
+ break;
+ }
if (unlikely(err)) {
kfree_skb(skb);
return err;
@@ -91,6 +91,7 @@ static bool actions_may_change_flow(const struct nlattr *actions)
case OVS_ACTION_ATTR_SET:
case OVS_ACTION_ATTR_SET_MASKED:
case OVS_ACTION_ATTR_METER:
+ case OVS_ACTION_ATTR_CHECK_PKT_LEN:
default:
return true;
}
@@ -2838,6 +2839,53 @@ static int validate_userspace(const struct nlattr *attr)
return 0;
}
+static int validate_check_pkt_len(const struct nlattr *attr)
+{
+ const struct nlattr *attrs[OVS_CHECK_PKT_LEN_ATTR_MAX + 1];
+ const struct nlattr *a;
+ const struct nlattr *pkt_len, *userspace_cond, *userspace;
+ int rem;
+ u8 cond_value;
+
+ memset(attrs, 0, sizeof(attrs));
+ nla_for_each_nested(a, attr, rem) {
+ int type = nla_type(a);
+
+ if (!type || type > OVS_CHECK_PKT_LEN_ATTR_MAX || attrs[type])
+ return -EINVAL;
+ attrs[type] = a;
+ }
+ if (rem)
+ return -EINVAL;
+
+ pkt_len = attrs[OVS_CHECK_PKT_LEN_ATTR_PKT_LEN];
+ if (!pkt_len || nla_len(pkt_len) != sizeof(u16))
+ return -EINVAL;
+
+ userspace_cond = attrs[OVS_CHECK_PKT_LEN_ATTR_USERSPACE_COND];
+ if (!userspace_cond || nla_len(userspace_cond) != sizeof(u8))
+ return -EINVAL;
+
+ cond_value = nla_get_u8(userspace_cond);
+ if (cond_value != OVS_CHECK_PKT_LEN_COND_GREATER &&
+ cond_value != OVS_CHECK_PKT_LEN_COND_LESSER_EQ)
+ return -EINVAL;
+
+ userspace = attrs[OVS_CHECK_PKT_LEN_ATTR_USERPACE];
+ if (!userspace)
+ return -EINVAL;
+
+ a = nla_data(userspace);
+
+ if (!a || nla_type(a) != OVS_ACTION_ATTR_USERSPACE)
+ return -EINVAL;
+
+ if (validate_userspace(a))
+ return -EINVAL;
+
+ return 0;
+}
+
static int copy_action(const struct nlattr *from,
struct sw_flow_actions **sfa, bool log)
{
@@ -2884,6 +2932,7 @@ static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr,
[OVS_ACTION_ATTR_POP_NSH] = 0,
[OVS_ACTION_ATTR_METER] = sizeof(u32),
[OVS_ACTION_ATTR_CLONE] = (u32)-1,
+ [OVS_ACTION_ATTR_CHECK_PKT_LEN] = (u32)-1,
};
const struct ovs_action_push_vlan *vlan;
int type = nla_type(a);
@@ -3085,6 +3134,12 @@ static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr,
break;
}
+ case OVS_ACTION_ATTR_CHECK_PKT_LEN:
+ err = validate_check_pkt_len(a);
+ if (err)
+ return err;
+ break;
+
default:
OVS_NLERR(log, "Unknown Action type %d", type);
return -EINVAL;