The 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. Signed-off-by: Ralf Lici <[email protected]> --- 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(-) diff --git a/drivers/net/ovpn/crypto_aead.c b/drivers/net/ovpn/crypto_aead.c index 76028f048606..1c5387f83eca 100644 --- a/drivers/net/ovpn/crypto_aead.c +++ b/drivers/net/ovpn/crypto_aead.c @@ -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); diff --git a/drivers/net/ovpn/crypto_aead.h b/drivers/net/ovpn/crypto_aead.h index 8b444744944e..92e8ed76fba6 100644 --- a/drivers/net/ovpn/crypto_aead.h +++ b/drivers/net/ovpn/crypto_aead.h @@ -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); diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index 23f52b7cc085..d76d48d945c5 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -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; } diff --git a/drivers/net/ovpn/pktid.h b/drivers/net/ovpn/pktid.h index 21845f353bc8..c922e11da6a0 100644 --- a/drivers/net/ovpn/pktid.h +++ b/drivers/net/ovpn/pktid.h @@ -68,6 +68,54 @@ static inline int ovpn_pktid_xmit_next(struct ovpn_pktid_xmit *pid, u32 *pktid) return 0; } +/** + * ovpn_pktid_xmit_range - reserve consecutive packet IDs + * @pid: transmit packet-ID state + * @count: non-zero number of IDs to reserve + * @pktid: first reserved packet ID + * + * The reservation is all-or-nothing. If the requested range would cross the + * end of the 32-bit packet-ID space, exhaust the state without returning the + * unused tail. Packet IDs can be skipped but never reused as an AEAD IV. + * + * Return: 0 on success or -ERANGE when no complete range remains + */ +static inline int ovpn_pktid_xmit_range(struct ovpn_pktid_xmit *pid, + unsigned int count, u32 *pktid) +{ + int old = atomic_read(&pid->seq_num); + u32 first, next; + bool fits; + + for (;;) { + first = (u32)old; + next = first + count; + + /* space is already exhausted */ + if (unlikely(!first)) + return -ERANGE; + + fits = count - 1 <= U32_MAX - first; + /* if it doesn't fit we still have to publish exhaustion (0) + * so concurrent allocators stop using this key + */ + if (unlikely(!fits)) + next = 0; + + /* if another CPU changed the state, we retry using the new + * seq_num value + */ + if (!atomic_try_cmpxchg(&pid->seq_num, &old, (int)next)) + continue; + + if (unlikely(!fits)) + return -ERANGE; + + *pktid = first; + return 0; + } +} + /* Write 12-byte AEAD IV to dest */ static inline void ovpn_pktid_aead_write(const u32 pktid, const u8 nt[], -- 2.55.0 _______________________________________________ Openvpn-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/openvpn-devel
