Files
linux/drivers/net/ovpn/socket.c
Antonio Quartulli ab66abbc76 ovpn: implement basic RX path (UDP)
Packets received over the socket are forwarded to the user device.

Implementation is UDP only. TCP will be added by a later patch.

Note: no decryption/decapsulation exists yet, packets are forwarded as
they arrive without much processing.

Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
Link: https://patch.msgid.link/20250415-b4-ovpn-v26-8-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>
2025-04-17 12:30:02 +02:00

208 lines
5.2 KiB
C

// 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 "udp.h"
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) {
ovpn_udp_socket_detach(sock);
netdev_put(sock->ovpn->dev, &sock->dev_tracker);
}
kfree_rcu(sock, rcu);
}
/**
* 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()
*/
static void ovpn_socket_put(struct ovpn_peer *peer, struct ovpn_socket *sock)
{
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 user is shutting
* down 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;
might_sleep();
sock = rcu_replace_pointer(peer->sock, NULL, true);
/* release may be invoked after socket was detached */
if (!sock)
return;
/* sanity check: we should not end up here if the socket
* was already closed
*/
if (!sock->sock->sk) {
DEBUG_NET_WARN_ON_ONCE(1);
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->sock->sk);
ovpn_socket_put(peer, sock);
release_sock(sock->sock->sk);
/* align all readers with sk_user_data being NULL */
synchronize_rcu();
}
static bool ovpn_socket_hold(struct ovpn_socket *sock)
{
return kref_get_unless_zero(&sock->refcount);
}
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);
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;
int ret;
lock_sock(sock->sk);
/* a TCP socket can only be owned by a single peer, therefore there
* can't be any other user
*/
if (sock->sk->sk_protocol == IPPROTO_TCP && sock->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 (sock->sk->sk_protocol == IPPROTO_UDP) {
u8 type = READ_ONCE(udp_sk(sock->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(sock->sk);
if (ovpn_sock) {
/* 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(sizeof(*ovpn_sock), GFP_KERNEL);
if (!ovpn_sock) {
ovpn_sock = ERR_PTR(-ENOMEM);
goto sock_release;
}
ovpn_sock->sock = sock;
kref_init(&ovpn_sock->refcount);
ret = ovpn_socket_attach(ovpn_sock, peer);
if (ret < 0) {
kfree(ovpn_sock);
ovpn_sock = ERR_PTR(ret);
goto sock_release;
}
if (sock->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);
}
rcu_assign_sk_user_data(sock->sk, ovpn_sock);
sock_release:
release_sock(sock->sk);
return ovpn_sock;
}