ovpn: implement TCP transport

With this change ovpn is allowed to communicate to peers also via TCP.
Parsing of incoming messages is implemented through the strparser API.

Note that ovpn redefines sk_prot and sk_socket->ops for the TCP socket
used to communicate with the peer.
For this reason it needs to access inet6_stream_ops, which is declared
as extern in the IPv6 module, but it is not fully exported.

Therefore this patch is also adding EXPORT_SYMBOL_GPL(inet6_stream_ops)
to net/ipv6/af_inet6.c.

Cc: David Ahern <dsahern@kernel.org>
Cc: Eric Dumazet <edumazet@google.com>
Cc: Jakub Kicinski <kuba@kernel.org>
Cc: Paolo Abeni <pabeni@redhat.com>
Cc: Simon Horman <horms@kernel.org>
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
Link: https://patch.msgid.link/20250415-b4-ovpn-v26-11-577f6097b964@openvpn.net
Reviewed-by: Sabrina Dubroca <sd@queasysnail.net>
Tested-by: Oleksandr Natalenko <oleksandr@natalenko.name>
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Antonio Quartulli
2025-04-17 12:30:03 +02:00
committed by Paolo Abeni
parent 04ca14955f
commit 11851cbd60
11 changed files with 717 additions and 11 deletions
+1
View File
@@ -125,6 +125,7 @@ config OVPN
select CRYPTO_AES
select CRYPTO_GCM
select CRYPTO_CHACHA20POLY1305
select STREAM_PARSER
help
This module enhances the performance of the OpenVPN userspace software
by offloading the data channel processing to kernelspace.
+1
View File
@@ -18,4 +18,5 @@ ovpn-y += peer.o
ovpn-y += pktid.o
ovpn-y += socket.o
ovpn-y += stats.o
ovpn-y += tcp.o
ovpn-y += udp.o
+4
View File
@@ -22,6 +22,7 @@
#include "crypto_aead.h"
#include "netlink.h"
#include "proto.h"
#include "tcp.h"
#include "udp.h"
#include "skb.h"
#include "socket.h"
@@ -211,6 +212,9 @@ void ovpn_encrypt_post(void *data, int ret)
case IPPROTO_UDP:
ovpn_udp_send_skb(peer, sock->sock, skb);
break;
case IPPROTO_TCP:
ovpn_tcp_send_skb(peer, sock->sock, skb);
break;
default:
/* no transport configured yet */
goto err_unlock;
+3
View File
@@ -22,6 +22,7 @@
#include "io.h"
#include "peer.h"
#include "proto.h"
#include "tcp.h"
#include "udp.h"
static int ovpn_net_init(struct net_device *dev)
@@ -177,6 +178,8 @@ static int __init ovpn_init(void)
goto unreg_rtnl;
}
ovpn_tcp_init();
return 0;
unreg_rtnl:
+1
View File
@@ -10,6 +10,7 @@
#ifndef _NET_OVPN_OVPNSTRUCT_H_
#define _NET_OVPN_OVPNSTRUCT_H_
#include <linux/workqueue.h>
#include <net/gro_cells.h>
#include <uapi/linux/if_link.h>
#include <uapi/linux/ovpn.h>
+35
View File
@@ -11,6 +11,7 @@
#define _NET_OVPN_OVPNPEER_H_
#include <net/dst_cache.h>
#include <net/strparser.h>
#include "crypto.h"
#include "socket.h"
@@ -25,6 +26,18 @@
* @vpn_addrs.ipv4: IPv4 assigned to peer on the tunnel
* @vpn_addrs.ipv6: IPv6 assigned to peer on the tunnel
* @sock: the socket being used to talk to this peer
* @tcp: keeps track of TCP specific state
* @tcp.strp: stream parser context (TCP only)
* @tcp.user_queue: received packets that have to go to userspace (TCP only)
* @tcp.out_queue: packets on hold while socket is taken by user (TCP only)
* @tcp.tx_in_progress: true if TX is already ongoing (TCP only)
* @tcp.out_msg.skb: packet scheduled for sending (TCP only)
* @tcp.out_msg.offset: offset where next send should start (TCP only)
* @tcp.out_msg.len: remaining data to send within packet (TCP only)
* @tcp.sk_cb.sk_data_ready: pointer to original cb (TCP only)
* @tcp.sk_cb.sk_write_space: pointer to original cb (TCP only)
* @tcp.sk_cb.prot: pointer to original prot object (TCP only)
* @tcp.sk_cb.ops: pointer to the original prot_ops object (TCP only)
* @crypto: the crypto configuration (ciphers, keys, etc..)
* @dst_cache: cache for dst_entry used to send to peer
* @bind: remote peer binding
@@ -45,6 +58,28 @@ struct ovpn_peer {
struct in6_addr ipv6;
} vpn_addrs;
struct ovpn_socket __rcu *sock;
struct {
struct strparser strp;
struct sk_buff_head user_queue;
struct sk_buff_head out_queue;
bool tx_in_progress;
struct {
struct sk_buff *skb;
int offset;
int len;
} out_msg;
struct {
void (*sk_data_ready)(struct sock *sk);
void (*sk_write_space)(struct sock *sk);
struct proto *prot;
const struct proto_ops *ops;
} sk_cb;
struct work_struct defer_del_work;
} tcp;
struct ovpn_crypto_state crypto;
struct dst_cache dst_cache;
struct ovpn_bind __rcu *bind;
+35 -9
View File
@@ -16,6 +16,7 @@
#include "io.h"
#include "peer.h"
#include "socket.h"
#include "tcp.h"
#include "udp.h"
static void ovpn_socket_release_kref(struct kref *kref)
@@ -23,12 +24,10 @@ static void ovpn_socket_release_kref(struct kref *kref)
struct ovpn_socket *sock = container_of(kref, struct ovpn_socket,
refcount);
if (sock->sock->sk->sk_protocol == IPPROTO_UDP) {
if (sock->sock->sk->sk_protocol == IPPROTO_UDP)
ovpn_udp_socket_detach(sock);
netdev_put(sock->ovpn->dev, &sock->dev_tracker);
}
kfree_rcu(sock, rcu);
else if (sock->sock->sk->sk_protocol == IPPROTO_TCP)
ovpn_tcp_socket_detach(sock);
}
/**
@@ -38,10 +37,12 @@ static void ovpn_socket_release_kref(struct kref *kref)
*
* This function is only used internally. Users willing to release
* references to the ovpn_socket should use ovpn_socket_release()
*
* Return: true if the socket was released, false otherwise
*/
static void ovpn_socket_put(struct ovpn_peer *peer, struct ovpn_socket *sock)
static bool ovpn_socket_put(struct ovpn_peer *peer, struct ovpn_socket *sock)
{
kref_put(&sock->refcount, ovpn_socket_release_kref);
return kref_put(&sock->refcount, ovpn_socket_release_kref);
}
/**
@@ -65,6 +66,7 @@ static void ovpn_socket_put(struct ovpn_peer *peer, struct ovpn_socket *sock)
void ovpn_socket_release(struct ovpn_peer *peer)
{
struct ovpn_socket *sock;
bool released;
might_sleep();
@@ -89,11 +91,26 @@ void ovpn_socket_release(struct ovpn_peer *peer)
* detached before it can be picked by a concurrent reader.
*/
lock_sock(sock->sock->sk);
ovpn_socket_put(peer, sock);
released = ovpn_socket_put(peer, sock);
release_sock(sock->sock->sk);
/* align all readers with sk_user_data being NULL */
synchronize_rcu();
/* following cleanup should happen with lock released */
if (released) {
if (sock->sock->sk->sk_protocol == IPPROTO_UDP) {
netdev_put(sock->ovpn->dev, &sock->dev_tracker);
} else if (sock->sock->sk->sk_protocol == IPPROTO_TCP) {
/* wait for TCP jobs to terminate */
ovpn_tcp_socket_wait_finish(sock);
ovpn_peer_put(sock->peer);
}
/* we can call plain kfree() because we already waited one RCU
* period due to synchronize_rcu()
*/
kfree(sock);
}
}
static bool ovpn_socket_hold(struct ovpn_socket *sock)
@@ -105,6 +122,8 @@ static int ovpn_socket_attach(struct ovpn_socket *sock, struct ovpn_peer *peer)
{
if (sock->sock->sk->sk_protocol == IPPROTO_UDP)
return ovpn_udp_socket_attach(sock, peer->ovpn);
else if (sock->sock->sk->sk_protocol == IPPROTO_TCP)
return ovpn_tcp_socket_attach(sock, peer);
return -EOPNOTSUPP;
}
@@ -191,7 +210,14 @@ struct ovpn_socket *ovpn_socket_new(struct socket *sock, struct ovpn_peer *peer)
goto sock_release;
}
if (sock->sk->sk_protocol == IPPROTO_UDP) {
/* TCP sockets are per-peer, therefore they are linked to their unique
* peer
*/
if (sock->sk->sk_protocol == IPPROTO_TCP) {
INIT_WORK(&ovpn_sock->tcp_tx_work, ovpn_tcp_tx_work);
ovpn_sock->peer = peer;
ovpn_peer_hold(peer);
} else if (sock->sk->sk_protocol == IPPROTO_UDP) {
/* in UDP we only link the ovpn instance since the socket is
* shared among multiple peers
*/
+6 -2
View File
@@ -21,9 +21,11 @@ struct ovpn_peer;
* struct ovpn_socket - a kernel socket referenced in the ovpn code
* @ovpn: ovpn instance owning this socket (UDP only)
* @dev_tracker: reference tracker for associated dev (UDP only)
* @peer: unique peer transmitting over this socket (TCP only)
* @sock: the low level sock object
* @refcount: amount of contexts currently referencing this object
* @rcu: member used to schedule RCU destructor callback
* @work: member used to schedule release routine (it may block)
* @tcp_tx_work: work for deferring outgoing packet processing (TCP only)
*/
struct ovpn_socket {
union {
@@ -31,11 +33,13 @@ struct ovpn_socket {
struct ovpn_priv *ovpn;
netdevice_tracker dev_tracker;
};
struct ovpn_peer *peer;
};
struct socket *sock;
struct kref refcount;
struct rcu_head rcu;
struct work_struct work;
struct work_struct tcp_tx_work;
};
struct ovpn_socket *ovpn_socket_new(struct socket *sock,
File diff suppressed because it is too large Load Diff
+36
View File
@@ -0,0 +1,36 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* OpenVPN data channel offload
*
* Copyright (C) 2019-2025 OpenVPN, Inc.
*
* Author: Antonio Quartulli <antonio@openvpn.net>
*/
#ifndef _NET_OVPN_TCP_H_
#define _NET_OVPN_TCP_H_
#include <linux/net.h>
#include <linux/skbuff.h>
#include <linux/types.h>
#include "peer.h"
#include "skb.h"
#include "socket.h"
void __init ovpn_tcp_init(void);
int ovpn_tcp_socket_attach(struct ovpn_socket *ovpn_sock,
struct ovpn_peer *peer);
void ovpn_tcp_socket_detach(struct ovpn_socket *ovpn_sock);
void ovpn_tcp_socket_wait_finish(struct ovpn_socket *sock);
/* Prepare skb and enqueue it for sending to peer.
*
* Preparation consist in prepending the skb payload with its size.
* Required by the OpenVPN protocol in order to extract packets from
* the TCP stream on the receiver side.
*/
void ovpn_tcp_send_skb(struct ovpn_peer *peer, struct socket *sock, struct sk_buff *skb);
void ovpn_tcp_tx_work(struct work_struct *work);
#endif /* _NET_OVPN_TCP_H_ */
+1
View File
@@ -715,6 +715,7 @@ const struct proto_ops inet6_stream_ops = {
#endif
.set_rcvlowat = tcp_set_rcvlowat,
};
EXPORT_SYMBOL_GPL(inet6_stream_ops);
const struct proto_ops inet6_dgram_ops = {
.family = PF_INET6,