| // 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; | |
| } |