// SPDX-License-Identifier: GPL-2.0/* OpenVPN data channel offload * * Copyright (C) 2020-2025 OpenVPN, Inc. * * Author: James Yonan <james@openvpn.net> * Antonio Quartulli <antonio@openvpn.net> */#include <linux/net.h>#include <linux/netdevice.h>#include <linux/udp.h>#include "ovpnpriv.h"#include "main.h"#include "io.h"#include "peer.h"#include "socket.h"#include "tcp.h"#include "udp.h"static void ovpn_socket_release_kref(struct kref *kref){ struct ovpn_socket *sock = container_of(kref, struct ovpn_socket, refcount); if (sock->sk->sk_protocol == IPPROTO_UDP) ovpn_udp_socket_detach(sock); else if (sock->sk->sk_protocol == IPPROTO_TCP) ovpn_tcp_socket_detach(sock);}/** * ovpn_socket_put - decrease reference counter * @peer: peer whose socket reference counter should be decreased * @sock: the RCU protected peer socket * * 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 bool ovpn_socket_put(struct ovpn_peer *peer, struct ovpn_socket *sock){ return kref_put(&sock->refcount, ovpn_socket_release_kref);}/** * ovpn_socket_release - release resources owned by socket user * @peer: peer whose socket should be released * * This function should be invoked when the peer is being removed * and wants to drop its link to the socket. * * In case of UDP, the detach routine will drop a reference to the * ovpn netdev, pointed by the ovpn_socket. * * In case of TCP, releasing the socket will cause dropping * the refcounter for the peer it is linked to, thus allowing the peer * disappear as well. * * This function is expected to be invoked exactly once per peer * * NOTE: this function may sleep */void ovpn_socket_release(struct ovpn_peer *peer){ struct ovpn_socket *sock; bool released; might_sleep(); sock = rcu_replace_pointer(peer->sock, NULL, true); /* release may be invoked after socket was detached */ if (!sock) return; /* Drop the reference while holding the sock lock to avoid * concurrent ovpn_socket_new call to mess up with a partially * detached socket. * * Holding the lock ensures that a socket with refcnt 0 is fully * detached before it can be picked by a concurrent reader. */ lock_sock(sock->sk); released = ovpn_socket_put(peer, sock); release_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->sk->sk_protocol == IPPROTO_UDP) { netdev_put(sock->ovpn->dev, &sock->dev_tracker); } else if (sock->sk->sk_protocol == IPPROTO_TCP) { /* wait for TCP jobs to terminate */ ovpn_tcp_socket_wait_finish(sock); ovpn_peer_put(sock->peer); } /* drop reference acquired in ovpn_socket_new() */ sock_put(sock->sk); /* 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){ return kref_get_unless_zero(&sock->refcount);}static int ovpn_socket_attach(struct ovpn_socket *ovpn_sock, struct socket *sock, struct ovpn_peer *peer){ if (sock->sk->sk_protocol == IPPROTO_UDP) return ovpn_udp_socket_attach(ovpn_sock, sock, peer->ovpn); else if (sock->sk->sk_protocol == IPPROTO_TCP) return ovpn_tcp_socket_attach(ovpn_sock, peer); return -EOPNOTSUPP;}/** * ovpn_socket_new - create a new socket and initialize it * @sock: the kernel socket to embed * @peer: the peer reachable via this socket * * Return: an openvpn socket on success or a negative error code otherwise */struct ovpn_socket *ovpn_socket_new(struct socket *sock, struct ovpn_peer *peer){ struct ovpn_socket *ovpn_sock; struct sock *sk = sock->sk; int ret; lock_sock(sk); /* a TCP socket can only be owned by a single peer, therefore there * can't be any other user */ if (sk->sk_protocol == IPPROTO_TCP && sk->sk_user_data) { ovpn_sock = ERR_PTR(-EBUSY); goto sock_release; } /* a UDP socket can be shared across multiple peers, but we must make * sure it is not owned by something else */ if (sk->sk_protocol == IPPROTO_UDP) { u8 type = READ_ONCE(udp_sk(sk)->encap_type); /* socket owned by other encapsulation module */ if (type && type != UDP_ENCAP_OVPNINUDP) { ovpn_sock = ERR_PTR(-EBUSY); goto sock_release; } rcu_read_lock(); ovpn_sock = rcu_dereference_sk_user_data(sk); if (ovpn_sock) { /* something else filled the sk_user_data without * setting the encap_type. Reject the socket. */ if (!type) { ovpn_sock = ERR_PTR(-EBUSY); rcu_read_unlock(); goto sock_release; } /* socket owned by another ovpn instance, we can't use it */ if (ovpn_sock->ovpn != peer->ovpn) { ovpn_sock = ERR_PTR(-EBUSY); rcu_read_unlock(); goto sock_release; } /* this socket is already owned by this instance, * therefore we can increase the refcounter and * use it as expected */ if (WARN_ON(!ovpn_socket_hold(ovpn_sock))) { /* this should never happen because setting * the refcnt to 0 and detaching the socket * is expected to be atomic */ ovpn_sock = ERR_PTR(-EAGAIN); rcu_read_unlock(); goto sock_release; } rcu_read_unlock(); goto sock_release; } rcu_read_unlock(); } /* socket is not owned: attach to this ovpn instance */ ovpn_sock = kzalloc_obj(*ovpn_sock); if (!ovpn_sock) { ovpn_sock = ERR_PTR(-ENOMEM); goto sock_release; } ovpn_sock->sk = sk; kref_init(&ovpn_sock->refcount); /* TCP sockets are per-peer, therefore they are linked to their unique * peer */ if (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 (sk->sk_protocol == IPPROTO_UDP) { /* in UDP we only link the ovpn instance since the socket is * shared among multiple peers */ ovpn_sock->ovpn = peer->ovpn; netdev_hold(peer->ovpn->dev, &ovpn_sock->dev_tracker, GFP_KERNEL); } /* the newly created ovpn_socket is holding reference to sk, * therefore we increase its refcounter. * * This ovpn_socket instance is referenced by all peers * using the same socket. * * ovpn_socket_release() will take care of dropping the reference. */ sock_hold(sk); ret = ovpn_socket_attach(ovpn_sock, sock, peer); if (ret < 0) { if (sk->sk_protocol == IPPROTO_TCP) ovpn_peer_put(peer); else if (sk->sk_protocol == IPPROTO_UDP) netdev_put(peer->ovpn->dev, &ovpn_sock->dev_tracker); sock_put(sk); kfree(ovpn_sock); ovpn_sock = ERR_PTR(ret); }sock_release: release_sock(sk); return ovpn_sock;}
// SPDX-License-Identifier: GPL-2.0
/* OpenVPN data channel offload
*
* Copyright (C) 2020-2025 OpenVPN, Inc.
*
* Author: James Yonan <james@openvpn.net>
* Antonio Quartulli <antonio@openvpn.net>
*/
#include <linux/net.h>
#include <linux/netdevice.h>
#include <linux/udp.h>
#include "ovpnpriv.h"
#include "main.h"
#include "io.h"
#include "peer.h"
#include "socket.h"
#include "tcp.h"
#include "udp.h"
static void ovpn_socket_release_kref(struct kref *kref)
{
struct ovpn_socket *sock = container_of(kref, struct ovpn_socket,
refcount);
if (sock->sk->sk_protocol == IPPROTO_UDP)
ovpn_udp_socket_detach(sock);
else if (sock->sk->sk_protocol == IPPROTO_TCP)
ovpn_tcp_socket_detach(sock);
}
/**
* ovpn_socket_put - decrease reference counter
* @peer: peer whose socket reference counter should be decreased
* @sock: the RCU protected peer socket
*
* 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 bool ovpn_socket_put(struct ovpn_peer *peer, struct ovpn_socket *sock)
{
return kref_put(&sock->refcount, ovpn_socket_release_kref);
}
/**
* ovpn_socket_release - release resources owned by socket user
* @peer: peer whose socket should be released
*
* This function should be invoked when the peer is being removed
* and wants to drop its link to the socket.
*
* In case of UDP, the detach routine will drop a reference to the
* ovpn netdev, pointed by the ovpn_socket.
*
* In case of TCP, releasing the socket will cause dropping
* the refcounter for the peer it is linked to, thus allowing the peer
* disappear as well.
*
* This function is expected to be invoked exactly once per peer
*
* NOTE: this function may sleep
*/
void ovpn_socket_release(struct ovpn_peer *peer)
{
struct ovpn_socket *sock;
bool released;
might_sleep();
sock = rcu_replace_pointer(peer->sock, NULL, true);
/* release may be invoked after socket was detached */
if (!sock)
return;
/* Drop the reference while holding the sock lock to avoid
* concurrent ovpn_socket_new call to mess up with a partially
* detached socket.
*
* Holding the lock ensures that a socket with refcnt 0 is fully
* detached before it can be picked by a concurrent reader.
*/
lock_sock(sock->sk);
released = ovpn_socket_put(peer, sock);
release_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->sk->sk_protocol == IPPROTO_UDP) {
netdev_put(sock->ovpn->dev, &sock->dev_tracker);
} else if (sock->sk->sk_protocol == IPPROTO_TCP) {
/* wait for TCP jobs to terminate */
ovpn_tcp_socket_wait_finish(sock);
ovpn_peer_put(sock->peer);
}
/* drop reference acquired in ovpn_socket_new() */
sock_put(sock->sk);
/* 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)
{
return kref_get_unless_zero(&sock->refcount);
}
static int ovpn_socket_attach(struct ovpn_socket *ovpn_sock,
struct socket *sock,
struct ovpn_peer *peer)
{
if (sock->sk->sk_protocol == IPPROTO_UDP)
return ovpn_udp_socket_attach(ovpn_sock, sock, peer->ovpn);
else if (sock->sk->sk_protocol == IPPROTO_TCP)
return ovpn_tcp_socket_attach(ovpn_sock, peer);
return -EOPNOTSUPP;
}
/**
* ovpn_socket_new - create a new socket and initialize it
* @sock: the kernel socket to embed
* @peer: the peer reachable via this socket
*
* Return: an openvpn socket on success or a negative error code otherwise
*/
struct ovpn_socket *ovpn_socket_new(struct socket *sock, struct ovpn_peer *peer)
{
struct ovpn_socket *ovpn_sock;
struct sock *sk = sock->sk;
int ret;
lock_sock(sk);
/* a TCP socket can only be owned by a single peer, therefore there
* can't be any other user
*/
if (sk->sk_protocol == IPPROTO_TCP && sk->sk_user_data) {
ovpn_sock = ERR_PTR(-EBUSY);
goto sock_release;
}
/* a UDP socket can be shared across multiple peers, but we must make
* sure it is not owned by something else
*/
if (sk->sk_protocol == IPPROTO_UDP) {
u8 type = READ_ONCE(udp_sk(sk)->encap_type);
/* socket owned by other encapsulation module */
if (type && type != UDP_ENCAP_OVPNINUDP) {
ovpn_sock = ERR_PTR(-EBUSY);
goto sock_release;
}
rcu_read_lock();
ovpn_sock = rcu_dereference_sk_user_data(sk);
if (ovpn_sock) {
/* something else filled the sk_user_data without
* setting the encap_type. Reject the socket.
*/
if (!type) {
ovpn_sock = ERR_PTR(-EBUSY);
rcu_read_unlock();
goto sock_release;
}
/* socket owned by another ovpn instance, we can't use it */
if (ovpn_sock->ovpn != peer->ovpn) {
ovpn_sock = ERR_PTR(-EBUSY);
rcu_read_unlock();
goto sock_release;
}
/* this socket is already owned by this instance,
* therefore we can increase the refcounter and
* use it as expected
*/
if (WARN_ON(!ovpn_socket_hold(ovpn_sock))) {
/* this should never happen because setting
* the refcnt to 0 and detaching the socket
* is expected to be atomic
*/
ovpn_sock = ERR_PTR(-EAGAIN);
rcu_read_unlock();
goto sock_release;
}
rcu_read_unlock();
goto sock_release;
}
rcu_read_unlock();
}
/* socket is not owned: attach to this ovpn instance */
ovpn_sock = kzalloc_obj(*ovpn_sock);
if (!ovpn_sock) {
ovpn_sock = ERR_PTR(-ENOMEM);
goto sock_release;
}
ovpn_sock->sk = sk;
kref_init(&ovpn_sock->refcount);
/* TCP sockets are per-peer, therefore they are linked to their unique
* peer
*/
if (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 (sk->sk_protocol == IPPROTO_UDP) {
/* in UDP we only link the ovpn instance since the socket is
* shared among multiple peers
*/
ovpn_sock->ovpn = peer->ovpn;
netdev_hold(peer->ovpn->dev, &ovpn_sock->dev_tracker,
GFP_KERNEL);
}
/* the newly created ovpn_socket is holding reference to sk,
* therefore we increase its refcounter.
*
* This ovpn_socket instance is referenced by all peers
* using the same socket.
*
* ovpn_socket_release() will take care of dropping the reference.
*/
sock_hold(sk);
ret = ovpn_socket_attach(ovpn_sock, sock, peer);
if (ret < 0) {
if (sk->sk_protocol == IPPROTO_TCP)
ovpn_peer_put(peer);
else if (sk->sk_protocol == IPPROTO_UDP)
netdev_put(peer->ovpn->dev, &ovpn_sock->dev_tracker);
sock_put(sk);
kfree(ovpn_sock);
ovpn_sock = ERR_PTR(ret);
}
sock_release:
release_sock(sk);
return ovpn_sock;
}