Skip to content
/ linux Public

Commit 0315bec

Browse files
ralfliciSasha Levin
authored andcommitted
ovpn: tcp - fix packet extraction from stream
[ Upstream commit d4f687f ] When processing TCP stream data in ovpn_tcp_recv, we receive large cloned skbs from __strp_rcv that may contain multiple coalesced packets. The current implementation has two bugs: 1. Header offset overflow: Using pskb_pull with large offsets on coalesced skbs causes skb->data - skb->head to exceed the u16 storage of skb->network_header. This causes skb_reset_network_header to fail on the inner decapsulated packet, resulting in packet drops. 2. Unaligned protocol headers: Extracting packets from arbitrary positions within the coalesced TCP stream provides no alignment guarantees for the packet data causing performance penalties on architectures without efficient unaligned access. Additionally, openvpn's 2-byte length prefix on TCP packets causes the subsequent 4-byte opcode and packet ID fields to be inherently misaligned. Fix both issues by allocating a new skb for each openvpn packet and using skb_copy_bits to extract only the packet content into the new buffer, skipping the 2-byte length prefix. Also, check the length before invoking the function that performs the allocation to avoid creating an invalid skb. If the packet has to be forwarded to userspace the 2-byte prefix can be pushed to the head safely, without misalignment. As a side effect, this approach also avoids the expensive linearization that pskb_pull triggers on cloned skbs with page fragments. In testing, this resulted in TCP throughput improvements of up to 74%. Fixes: 11851cb ("ovpn: implement TCP transport") Signed-off-by: Ralf Lici <ralf@mandelbit.com> Signed-off-by: Antonio Quartulli <antonio@openvpn.net> Reviewed-by: Sabrina Dubroca <sd@queasysnail.net> Signed-off-by: David S. Miller <davem@davemloft.net> Signed-off-by: Sasha Levin <sashal@kernel.org>
1 parent 8178567 commit 0315bec

File tree

1 file changed

+36
-17
lines changed

1 file changed

+36
-17
lines changed

drivers/net/ovpn/tcp.c

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -70,37 +70,56 @@ static void ovpn_tcp_to_userspace(struct ovpn_peer *peer, struct sock *sk,
7070
peer->tcp.sk_cb.sk_data_ready(sk);
7171
}
7272

73-
static void ovpn_tcp_rcv(struct strparser *strp, struct sk_buff *skb)
73+
static struct sk_buff *ovpn_tcp_skb_packet(const struct ovpn_peer *peer,
74+
struct sk_buff *orig_skb,
75+
const int pkt_len, const int pkt_off)
7476
{
75-
struct ovpn_peer *peer = container_of(strp, struct ovpn_peer, tcp.strp);
76-
struct strp_msg *msg = strp_msg(skb);
77-
size_t pkt_len = msg->full_len - 2;
78-
size_t off = msg->offset + 2;
79-
u8 opcode;
77+
struct sk_buff *ovpn_skb;
78+
int err;
8079

81-
/* ensure skb->data points to the beginning of the openvpn packet */
82-
if (!pskb_pull(skb, off)) {
83-
net_warn_ratelimited("%s: packet too small for peer %u\n",
84-
netdev_name(peer->ovpn->dev), peer->id);
80+
/* create a new skb with only the content of the current packet */
81+
ovpn_skb = netdev_alloc_skb(peer->ovpn->dev, pkt_len);
82+
if (unlikely(!ovpn_skb))
8583
goto err;
86-
}
8784

88-
/* strparser does not trim the skb for us, therefore we do it now */
89-
if (pskb_trim(skb, pkt_len) != 0) {
90-
net_warn_ratelimited("%s: trimming skb failed for peer %u\n",
85+
skb_copy_header(ovpn_skb, orig_skb);
86+
err = skb_copy_bits(orig_skb, pkt_off, skb_put(ovpn_skb, pkt_len),
87+
pkt_len);
88+
if (unlikely(err)) {
89+
net_warn_ratelimited("%s: skb_copy_bits failed for peer %u\n",
9190
netdev_name(peer->ovpn->dev), peer->id);
91+
kfree_skb(ovpn_skb);
9292
goto err;
9393
}
9494

95-
/* we need the first 4 bytes of data to be accessible
95+
consume_skb(orig_skb);
96+
return ovpn_skb;
97+
err:
98+
kfree_skb(orig_skb);
99+
return NULL;
100+
}
101+
102+
static void ovpn_tcp_rcv(struct strparser *strp, struct sk_buff *skb)
103+
{
104+
struct ovpn_peer *peer = container_of(strp, struct ovpn_peer, tcp.strp);
105+
struct strp_msg *msg = strp_msg(skb);
106+
int pkt_len = msg->full_len - 2;
107+
u8 opcode;
108+
109+
/* we need at least 4 bytes of data in the packet
96110
* to extract the opcode and the key ID later on
97111
*/
98-
if (!pskb_may_pull(skb, OVPN_OPCODE_SIZE)) {
112+
if (unlikely(pkt_len < OVPN_OPCODE_SIZE)) {
99113
net_warn_ratelimited("%s: packet too small to fetch opcode for peer %u\n",
100114
netdev_name(peer->ovpn->dev), peer->id);
101115
goto err;
102116
}
103117

118+
/* extract the packet into a new skb */
119+
skb = ovpn_tcp_skb_packet(peer, skb, pkt_len, msg->offset + 2);
120+
if (unlikely(!skb))
121+
goto err;
122+
104123
/* DATA_V2 packets are handled in kernel, the rest goes to user space */
105124
opcode = ovpn_opcode_from_skb(skb, 0);
106125
if (unlikely(opcode != OVPN_DATA_V2)) {
@@ -113,7 +132,7 @@ static void ovpn_tcp_rcv(struct strparser *strp, struct sk_buff *skb)
113132
/* The packet size header must be there when sending the packet
114133
* to userspace, therefore we put it back
115134
*/
116-
skb_push(skb, 2);
135+
*(__be16 *)__skb_push(skb, sizeof(u16)) = htons(pkt_len);
117136
ovpn_tcp_to_userspace(peer, strp->sk, skb);
118137
return;
119138
}

0 commit comments

Comments
 (0)