ovpn: preserve GSO and GRO batching
Subsystems (3)
Ralf Lici
reviewercover ovpn: preserve GSO and GRO batching
Hi,
This series is a reworked version of my earlier RFC exploring how ovpn can preserve GSO and GRO batching across its data path. Since sending the RFC, I spent more time profiling the paths on different systems and refining the implementation. The result is a smaller series with some additional optimizations and a fresh set of measurements.
On transmit, it lets GSO skbs reach ovpn, completes each inner packet's checksum during software segmentation and, when the layout permits, encrypts the resulting packets directly into a UDP GSO aggregate. Ordinary packets and frag-list GSO children retain the existing in-place encryption path. Packet IDs for a UDP GSO batch are reserved as a single range instead of one segment at a time.
On receive, ovpn uses UDP GRO to collect compatible DATA_V2 records in a frag-list, then detaches and authenticates each record independently. Small transmit and receive prefetch changes use the resulting lookahead to overlap memory access with encryption and decryption.
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 raised mean throughput from 12.053 to 24.166 Gbit/s forward and from 10.992 to 23.558 Gbit/s reverse. Individual patch messages contain the intermediate measurements and test details.
Thanks,
Ralf Lici
Mandelbit Srl
---
Changes since RFC v4 https://lore.kernel.org/openvpn-devel/[email protected]/
- Drop the opt-in direct GRO mode and the generic GRO ownership change
it required. The series now retains the complete outer receive stack.
- Preserve safe page-backed payload fragments during GSO segmentation
and map them directly into the AEAD source scatterlist.
- Reserve one consecutive packet-ID range for each UDP GSO batch instead
of updating the counter once per segment.
- Extend transmit prefetch to the first payload fragment of segments
encrypted into a UDP GSO aggregate.
- Refresh the GSO, GRO and prefetch measurements using two directly
connected 100-Gbit/s mlx5 ports.
Ralf Lici (8):
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: reserve packet-ID ranges for UDP GSO batches
ovpn: coalesce UDP data records with GRO
ovpn: prefetch encrypted records before GRO batch decryption
ovpn: prefetch GSO segments before encryption
drivers/net/ovpn/crypto_aead.c | 171 +++++++++++++---- drivers/net/ovpn/crypto_aead.h | 4 + drivers/net/ovpn/io.c | 339 ++++++++++++++++++++++++++++++--- drivers/net/ovpn/io.h | 18 +- drivers/net/ovpn/main.c | 5 +- drivers/net/ovpn/pktid.h | 48 +++++ 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 | 258 ++++++++++++++++++++++++- net/core/gro.c | 1 + net/ipv4/udp_offload.c | 3 +- 13 files changed, 810 insertions(+), 88 deletions(-)
base-commit: b8e9e7d82e7eefd5d2d528469d94ec20e96b38c3
Signature · 7 lines
2.55.0 _______________________________________________ Openvpn-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/openvpn-devel
Ralf Lici
reviewerPATCH 1/8 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 RFC v4 https://lore.kernel.org/openvpn-devel/1bbfde37488ade61928554db3a119525adf7b608.1789558856.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
@@ -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
@@ -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
Ralf Lici
reviewerPATCH 2/8 ovpn: accept frag-list GSO input
In reply to Ralf LiciForwarded 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 RFC v4 https://lore.kernel.org/openvpn-devel/5baa1d94e29e7b109ffd412b4f1d5d113a319389.1789558856.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
@@ -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
@@ -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
Ralf Lici
reviewerPATCH 3/8 ovpn: refactor AEAD encryption helpers
In reply to Ralf LiciMove 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 RFC v4 https://lore.kernel.org/openvpn-devel/f075b9782b14d64756c84e132c138482f08287b9.1789558856.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
@@ -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
@@ -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
@@ -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
Ralf Lici
reviewerPATCH 4/8 ovpn: convert GSO input into UDP GSO output
In reply to Ralf LiciWhenever a GSO skb arrives at ovpn's ndo_start_xmit, segment the inner skb while completing its checksums. Allow the segmentation code to retain safe page-backed payload fragments, then emit eligible fixed-size inputs as UDP GSO skb(s).
Allocate one final page-backed aggregate per batch before submitting encryption. Map each segment's linear data and page fragments directly into the AEAD source scatterlist, and have each request write out of place into its final record slot. This avoids copying page-backed payload into a linear segment. The skb core can still linearize externally mutable fragments when needed to complete their checksums safely.
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 mean throughput:
Forward Reverse
Without UDP GSO 12.053 Gbit/s 10.992 Gbit/s With UDP GSO 19.262 Gbit/s 19.718 Gbit/s
This is a 59.8% improvement forward and a 79.4% improvement reverse. With lower-device UDP segmentation disabled, the GSO path raised median throughput from 12.386 to 15.489 Gbit/s forward and from 10.206 to 14.816 Gbit/s reverse, improvements of 25.1% and 45.2%, respectively.
---
Changes since RFC v4 https://lore.kernel.org/openvpn-devel/9359f737627fc84be08ee7d301415c023ebadeb2.1789558856.git.ralf@mandelbit.com/
- Preserve safe page-backed payload fragments during software
segmentation instead of forcing every segment to become linear.
- Map each segment's linear data and fragments directly into the AEAD
source scatterlist.
- Let the encryption caller supply the packet ID, preparing the path for
batch range reservation in the following patch.
- Refresh the hardware and software segmentation measurements.
drivers/net/ovpn/crypto_aead.c | 109 +++++++++++--- 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, 391 insertions(+), 54 deletions(-)
drivers/net/ovpn/crypto_aead.c +90 -19
@@ -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 );
@@ -151,19 +155,11 @@ static struct aead_request *ovpn_aead_request_alloc(struct crypto_aead *aead,return req; } -static int ovpn_aead_encrypt_header (struct ovpn_peer *peer, -struct ovpn_crypto_key_slot *ks, -u8 *iv,u8 *data) +static void ovpn_aead_encrypt_header (struct ovpn_peer *peer, +struct ovpn_crypto_key_slot *ks, +u32 pktid,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; +u32 op;/* concat 4 bytes packet id and 8 bytes nonce tail into 12 bytes * nonce
@@ -175,8 +171,6 @@ static int ovpn_aead_encrypt_header(struct ovpn_peer *peer,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,
@@ -187,6 +181,7 @@ 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;u8 *iv;ovpn_skb_cb (skb)->peer = peer;
@@ -217,7 +212,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);
@@ -243,9 +238,11 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,/* 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); +/* obtain the packet ID used by both the nonce and associated data */ + ret =ovpn_pktid_xmit_next (&ks->pid_xmit, &pktid);if (unlikely (ret <0 ))return ret; +ovpn_aead_encrypt_header (peer, ks, pktid, iv, skb->data);/* AEAD Additional data */ sg_set_buf (sg, skb->data,OVPN_AAD_SIZE );
@@ -261,6 +258,80 @@ 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 =skb_shinfo (skb)->nr_frags +2 ; +struct scatterlist *src, *dst; +struct aead_request *req; +unsigned int payload_off; +int dst_idx, mapped, ret; +u32 pktid; +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; + + req =ovpn_aead_request_alloc (ks->encrypt, skb, src_nents + dst_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_pktid_xmit_next (&ks->pid_xmit, &pktid); +if (unlikely (ret <0 )) +return ret; +ovpn_aead_encrypt_header (peer, ks, pktid, iv, aad); + + 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 ); + mapped =skb_to_sgvec_nomark (skb, src +1 ,0 , skb->len); +if (unlikely (mapped <0 )) +return mapped; +sg_mark_end (&src[mapped]); + +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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
Sabrina Dubroca
reviewerRe: [Openvpn-devel] [PATCH ovpn net-next 4/8] ovpn: convert GSO input into UDP GSO output
Quoted 44 lines · Ralf Lici
void
ovpn_encrypt_post(void *data, int ret)
{drivers/net/ovpn/io.c +12
-4+unsigned int orig_len, packets =1 ;struct ovpn_crypto_key_slot *ks;struct sk_buff *skb = data;struct ovpn_socket *sock; +struct ovpn_cb *batch_cb;struct ovpn_peer *peer; -unsigned int orig_len; +struct sk_buff *batch;/* encryption is happening asynchronously. This function will be * called later by the crypto callback with a proper return value @@ -249,15 +259,19 @@ void ovpn_encrypt_post(void *data, int ret)if (unlikely (ret == -EINPROGRESS ))return ; - ks =ovpn_skb_cb (skb)->ks; +/* ordinary encryption leaves batch zeroed; a GSO input uses it to find +* the aggregate whose lifetime is shared by all segment requests +*/ + batch =ovpn_skb_cb (skb)->batch; peer =ovpn_skb_cb (skb)->peer; + ks =ovpn_skb_cb (skb)->ks;/* crypto is done, cleanup skb CB and its members */ kfree (ovpn_skb_cb (skb)->crypto_tmp);if (unlikely (ret == -ERANGE )) {/* we ran out of IVs and we must kill the key as it can't be -* use anymore +* used anymore */ netdev_warn(peer->ovpn->dev, "killing key %u for peer %u\n", ks->key_id,@@ -265,8 +279,30 @@ void ovpn_encrypt_post(void *data, int ret)if (ovpn_crypto_kill_key (&peer->crypto, ks->key_id))/* let userspace know so that a new key must be negotiated */ ovpn_nl_key_swap_notify (peer, ks->key_id); + } - goto err; +if (batch) { + batch_cb =ovpn_skb_cb (batch);
drivers/net/ovpn/io.c void ovpn_encrypt_post(void *data, int ret) · line 295
Quoted 10 lines
drivers/net/ovpn/io.c +10
-0+/* every segment publishes its result before releasing its +* pending count and only the final completion continues with +* the aggregate +*/ +if (unlikely (ret <0 )) +atomic_set (&batch_cb->batch_state.failed,1 ); + +kfree_skb (skb); +if (!atomic_dec_and_test (&batch_cb->batch_state.pending)) +return ;
Without reviewing the whole thing in detail:
You're getting into the same kind of horrible mess that ktls had to deal with because of async crypto... maybe AI reviews manage to make this type of code actually work now, but I wouldn't bet my life on that.
Signature · 7 lines
Sabrina _______________________________________________ Openvpn-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/openvpn-devel
Ralf Lici
reviewerRe: [Openvpn-devel] [PATCH ovpn net-next 4/8] ovpn: convert GSO input into UDP GSO output
Hi Sabrina,
Quoted 58 lines · Sabrina Dubroca
Quoted 44 lines · Ralf Lici
void
ovpn_encrypt_post(void *data, int ret)
{drivers/net/ovpn/io.c +12
-4+unsigned int orig_len, packets =1 ;struct ovpn_crypto_key_slot *ks;struct sk_buff *skb = data;struct ovpn_socket *sock; +struct ovpn_cb *batch_cb;struct ovpn_peer *peer; -unsigned int orig_len; +struct sk_buff *batch;/* encryption is happening asynchronously. This function will be * called later by the crypto callback with a proper return value @@ -249,15 +259,19 @@ void ovpn_encrypt_post(void *data, int ret)if (unlikely (ret == -EINPROGRESS ))return ; - ks =ovpn_skb_cb (skb)->ks; +/* ordinary encryption leaves batch zeroed; a GSO input uses it to find +* the aggregate whose lifetime is shared by all segment requests +*/ + batch =ovpn_skb_cb (skb)->batch; peer =ovpn_skb_cb (skb)->peer; + ks =ovpn_skb_cb (skb)->ks;/* crypto is done, cleanup skb CB and its members */ kfree (ovpn_skb_cb (skb)->crypto_tmp);if (unlikely (ret == -ERANGE )) {/* we ran out of IVs and we must kill the key as it can't be -* use anymore +* used anymore */ netdev_warn(peer->ovpn->dev, "killing key %u for peer %u\n", ks->key_id,@@ -265,8 +279,30 @@ void ovpn_encrypt_post(void *data, int ret)if (ovpn_crypto_kill_key (&peer->crypto, ks->key_id))/* let userspace know so that a new key must be negotiated */ ovpn_nl_key_swap_notify (peer, ks->key_id); + } - goto err; +if (batch) { + batch_cb =ovpn_skb_cb (batch);drivers/net/ovpn/io.c void ovpn_encrypt_post(void *data, int ret) · line 295
Quoted 10 lines
drivers/net/ovpn/io.c +10
-0+/* every segment publishes its result before releasing its +* pending count and only the final completion continues with +* the aggregate +*/ +if (unlikely (ret <0 )) +atomic_set (&batch_cb->batch_state.failed,1 ); + +kfree_skb (skb); +if (!atomic_dec_and_test (&batch_cb->batch_state.pending)) +return ;Without reviewing the whole thing in detail:
Quoted 5 lines
You're getting into the same kind of horrible mess that ktls had to deal with because of async crypto... maybe AI reviews manage to make this type of code actually work now, but I wouldn't bet my life on that.
Oh, that sounds a bit unsettling.
Thanks for the pointer though, I went through kTLS's history and I think I have identified what you're referring to: commit a42055e8d2c3 ("net/tls: Add support for async encryption of records for performance") and the long chain of fixes that followed. However, just by reading that commit message, I think we're really talking about two very different architectures.
In this series, the batch size and output aggregate are fixed before the first submission, and the pending count already includes every segment. Each request owns one input skb and writes only to a disjoint output range. The aggregate remains alive, and its peer and key references remain held, until every request has reached a terminal completion.
There is no ordered ready list, userspace buffer handoff, partial transmitted record, waiter or worker. The final completion simply sends or drops the aggregate.
We also exercised this path with the asynchronous Safexcel backend on the GL.iNet routers, so it survived async crypto in that setup.
If you see an async-specific lifetime or ordering case that this design does not cover, I'd appreciate a pointer. If necessary, we could gate the batching path to synchronous backends, although so far I don't see a concrete reason to do so.
Thanks.
Signature · 8 lines
Ralf Lici Mandelbit Srl _______________________________________________ Openvpn-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/openvpn-devel
Ralf Lici
reviewerPATCH 5/8 ovpn: reserve packet-ID ranges for UDP GSO batches
In reply to Ralf LiciThe UDP GSO path currently allocates one packet ID for every input segment. This performs one atomic update per segment even though the whole batch is prepared by a single caller.
Reserve the complete consecutive range before dequeuing any input skb, then assign its IDs to segments in queue order. This reduces packet-ID allocation to one atomic operation per batch and leaves the input queue intact when no complete range remains, allowing the ordinary transmit path to retry with a newly installed key.
If the requested range would cross the end of the packet-ID space, publish exhaustion immediately. Packet IDs abandoned after a later encryption failure remain unused, so an AEAD IV is never reused.
---
New patch since RFC v4.
drivers/net/ovpn/crypto_aead.c | 7 ++--- drivers/net/ovpn/crypto_aead.h | 2 +- drivers/net/ovpn/io.c | 32 ++++++++++++++++++----- drivers/net/ovpn/pktid.h | 48 ++++++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 13 deletions(-)
drivers/net/ovpn/crypto_aead.c +2 -5
@@ -260,7 +260,8 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,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) +struct sk_buff *gso_skb,unsigned int offset, +u32 pktid) {const unsigned int dst_nents =skb_shinfo (gso_skb)->nr_frags +4 ;const unsigned int src_nents =skb_shinfo (skb)->nr_frags +2 ;
@@ -268,7 +269,6 @@ int ovpn_aead_encrypt_gso(struct ovpn_peer *peer,struct aead_request *req;unsigned int payload_off;int dst_idx, mapped, ret; -u32 pktid;u8 *aad, *iv;/* each input records the shared peer and key for the common completion
@@ -285,9 +285,6 @@ int ovpn_aead_encrypt_gso(struct ovpn_peer *peer, dst = src + src_nents; aad = (u8 *)(dst + dst_nents); - ret =ovpn_pktid_xmit_next (&ks->pid_xmit, &pktid); -if (unlikely (ret <0 )) -return ret;ovpn_aead_encrypt_header (peer, ks, pktid, iv, aad); ret =skb_store_bits (gso_skb, offset, aad,OVPN_AAD_SIZE );
drivers/net/ovpn/crypto_aead.h +1 -1
@@ -20,7 +20,7 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,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); +unsigned int offset,u32 pktid);int ovpn_aead_decrypt (struct ovpn_peer *peer,struct ovpn_crypto_key_slot *ks,struct sk_buff *skb);
drivers/net/ovpn/io.c +25 -7
@@ -243,6 +243,16 @@ void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb)ovpn_decrypt_post (skb,ovpn_aead_decrypt (peer, ks, skb)); } +static void ovpn_encrypt_key_exhausted (struct ovpn_peer *peer, +struct ovpn_crypto_key_slot *ks) +{ +netdev_warn (peer->ovpn->dev,"killing key %u for peer %u\n" , + ks->key_id, peer->id); +if (ovpn_crypto_kill_key (&peer->crypto, ks->key_id)) +/* let userspace know so that a new key must be negotiated */ +ovpn_nl_key_swap_notify (peer, ks->key_id); +} +void ovpn_encrypt_post (void *data,int ret) {unsigned int orig_len, packets =1 ;
@@ -273,12 +283,7 @@ void ovpn_encrypt_post(void *data, int ret)/* we ran out of IVs and we must kill the key as it can't be * used anymore */ -netdev_warn (peer->ovpn->dev, -"killing key %u for peer %u\n" , ks->key_id, - peer->id); -if (ovpn_crypto_kill_key (&peer->crypto, ks->key_id)) -/* let userspace know so that a new key must be negotiated */ -ovpn_nl_key_swap_notify (peer, ks->key_id); +ovpn_encrypt_key_exhausted (peer, ks); }if (batch) {
@@ -392,6 +397,7 @@ static bool ovpn_encrypt_gso_queue(struct sk_buff_head *skbs,unsigned int offset =0 , i, len;struct ovpn_crypto_key_slot *ks;struct sk_buff *skb; +u32 first_pktid;/* acquire all shared state before removing the first input skb so that * failure can leave the queue intact for the ordinary transmit path
@@ -400,6 +406,17 @@ static bool ovpn_encrypt_gso_queue(struct sk_buff_head *skbs,if (unlikely (!ks))return false; +/* reserve the complete range before dequeuing any input skb so that a +* failure leaves the ordinary transmit path available as a fallback +*/ +if (unlikely (ovpn_pktid_xmit_range (&ks->pid_xmit, segments, + &first_pktid))) { +ovpn_encrypt_key_exhausted (peer, ks); +ovpn_crypto_key_slot_put (ks); +ovpn_peer_put (peer); +return false; + } +/* the aggregate owns these references until every sync or async crypto * completion has finished */
@@ -416,7 +433,8 @@ static bool ovpn_encrypt_gso_queue(struct sk_buff_head *skbs,memset (ovpn_skb_cb (skb),0 ,sizeof (struct ovpn_cb ));ovpn_skb_cb (skb)->batch = batch;ovpn_encrypt_post (skb,ovpn_aead_encrypt_gso (peer, ks, skb, - batch, offset)); + batch, offset, + first_pktid + i)); offset += len; }
drivers/net/ovpn/pktid.h +48 -0
54 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
Ralf Lici
reviewerPATCH 6/8 ovpn: coalesce UDP data records with GRO
In reply to Ralf LiciRegister 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.
Packet taps may clone a completed aggregate before UDP receives it. Making the aggregate head private retains shared references to its child skbs, so also clone and unshare each shared child before detaching the list and handing its records to crypto.
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 mean throughput:
Forward Reverse
Without receive GRO 19.279 Gbit/s 19.819 Gbit/s With frag-list GRO 22.773 Gbit/s 22.796 Gbit/s
The preceding UDP GSO transmit path and hardware UDP segmentation were enabled in both cases. Frag-list GRO improved throughput by 18.1% forward and 15.0% reverse.
---
Changes since RFC v4 https://lore.kernel.org/openvpn-devel/e8f042bbdaeca7d9993cae49b08c4aedd8b2cbb9.1789558856.git.ralf@mandelbit.com/
- Refresh the performance measurements.
drivers/net/ovpn/io.c | 7 +- drivers/net/ovpn/udp.c | 214 ++++++++++++++++++++++++++++++++++++++++- net/core/gro.c | 1 + net/ipv4/udp_offload.c | 3 +- 4 files changed, 218 insertions(+), 7 deletions(-)
drivers/net/ovpn/io.c +3 -4
@@ -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 +212 -2
252 lines not on this page · raw message
net/core/gro.c +1 -0
@@ -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
@@ -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
Ralf Lici
reviewerPATCH 7/8 ovpn: prefetch encrypted records before GRO batch decryption
In reply to Ralf LiciFrag-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 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 mean throughput:
Forward Reverse
Without RX prefetch 22.773 Gbit/s 22.796 Gbit/s With RX prefetch 24.028 Gbit/s 23.405 Gbit/s
RX prefetch improved throughput by 5.5% forward and 2.7% reverse.
---
Changes since RFC v4 https://lore.kernel.org/openvpn-devel/b4d7ce8d66db966e5e43669f8180f6c59d4f6df0.1789558856.git.ralf@mandelbit.com/
- Cover every linear cache line when skb data is not cache-line aligned.
- Rename the prefetch helper to make its linear-only scope explicit.
- Document the comparison that selected a prefetch distance of two.
- Refresh the performance measurements.
drivers/net/ovpn/io.h | 15 +++++++++++++++ drivers/net/ovpn/udp.c | 21 ++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-)
drivers/net/ovpn/io.h +15 -0
@@ -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,18 @@#define OVPN_KEEPALIVE_SIZE 16extern const unsigned char ovpn_keepalive_message[OVPN_KEEPALIVE_SIZE ]; +static inline void ovpn_skb_prefetchw_linear (const struct sk_buff *skb) +{ +unsigned long addr = (unsigned long )skb->data; +unsigned long end = addr +skb_headlen (skb); + +/* crypto overwrites data in place, so request write ownership of each +* linear cache line before the AEAD implementation reaches it +*/ +for (; addr < end; addr =ALIGN (addr +1 ,L1_CACHE_BYTES )) +prefetchw ((void *)addr); +} +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
@@ -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 fraglist 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_linear (prefetch); + prefetch = prefetch->next; + } + skb_list_walk_safe(skb, skb, next) { +if (prefetch) { +ovpn_skb_prefetchw_linear (prefetch); + prefetch = prefetch->next; + } +/* skb_unclone can leave fraglist children shared with a packet * tap, so make each skb header private before changing its * list pointer or control block. skb_cow_data makes the packet
Signature · 8 lines
2.55.0 _______________________________________________ Openvpn-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/openvpn-devel
Ralf Lici
reviewerPATCH 8/8 ovpn: prefetch GSO segments before encryption
In reply to Ralf LiciSoftware segmentation exposes the complete skb list before encryption begins. Use encryption of the current segment to overlap fetching data needed by the next one.
For segments encrypted in place, request write ownership of every cache line in the next segment's linear area. When converting ordinary GSO input into an out-of-place UDP GSO aggregate, the linear head has just been touched by segmentation while most payload remains in shared page fragments. Read-prefetch the first direct fragment without repeating the complete scatter-gather walk performed by crypto.
Fully linear segments and fragments without a directly accessible kernel virtual address skip the fragment hint. A single skb naturally skips both forms of lookahead.
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 mean throughput:
Forward Reverse
Without TX prefetch 24.028 Gbit/s 23.405 Gbit/s With TX prefetch 24.166 Gbit/s 23.558 Gbit/s
This is a modest 0.6% increase in both directions.
---
Changes since RFC v4 https://lore.kernel.org/openvpn-devel/d085dda71a91670680cbaee1029a37d952338c35.1789558856.git.ralf@mandelbit.com/
- Retain write-prefetch for segments encrypted in place and add
read-prefetch of the first payload fragment for segments encrypted
into a UDP GSO aggregate.
- Account for fragments without a directly accessible kernel virtual
address.
- Refresh the commit message and add performance measurements.
drivers/net/ovpn/io.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+)
drivers/net/ovpn/io.c +34 -0
@@ -388,6 +388,28 @@ static bool ovpn_encrypt_one(struct ovpn_peer *peer, struct sk_buff *skb)return true; } +static void ovpn_skb_prefetch_frag (const struct sk_buff *skb) +{ +unsigned long addr, end; +const skb_frag_t *frag; + +if (!skb_shinfo (skb)->nr_frags) +return ; + + frag = &skb_shinfo (skb)->frags[0 ]; +/* prefetch requires a directly accessible kernel virtual address */ + addr = (unsigned long )skb_frag_address_safe (frag); +if (!addr) +return ; + + end = addr +skb_frag_size (frag); +/* the linear head was just touched by segmentation, so prefetch the +* first shared payload frag without repeating crypto's full sg walk +*/ +for (; addr < end; addr =ALIGN (addr +1 ,L1_CACHE_BYTES )) +prefetch ((void *)addr); +} + static bool ovpn_encrypt_gso_queue(struct sk_buff_head *skbs, struct ovpn_peer *peer, struct sk_buff *batch,
@@ -429,6 +451,12 @@ static bool ovpn_encrypt_gso_queue(struct sk_buff_head *skbs, skb =__skb_dequeue (skbs); len = skb->len +OVPN_DATA_V2_OVERHEAD ; +/* overlap fetching the next segment's shared payload with +* encryption of the current segment +*/ +if (i +1 < segments) +ovpn_skb_prefetch_frag (skb_peek (skbs)); +memset (ovpn_skb_cb (skb),0 ,sizeof (struct ovpn_cb ));ovpn_skb_cb (skb)->batch = batch; ovpn_encrypt_post(skb, ovpn_aead_encrypt_gso(peer, ks, skb,
@@ -490,6 +518,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_linear (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