253 lines · 6.7 KB · C
RawDownload
// 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;}