Skip to contentSkip to conversation

ovpn: preserve GSO and GRO batching

RFC Superseded Ralf Lici 1 participant 10 messages Sep 16

Subsystems (5)

v3 of 5 Superseded Latest is v5 →

lore
Share & tools
lore

Ralf Lici

author reviewer
#0 lore raw

v3 cover ovpn: preserve GSO and GRO batching

Hi,

This series adds support for preserving GSO and GRO batching across ovpn, including forwarded TCP traffic. The transmit patches let GSO skbs reach ovpn, complete each inner packet's checksum before encryption, and, when the layout permits, put the encrypted records into a UDP GSO skb. Ordinary packets and frag-list GSO children retain the existing in-place path.

On receive, ovpn uses UDP GRO to collect compatible encrypted records in a frag-list and then decrypts them individually. The series also includes an AEAD helper refactor and small transmit and receive prefetch changes. The patch messages contain the measurements.

The final patch adds an opt-in direct GRO mode for discussion. Unlike the default full-stack mode, it consumes DATA_V2 records in the GRO callback and bypasses parts of the outer receive stack, including TC, netfilter, socket XFRM policy and normal UDP accounting. Its advantage over the prefetched full-stack path is small in my latest 100-Gbit/s tests.

Direct GRO also depends on a generic GRO ownership change in the preceding patch. That change, the skb_gro_receive_list export and UDP tunnel GRO callback accounting touch broader networking code. These prerequisites would need discussion on netdev before any merge (this RFC is first for feedback on the ovpn design).

Thanks,

Ralf Lici
Mandelbit Srl

---
Changes since v2 https://lore.kernel.org/openvpn-devel/[email protected]/
- Unclone incoming GRO aggregates before segmenting them in 5/9.
(Sashiko)
- Fix endianness bug serializing 1-byte netlink attr from 4-byte enum in
9/9. (Sashiko)

Changes since v1 https://lore.kernel.org/openvpn-devel/[email protected]/
- Fix a couple of wrong assumptions in 5/9 ("ovpn: coalesce UDP data
records with GRO"). (Sashiko)

Ralf Lici (9):
ovpn: advertise checksum offload for GSO packets
ovpn: accept frag-list GSO input
ovpn: refactor AEAD encryption helpers
ovpn: convert GSO input into UDP GSO output
ovpn: coalesce UDP data records with GRO
ovpn: prefetch encrypted records before GRO batch decryption
ovpn: prefetch GSO segments before in-place encryption
net: gro: honor skbs consumed by protocol callbacks
ovpn: add opt-in direct GRO receive mode

 Documentation/netlink/specs/rt-link.yaml      |  12 +
 drivers/net/ovpn/crypto_aead.c                | 182 +++++++---
 drivers/net/ovpn/crypto_aead.h                |   4 +
 drivers/net/ovpn/io.c                         | 281 +++++++++++++--
 drivers/net/ovpn/io.h                         |  17 +-
 drivers/net/ovpn/main.c                       |  20 +-
 drivers/net/ovpn/ovpnpriv.h                   |   2 +
 drivers/net/ovpn/proto.h                      |   4 +
 drivers/net/ovpn/skb.h                        |  27 +-
 drivers/net/ovpn/stats.h                      |  16 +-
 drivers/net/ovpn/tcp.c                        |   4 +-
 drivers/net/ovpn/udp.c                        | 337 ++++++++++++++++--
 include/net/gro.h                             |  19 +-
 include/uapi/linux/if_link.h                  |   6 +
 net/core/gro.c                                |   1 +
 net/ipv4/udp_offload.c                        |   3 +-
 tools/testing/selftests/net/ovpn/Makefile     |   1 +
 tools/testing/selftests/net/ovpn/common.sh    |   4 +-
 tools/testing/selftests/net/ovpn/ovpn-cli.c   |  40 ++-
 .../selftests/net/ovpn/test-gro-direct.sh     |  10 +
 tools/testing/selftests/net/ovpn/test.sh      |  65 ++++
 21 files changed, 924 insertions(+), 131 deletions(-)
 create mode 100755 tools/testing/selftests/net/ovpn/test-gro-direct.sh

base-commit: b8e9e7d82e7eefd5d2d528469d94ec20e96b38c3

Signature · 8 lines
2.55.0



_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel
author reviewer
#1 lore raw

RFC v3 1/9 ovpn: advertise checksum offload for GSO packets

ovpn already advertises software GSO support and segments GSO skbs in its transmit path. However, without checksum offload in the device features, the networking core has to segment GSO packets before they reach ovpn because TCP GSO packets normally carry CHECKSUM_PARTIAL state.

Advertise NETIF_F_HW_CSUM so the stack can pass such packets to ovpn. Complete partial checksums after any GSO segmentation and before submitting packets for encryption, since the inner packet checksum can no longer be fixed after the packet has been encrypted.

Also pass the ovpn feature set to skb_gso_segment with GSO capabilities masked out: this forces software segmentation, but still lets the segmenter preserve supported non-GSO properties such as non-linear skb data instead of needlessly linearizing.

---
No changes since v2 https://lore.kernel.org/openvpn-devel/1bbfde37488ade61928554db3a119525adf7b608.1789540779.git.ralf@mandelbit.com/

No changes since v1 https://lore.kernel.org/openvpn-devel/1bbfde37488ade61928554db3a119525adf7b608.1789485693.git.ralf@mandelbit.com/

 drivers/net/ovpn/io.c   | 20 ++++++++++++++++++--
 drivers/net/ovpn/main.c |  2 +-
 2 files changed, 19 insertions(+), 3 deletions(-)
drivers/net/ovpn/io.c +18 -2
index 9526f8096da6..112067ded401 100644
@@ -358,6 +358,7 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
 	struct ovpn_priv *ovpn = netdev_priv(dev);
 	struct sk_buff *segments, *curr, *next;
 	struct sk_buff_head skb_list;
+	netdev_features_t features;
 	unsigned int tx_bytes = 0;
 	struct ovpn_peer *peer;
 	__be16 proto;
@@ -392,8 +393,13 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
 	skb_dst_drop(skb);
 
 	if (skb_is_gso(skb)) {
-		segments = skb_gso_segment(skb, 0);
-		if (IS_ERR(segments)) {
+		/* force software segmentation, but keep ovpn's non-GSO feature
+		 * bits so the generated segments can preserve non-linear skb
+		 * data where possible
+		 */
+		features = netif_skb_features(skb);
+		segments = skb_gso_segment(skb, features & ~NETIF_F_GSO_MASK);
+		if (IS_ERR_OR_NULL(segments)) {
 			ret = PTR_ERR(segments);
 			net_err_ratelimited("%s: cannot segment payload packet: %d\n",
 					    netdev_name(dev), ret);
@@ -418,6 +424,16 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
 			continue;
 		}
 
+		/* NETIF_F_HW_CSUM requires completing partial checksums */
+		if (unlikely(curr->ip_summed == CHECKSUM_PARTIAL &&
+			     skb_checksum_help(curr) < 0)) {
+			net_err_ratelimited("%s: skb_checksum_help failed for payload packet\n",
+					    netdev_name(dev));
+			ovpn_dev_dstats_tx_dropped(ovpn->dev);
+			kfree_skb(curr);
+			continue;
+		}
+
 		/* only count what we actually send */
 		tx_bytes += curr->len;
 		__skb_queue_tail(&skb_list, curr);
drivers/net/ovpn/main.c +1 -1
index 0708249e9607..28e1eb06e127 100644
@@ -157,7 +157,7 @@ static const struct ethtool_ops ovpn_ethtool_ops = {
 
 static void ovpn_setup(struct net_device *dev)
 {
-	netdev_features_t feat = NETIF_F_SG | NETIF_F_GSO |
+	netdev_features_t feat = NETIF_F_HW_CSUM | NETIF_F_SG | NETIF_F_GSO |
 				 NETIF_F_GSO_SOFTWARE | NETIF_F_HIGHDMA;
 
 	dev->needs_free_netdev = true;
Signature · 8 lines
2.55.0



_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel
author reviewer
#2 lore raw

RFC v3 2/9 ovpn: accept frag-list GSO input

In reply to Ralf Lici

Forwarded TCP traffic can be coalesced into SKB_GSO_FRAGLIST when the receiving host has no local TCP socket for the flow. Although ovpn segments every GSO input itself, it does not advertise NETIF_F_FRAGLIST, so generic transmit validation segments these aggregates before calling ovpn_net_xmit. That segmentation is functionally correct, but causes ovpn_net_xmit to be invoked separately for every resulting packet.

Advertise frag-list storage so ovpn receives the aggregate intact and performs protocol validation and destination-to-peer lookup once before segmenting it. Frag-list GSO segmentation recovers the complete child skbs, which can then be encrypted in place and transmitted independently. Rebuilding those children into a replacement UDP GSO aggregate was found to add cost rather than improve throughput.

The feature also admits non-GSO frag lists, which describe one packet split across several skbs. Let skb_cow_data preserve small lists directly. If a list exceeds the AEAD scatterlist limit, linearize it and continue rather than rejecting an otherwise valid packet.

---
No changes since v2 https://lore.kernel.org/openvpn-devel/5baa1d94e29e7b109ffd412b4f1d5d113a319389.1789540779.git.ralf@mandelbit.com/

No changes since v1 https://lore.kernel.org/openvpn-devel/5baa1d94e29e7b109ffd412b4f1d5d113a319389.1789485693.git.ralf@mandelbit.com/

 drivers/net/ovpn/crypto_aead.c | 8 ++++++--
 drivers/net/ovpn/main.c        | 3 ++-
 2 files changed, 8 insertions(+), 3 deletions(-)
drivers/net/ovpn/crypto_aead.c +6 -2
index 74eaf6fac2f5..2af493fd5735 100644
@@ -168,8 +168,12 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
 	if (unlikely(nfrags < 0))
 		return nfrags;
 
-	if (unlikely(nfrags + 2 > (MAX_SKB_FRAGS + 2)))
-		return -ENOSPC;
+	if (unlikely(nfrags > MAX_SKB_FRAGS)) {
+		ret = skb_linearize(skb);
+		if (unlikely(ret))
+			return ret;
+		nfrags = 1;
+	}
 
 	/* allocate temporary memory for iv, sg and req */
 	tmp = kmalloc(ovpn_aead_crypto_tmp_size(ks->encrypt, nfrags),
drivers/net/ovpn/main.c +2 -1
index 28e1eb06e127..ac4e0d85e215 100644
@@ -158,7 +158,8 @@ static const struct ethtool_ops ovpn_ethtool_ops = {
 static void ovpn_setup(struct net_device *dev)
 {
 	netdev_features_t feat = NETIF_F_HW_CSUM | NETIF_F_SG | NETIF_F_GSO |
-				 NETIF_F_GSO_SOFTWARE | NETIF_F_HIGHDMA;
+				 NETIF_F_GSO_SOFTWARE | NETIF_F_FRAGLIST |
+				 NETIF_F_HIGHDMA;
 
 	dev->needs_free_netdev = true;
 
Signature · 8 lines
2.55.0



_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel
author reviewer
#3 lore raw

RFC v3 3/9 ovpn: refactor AEAD encryption helpers

In reply to Ralf Lici

Move DATA_V2 framing sizes to the protocol header and factor request allocation and header construction into helpers. This prepares the transmit path for an out-of-place encryption destination without changing packet handling.

---
No changes since v2 https://lore.kernel.org/openvpn-devel/f075b9782b14d64756c84e132c138482f08287b9.1789540779.git.ralf@mandelbit.com/

No changes since v1 https://lore.kernel.org/openvpn-devel/f075b9782b14d64756c84e132c138482f08287b9.1789485693.git.ralf@mandelbit.com/

 drivers/net/ovpn/crypto_aead.c | 97 ++++++++++++++++++++--------------
 drivers/net/ovpn/io.h          |  3 +-
 drivers/net/ovpn/proto.h       |  4 ++
 3 files changed, 63 insertions(+), 41 deletions(-)
drivers/net/ovpn/crypto_aead.c +58 -39
index 2af493fd5735..30299581422d 100644
@@ -24,9 +24,6 @@
 #include "proto.h"
 #include "skb.h"
 
-#define OVPN_AUTH_TAG_SIZE	16
-#define OVPN_AAD_SIZE		(OVPN_OPCODE_SIZE + OVPN_NONCE_WIRE_SIZE)
-
 #define ALG_NAME_AES		"gcm(aes)"
 #define ALG_NAME_CHACHAPOLY	"rfc7539(chacha20,poly1305)"
 
@@ -42,7 +39,7 @@ static int ovpn_aead_encap_overhead(const struct ovpn_crypto_key_slot *ks)
  *			       an AEAD request structure with extra space for SG
  *			       and IV.
  * @tfm: the AEAD cipher handle
- * @nfrags: the number of fragments in the skb
+ * @nents: the number of scatterlist entries
  *
  * This function calculates the size of a contiguous memory block that includes
  * the initialization vector (IV), the AEAD request, and an array of scatterlist
@@ -54,7 +51,7 @@ static int ovpn_aead_encap_overhead(const struct ovpn_crypto_key_slot *ks)
  * Return: the size of the temporary memory that needs to be allocated
  */
 static unsigned int ovpn_aead_crypto_tmp_size(struct crypto_aead *tfm,
-					      const unsigned int nfrags)
+					      const unsigned int nents)
 {
 	unsigned int len = OVPN_NONCE_SIZE;
 
@@ -70,8 +67,8 @@ static unsigned int ovpn_aead_crypto_tmp_size(struct crypto_aead *tfm,
 	/* round up to the next multiple of the scatterlist alignment */
 	len = ALIGN(len, __alignof__(struct scatterlist));
 
-	/* add enough space for nfrags + 2 scatterlist entries */
-	len += array_size(sizeof(struct scatterlist), nfrags + 2);
+	/* add enough space for the scatterlist entries */
+	len += array_size(sizeof(struct scatterlist), nents);
 	return len;
 }
 
@@ -135,6 +132,53 @@ static struct scatterlist *ovpn_aead_crypto_req_sg(struct crypto_aead *aead,
 			     __alignof__(struct scatterlist));
 }
 
+static struct aead_request *ovpn_aead_request_alloc(struct crypto_aead *aead,
+						    struct sk_buff *skb,
+						    unsigned int nents, u8 **iv)
+{
+	struct aead_request *req;
+	void *tmp;
+
+	/* allocate IV, request and scatterlist entries in one block */
+	tmp = kmalloc(ovpn_aead_crypto_tmp_size(aead, nents), GFP_ATOMIC);
+	if (unlikely(!tmp))
+		return ERR_PTR(-ENOMEM);
+
+	ovpn_skb_cb(skb)->crypto_tmp = tmp;
+	*iv = ovpn_aead_crypto_tmp_iv(aead, tmp);
+	req = ovpn_aead_crypto_tmp_req(aead, *iv);
+
+	return req;
+}
+
+static int ovpn_aead_encrypt_header(struct ovpn_peer *peer,
+				    struct ovpn_crypto_key_slot *ks,
+				    u8 *iv, u8 *data)
+{
+	u32 pktid, op;
+	int ret;
+
+	/* obtain packet ID, which is used both as a first
+	 * 4 bytes of nonce and last 4 bytes of associated data.
+	 */
+	ret = ovpn_pktid_xmit_next(&ks->pid_xmit, &pktid);
+	if (unlikely(ret < 0))
+		return ret;
+
+	/* concat 4 bytes packet id and 8 bytes nonce tail into 12 bytes
+	 * nonce
+	 */
+	ovpn_pktid_aead_write(pktid, ks->nonce_tail_xmit, iv);
+
+	/* add the packet opcode and wire nonce as associated data */
+	op = ovpn_opcode_compose(OVPN_DATA_V2, ks->key_id, peer->tx_id);
+	BUILD_BUG_ON(sizeof(op) != OVPN_OPCODE_SIZE);
+	*(__force __be32 *)data = htonl(op);
+	memcpy(data + OVPN_OPCODE_SIZE, iv, OVPN_NONCE_WIRE_SIZE);
+
+	return 0;
+}
+
 int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
 		      struct sk_buff *skb)
 {
@@ -143,8 +187,6 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
 	struct sk_buff *trailer;
 	struct scatterlist *sg;
 	int nfrags, ret;
-	u32 pktid, op;
-	void *tmp;
 	u8 *iv;
 
 	ovpn_skb_cb(skb)->peer = peer;
@@ -175,16 +217,9 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
 		nfrags = 1;
 	}
 
-	/* allocate temporary memory for iv, sg and req */
-	tmp = kmalloc(ovpn_aead_crypto_tmp_size(ks->encrypt, nfrags),
-		      GFP_ATOMIC);
-	if (unlikely(!tmp))
-		return -ENOMEM;
-
-	ovpn_skb_cb(skb)->crypto_tmp = tmp;
-
-	iv = ovpn_aead_crypto_tmp_iv(ks->encrypt, tmp);
-	req = ovpn_aead_crypto_tmp_req(ks->encrypt, iv);
+	req = ovpn_aead_request_alloc(ks->encrypt, skb, nfrags + 2, &iv);
+	if (IS_ERR(req))
+		return PTR_ERR(req);
 	sg = ovpn_aead_crypto_req_sg(ks->encrypt, req);
 
 	/* sg table:
@@ -206,28 +241,12 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
 	__skb_push(skb, tag_size);
 	sg_set_buf(sg + ret + 1, skb->data, tag_size);
 
-	/* obtain packet ID, which is used both as a first
-	 * 4 bytes of nonce and last 4 bytes of associated data.
-	 */
-	ret = ovpn_pktid_xmit_next(&ks->pid_xmit, &pktid);
+	/* make space for the additional data and push it to the front */
+	__skb_push(skb, OVPN_AAD_SIZE);
+	ret = ovpn_aead_encrypt_header(peer, ks, iv, skb->data);
 	if (unlikely(ret < 0))
 		return ret;
 
-	/* concat 4 bytes packet id and 8 bytes nonce tail into 12 bytes
-	 * nonce
-	 */
-	ovpn_pktid_aead_write(pktid, ks->nonce_tail_xmit, iv);
-
-	/* make space for packet id and push it to the front */
-	__skb_push(skb, OVPN_NONCE_WIRE_SIZE);
-	memcpy(skb->data, iv, OVPN_NONCE_WIRE_SIZE);
-
-	/* add packet op as head of additional data */
-	op = ovpn_opcode_compose(OVPN_DATA_V2, ks->key_id, peer->tx_id);
-	__skb_push(skb, OVPN_OPCODE_SIZE);
-	BUILD_BUG_ON(sizeof(op) != OVPN_OPCODE_SIZE);
-	*((__force __be32 *)skb->data) = htonl(op);
-
 	/* AEAD Additional data */
 	sg_set_buf(sg, skb->data, OVPN_AAD_SIZE);
 
@@ -281,7 +300,7 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
 		return -ENOSPC;
 
 	/* allocate temporary memory for iv, sg and req */
-	tmp = kmalloc(ovpn_aead_crypto_tmp_size(ks->decrypt, nfrags),
+	tmp = kmalloc(ovpn_aead_crypto_tmp_size(ks->decrypt, nfrags + 2),
 		      GFP_ATOMIC);
 	if (unlikely(!tmp))
 		return -ENOMEM;
drivers/net/ovpn/io.h +1 -2
index db9e10f9077c..1a94f0fda1d1 100644
@@ -11,8 +11,7 @@
 #define _NET_OVPN_OVPN_H_
 
 /* DATA_V2 header size with AEAD encryption */
-#define OVPN_HEAD_ROOM (OVPN_OPCODE_SIZE + OVPN_NONCE_WIRE_SIZE +	   \
-			16 /* AEAD TAG length */ +			   \
+#define OVPN_HEAD_ROOM (OVPN_DATA_V2_OVERHEAD +				   \
 			max(sizeof(struct udphdr), sizeof(struct tcphdr)) +\
 			max(sizeof(struct ipv6hdr), sizeof(struct iphdr)))
 
drivers/net/ovpn/proto.h +4 -0
index b7d285b4d9c1..f3b305cbefe5 100644
@@ -39,6 +39,10 @@
 #define OVPN_NONCE_WIRE_SIZE (OVPN_NONCE_SIZE - OVPN_NONCE_TAIL_SIZE)
 
 #define OVPN_OPCODE_SIZE		4 /* DATA_V2 opcode size */
+#define OVPN_AUTH_TAG_SIZE		16
+#define OVPN_AAD_SIZE			(OVPN_OPCODE_SIZE + \
+					 OVPN_NONCE_WIRE_SIZE)
+#define OVPN_DATA_V2_OVERHEAD		(OVPN_AAD_SIZE + OVPN_AUTH_TAG_SIZE)
 #define OVPN_OPCODE_KEYID_MASK		0x07000000
 #define OVPN_OPCODE_PKTTYPE_MASK	0xF8000000
 #define OVPN_OPCODE_PEERID_MASK		0x00FFFFFF
Signature · 8 lines
2.55.0



_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel
author reviewer
#4 lore raw

RFC v3 4/9 ovpn: convert GSO input into UDP GSO output

In reply to Ralf Lici

Whenever a GSO skb arrives at ovpn's ndo_start_xmit, segment the inner skb into linear packets while completing their checksums, then emit eligible fixed-size inputs as UDP GSO skb(s).

Allocate one final page-backed aggregate per batch before submitting encryption and have each AEAD request write out of place directly into its record slot. Attempting in-place encryption would be complex (the OpenVPN wire layout adds a header and authentication tag to every segment) and not necessarily more performant: encrypting into individual skbs would still require assembling or copying those records into the UDP GSO skb. The destination allocation and lifetime are instead amortized over the whole batch.

Keep the existing in-place path for ordinary packets, where allocating and retiring a separate output skb for every record would provide no aggregate construction benefit. Also retain that path for frag-list GSO
input: it already stores complete segments as child skbs, and measurements show that copying those children into another aggregate is counterproductive.

Transmit the aggregate as SKB_GSO_UDP_L4 only after every record succeeds, and discard it if any request fails. Split aggregates at the legacy GSO size limit and fall back to individual records when batching is unavailable or the segment geometry is unsuitable.

Preserve the input priority, flow hash and sender CPU on the replacement aggregate. If the input has real write ownership, charge the aggregate to the same socket as well. This retains socket lifetime and write-memory accounting and lets lower-device queue selection use the socket's cached TX queue instead of choosing a new queue after crypto completion.

On two directly connected 100-Gbit/s mlx5 ports, five interleaved iperf3 -t 60 -O 10 single-flow AES-128-GCM runs in each direction produced the following throughput:

Forward Reverse

Before this change          11.522 Gbit/s     9.902 Gbit/s
Software UDP segmentation   12.879 Gbit/s    12.516 Gbit/s
Hardware UDP segmentation   18.308 Gbit/s    19.233 Gbit/s

The equal-weight mean of the two directional results increased from 10.712 to 18.770 Gbit/s with hardware UDP segmentation, a 75.2% improvement. With segmentation performed in software, it increased to 12.697 Gbit/s, an 18.5% improvement.

---
No changes since v2 https://lore.kernel.org/openvpn-devel/9359f737627fc84be08ee7d301415c023ebadeb2.1789540779.git.ralf@mandelbit.com/

No changes since v1 https://lore.kernel.org/openvpn-devel/9359f737627fc84be08ee7d301415c023ebadeb2.1789485693.git.ralf@mandelbit.com/

 drivers/net/ovpn/crypto_aead.c |  85 ++++++++++-
 drivers/net/ovpn/crypto_aead.h |   4 +
 drivers/net/ovpn/io.c          | 258 ++++++++++++++++++++++++++++++---
 drivers/net/ovpn/skb.h         |  27 +++-
 drivers/net/ovpn/stats.h       |  16 +-
 drivers/net/ovpn/tcp.c         |   4 +-
 drivers/net/ovpn/udp.c         |  27 +++-
 7 files changed, 382 insertions(+), 39 deletions(-)
drivers/net/ovpn/crypto_aead.c +81 -4
index 30299581422d..8eb76268dc3a 100644
@@ -134,13 +134,17 @@ static struct scatterlist *ovpn_aead_crypto_req_sg(struct crypto_aead *aead,
 
 static struct aead_request *ovpn_aead_request_alloc(struct crypto_aead *aead,
 						    struct sk_buff *skb,
-						    unsigned int nents, u8 **iv)
+						    unsigned int nents,
+						    unsigned int extra, u8 **iv)
 {
 	struct aead_request *req;
 	void *tmp;
 
-	/* allocate IV, request and scatterlist entries in one block */
-	tmp = kmalloc(ovpn_aead_crypto_tmp_size(aead, nents), GFP_ATOMIC);
+	/* allocate IV, request, scatterlist entries and caller scratch space
+	 * in one block
+	 */
+	tmp = kmalloc(ovpn_aead_crypto_tmp_size(aead, nents) + extra,
+		      GFP_ATOMIC);
 	if (unlikely(!tmp))
 		return ERR_PTR(-ENOMEM);
 
@@ -217,7 +221,7 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
 		nfrags = 1;
 	}
 
-	req = ovpn_aead_request_alloc(ks->encrypt, skb, nfrags + 2, &iv);
+	req = ovpn_aead_request_alloc(ks->encrypt, skb, nfrags + 2, 0, &iv);
 	if (IS_ERR(req))
 		return PTR_ERR(req);
 	sg = ovpn_aead_crypto_req_sg(ks->encrypt, req);
@@ -261,6 +265,79 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
 	return crypto_aead_encrypt(req);
 }
 
+int ovpn_aead_encrypt_gso(struct ovpn_peer *peer,
+			  struct ovpn_crypto_key_slot *ks, struct sk_buff *skb,
+			  struct sk_buff *gso_skb, unsigned int offset)
+{
+	const unsigned int dst_nents = skb_shinfo(gso_skb)->nr_frags + 4;
+	const unsigned int src_nents = 2;
+	unsigned int nents, payload_off;
+	struct scatterlist *src, *dst;
+	struct aead_request *req;
+	int dst_idx, mapped, ret;
+	u8 *aad, *iv;
+
+	/* each input records the shared peer and key for the common completion
+	 * path but their references remain owned by the output aggregate
+	 */
+	ovpn_skb_cb(skb)->peer = peer;
+	ovpn_skb_cb(skb)->ks = ks;
+
+	if (WARN_ON_ONCE(skb_is_nonlinear(skb)))
+		return -EINVAL;
+
+	nents = src_nents + dst_nents;
+	req = ovpn_aead_request_alloc(ks->encrypt, skb, nents, OVPN_AAD_SIZE,
+				      &iv);
+	if (IS_ERR(req))
+		return PTR_ERR(req);
+	src = ovpn_aead_crypto_req_sg(ks->encrypt, req);
+	dst = src + src_nents;
+	aad = (u8 *)(dst + dst_nents);
+
+	ret = ovpn_aead_encrypt_header(peer, ks, iv, aad);
+	if (unlikely(ret < 0))
+		return ret;
+
+	ret = skb_store_bits(gso_skb, offset, aad, OVPN_AAD_SIZE);
+	if (unlikely(ret < 0))
+		return ret;
+
+	/* encrypt out of place from the original segmented skb directly into
+	 * its final range in the UDP GSO skb
+	 */
+	sg_init_table(src, src_nents);
+	sg_set_buf(src, aad, OVPN_AAD_SIZE);
+	sg_set_buf(src + 1, skb->data, skb->len);
+
+	sg_init_table(dst, dst_nents);
+	dst_idx = skb_to_sgvec_nomark(gso_skb, dst, offset, OVPN_AAD_SIZE);
+	if (unlikely(dst_idx < 0))
+		return dst_idx;
+
+	payload_off = offset + OVPN_AAD_SIZE + OVPN_AUTH_TAG_SIZE;
+	mapped = skb_to_sgvec_nomark(gso_skb, dst + dst_idx, payload_off,
+				     skb->len);
+	if (unlikely(mapped < 0))
+		return mapped;
+	dst_idx += mapped;
+
+	mapped = skb_to_sgvec_nomark(gso_skb, dst + dst_idx,
+				     offset + OVPN_AAD_SIZE,
+				     OVPN_AUTH_TAG_SIZE);
+	if (unlikely(mapped < 0))
+		return mapped;
+	dst_idx += mapped;
+	sg_mark_end(&dst[dst_idx - 1]);
+
+	aead_request_set_tfm(req, ks->encrypt);
+	aead_request_set_callback(req, 0, ovpn_encrypt_post, skb);
+	aead_request_set_crypt(req, src, dst, skb->len, iv);
+	aead_request_set_ad(req, OVPN_AAD_SIZE);
+
+	return crypto_aead_encrypt(req);
+}
+
 int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
 		      struct sk_buff *skb)
 {
drivers/net/ovpn/crypto_aead.h +4 -0
index fae3b585a43b..8b444744944e 100644
@@ -17,6 +17,10 @@
 
 int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
 		      struct sk_buff *skb);
+int ovpn_aead_encrypt_gso(struct ovpn_peer *peer,
+			  struct ovpn_crypto_key_slot *ks,
+			  struct sk_buff *skb, struct sk_buff *gso_skb,
+			  unsigned int offset);
 int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
 		      struct sk_buff *skb);
 
drivers/net/ovpn/io.c +237 -21

396 lines not on this page · raw message

drivers/net/ovpn/skb.h +22 -5
index 4fb7ea025426..cca29479c038 100644
@@ -10,6 +10,7 @@
 #ifndef _NET_OVPN_SKB_H_
 #define _NET_OVPN_SKB_H_
 
+#include <linux/atomic.h>
 #include <linux/in.h>
 #include <linux/in6.h>
 #include <linux/ip.h>
@@ -20,19 +21,35 @@
 
 /**
  * struct ovpn_cb - ovpn skb control block
- * @peer: the peer this skb was received from/sent to
- * @ks: the crypto key slot used to encrypt/decrypt this skb
  * @crypto_tmp: pointer to temporary memory used for crypto operations
  *		containing the IV, the scatter gather list and the aead request
+ * @peer: peer used by this crypto operation or owned by this aggregate
+ * @ks: crypto key slot used by this operation or owned by this aggregate
  * @payload_offset: offset in the skb where the payload starts
  * @nosignal: whether this skb should be sent with the MSG_NOSIGNAL flag (TCP)
+ * @batch: UDP GSO aggregate receiving this input skb's encrypted payload
+ * @batch_state: completion state owned by a UDP GSO aggregate
  */
 struct ovpn_cb {
+	void *crypto_tmp;
 	struct ovpn_peer *peer;
 	struct ovpn_crypto_key_slot *ks;
-	void *crypto_tmp;
-	unsigned int payload_offset;
-	bool nosignal;
+
+	/* Ordinary encryption leaves this union zeroed. Decryption and TCP use
+	 * their ordinary fields, a UDP GSO input stores its output aggregate,
+	 * and that aggregate uses the same space to coordinate its completions.
+	 */
+	union {
+		struct {
+			unsigned int payload_offset;
+			bool nosignal;
+		};
+		struct sk_buff *batch;
+		struct {
+			atomic_t pending;
+			atomic_t failed;
+		} batch_state;
+	};
 };
 
 static inline struct ovpn_cb *ovpn_skb_cb(struct sk_buff *skb)
drivers/net/ovpn/stats.h +13 -3
index 3a45b97c0056..b3fe006c01f6 100644
@@ -40,16 +40,26 @@ static inline void ovpn_peer_stats_increment_rx(struct ovpn_peer_stats *stats,
 	ovpn_peer_stats_increment(&stats->rx, n);
 }
 
+static inline void ovpn_peer_stats_add_tx(struct ovpn_peer_stats *stats,
+					  const unsigned int bytes,
+					  unsigned int packets)
+{
+	atomic64_add(bytes, &stats->tx.bytes);
+	atomic64_add(packets, &stats->tx.packets);
+}
+
 static inline void ovpn_peer_stats_increment_tx(struct ovpn_peer_stats *stats,
 						const unsigned int n)
 {
-	ovpn_peer_stats_increment(&stats->tx, n);
+	ovpn_peer_stats_add_tx(stats, n, 1);
 }
 
-static inline void ovpn_dev_dstats_tx_dropped(struct net_device *dev)
+static inline void ovpn_dev_dstats_tx_dropped(struct net_device *dev,
+					      unsigned int packets)
 {
 	local_bh_disable();
-	dev_dstats_tx_dropped(dev);
+	while (packets--)
+		dev_dstats_tx_dropped(dev);
 	local_bh_enable();
 }
 
drivers/net/ovpn/tcp.c +2 -2
index 8fe8a8e750a4..5cba35e4a8ee 100644
@@ -332,7 +332,7 @@ static void ovpn_tcp_send_sock_skb(struct ovpn_peer *peer, struct sock *sk,
 		ovpn_tcp_send_sock(peer, sk);
 
 	if (peer->tcp.out_msg.skb) {
-		ovpn_dev_dstats_tx_dropped(peer->ovpn->dev);
+		ovpn_dev_dstats_tx_dropped(peer->ovpn->dev, 1);
 		kfree_skb(skb);
 		return;
 	}
@@ -354,7 +354,7 @@ void ovpn_tcp_send_skb(struct ovpn_peer *peer, struct sock *sk,
 	if (sock_owned_by_user(sk)) {
 		if (skb_queue_len(&peer->tcp.out_queue) >=
 		    READ_ONCE(net_hotdata.max_backlog)) {
-			ovpn_dev_dstats_tx_dropped(peer->ovpn->dev);
+			ovpn_dev_dstats_tx_dropped(peer->ovpn->dev, 1);
 			kfree_skb(skb);
 			goto unlock;
 		}
drivers/net/ovpn/udp.c +23 -4
index 7f69e8890b5b..4802d982de08 100644
@@ -121,6 +121,7 @@ static int ovpn_udp_encap_recv(struct sock *sk, struct sk_buff *skb)
 
 	/* pop off outer UDP header */
 	__skb_pull(skb, sizeof(struct udphdr));
+	skb_mark_not_on_list(skb);
 	ovpn_recv(peer, skb);
 	return 0;
 
@@ -196,9 +197,13 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind,
 	dst_cache_set_ip4(cache, &rt->dst, fl.saddr);
 
 transmit:
+	/* an already-built UDP GSO needs a checksum seed even if the socket's
+	 * no-check option changed while encryption was in flight
+	 */
 	udp_tunnel_xmit_skb(rt, sk, skb, fl.saddr, fl.daddr, 0,
 			    ip4_dst_hoplimit(&rt->dst), 0, fl.fl4_sport,
-			    fl.fl4_dport, false, sk->sk_no_check_tx, 0);
+			    fl.fl4_dport, false,
+			    !skb_is_gso(skb) && sk->sk_no_check_tx, 0);
 	ret = 0;
 err:
 	local_bh_enable();
@@ -271,9 +276,13 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind,
 	 * udp_tunnel_xmit_skb()
 	 */
 	skb->ignore_df = 1;
+	/* keep checksum offload enabled for an in-flight UDP GSO batch even if
+	 * the socket's no-check option has changed since batch creation
+	 */
 	udp_tunnel6_xmit_skb(dst, sk, skb, skb->dev, &fl.saddr, &fl.daddr, 0,
 			     ip6_dst_hoplimit(dst), 0, fl.fl6_sport,
-			     fl.fl6_dport, udp_get_no_check6_tx(sk), 0);
+			     fl.fl6_dport,
+			     !skb_is_gso(skb) && udp_get_no_check6_tx(sk), 0);
 	ret = 0;
 err:
 	local_bh_enable();
@@ -344,8 +353,18 @@ void ovpn_udp_send_skb(struct ovpn_peer *peer, struct sock *sk,
 
 	skb->dev = peer->ovpn->dev;
 	skb->mark = READ_ONCE(sk->sk_mark);
-	/* no checksum performed at this layer */
-	skb->ip_summed = CHECKSUM_NONE;
+	if (skb_is_gso(skb)) {
+		/* udp_tunnel_xmit_skb installs the outer UDP header after this
+		 * function returns: point CHECKSUM_PARTIAL at that future
+		 * header so both hw and sw UDP GSO can complete the checksum.
+		 */
+		skb->ip_summed = CHECKSUM_PARTIAL;
+		skb->csum_start = skb_headroom(skb) - sizeof(struct udphdr);
+		skb->csum_offset = offsetof(struct udphdr, check);
+	} else {
+		/* no checksum performed at this layer */
+		skb->ip_summed = CHECKSUM_NONE;
+	}
 
 	/* crypto layer -> transport (UDP) */
 	ret = ovpn_udp_output(peer, &peer->dst_cache, sk, skb);
Signature · 8 lines
2.55.0



_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel
author reviewer
#5 lore raw

RFC v3 5/9 ovpn: coalesce UDP data records with GRO

In reply to Ralf Lici

Register UDP tunnel GRO callbacks for ovpn data sockets and coalesce compatible DATA_V2 records from one transport flow. Keep each encrypted record as a separate frag-list entry so the receive path can detach and authenticate records independently.

Match the complete opcode/key/peer header and require compatible outer- network and checksum state. Flush on short records, differing segment geometry, existing GSO input, or the 64-record limit.

Export skb_gro_receive_list, which is already shared by the core UDP and TCP frag-list GRO paths, so modular ovpn can use the same primitive instead of maintaining a local copy.

On two directly connected 100-Gbit/s mlx5 ports, five interleaved iperf3 -t 60 -O 10 single-flow AES-128-GCM runs in each direction produced the following throughput:

Forward Reverse

Without receive GRO     18.308 Gbit/s    19.233 Gbit/s
With frag-list GRO      22.087 Gbit/s    22.962 Gbit/s

The preceding UDP GSO transmit path and hardware UDP segmentation were enabled in both cases. The equal-weight mean of the two directional results increased from 18.770 to 22.524 Gbit/s, a 20.0% improvement.

---
Changes since v2 https://lore.kernel.org/openvpn-devel/76cb811457ac17b519d219b13fb2a4317a6d5e74.1789540779.git.ralf@mandelbit.com/
- Unclone GRO aggregates to avoid shared skb corruption in
ovpn_udp_gro_detach. (Sashiko)

Changes since v1 https://lore.kernel.org/openvpn-devel/dd08a7a2fe0dfc88509115b5d7ee17825020c012.1789485693.git.ralf@mandelbit.com/
- Preserve the outer network offset in ovpn_udp_gro_receive_fraglist.
(Sashiko)
- Detach frag_list only from a UDP-tunnel GSO aggregate. (Sashiko)

 drivers/net/ovpn/io.c  |   7 +-
 drivers/net/ovpn/udp.c | 197 ++++++++++++++++++++++++++++++++++++++++-
 net/core/gro.c         |   1 +
 net/ipv4/udp_offload.c |   3 +-
 4 files changed, 201 insertions(+), 7 deletions(-)
drivers/net/ovpn/io.c +3 -4
index 3ad4cadeeb02..11f7f16d7b79 100644
@@ -70,11 +70,10 @@ static void ovpn_netdev_write(struct ovpn_peer *peer, struct sk_buff *skb)
 	unsigned int pkt_len;
 	int ret;
 
-	/*
-	 * GSO state from the transport layer is not valid for the tunnel/data
-	 * path. Reset all GSO fields to prevent any further GSO processing
-	 * from entering an inconsistent state.
+	/* the transport encapsulation and its GSO metadata do not describe the
+	 * decrypted inner packet
 	 */
+	skb->encapsulation = 0;
 	skb_gso_reset(skb);
 
 	/* we can't guarantee the packet wasn't corrupted before entering the
drivers/net/ovpn/udp.c +195 -2

235 lines not on this page · raw message

net/core/gro.c +1 -0
index 29b4d02bf519..b6acedc919f8 100644
@@ -262,6 +262,7 @@ int skb_gro_receive_list(struct sk_buff *p, struct sk_buff *skb)
 
 	return 0;
 }
+EXPORT_SYMBOL(skb_gro_receive_list);
 
 static void gro_complete(struct gro_node *gro, struct sk_buff *skb)
 {
net/ipv4/udp_offload.c +2 -1
index cf07c3c6611a..187f108f3ee8 100644
@@ -40,7 +40,8 @@ struct udp_tunnel_type_entry {
 #define UDP_MAX_TUNNEL_TYPES (IS_ENABLED(CONFIG_GENEVE) + \
 			      IS_ENABLED(CONFIG_VXLAN) * 2 + \
 			      IS_ENABLED(CONFIG_NET_FOU) * 2 + \
-			      IS_ENABLED(CONFIG_XFRM) * 2)
+			      IS_ENABLED(CONFIG_XFRM) * 2 + \
+			      IS_ENABLED(CONFIG_OVPN))
 
 DEFINE_STATIC_CALL(udp_tunnel_gro_rcv, dummy_gro_rcv);
 static DEFINE_STATIC_KEY_FALSE(udp_tunnel_static_call);
Signature · 8 lines
2.55.0



_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel
author reviewer
#6 lore raw

RFC v3 6/9 ovpn: prefetch encrypted records before GRO batch decryption

In reply to Ralf Lici

Frag-list GRO exposes later encrypted records before the receive path decrypts the current one. Use this lookahead to request write ownership of linear ciphertext cache lines two records in advance (and maintain the same distance throughout the batch), overlapping their memory access latency with the current AEAD operation. An ordinary single-record UDP receive has no later record and therefore skips the prefetch path.

Only prefetch the linear part of each skb. Walking non-linear fragments here would duplicate the scatterlist walk performed by crypto and could cost more than the cache hint saves.

A same-binary comparison using three 30-second samples per direction found distances one and two effectively tied forward, while distance two was 3.3% faster reverse and less variable in both directions. Profiling also measured slightly fewer decrypt cycles at distance two than at one, while wider distances provided no repeatable benefit.

On a direct 100 Gbit/s ConnectX-5 link using one TCP stream, AES-128-GCM, a 1408-byte inner MTU and 8192-entry rings, five interleaved 60-second samples per direction increased throughput by 16.9% forward and 18.0% reverse.

---
No changes since v2 https://lore.kernel.org/openvpn-devel/4d8357e320fc7c8fd4ab6e42a0bc38b275b1ff5d.1789540779.git.ralf@mandelbit.com/

No changes since v1 https://lore.kernel.org/openvpn-devel/712708baf265c5509aed4f9d476abf514a242b8a.1789485693.git.ralf@mandelbit.com/

 drivers/net/ovpn/io.h  | 14 ++++++++++++++
 drivers/net/ovpn/udp.c | 21 ++++++++++++++++++++-
 2 files changed, 34 insertions(+), 1 deletion(-)
drivers/net/ovpn/io.h +14 -0
index 1a94f0fda1d1..49180214fe08 100644
@@ -10,6 +10,9 @@
 #ifndef _NET_OVPN_OVPN_H_
 #define _NET_OVPN_OVPN_H_
 
+#include <linux/cache.h>
+#include <linux/prefetch.h>
+
 /* DATA_V2 header size with AEAD encryption */
 #define OVPN_HEAD_ROOM (OVPN_DATA_V2_OVERHEAD +				   \
 			max(sizeof(struct udphdr), sizeof(struct tcphdr)) +\
@@ -21,6 +24,17 @@
 #define OVPN_KEEPALIVE_SIZE 16
 extern const unsigned char ovpn_keepalive_message[OVPN_KEEPALIVE_SIZE];
 
+static inline void ovpn_skb_prefetchw(const struct sk_buff *skb)
+{
+	unsigned int offset;
+
+	/* crypto overwrites data in place, so request write ownership of each
+	 * linear cache line before the AEAD implementation reaches it
+	 */
+	for (offset = 0; offset < skb_headlen(skb); offset += L1_CACHE_BYTES)
+		prefetchw(skb->data + offset);
+}
+
 netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev);
 
 void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb);
drivers/net/ovpn/udp.c +20 -1
index ee3b9d1aec25..ce2584ecc934 100644
@@ -32,6 +32,9 @@
 /* like UDP and TCP frag-list GRO */
 #define OVPN_UDP_GRO_CNT_MAX 64
 
+/* leave enough work between a cache hint and the record which consumes it */
+#define OVPN_UDP_GRO_PREFETCH_DISTANCE 2
+
 static bool ovpn_udp_gro_header(struct sk_buff *skb, u32 *header)
 {
 	const unsigned int offset = skb_gro_offset(skb);
@@ -187,7 +190,8 @@ static struct sk_buff *ovpn_udp_gro_detach(struct sk_buff *skb)
 
 static void ovpn_udp_recv(struct ovpn_peer *peer, struct sk_buff *skb)
 {
-	struct sk_buff *list, *next;
+	struct sk_buff *list, *next, *prefetch;
+	unsigned int i;
 
 	list = ovpn_udp_gro_detach(skb);
 	if (IS_ERR(list)) {
@@ -198,8 +202,23 @@ static void ovpn_udp_recv(struct ovpn_peer *peer, struct sk_buff *skb)
 	}
 	skb->next = list;
 
+	/* a frag-list GRO aggregate makes later ciphertext visible before the
+	 * current record is decrypted, so we prime the first two records, then
+	 * keep the cache hints the same distance ahead while draining the list
+	 */
+	prefetch = skb->next ? skb : NULL;
+	for (i = 0; i < OVPN_UDP_GRO_PREFETCH_DISTANCE && prefetch; i++) {
+		ovpn_skb_prefetchw(prefetch);
+		prefetch = prefetch->next;
+	}
+
 	skb_list_walk_safe(skb, skb, next)
 	{
+		if (prefetch) {
+			ovpn_skb_prefetchw(prefetch);
+			prefetch = prefetch->next;
+		}
+
 		skb_mark_not_on_list(skb);
 
 		/* keep the current reference alive for the next record before
Signature · 8 lines
2.55.0



_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel
author reviewer
#7 lore raw

RFC v3 7/9 ovpn: prefetch GSO segments before in-place encryption

In reply to Ralf Lici

Software segmentation exposes the complete skb list before encryption begins. While encrypting each segment, request write ownership of the next linear segment cache line by cache line. A single skb naturally skips the prefetch path.

---
No changes since v2 https://lore.kernel.org/openvpn-devel/3f903692549ee58887b1bb421f8b283059f66a1d.1789540779.git.ralf@mandelbit.com/

No changes since v1 https://lore.kernel.org/openvpn-devel/382879c187b1acca65198c467d1263266243ea50.1789485693.git.ralf@mandelbit.com/

 drivers/net/ovpn/io.c | 6 ++++++
 1 file changed, 6 insertions(+)
drivers/net/ovpn/io.c +6 -0
index 11f7f16d7b79..cb7503a3e79c 100644
@@ -472,6 +472,12 @@ static void ovpn_send(struct ovpn_priv *ovpn, struct sk_buff *skb,
 	 * independently
 	 */
 	skb_list_walk_safe(skb, curr, next) {
+		/* encrypting this segment can hide the cost of fetching the
+		 * next segment's data into the cache
+		 */
+		if (next)
+			ovpn_skb_prefetchw(next);
+
 		if (unlikely(!ovpn_encrypt_one(peer, curr))) {
 			ovpn_dev_dstats_tx_dropped(ovpn->dev, 1);
 			kfree_skb(curr);
Signature · 8 lines
2.55.0



_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel
author reviewer
#8 lore raw

RFC v3 8/9 net: gro: honor skbs consumed by protocol callbacks

In reply to Ralf Lici

XFRM's ESP GRO callbacks may consume an skb and return ERR_PTR(-EINPROGRESS) as an ownership marker. dev_gro_receive already recognizes this marker unconditionally and converts it to GRO_CONSUMED, so -EINPROGRESS is reserved by the generic GRO callback interface and cannot represent an ordinary callback error. The nested flush helpers currently avoid accessing a consumed skb only when XFRM offload is configured, because XFRM has so far been the sole user of the convention.

Make the ownership check unconditional so other protocol callbacks can safely use the existing marker without acquiring an unrelated CONFIG_XFRM_OFFLOAD dependency. Callbacks which do not return the marker are unaffected.

---
No changes since v2 https://lore.kernel.org/openvpn-devel/3192bff7d69f958114e5fc9efe0dc5039c5be147.1789540779.git.ralf@mandelbit.com/

No changes since v1 https://lore.kernel.org/openvpn-devel/893f6c2b385f9df3e9617a9b7f547734061df195.1789485693.git.ralf@mandelbit.com/

 include/net/gro.h | 19 +++----------------
 1 file changed, 3 insertions(+), 16 deletions(-)
include/net/gro.h +3 -16
index 2300b6da05b2..20ddc5488789 100644
@@ -361,9 +361,11 @@ static inline void skb_gro_remcsum_cleanup(struct sk_buff *skb,
 	remcsum_unadjust((__sum16 *)ptr, grc->delta);
 }
 
-#ifdef CONFIG_XFRM_OFFLOAD
 static inline void skb_gro_flush_final(struct sk_buff *skb, struct sk_buff *pp, int flush)
 {
+	/* a GRO callback may consume skb and return this marker to prevent
+	 * accessing the skb while unwinding through the enclosing GRO layers
+	 */
 	if (PTR_ERR(pp) != -EINPROGRESS)
 		NAPI_GRO_CB(skb)->flush |= flush;
 }
@@ -378,21 +380,6 @@ static inline void skb_gro_flush_final_remcsum(struct sk_buff *skb,
 		skb->remcsum_offload = 0;
 	}
 }
-#else
-static inline void skb_gro_flush_final(struct sk_buff *skb, struct sk_buff *pp, int flush)
-{
-	NAPI_GRO_CB(skb)->flush |= flush;
-}
-static inline void skb_gro_flush_final_remcsum(struct sk_buff *skb,
-					       struct sk_buff *pp,
-					       int flush,
-					       struct gro_remcsum *grc)
-{
-	NAPI_GRO_CB(skb)->flush |= flush;
-	skb_gro_remcsum_cleanup(skb, grc);
-	skb->remcsum_offload = 0;
-}
-#endif
 
 INDIRECT_CALLABLE_DECLARE(struct sk_buff *ipv6_gro_receive(struct list_head *,
 							   struct sk_buff *));
Signature · 8 lines
2.55.0



_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel
author reviewer
#9 lore raw

RFC v3 9/9 ovpn: add opt-in direct GRO receive mode

In reply to Ralf Lici

The normal ovpn UDP GRO path retains compatible encrypted records in a frag-list and lets the completed aggregate traverse the outer IP and UDP receive stack before handing its records to the existing decryption path.

Add an optional UDP GRO mode which instead consumes DATA_V2 packets from the UDP tunnel GRO callback and starts their existing per-record decryption immediately, similarly to xfrm. Control packets are flushed from GRO, restored, and continue through the normal stack.

Select the mode through the immutable IFLA_OVPN_UDP_GRO_MODE link attribute. FULL_STACK remains the default for existing userspace, while DIRECT enables the new path. Keep this local receive policy per ovpn interface and select the corresponding GRO callback when each UDP socket is attached. This makes all sockets attached to the interface behave consistently without performing a mode lookup and dispatch for every packet. Account for both ovpn callbacks in the UDP tunnel GRO callback limit.

The direct mode deliberately bypasses packet taps, TC ingress, outer IP validation and routing, netfilter hooks, the final UDP lookup, socket XFRM policy, and normal UDP receive accounting for DATA_V2. It is therefore an explicit operator choice for controlled transport interfaces rather than a transparent replacement for the full receive stack.

Extend the UDP throughput selftest with a PRE_ROUTING nftables counter, verifying that full-stack aggregates reach the hook while direct-GRO aggregates bypass it.

Before ciphertext prefetch was added to FULL_STACK, five interleaved single-flow AES-128-GCM runs per direction on two directly connected 100-Gbit/s mlx5 ports measured 22.087/22.962 Gbit/s forward/reverse in FULL_STACK and 24.315/25.313 Gbit/s in DIRECT, a 10.2% equal-weight gain. With the preceding prefetch commit enabled in FULL_STACK, later checks measured DIRECT within 1.3% forward and 0.7% reverse of FULL_STACK.

---
Changes since v2 https://lore.kernel.org/openvpn-devel/69c873c8837a0d8028c0427509aa384ee73be02b.1789540779.git.ralf@mandelbit.com/
- Copy the GRO mode into a byte-sized local before constructing the
netlink attribute in ovpn-cli.c. (Sashiko)

No changes since v1 https://lore.kernel.org/openvpn-devel/b4bd25d6c4c01a81447a31054abd095e0533fcdd.1789485693.git.ralf@mandelbit.com/

 Documentation/netlink/specs/rt-link.yaml      | 12 +++
 drivers/net/ovpn/main.c                       | 15 ++-
 drivers/net/ovpn/ovpnpriv.h                   |  2 +
 drivers/net/ovpn/udp.c                        | 98 ++++++++++++++-----
 include/uapi/linux/if_link.h                  |  6 ++
 net/ipv4/udp_offload.c                        |  2 +-
 tools/testing/selftests/net/ovpn/Makefile     |  1 +
 tools/testing/selftests/net/ovpn/common.sh    |  4 +-
 tools/testing/selftests/net/ovpn/ovpn-cli.c   | 40 +++++++-
 .../selftests/net/ovpn/test-gro-direct.sh     | 10 ++
 tools/testing/selftests/net/ovpn/test.sh      | 65 ++++++++++++
 11 files changed, 221 insertions(+), 34 deletions(-)
 create mode 100755 tools/testing/selftests/net/ovpn/test-gro-direct.sh
Documentation/netlink/specs/rt-link.yaml +12 -0
index 7a72cd1b7e1e..d43e995c7f09 100644
@@ -844,6 +844,14 @@ definitions:
     entries:
       - p2p
       - mp
+  -
+    name: ovpn-udp-gro-mode
+    enum-name: ovpn-udp-gro-mode
+    name-prefix: ovpn-udp-gro-mode
+    type: enum
+    entries:
+      - full-stack
+      - direct
   -
     name: br-stp-mode
     type: enum
@@ -2365,6 +2373,10 @@ attribute-sets:
         name: mode
         type: u8
         enum: ovpn-mode
+      -
+        name: udp-gro-mode
+        type: u8
+        enum: ovpn-udp-gro-mode
 
 sub-messages:
   -
drivers/net/ovpn/main.c +13 -2
index ac4e0d85e215..ecf27d1e2420 100644
@@ -129,6 +129,9 @@ static const struct device_type ovpn_type = {
 static const struct nla_policy ovpn_policy[IFLA_OVPN_MAX + 1] = {
 	[IFLA_OVPN_MODE] = NLA_POLICY_RANGE(NLA_U8, OVPN_MODE_P2P,
 					    OVPN_MODE_MP),
+	[IFLA_OVPN_UDP_GRO_MODE] =
+		NLA_POLICY_RANGE(NLA_U8, OVPN_UDP_GRO_MODE_FULL_STACK,
+				 OVPN_UDP_GRO_MODE_DIRECT),
 };
 
 /**
@@ -200,6 +203,7 @@ static int ovpn_newlink(struct net_device *dev,
 			struct rtnl_newlink_params *params,
 			struct netlink_ext_ack *extack)
 {
+	enum ovpn_udp_gro_mode gro_mode = OVPN_UDP_GRO_MODE_FULL_STACK;
 	struct ovpn_priv *ovpn = netdev_priv(dev);
 	struct nlattr **data = params->data;
 	enum ovpn_mode mode = OVPN_MODE_P2P;
@@ -209,9 +213,14 @@ static int ovpn_newlink(struct net_device *dev,
 		mode = nla_get_u8(data[IFLA_OVPN_MODE]);
 		netdev_dbg(dev, "setting device mode: %u\n", mode);
 	}
+	if (data && data[IFLA_OVPN_UDP_GRO_MODE]) {
+		gro_mode = nla_get_u8(data[IFLA_OVPN_UDP_GRO_MODE]);
+		netdev_dbg(dev, "setting UDP GRO mode: %u\n", gro_mode);
+	}
 
 	ovpn->dev = dev;
 	ovpn->mode = mode;
+	ovpn->gro_mode = gro_mode;
 	spin_lock_init(&ovpn->lock);
 	INIT_DELAYED_WORK(&ovpn->keepalive_work, ovpn_peer_keepalive_work);
 
@@ -237,8 +246,8 @@ static int ovpn_newlink(struct net_device *dev,
 
 static size_t ovpn_get_size(const struct net_device *dev)
 {
-	/* IFLA_OVPN_MODE */
-	return nla_total_size(sizeof(u8));
+	/* IFLA_OVPN_MODE and IFLA_OVPN_UDP_GRO_MODE */
+	return nla_total_size(sizeof(u8)) + nla_total_size(sizeof(u8));
 }
 
 static int ovpn_fill_info(struct sk_buff *skb, const struct net_device *dev)
@@ -247,6 +256,8 @@ static int ovpn_fill_info(struct sk_buff *skb, const struct net_device *dev)
 
 	if (nla_put_u8(skb, IFLA_OVPN_MODE, ovpn->mode))
 		return -EMSGSIZE;
+	if (nla_put_u8(skb, IFLA_OVPN_UDP_GRO_MODE, ovpn->gro_mode))
+		return -EMSGSIZE;
 
 	return 0;
 }
drivers/net/ovpn/ovpnpriv.h +2 -0
index 84499140e4bd..dc6210b99f4a 100644
@@ -40,6 +40,7 @@ struct ovpn_peer_collection {
  * struct ovpn_priv - per ovpn interface state
  * @dev: the actual netdev representing the tunnel
  * @mode: device operation mode (i.e. p2p, mp, ..)
+ * @gro_mode: whether UDP data follows the full stack or is decrypted from GRO
  * @lock: protect this object
  * @peers: data structures holding multi-peer references
  * @peer: in P2P mode, this is the only remote peer
@@ -49,6 +50,7 @@ struct ovpn_peer_collection {
 struct ovpn_priv {
 	struct net_device *dev;
 	enum ovpn_mode mode;
+	enum ovpn_udp_gro_mode gro_mode;
 	spinlock_t lock; /* protect writing to the ovpn_priv object */
 	struct ovpn_peer_collection *peers;
 	struct ovpn_peer __rcu *peer;
drivers/net/ovpn/udp.c +73 -25

142 lines not on this page · raw message

include/uapi/linux/if_link.h +6 -0
index 245b36204525..2d7b320f83be 100644
@@ -2067,9 +2067,15 @@ enum ovpn_mode {
 	OVPN_MODE_MP,
 };
 
+enum ovpn_udp_gro_mode {
+	OVPN_UDP_GRO_MODE_FULL_STACK,
+	OVPN_UDP_GRO_MODE_DIRECT,
+};
+
 enum {
 	IFLA_OVPN_UNSPEC,
 	IFLA_OVPN_MODE,
+	IFLA_OVPN_UDP_GRO_MODE,
 	__IFLA_OVPN_MAX,
 };
 
net/ipv4/udp_offload.c +1 -1
index 187f108f3ee8..bfe23ec9dfca 100644
@@ -41,7 +41,7 @@ struct udp_tunnel_type_entry {
 			      IS_ENABLED(CONFIG_VXLAN) * 2 + \
 			      IS_ENABLED(CONFIG_NET_FOU) * 2 + \
 			      IS_ENABLED(CONFIG_XFRM) * 2 + \
-			      IS_ENABLED(CONFIG_OVPN))
+			      IS_ENABLED(CONFIG_OVPN) * 2)
 
 DEFINE_STATIC_CALL(udp_tunnel_gro_rcv, dummy_gro_rcv);
 static DEFINE_STATIC_KEY_FALSE(udp_tunnel_static_call);
tools/testing/selftests/net/ovpn/Makefile +1 -0
index 169f0464ac3a..412a70abf739 100644
@@ -37,6 +37,7 @@ TEST_PROGS := \
 	test-close-socket-tcp.sh \
 	test-close-socket.sh \
 	test-float.sh \
+	test-gro-direct.sh \
 	test-large-mtu.sh \
 	test-mark.sh \
 	test-symmetric-id-float.sh \
tools/testing/selftests/net/ovpn/common.sh +3 -1
index 2d844eb3aa6e..1467caa95168 100644
@@ -10,6 +10,7 @@ source "$OVPN_COMMON_DIR/../../kselftest/ktap_helpers.sh"
 OVPN_UDP_PEERS_FILE=${OVPN_UDP_PEERS_FILE:-udp_peers.txt}
 OVPN_TCP_PEERS_FILE=${OVPN_TCP_PEERS_FILE:-tcp_peers.txt}
 OVPN_CLI=${OVPN_CLI:-${OVPN_COMMON_DIR}/ovpn-cli}
+OVPN_UDP_GRO_MODE=${OVPN_UDP_GRO_MODE:-FULL_STACK}
 OVPN_YNL=${OVPN_YNL:-${OVPN_COMMON_DIR}/../../../../net/ynl/pyynl/cli.py}
 OVPN_ALG=${OVPN_ALG:-aes}
 OVPN_PROTO=${OVPN_PROTO:-UDP}
@@ -162,7 +163,8 @@ ovpn_setup_ns() {
 		done
 	fi
 
-	ip netns exec "${peer}" ${OVPN_CLI} new_iface tun${1} $MODE
+	ip netns exec "${peer}" ${OVPN_CLI} new_iface tun${1} $MODE \
+		"${OVPN_UDP_GRO_MODE}"
 	ip -n "${peer}" addr add ${2} dev tun${1}
 	# add a secondary IP to peer 1, to test a LAN behind a client
 	if [ ${1} -eq 1 -a -n "${OVPN_LAN_IP}" ]; then
tools/testing/selftests/net/ovpn/ovpn-cli.c +35 -5
index f4effa7580c0..5d3ae8fb1152 100644
@@ -123,6 +123,8 @@ struct ovpn_ctx {
 	char ifname[IFNAMSIZ];
 	enum ovpn_mode mode;
 	bool mode_set;
+	enum ovpn_udp_gro_mode udp_gro_mode;
+	bool udp_gro_mode_set;
 
 	int socket;
 	int cli_sockets[MAX_PEERS];
@@ -1375,10 +1377,12 @@ static int ovpn_new_iface(struct ovpn_ctx *ovpn)
 {
 	struct rtattr *linkinfo, *data;
 	struct ovpn_link_req req = { 0 };
+	uint8_t udp_gro_mode;
 	int ret = -1;
 
-	fprintf(stdout, "Creating interface %s with mode %u\n", ovpn->ifname,
-		ovpn->mode);
+	fprintf(stdout,
+		"Creating interface %s with mode %u and UDP GRO mode %u\n",
+		ovpn->ifname, ovpn->mode, ovpn->udp_gro_mode);
 
 	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
 	req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL;
@@ -1396,15 +1400,22 @@ static int ovpn_new_iface(struct ovpn_ctx *ovpn)
 			 strlen(OVPN_FAMILY_NAME) + 1) < 0)
 		goto err;
 
-	if (ovpn->mode_set) {
+	if (ovpn->mode_set || ovpn->udp_gro_mode_set) {
 		data = ovpn_nest_start(&req.n, sizeof(req), IFLA_INFO_DATA);
 		if (!data)
 			goto err;
 
-		if (ovpn_addattr(&req.n, sizeof(req), IFLA_OVPN_MODE,
+		if (ovpn->mode_set &&
+		    ovpn_addattr(&req.n, sizeof(req), IFLA_OVPN_MODE,
 				 &ovpn->mode, sizeof(uint8_t)) < 0)
 			goto err;
 
+		udp_gro_mode = ovpn->udp_gro_mode;
+		if (ovpn->udp_gro_mode_set &&
+		    ovpn_addattr(&req.n, sizeof(req), IFLA_OVPN_UDP_GRO_MODE,
+				 &udp_gro_mode, sizeof(udp_gro_mode)) < 0)
+			goto err;
+
 		ovpn_nest_end(&req.n, data);
 	}
 
@@ -1666,11 +1677,16 @@ static void usage(const char *cmd)
 		cmd);
 	fprintf(stderr, "where <command> can be one of the following\n\n");
 
-	fprintf(stderr, "* new_iface <iface> [mode]: create new ovpn interface\n");
+	fprintf(stderr,
+		"* new_iface <iface> [mode] [udp-gro-mode]: create new ovpn interface\n");
 	fprintf(stderr, "\tiface: ovpn interface name\n");
 	fprintf(stderr, "\tmode:\n");
 	fprintf(stderr, "\t\t- P2P for peer-to-peer mode (i.e. client)\n");
 	fprintf(stderr, "\t\t- MP for multi-peer mode (i.e. server)\n");
+	fprintf(stderr, "\tudp-gro-mode:\n");
+	fprintf(stderr, "\t\t- FULL_STACK for the normal receive stack\n");
+	fprintf(stderr,
+		"\t\t- DIRECT to decrypt data from the UDP GRO callback\n");
 
 	fprintf(stderr, "* del_iface <iface>: delete ovpn interface\n");
 	fprintf(stderr, "\tiface: ovpn interface name\n");
@@ -2206,6 +2222,20 @@ static int ovpn_parse_cmd_args(struct ovpn_ctx *ovpn, int argc, char *argv[])
 			return -1;
 		}
 		ovpn->mode_set = true;
+
+		if (argc < 5)
+			break;
+
+		if (!strcmp(argv[4], "FULL_STACK")) {
+			ovpn->udp_gro_mode = OVPN_UDP_GRO_MODE_FULL_STACK;
+		} else if (!strcmp(argv[4], "DIRECT")) {
+			ovpn->udp_gro_mode = OVPN_UDP_GRO_MODE_DIRECT;
+		} else {
+			fprintf(stderr, "Cannot parse UDP GRO mode: %s\n",
+				argv[4]);
+			return -1;
+		}
+		ovpn->udp_gro_mode_set = true;
 		break;
 	case CMD_DEL_IFACE:
 		break;
tools/testing/selftests/net/ovpn/test-gro-direct.sh new file +10 -0
new file mode 100755
index 000000000000..f35db23eb425
@@ -0,0 +1,10 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2020-2025 OpenVPN, Inc.
+#
+#	Author:	Ralf Lici <[email protected]>
+#		Antonio Quartulli <[email protected]>
+
+OVPN_UDP_GRO_MODE="DIRECT"
+
+source test.sh
tools/testing/selftests/net/ovpn/test.sh +65 -0

83 lines not on this page · raw message

Signature · 8 lines
2.55.0



_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel