mirror of https://github.com/torvalds/linux.git
242 lines
6.4 KiB
C
242 lines
6.4 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 "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) {
|
|
/* 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->sk = sk;
|
|
kref_init(&ovpn_sock->refcount);
|
|
|
|
/* 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) {
|
|
sock_put(sk);
|
|
kfree(ovpn_sock);
|
|
ovpn_sock = ERR_PTR(ret);
|
|
goto sock_release;
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
rcu_assign_sk_user_data(sk, ovpn_sock);
|
|
sock_release:
|
|
release_sock(sk);
|
|
return ovpn_sock;
|
|
}
|