diff mbox series

[ovs-dev,RFC,1/2] datapath: Add a new action check_pkt_len

Message ID 20181121180752.4849-1-nusiddiq@redhat.com
State Superseded
Headers show
Series [ovs-dev,RFC,1/2] datapath: Add a new action check_pkt_len | expand

Commit Message

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

[Please note, this patch is submitted as RFC in ovs-dev ML to
get feedback before submitting to netdev ML]

This patch adds a new action which checks the packet length and
sends it to the userspace based on the condition.

This action takes 3 nlattr's - pkt_len, userspace condition and
nested userspace attr - OVS_ACTION_ATTR_USERSPACE. It 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.

The main use case for adding this action is to solve the packet
drops because of MTU mismatch in OVN virtual networking solution.
When a VM (which belongs to a logical switch of OVN) sends a packet
destined to go via the gateway router and if the nic which provides
external connectivity, has a lesser MTU, OVS drops the packet
if the packet length is greater than this MTU.

With the help of this action, OVN will check the packet length
and if it is greater than the MTU size, it will generate an
ICMP packet (type 3, code 4) and includes the next hop mtu in it
so that the sender can fragment the packets.

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: Justin Pettit <jpettit@ovn.org>
CC: Pravin B Shelar <pshelar@ovn.org>
CC: Flavio Leitner <fbl@redhat.com>
CC: Yifeng Sun <pkusunyifeng@gmail.com>
---
 include/uapi/linux/openvswitch.h | 34 ++++++++++++++++++-
 net/openvswitch/actions.c        | 57 +++++++++++++++++++++++++++++++-
 net/openvswitch/flow_netlink.c   | 55 ++++++++++++++++++++++++++++++
 3 files changed, 144 insertions(+), 2 deletions(-)
diff mbox series

Patch

diff --git a/include/uapi/linux/openvswitch.h b/include/uapi/linux/openvswitch.h
index dbe0cbe4f1b7..770e55767108 100644
--- a/include/uapi/linux/openvswitch.h
+++ b/include/uapi/linux/openvswitch.h
@@ -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
diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c
index 85ae53d8fd09..a96e1fa6153c 100644
--- a/net/openvswitch/actions.c
+++ b/net/openvswitch/actions.c
@@ -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;
diff --git a/net/openvswitch/flow_netlink.c b/net/openvswitch/flow_netlink.c
index 865ecef68196..a44609cc0551 100644
--- a/net/openvswitch/flow_netlink.c
+++ b/net/openvswitch/flow_netlink.c
@@ -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;