ovpn: construct and process GRO batches
Subsystems (4)
Ralf Lici
reviewerv3 cover ovpn: construct and process GRO batches
Hi,
These patches were initially included in a joint GSO-GRO series [1] implementing both TX and RX batching mechanisms in the ovpn data path. Sashiko's review of v2 of that series identified fundamental flaws in the GRO part which required a broad redesign of the new RX path. So I decided to proceed with the GSO series separately [2] and keep this series GRO-specific since the two mechanisms are not functionally dependent. The series applies both on its own and on top of [2].
The redesign uses the standard SKB_GSO_UDP_L4 | SKB_GSO_FRAGLIST representation instead of giving an ovpn aggregate tunnel-specific completion semantics. This requires UDP to let an encapsulation callback opt into consuming that representation, while retaining a segment-and-redeliver fallback when the callback is absent or declines it. It also requires detachable GRO callbacks: unlike a conventional kernel-owned tunnel socket, ovpn attaches to a userspace socket which can outlive its association with the driver. The first patch fixes a pre-existing UDP frag-list GRO checksum problem on CHECKSUM_COMPLETE devices which the new receive path would otherwise hit; it carries a Fixes tag and does not depend on the rest of the series. The next three provide those core contracts and make UDP_ENCAP changes retire detachable callbacks before replacing the encapsulation state. Clearly, these four patches are more suited to netdev than openvpn-devel, but in order to keep this series applicable and reviewable, they are included in this openvpn-devel-targeted series. Patch 2 explains why this opt-in does not reopen commit 3d010c8031e3 and shows that consuming the aggregate directly is 22.7% to 25.2% faster than letting UDP segment it first.
On receive, the ovpn GRO callback collects compatible DATA_V2 records from one outer transport flow, matching their opcode, key and peer header while retaining every complete encrypted record as a separate frag-list entry. The aggregate continues through the ordinary outer receive stack. At the encapsulation callback, ovpn validates its layout, ownership, headers and outer tuple before detaching the records, propagating post-GRO metadata and authenticating each one independently. Any aggregate which ovpn cannot accept whole, such as a foreign or modified one, is declined, causing UDP to segment it and retry delivery one datagram at a time. The final patch uses the resulting lookahead to prefetch ciphertext two records before it is decrypted.
Performance was measured with the GSO series [2] applied solely to keep transmit from limiting the receive comparison. Five interleaved 60-second iperf3 -Z single-flow AES-128-GCM runs in each direction, after a 10-second warm-up, over two directly connected 100-Gbit/s mlx5 ports produced:
Forward Reverse
Without GRO 18.721 Gbit/s 18.777 Gbit/s With GRO 21.542 Gbit/s 21.366 Gbit/s With GRO + RX prefetch 25.741 Gbit/s 25.150 Gbit/s
The complete series improved throughput by 37.5% forward and 33.9% reverse. Across the monitored data-path CPUs, mean cycles per byte fell from 4.535 to 3.896 (-14.1%) and instructions per byte from 10.913 to 9.492 (-13.0%). Individual patch messages contain the incremental measurements and further test details.
Thanks,
Ralf Lici
Mandelbit Srl
[1] https://lore.kernel.org/openvpn-devel/[email protected]/
[2] https://lore.kernel.org/openvpn-devel/[email protected]/
---
Changes since v2 https://lore.kernel.org/openvpn-devel/[email protected]/
- Point the Fixes tag of the checksum fix at the commit which stopped
converting CHECKSUM_COMPLETE, and explain why only the checksum state
can carry the GRO verdict. (Sashiko)
- Restore the pre-encapsulation checksum bypass for accepted aggregates,
since a tunnel can reset their checksum state. (Sashiko)
- Refuse records inside FOU or GUE in the ovpn GRO callback. (Sashiko)
- Rebase on current ovpn-net-next.
Changes since v1 https://lore.kernel.org/openvpn-devel/[email protected]/
- Add a UDP fix which turns CHECKSUM_COMPLETE frag-list aggregates into CHECKSUM_UNNECESSARY at GRO completion, and drop the pre-encapsulation checksum bypass which it makes redundant. (Sashiko)
- Run the inbound XFRM policy check and conntrack reset once for an
accepted aggregate, so that a declined one is not checked again
without that state, and count every datagram of a dropped aggregate.
(Sashiko)
- Read frag-list acceptance with acquire ordering and warn when setting
up a socket which still accepts frag-lists. (Sashiko)
- Require a detachable GRO socket to have no registered GRO callback,
and refuse to attach ovpn to a socket which another encapsulation has configured. (Sashiko)
- Read a held aggregate's OpenVPN header without pulling it, and never
pull an aggregate before deciding whether to consume it. (Sashiko)
- Clear the redirect, classification and delivery time state which the
receive stack consumed on the aggregate head only. (Sashiko)
Ralf Lici (6):
udp: keep the GRO checksum verdict for CHECKSUM_COMPLETE frag-lists
udp: let encapsulation callbacks consume UDP frag-list GSO
udp_tunnel: support detachable GRO receive callbacks
udp: detach tunnel GRO before changing UDP encapsulation
ovpn: coalesce UDP data records with GRO
ovpn: prefetch encrypted records before GRO batch decryption
drivers/net/ovpn/udp.c | 368 +++++++++++++++++++++++++++++++++++-- include/linux/udp.h | 18 ++ include/net/udp_tunnel.h | 3 + net/core/gro.c | 1 + net/ipv4/udp.c | 79 ++++++-- net/ipv4/udp_offload.c | 67 ++++++- net/ipv4/udp_tunnel_core.c | 37 +++- net/ipv6/udp.c | 73 ++++++-- net/ipv6/udp_offload.c | 8 + 9 files changed, 608 insertions(+), 46 deletions(-)
base-commit: 7b6ab4dbc156e2f96419715130b3d5738a306c93
Signature · 8 lines
2.56.0 _______________________________________________ Openvpn-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/openvpn-devel
Ralf Lici
reviewerPATCH v3 1/6 udp: keep the GRO checksum verdict for CHECKSUM_COMPLETE frag-lists
Frag-list GRO validates the UDP checksum of every datagram before linking it into an aggregate. Completion makes the head's uh->len span the whole aggregate, but keeps uh->check for the first datagram, which segmentation restores, so only the checksum state can carry that verdict. Completion records it by turning a CHECKSUM_NONE head into CHECKSUM_UNNECESSARY, but leaves a CHECKSUM_COMPLETE head untouched, whose checksum then covers only the first datagram.
Commit f0b8c3034556 ("udp: do not transition UDP GRO fraglist partial checksums to unnecessary") limited the conversion to CHECKSUM_NONE so that CHECKSUM_PARTIAL packets are not marked as validated. It kept CHECKSUM_COMPLETE because segmentation restores valid datagrams, but the aggregate is validated before it is segmented.
Any later validation of such an aggregate fails. udp4_csum_init and udp6_csum_init compute the complete checksum of an aggregate no longer than CHECKSUM_BREAK and drop it as a checksum error. For a larger aggregate, conntrack's nf_checksum in PRE_ROUTING falls back to a software checksum of the whole skb, which fails, marks the packet invalid and sets csum_complete_sw, so UDP drops the aggregate too.
Record the verdict for a CHECKSUM_COMPLETE head as for a device which validated the checksums itself, so that completion yields the same CHECKSUM_UNNECESSARY state as for CHECKSUM_NONE and CHECKSUM_UNNECESSARY. CHECKSUM_PARTIAL stays as that commit intended.
With rx-gro-list enabled on an mlx5 port, which reports CHECKSUM_COMPLETE, a 100 Mbit/s flow of 30-byte UDP datagrams to a local socket raised UdpInCsumErrors by 3880 in five seconds and lost exactly twice as many datagrams, one pair per dropped aggregate. With rx-gro-list disabled, the counter stayed at zero.
TCP frag-list completion follows the same pattern and is not changed here.
Sashiko reported the issue while reviewing an ovpn series which uses frag-list GRO.
---
Changes since v2 https://lore.kernel.org/openvpn-devel/86994f75fd7d92f745f06e4392f89ff2ee12c3d8.1794329430.git.ralf@mandelbit.com/
- Reference the commit which stopped converting CHECKSUM_COMPLETE in the
Fixes tag. (Sashiko)
- Explain why only the checksum state can carry the GRO verdict.
New in v2.
net/ipv4/udp_offload.c | 8 ++++++++ net/ipv6/udp_offload.c | 8 ++++++++ 2 files changed, 16 insertions(+)
net/ipv4/udp_offload.c +8 -0
@@ -965,6 +965,14 @@ INDIRECT_CALLABLE_SCOPE int udp4_gro_complete(struct sk_buff *skb, int nhoff)skb_shinfo (skb)->gso_type |= (SKB_GSO_FRAGLIST |SKB_GSO_UDP_L4 );skb_shinfo (skb)->gso_segs =NAPI_GRO_CB (skb)->count; +/* GRO validated every datagram, but a CHECKSUM_COMPLETE value +* covers only the first one: keep the verdict instead, as for +* a device which validated the checksums itself +*/ +if (skb->ip_summed ==CHECKSUM_COMPLETE ) { + skb->ip_summed =CHECKSUM_UNNECESSARY ; + skb->csum_level =0 ; + }__skb_incr_checksum_unnecessary (skb);return 0 ;
net/ipv6/udp_offload.c +8 -0
@@ -176,6 +176,14 @@ int udp6_gro_complete(struct sk_buff *skb, int nhoff)skb_shinfo (skb)->gso_type |= (SKB_GSO_FRAGLIST |SKB_GSO_UDP_L4 );skb_shinfo (skb)->gso_segs =NAPI_GRO_CB (skb)->count; +/* GRO validated every datagram, but a CHECKSUM_COMPLETE value +* covers only the first one: keep the verdict instead, as for +* a device which validated the checksums itself +*/ +if (skb->ip_summed ==CHECKSUM_COMPLETE ) { + skb->ip_summed =CHECKSUM_UNNECESSARY ; + skb->csum_level =0 ; + }__skb_incr_checksum_unnecessary (skb);return 0 ;
Signature · 8 lines
2.56.0 _______________________________________________ Openvpn-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/openvpn-devel
Ralf Lici
reviewerPATCH v3 2/6 udp: let encapsulation callbacks consume UDP frag-list GSO
In reply to Ralf LiciSince commit 3d010c8031e3 ("udp: do not accept non-tunnel GSO skbs landing in a tunnel"), udp_unexpected_gso segments generic UDP L4 and frag-list GSO before passing packets to an encapsulation callback, because GRO socket lookup cannot prove that such an aggregate will not later reach a tunnel. This protects callbacks which only understand UDP tunnel GSO packets, but also prevents an encapsulator from deliberately batching independent outer UDP records as a standard frag-list aggregate.
Add an explicit per-socket opt-in for an encap_rcv callback which accepts the exact SKB_GSO_UDP_L4 | SKB_GSO_FRAGLIST representation. Publish acceptance only after the callbacks which consume that representation, and read it with acquire ordering.
GRO validated every datagram before linking it into this representation, while the header checksum of the aggregate covers only the first one, and a tunnel can reset the checksum state on the way. Trust the GRO verdict for an accepted aggregate instead of checking it before encapsulation, as udp_post_segment_fix_csum does after segmentation.
Treat the opt-in as provisional: segment the aggregate and process every datagram normally if the callback is absent or declines it. A declining callback must leave the aggregate unmodified. This keeps the combined representation away from the UDP socket and leaves callbacks which do not opt in protected as before.
Check the inbound XFRM policy and reset conntrack state once for an aggregate passed to such a callback. Its datagrams share the verdict, and a check after a fallback would lack the state which the first one consumed.
Preserve the sampled acceptance decision across concurrent callback changes and account every consumed datagram in UDP statistics.
Consuming the aggregate whole avoids rebuilding every datagram only for the encapsulator to discard its outer headers, and lets the callback amortize per-batch work and look ahead across records. With this series and the ovpn UDP GSO transmit series applied, five interleaved 60-second iperf3 -Z single-flow AES-128-GCM runs in each direction over two directly connected 100-Gbit/s mlx5 ports, after a 10-second warm-up, compared direct consumption by the ovpn GRO callback with the same kernel in which ovpn declines every aggregate:
Forward Reverse
UDP segmentation 20.561 Gbit/s 20.498 Gbit/s Direct consumption 25.741 Gbit/s 25.150 Gbit/s
Direct consumption improved throughput by 25.2% forward and 22.7% reverse, and reduced mean cycles per byte across the monitored data-path CPUs by 10.6% and 8.2%, respectively.
Export skb_gro_receive_list, already shared by the core UDP and TCP frag-list GRO paths, so modular encapsulators can construct the same standard representation.
---
Changes since v2 https://lore.kernel.org/openvpn-devel/22dd867c764497525c4544c90fe08b23dbb6616e.1794329430.git.ralf@mandelbit.com/
- Restore the pre-encapsulation checksum bypass for accepted aggregates,
since a tunnel can reset their checksum state. (Sashiko)
Changes since v1 https://lore.kernel.org/openvpn-devel/19426aa5f9a5805f925014be8b057dc2fba335d7.1794314969.git.ralf@mandelbit.com/
- Drop the pre-encapsulation checksum bypass, made redundant by the
previous patch. (Sashiko)
- Check the inbound XFRM policy and reset conntrack state once for an
accepted aggregate, and count all its datagrams on a drop. (Sashiko)
- Read frag-list acceptance with acquire ordering and warn when setting
up a socket which still accepts frag-lists. (Sashiko)
- Require a declined aggregate to be left unmodified, without the
allowance for layout-only pulls.
include/linux/udp.h | 17 +++++++++ include/net/udp_tunnel.h | 1 + net/core/gro.c | 1 + net/ipv4/udp.c | 73 +++++++++++++++++++++++++++++++------- net/ipv4/udp_tunnel_core.c | 12 +++++++ net/ipv6/udp.c | 73 +++++++++++++++++++++++++++++++------- 6 files changed, 151 insertions(+), 26 deletions(-)
include/linux/udp.h +17 -0
@@ -67,6 +67,10 @@ enum {UDP_FLAGS_ACCEPT_FRAGLIST ,UDP_FLAGS_ACCEPT_L4 ,UDP_FLAGS_ENCAP_ENABLED ,/* This socket enabled encap */ +/* encap_rcv accepts UDP L4 frag-list GSO. If it does not consume such +* an aggregate, it must leave it unmodified. +*/ + UDP_FLAGS_ENCAP_ACCEPT_FRAGLIST, };/* per NUMA structure for lockless producer usage. */
@@ -211,6 +215,19 @@ static inline bool udp_encap_needed(void)return false; } +static inline bool udp_encap_accepts_fraglist (struct sock *sk, +struct sk_buff *skb) +{ +/* pairs with set_mask_bits in setup_udp_tunnel_sock, so the callbacks +* published before acceptance are visible once it is observed +*/ +return skb_is_gso (skb) && +skb_shinfo (skb)->gso_type == + (SKB_GSO_UDP_L4 |SKB_GSO_FRAGLIST ) && +test_bit_acquire (UDP_FLAGS_ENCAP_ACCEPT_FRAGLIST , + &udp_sk (sk)->udp_flags); +} +static inline bool udp_unexpected_gso (struct sock *sk,struct sk_buff *skb) {if (!skb_is_gso (skb))
include/net/udp_tunnel.h +1 -0
@@ -85,6 +85,7 @@ struct udp_tunnel_sock_cfg {void *sk_user_data;/* user data used by encap_rcv call back */ /* Used for setting up udp_sock fields, see udp.h for details */ __u8 encap_type; +unsigned long udp_flags;udp_tunnel_encap_rcv_t encap_rcv;udp_tunnel_encap_err_lookup_t encap_err_lookup;udp_tunnel_encap_err_rcv_t encap_err_rcv;
net/core/gro.c +1 -0
@@ -266,6 +266,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.c +60 -13
123 lines not on this page · raw message
net/ipv4/udp_tunnel_core.c +12 -0
@@ -71,6 +71,13 @@ static bool sk_saddr_any(struct sock *sk)void setup_udp_tunnel_sock (struct net *net,struct sock *sk,struct udp_tunnel_sock_cfg *cfg) { +const unsigned long allowed =BIT (UDP_FLAGS_ENCAP_ACCEPT_FRAGLIST ); +unsigned long flags = cfg->udp_flags & allowed; + +WARN_ON_ONCE (cfg->udp_flags & ~allowed); +/* a socket must stop accepting frag-lists before it is set up again */ +WARN_ON_ONCE (udp_test_bit (ENCAP_ACCEPT_FRAGLIST , sk)); +/* Disable multicast loopback */ inet_clear_bit (MC_LOOP , sk);
@@ -87,6 +94,11 @@ void setup_udp_tunnel_sock(struct net *net, struct sock *sk,udp_sk (sk)->gro_receive = cfg->gro_receive;udp_sk (sk)->gro_complete = cfg->gro_complete; +/* Publish frag-list acceptance only after the callbacks which consume +* that representation +*/ +set_mask_bits (&udp_sk (sk)->udp_flags, allowed, flags); +udp_tunnel_encap_enable (sk);udp_tunnel_update_gro_rcv (sk, true);
net/ipv6/udp.c +60 -13
116 lines not on this page · raw message
Signature · 8 lines
2.56.0 _______________________________________________ Openvpn-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/openvpn-devel
Ralf Lici
reviewerPATCH v3 3/6 udp_tunnel: support detachable GRO receive callbacks
In reply to Ralf LiciUDP tunnel GRO callbacks normally remain installed until their socket is destroyed. This permits the tunnel callback registry to use one callback as a global static-call target when only one tunnel type is active.
A userspace-owned socket can instead outlive its association with an encapsulator. Allow a receive callback which produces only standard UDP frag-list GSO to opt out of the callback registry and be detached while the socket remains live. Such aggregates need no tunnel completion callback and remain valid after detach. The socket must not already have a GRO callback, which would stay pinned in the registry.
Publish the detachable dispatch mode before the callback, then publish frag-list acceptance only after all callbacks which consume that representation. Load each callback once before invocation.
Provide a helper which removes the socket from GRO lookup, clears its callbacks and frag-list acceptance, and waits for in-flight network readers before retiring detachable dispatch mode. Keep the helper in udp_offload.c, which is built into the UDP stack even when NET_UDP_TUNNEL is a module, so built-in callers do not gain a dependency on udp_tunnel.ko.
Existing tunnel sockets retain the static-call path and semantics.
---
No changes since v2 https://lore.kernel.org/openvpn-devel/a98eab82229da1233a8debbe146feddc57e87c10.1794329430.git.ralf@mandelbit.com/
Changes since v1 https://lore.kernel.org/openvpn-devel/a648e3aea95cdd32b6411a2a920ada54905ba293.1794314969.git.ralf@mandelbit.com/
- Warn if a detachable socket already has a GRO callback, which would
stay pinned in the registry. (Sashiko)
include/linux/udp.h | 1 + include/net/udp_tunnel.h | 2 ++ net/ipv4/udp_offload.c | 59 +++++++++++++++++++++++++++++++++----- net/ipv4/udp_tunnel_core.c | 29 ++++++++++++++++--- 4 files changed, 80 insertions(+), 11 deletions(-)
include/linux/udp.h +1 -0
@@ -71,6 +71,7 @@ enum {* an aggregate, it must leave it unmodified. */ UDP_FLAGS_ENCAP_ACCEPT_FRAGLIST , + UDP_FLAGS_GRO_RCV_DETACHABLE, /* gro_receive may be detached */ };/* per NUMA structure for lockless producer usage. */
include/net/udp_tunnel.h +2 -0
@@ -211,10 +211,12 @@ static inline int udp_tunnel_handle_offloads(struct sk_buff *skb, bool udp_csum)#if IS_ENABLED (CONFIG_NET_UDP_TUNNEL )void udp_tunnel_update_gro_lookup (struct net *net,struct sock *sk,bool add);void udp_tunnel_update_gro_rcv (struct sock *sk,bool add); +void udp_tunnel_detach_gro (struct sock *sk);#else static inline void udp_tunnel_update_gro_lookup (struct net *net,struct sock *sk,bool add) {}static inline void udp_tunnel_update_gro_rcv (struct sock *sk,bool add) {} +static inline void udp_tunnel_detach_gro (struct sock *sk) {}#endif static inline void udp_tunnel_cleanup_gro (struct sock *sk)
net/ipv4/udp_offload.c +52 -7
125 lines not on this page · raw message
net/ipv4/udp_tunnel_core.c +25 -4
@@ -71,12 +71,28 @@ static bool sk_saddr_any(struct sock *sk)void setup_udp_tunnel_sock (struct net *net,struct sock *sk,struct udp_tunnel_sock_cfg *cfg) { -const unsigned long allowed =BIT (UDP_FLAGS_ENCAP_ACCEPT_FRAGLIST ); +const unsigned long allowed =BIT (UDP_FLAGS_GRO_RCV_DETACHABLE ) | +BIT (UDP_FLAGS_ENCAP_ACCEPT_FRAGLIST );unsigned long flags = cfg->udp_flags & allowed;WARN_ON_ONCE (cfg->udp_flags & ~allowed);/* a socket must stop accepting frag-lists before it is set up again */ WARN_ON_ONCE (udp_test_bit (ENCAP_ACCEPT_FRAGLIST , sk)); +set_mask_bits (&udp_sk (sk)->udp_flags,BIT (UDP_FLAGS_GRO_RCV_DETACHABLE ), + flags &BIT (UDP_FLAGS_GRO_RCV_DETACHABLE )); +if (flags &BIT (UDP_FLAGS_GRO_RCV_DETACHABLE )) { +WARN_ON_ONCE (cfg->gro_complete || + !(flags &BIT (UDP_FLAGS_ENCAP_ACCEPT_FRAGLIST ))); +/* the registry ignores detachable sockets, so a callback which +* is already registered there would stay pinned +*/ +WARN_ON_ONCE (udp_sk (sk)->gro_receive); +/* The socket may already be receiving packets so drain readers +* which could have observed the old dispatch mode before +* publishing the detachable callback. +*/ +synchronize_net (); + }/* Disable multicast loopback */ inet_clear_bit (MC_LOOP , sk);
@@ -91,13 +107,18 @@ void setup_udp_tunnel_sock(struct net *net, struct sock *sk,udp_sk (sk)->encap_err_rcv = cfg->encap_err_rcv;udp_sk (sk)->encap_err_lookup = cfg->encap_err_lookup;udp_sk (sk)->encap_destroy = cfg->encap_destroy; -udp_sk (sk)->gro_receive = cfg->gro_receive; -udp_sk (sk)->gro_complete = cfg->gro_complete; +WRITE_ONCE (udp_sk (sk)->gro_complete, cfg->gro_complete); +/* Publish receive after the completion callback which its aggregates +* may later require +*/ +WRITE_ONCE (udp_sk (sk)->gro_receive, cfg->gro_receive);/* Publish frag-list acceptance only after the callbacks which consume * that representation */ -set_mask_bits (&udp_sk (sk)->udp_flags, allowed, flags); +set_mask_bits (&udp_sk (sk)->udp_flags, +BIT (UDP_FLAGS_ENCAP_ACCEPT_FRAGLIST ), + flags &BIT (UDP_FLAGS_ENCAP_ACCEPT_FRAGLIST ));udp_tunnel_encap_enable (sk);
Signature · 8 lines
2.56.0 _______________________________________________ Openvpn-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/openvpn-devel
Ralf Lici
reviewerPATCH v3 4/6 udp: detach tunnel GRO before changing UDP encapsulation
In reply to Ralf LiciA UDP socket may retain a detachable tunnel GRO callback while it remains visible to userspace. If userspace subsequently changes UDP_ENCAP, replacing the encapsulation callbacks without first retiring GRO leaves the old callback lifetime and frag-list acceptance state active.
Validate the requested encapsulation type, then detach such a GRO callback before installing the new encapsulation implementation. Existing sockets are unaffected because no current user enables detachable GRO callbacks.
UDP_ENCAP already permits userspace to change the encapsulation type of a live socket even when driver state remains attached to it. Resolving that pre-existing ownership issue is outside the scope of this change. This only keeps detachable GRO state and callback lifetimes consistent when the existing transition occurs; it does not make cross-encapsulator ownership handoff supported.
---
No changes since v2 https://lore.kernel.org/openvpn-devel/26279b0076d6ad26368041341247897fc1e95f08.1794329430.git.ralf@mandelbit.com/
No changes since v1 https://lore.kernel.org/openvpn-devel/99b9cccf9845899bc36e92165d9443499fe8a284.1794314969.git.ralf@mandelbit.com/
net/ipv4/udp.c | 6 ++++++ 1 file changed, 6 insertions(+)
net/ipv4/udp.c +6 -0
@@ -3023,6 +3023,9 @@ int udp_lib_setsockopt(struct sock *sk, int level, int optname,case 0 :#ifdef CONFIG_XFRM case UDP_ENCAP_ESPINUDP : +if (udp_test_bit (GRO_RCV_DETACHABLE , sk)) +udp_tunnel_detach_gro (sk); +set_xfrm_gro_udp_encap_rcv (val, sk->sk_family, sk);#if IS_ENABLED (CONFIG_IPV6 )if (sk->sk_family ==AF_INET6 )
@@ -3035,6 +3038,9 @@ int udp_lib_setsockopt(struct sock *sk, int level, int optname,#endif fallthrough;case UDP_ENCAP_L2TPINUDP : +if (udp_test_bit (GRO_RCV_DETACHABLE , sk)) +udp_tunnel_detach_gro (sk); +WRITE_ONCE (up->encap_type, val);udp_tunnel_encap_enable (sk);break ;
Signature · 8 lines
2.56.0 _______________________________________________ Openvpn-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/openvpn-devel
Ralf Lici
reviewerPATCH v3 5/6 ovpn: coalesce UDP data records with GRO
In reply to Ralf LiciRegister a UDP tunnel GRO receive callback for ovpn data sockets and coalesce compatible DATA_V2 records from one transport flow. Keep every encrypted record as a separate frag-list entry so the receive path can authenticate it independently.
Match the complete opcode, key and peer header and require compatible outer-network and checksum state. Flush on short records, differing segment geometry, existing GSO input, records inside FOU or GUE, or the 64-record limit. Complete a held aggregate before an incompatible record from the same transport flow, preventing the new record from overtaking it.
GRO socket lookup is not authoritative, so a generic UDP frag-list aggregate may later land on an ovpn socket, for example after DNAT. Consume only aggregates with standard, unmodified geometry, private head and child skbs, linear and uniform OpenVPN headers, and an outer tuple unchanged since GRO. Decline every other aggregate so UDP segments it and retries delivery one datagram at a time. Inspect aggregates without pulling, since a declined one must reach UDP unmodified.
Detach compatible frag lists directly instead of rebuilding standalone UDP packets whose outer headers would be discarded after decryption. Repair the aggregate head accounting, retain the outer headers needed for authenticated endpoint updates, propagate post-GRO protocol, ingress interface, priority, security mark and tc index metadata to every record. Clear the outer VLAN tag, redirect and classification flags and delivery time, which the receive stack consumed on the head only.
The socket also opts into the detachable GRO receive contract, since it's owned by userspace. Refuse a socket which another encapsulation, such as ESP-in-UDP, has configured without setting sk_user_data, since its GRO callback could not be replaced. Teardown removes callbacks only while they remain owned by ovpn and waits for readers which may already have captured one. This keeps ovpn detachable GRO state and callback lifetimes consistent if UDP_ENCAP changes the socket configuration.
The GRO implementation does not depend on the linked ovpn UDP GSO transmit series, but the measurement kernel carried it so that transmit did not limit throughput. On two directly connected 100-Gbit/s mlx5 ports, five interleaved 60-second iperf3 -Z single-flow AES-128-GCM runs in each direction, after a 10-second warm-up, produced the following mean throughput:
Forward Reverse
Without GRO 18.721 Gbit/s 18.777 Gbit/s With GRO 21.542 Gbit/s 21.366 Gbit/s
GRO improved throughput by 15.1% forward and 13.8% reverse. Mean cycles and instructions per byte across the monitored data-path CPUs fell from 4.535 to 4.207 (7.2%) and from 10.913 to 9.417 (13.7%), respectively.
---
Changes since v2 https://lore.kernel.org/openvpn-devel/3f88e036a4684c371956d26962e2ce6d02d61ab1.1794329430.git.ralf@mandelbit.com/
- Refuse records inside FOU or GUE in the GRO callback. (Sashiko)
- Mark the rare admission conditions as unlikely and complete held
records through a common exit.
Changes since v1 https://lore.kernel.org/openvpn-devel/36b08ba627b885149ef45de3b6a26c52f857387d.1794314969.git.ralf@mandelbit.com/
- Refuse a socket which another encapsulation has configured without
setting sk_user_data. (Sashiko)
- Read a held aggregate's header without pulling it. (Sashiko)
- Check aggregates in the encap callback without pulling them. (Sashiko)
- Clear the redirect, classification and delivery time state which the
receive stack consumed on the aggregate head only. (Sashiko)
- Test skb_is_gso before pulling a new record's header.
drivers/net/ovpn/udp.c | 336 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 325 insertions(+), 11 deletions(-)
drivers/net/ovpn/udp.c +325 -11
@@ -8,11 +8,14 @@#include <linux/netdevice.h> #include <linux/inetdevice.h> +#include <linux/if_vlan.h> #include <linux/skbuff.h> #include <linux/socket.h> #include <linux/udp.h> +#include <linux/unaligned.h> #include <net/addrconf.h> #include <net/dst_cache.h> +#include <net/gro.h> #include <net/route.h> #include <net/transp_v6.h> #include <net/udp.h>
@@ -27,6 +30,252 @@#include "socket.h" #include "udp.h" +/* like UDP and TCP frag-list GRO */ +#define OVPN_UDP_GRO_CNT_MAX 64 + +static bool ovpn_udp_gro_header (struct sk_buff *skb,u32 *header) +{ +const unsigned int offset =skb_gro_offset (skb); + +/* GRO replaces its frag0 pointer after holding an skb, so keep the +* openvpn header linear for later candidate comparisons +*/ +if (!pskb_may_pull (skb, offset +OVPN_OPCODE_SIZE )) +return false; + + *header =get_unaligned_be32 (skb->data + offset); +return true; +} + +static struct sk_buff *ovpn_udp_gro_receive_fraglist (struct sock *sk, +struct list_head *head, +struct sk_buff *skb) +{ +const unsigned int gso_size =skb_gro_len (skb); +struct sk_buff *p, *pp =NULL ; +int ret =0 , nhoff; +bool ip_flush; +u32 header; + +/* FOU and GUE clear encap_mark and set is_fou before passing the inner +* UDP packet to GRO, so frag-list completion would run on their outer +* UDP header and skip their own completion +*/ +if (unlikely (NAPI_GRO_CB (skb)->flush ||NAPI_GRO_CB (skb)->is_fou || + skb->encapsulation ||skb_is_gso (skb))) + goto flush; + +if (!ovpn_udp_gro_header (skb, &header) || +FIELD_GET (OVPN_OPCODE_PKTTYPE_MASK , header) !=OVPN_DATA_V2 ) + goto flush; + +/* udp_gro_receive sets encap_mark before invoking a tunnel callback, +* normally because the callback recurses into an inner protocol. ovpn +* retains complete outer UDP records instead, so clear it and use +* ordinary UDP frag-list completion. +*/ +NAPI_GRO_CB (skb)->encap_mark =0 ; +NAPI_GRO_CB (skb)->is_flist =1 ; + +list_for_each_entry (p, head, list) { +if (!NAPI_GRO_CB (p)->same_flow) +continue ; + +/* A different representation, opcode, key or peer ID completes +* the held aggregate; this record can start the next one. +* Read the held header without pulling which might change the +* skb shape. +*/ +if (NAPI_GRO_CB (p)->encap_mark || !NAPI_GRO_CB (p)->is_flist || +skb_headlen (p) <skb_gro_offset (p) +OVPN_OPCODE_SIZE || +get_unaligned_be32 (p->data +skb_gro_offset (p)) != header) +return p; + +/* GRO has already matched the outer addresses and UDP ports; +* check the remaining outer IP fields +*/ + ip_flush =gro_receive_network_flush (udp_hdr (skb), +udp_hdr (p), p); + +/* The first record determines the nominal GSO size. A shorter +* final record may follow it, but a larger record cannot. +* Checksum metadata must also be uniform because the aggregate +* exposes only one checksum state. +*/ +if (gso_size >skb_shinfo (p)->gso_size || ip_flush || + skb->ip_summed != p->ip_summed || + skb->csum_level != p->csum_level) { + pp = p; + }else { +/* skb_gro_receive_list pulls the headers already +* processed by GRO before linking this skb to the +* record list so we have to manually preserve the +* outer network header location for later handling +*/ + nhoff =NAPI_GRO_CB (skb)->network_offset; +skb_set_network_header (skb, nhoff); + ret =skb_gro_receive_list (p, skb); + } + +/* complete the aggregate if the append failed, or after +* appending a shorter final record, or after reaching the +* record-count limit +*/ +if (ret || gso_size !=skb_shinfo (p)->gso_size || +NAPI_GRO_CB (p)->count >=OVPN_UDP_GRO_CNT_MAX ) + pp = p; + +return pp; + } + +return NULL ; + +flush: +/* A packet which cannot join an aggregate must not overtake records of +* the same flow which GRO is still holding. Complete those first, as +* the other GRO handlers do. +*/ +NAPI_GRO_CB (skb)->flush =1 ; +list_for_each_entry (p, head, list) +if (NAPI_GRO_CB (p)->same_flow) +return p; +return NULL ; +} + +static bool ovpn_udp_outer_match (const struct sk_buff *head, +const struct sk_buff *frag) +{ +const struct udphdr *uhf =udp_hdr (frag); +const struct udphdr *uhh =udp_hdr (head); + +/* NAT might have rewritten endpoints after GRO */ +if (unlikely (uhh->source != uhf->source || uhh->dest != uhf->dest)) +return false; + +switch (head->protocol) { +case htons (ETH_P_IP ): +return ip_hdr (head)->saddr ==ip_hdr (frag)->saddr && +ip_hdr (head)->daddr ==ip_hdr (frag)->daddr; +case htons (ETH_P_IPV6 ): +return ipv6_addr_equal (&ipv6_hdr (head)->saddr, + &ipv6_hdr (frag)->saddr) && +ipv6_addr_equal (&ipv6_hdr (head)->daddr, + &ipv6_hdr (frag)->daddr); +default : +return false; + } +} + +/* validate an skb and turn it into a list of uniform OpenVPN records or return +* NULL without modifying an aggregate which UDP must segment +*/ +static struct sk_buff *ovpn_udp_gso_prepare (struct sk_buff *skb,u32 header) +{ +unsigned int len =0 , truesize =0 ; +struct sk_buff *list, *record; + +if (!skb_is_gso (skb)) { +skb_mark_not_on_list (skb); + goto out; + } + + list =skb_shinfo (skb)->frag_list; +if (unlikely (!list)) +return NULL ; + +/* if any of the skbs is not private, ask the stack to segment */ +if (unlikely (skb_shared (skb) ||skb_cloned (skb))) +return NULL ; + +/* transformations after GRO only update the aggregate head: if its +* outer tuple changed, ask UDP to propagate it while segmenting +*/ +if (unlikely (skb_pagelen (skb) !=sizeof (struct udphdr ) + +skb_shinfo (skb)->gso_size || + !ovpn_udp_outer_match (skb, list))) +return NULL ; + + skb_walk_frags(skb, record) { +/* if the OpenVPN header is not uniform (batch created by +* generic GRO by coalescing unrelated packets) ask the stack +* to segment and handle each segment independently +*/ +if (unlikely (skb_shared (record) ||skb_cloned (record) || +skb_headlen (record) <OVPN_OPCODE_SIZE || +get_unaligned_be32 (record->data) != header)) +return NULL ; + + len += record->len; + truesize += record->truesize; + } + +if (unlikely (skb->data_len < len || skb->len < len || + skb->truesize < truesize)) +return NULL ; + +/* detach the exclusively owned fraglist */ +skb_shinfo (skb)->frag_list =NULL ; + skb->data_len -= len; + skb->truesize -= truesize; + skb->len -= len; + skb->next = list; +skb_gso_reset (skb); + +out: +/* skb_gro_receive_list has already pulled the UDP headers from child +* records, while the aggregate head still starts at UDP, so pull its +* header too before returning the list +*/ +__skb_pull (skb,sizeof (struct udphdr )); +return skb; +} + +static void ovpn_udp_recv (struct ovpn_peer *peer,struct sk_buff *list) +{ +const __be16 proto = list->protocol; +const u32 priority = list->priority; +#ifdef CONFIG_NET_SCHED +const u16 tc_index = list->tc_index; +#endif +#ifdef CONFIG_NETWORK_SECMARK +const u32 secmark = list->secmark; +#endif +const int skb_iif = list->skb_iif; +struct sk_buff *skb, *next; + + skb_list_walk_safe(list, skb, next) { +/* propagate post-GRO metadata from the list head, and clear +* the state which the receive stack consumed on the head only +*/ + skb->protocol = proto; + skb->skb_iif = skb_iif; + skb->priority = priority; +#ifdef CONFIG_NETWORK_SECMARK + skb->secmark = secmark; +#endif +#ifdef CONFIG_NET_SCHED + skb->tc_index = tc_index; +#endif +__vlan_hwaccel_clear_tag (skb); +skb_reset_redirect (skb); +#ifdef CONFIG_NET_XGRESS + skb->tc_skip_classify =0 ; +#endif +skb_clear_delivery_time (skb); +skb_mark_not_on_list (skb); + +/* keep the current reference alive for the next record before +* handing this one to crypto +*/ +if (next &&unlikely (!ovpn_peer_hold (peer))) { +DEBUG_NET_WARN_ON_ONCE (1 ); +kfree_skb_list (next); + next =NULL ; + } +ovpn_recv (peer, skb); + } +} +/* Retrieve the corresponding ovpn object from a UDP socket * rcu_read_lock must be held on entry */
@@ -69,7 +318,9 @@ static int ovpn_udp_encap_recv(struct sock *sk, struct sk_buff *skb)struct ovpn_socket *ovpn_sock;struct ovpn_priv *ovpn;struct ovpn_peer *peer; -u32 peer_id; +bool aggregate = false; +struct sk_buff *list; +u32 header, peer_id;u8 opcode; ovpn_sock =ovpn_socket_from_udp_sock (sk);
@@ -89,14 +340,28 @@ static int ovpn_udp_encap_recv(struct sock *sk, struct sk_buff *skb)* header are accessible. * They are required to fetch the OP code, the key ID and the peer ID. */ -if (unlikely (!pskb_may_pull (skb,sizeof (struct udphdr ) + -OVPN_OPCODE_SIZE ))) { +if (skb_is_gso (skb)) { +/* Only an accepted frag-list aggregate can carry OpenVPN +* records. It is checked without pulling, since one which we +* decline must reach UDP unmodified so that it can segment it. +*/ +if (unlikely (skb_shinfo (skb)->gso_type != + (SKB_GSO_UDP_L4 |SKB_GSO_FRAGLIST ))) + goto drop; + aggregate = true; + +if (unlikely (skb_headlen (skb) < +sizeof (struct udphdr ) +OVPN_OPCODE_SIZE )) + goto drop; + }else if (unlikely (!pskb_may_pull (skb,sizeof (struct udphdr ) + +OVPN_OPCODE_SIZE ))) {net_dbg_ratelimited ("%s: packet too small from UDP socket\n" ,netdev_name (ovpn->dev)); goto drop; } - opcode =ovpn_opcode_from_skb (skb,sizeof (struct udphdr )); + header =get_unaligned_be32 (skb->data +sizeof (struct udphdr )); + opcode =FIELD_GET (OVPN_OPCODE_PKTTYPE_MASK , header);if (unlikely (opcode !=OVPN_DATA_V2 )) {/* DATA_V1 is not supported */ if (opcode ==OVPN_DATA_V1 )
@@ -106,7 +371,7 @@ static int ovpn_udp_encap_recv(struct sock *sk, struct sk_buff *skb)return 1 ; } - peer_id =ovpn_peer_id_from_skb (skb,sizeof (struct udphdr )); + peer_id =FIELD_GET (OVPN_OPCODE_PEERID_MASK , header);/* some OpenVPN server implementations send data packets with the * peer-id set to UNDEF. In this case we skip the peer lookup by peer-id * and we try with the transport address
@@ -119,12 +384,27 @@ static int ovpn_udp_encap_recv(struct sock *sk, struct sk_buff *skb)if (unlikely (!peer)) goto drop; -/* pop off outer UDP header */ -__skb_pull (skb,sizeof (struct udphdr )); -ovpn_recv (peer, skb); +/* A generic aggregate may contain records for different consumers and +* post-GRO transformations may have changed only its head. Prepare +* only a private, unmodified aggregate with uniform opcode, key ID and +* peer ID; otherwise return it to UDP for segmentation. +*/ + list =ovpn_udp_gso_prepare (skb, header); +if (unlikely (!list)) { +ovpn_peer_put (peer); +return 1 ; + } + +ovpn_udp_recv (peer, list);return 0 ; drop: +/* let UDP segment an accepted aggregate so that every datagram gets +* its own verdict +*/ +if (aggregate) +return 1 ; +ovpn_dev_dstats_rx_dropped (ovpn->dev); drop_noovpn:kfree_skb (skb);
@@ -512,8 +792,11 @@ int ovpn_udp_socket_attach(struct ovpn_socket *ovpn_sock, struct socket *sock,struct udp_tunnel_sock_cfg cfg = { .sk_user_data = ovpn_sock, .encap_type =UDP_ENCAP_OVPNINUDP , + .udp_flags =BIT (UDP_FLAGS_GRO_RCV_DETACHABLE ) | +BIT (UDP_FLAGS_ENCAP_ACCEPT_FRAGLIST ), .encap_rcv = ovpn_udp_encap_recv, .encap_destroy = ovpn_udp_encap_destroy, + .gro_receive = ovpn_udp_gro_receive_fraglist, };struct ovpn_socket *old_data;int ret;
@@ -522,6 +805,18 @@ int ovpn_udp_socket_attach(struct ovpn_socket *ovpn_sock, struct socket *sock,rcu_read_lock (); old_data =rcu_dereference_sk_user_data (ovpn_sock->sk);if (!old_data) { +/* another encapsulation (such as ESP-in-UDP) may own the +* socket without setting sk_user_data, and the GRO callback +* which it may have registered cannot be replaced +*/ +if (READ_ONCE (udp_sk (ovpn_sock->sk)->encap_type) || +READ_ONCE (udp_sk (ovpn_sock->sk)->gro_receive)) { +rcu_read_unlock (); +netdev_dbg (ovpn->dev, +"provided socket already taken by other user\n" ); +return -EBUSY ; + } +/* socket is currently unused - we can take it */ rcu_read_unlock ();setup_udp_tunnel_sock (sock_net (ovpn_sock->sk), sock->sk, &cfg);
@@ -558,16 +853,35 @@ int ovpn_udp_socket_attach(struct ovpn_socket *ovpn_sock, struct socket *sock,*/ void ovpn_udp_socket_detach (struct ovpn_socket *ovpn_sock) { +struct udp_sock *up =udp_sk (ovpn_sock->sk);struct sock *sk = ovpn_sock->sk; +/* Stop accepting aggregates while encap_rcv is still available. A +* reader which sampled acceptance before it was cleared can either be +* consumed by ovpn or fall back to UDP segmentation. +*/ +if (udp_test_bit (GRO_RCV_DETACHABLE , sk) && +READ_ONCE (up->gro_receive) == ovpn_udp_gro_receive_fraglist) +udp_tunnel_detach_gro (sk); + +/* UDP_ENCAP may have replaced encap_rcv while leaving other ovpn state +* attached. Clear only callbacks still owned by ovpn. The final RCU +* grace period in ovpn_socket_release drains readers which captured +* encap_rcv before this point. +*/ +if (READ_ONCE (up->encap_rcv) == ovpn_udp_encap_recv) +WRITE_ONCE (up->encap_rcv,NULL ); +if (READ_ONCE (up->encap_destroy) == ovpn_udp_encap_destroy) +WRITE_ONCE (up->encap_destroy,NULL ); +/* Re-enable multicast loopback */ inet_set_bit (MC_LOOP , sk);/* Disable CHECKSUM_UNNECESSARY to CHECKSUM_COMPLETE conversion */ inet_dec_convert_csum (sk); -WRITE_ONCE (udp_sk (sk)->encap_type,0 ); -WRITE_ONCE (udp_sk (sk)->encap_rcv,NULL ); -WRITE_ONCE (udp_sk (sk)->encap_destroy,NULL ); +/* UDP_ENCAP might have rewritten the encap_type */ +if (READ_ONCE (up->encap_type) ==UDP_ENCAP_OVPNINUDP ) +WRITE_ONCE (up->encap_type,0 );rcu_assign_sk_user_data (sk,NULL ); }
Signature · 8 lines
2.56.0 _______________________________________________ Openvpn-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/openvpn-devel
Ralf Lici
reviewerPATCH v3 6/6 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.
This optimization primarily targets synchronous software AEAD, where the CPU which issues the hint subsequently overwrites the prefetched lines. An asynchronous accelerator may instead access them through DMA and is not expected to benefit in the same way; the cache hint remains correctness-neutral.
With the ovpn UDP GSO transmit series also applied, five interleaved 60-second iperf3 -Z single-flow AES-128-GCM runs in each direction over two directly connected 100-Gbit/s mlx5 ports, after a 10-second warm-up, produced the following mean throughput:
Forward Reverse
Without RX prefetch 21.542 Gbit/s 21.366 Gbit/s With RX prefetch 25.741 Gbit/s 25.150 Gbit/s
RX prefetch improved throughput by 19.5% forward and 17.7% reverse. Mean cycles per byte across the monitored data-path CPUs fell from 4.207 to 3.896, a 7.4% reduction.
A same-binary comparison using the aforementioned setup found distances one and two effectively tied forward, while distance two was 3.3% faster reverse and less variable.
---
No changes since v2 https://lore.kernel.org/openvpn-devel/bad00484f77503ede2affa16075325a00b5cef04.1794329430.git.ralf@mandelbit.com/
No changes since v1 https://lore.kernel.org/openvpn-devel/e18ef3363a3d5167cddb8b0528d447ba1d5aa2ed.1794314969.git.ralf@mandelbit.com/
drivers/net/ovpn/udp.c | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-)
drivers/net/ovpn/udp.c +33 -1
@@ -6,9 +6,11 @@* Author: Antonio Quartulli <[email protected]> */ +#include <linux/cache.h> #include <linux/netdevice.h> #include <linux/inetdevice.h> #include <linux/if_vlan.h> +#include <linux/prefetch.h> #include <linux/skbuff.h> #include <linux/socket.h> #include <linux/udp.h>
@@ -33,6 +35,21 @@/* 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 void ovpn_udp_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); +} +static bool ovpn_udp_gro_header (struct sk_buff *skb,u32 *header) {const unsigned int offset =skb_gro_offset (skb);
@@ -232,6 +249,7 @@ static struct sk_buff *ovpn_udp_gso_prepare(struct sk_buff *skb, u32 header)static void ovpn_udp_recv (struct ovpn_peer *peer,struct sk_buff *list) { +struct sk_buff *skb, *next, *prefetch;const __be16 proto = list->protocol;const u32 priority = list->priority;#ifdef CONFIG_NET_SCHED
@@ -241,9 +259,23 @@ static void ovpn_udp_recv(struct ovpn_peer *peer, struct sk_buff *list)const u32 secmark = list->secmark;#endif const int skb_iif = list->skb_iif; -struct sk_buff *skb, *next; +int i; + +/* 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 = list->next ? list :NULL ; +for (i =0 ; i <OVPN_UDP_GRO_PREFETCH_DISTANCE && prefetch; i++) { +ovpn_udp_prefetchw_linear (prefetch); + prefetch = prefetch->next; + } skb_list_walk_safe(list, skb, next) { +if (prefetch) { +ovpn_udp_prefetchw_linear (prefetch); + prefetch = prefetch->next; + }/* propagate post-GRO metadata from the list head, and clear * the state which the receive stack consumed on the head only */
Signature · 8 lines
2.56.0 _______________________________________________ Openvpn-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/openvpn-devel