mirror of
https://github.com/torvalds/linux.git
synced 2026-03-08 03:24:45 +01:00
ovpn: introduce the ovpn_peer object
An ovpn_peer object holds the whole status of a remote peer (regardless whether it is a server or a client). This includes status for crypto, tx/rx buffers, napi, etc. Only support for one peer is introduced (P2P mode). Multi peer support is introduced with a later patch. Along with the ovpn_peer, also the ovpn_bind object is introcued as the two are strictly related. An ovpn_bind object wraps a sockaddr representing the local coordinates being used to talk to a specific peer. Signed-off-by: Antonio Quartulli <antonio@openvpn.net> Link: https://patch.msgid.link/20250415-b4-ovpn-v26-5-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>
This commit is contained in:
parent
8327a3baa9
commit
80747caef3
8 changed files with 670 additions and 1 deletions
|
|
@ -119,6 +119,7 @@ config OVPN
|
|||
tristate "OpenVPN data channel offload"
|
||||
depends on NET && INET
|
||||
depends on IPV6 || !IPV6
|
||||
select DST_CACHE
|
||||
help
|
||||
This module enhances the performance of the OpenVPN userspace software
|
||||
by offloading the data channel processing to kernelspace.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@
|
|||
# Author: Antonio Quartulli <antonio@openvpn.net>
|
||||
|
||||
obj-$(CONFIG_OVPN) := ovpn.o
|
||||
ovpn-y += bind.o
|
||||
ovpn-y += main.o
|
||||
ovpn-y += io.o
|
||||
ovpn-y += netlink.o
|
||||
ovpn-y += netlink-gen.o
|
||||
ovpn-y += peer.o
|
||||
|
|
|
|||
58
drivers/net/ovpn/bind.c
Normal file
58
drivers/net/ovpn/bind.c
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* OpenVPN data channel offload
|
||||
*
|
||||
* Copyright (C) 2012-2025 OpenVPN, Inc.
|
||||
*
|
||||
* Author: James Yonan <james@openvpn.net>
|
||||
* Antonio Quartulli <antonio@openvpn.net>
|
||||
*/
|
||||
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/socket.h>
|
||||
|
||||
#include "ovpnpriv.h"
|
||||
#include "bind.h"
|
||||
#include "peer.h"
|
||||
|
||||
/**
|
||||
* ovpn_bind_from_sockaddr - retrieve binding matching sockaddr
|
||||
* @ss: the sockaddr to match
|
||||
*
|
||||
* Return: the bind matching the passed sockaddr if found, NULL otherwise
|
||||
*/
|
||||
struct ovpn_bind *ovpn_bind_from_sockaddr(const struct sockaddr_storage *ss)
|
||||
{
|
||||
struct ovpn_bind *bind;
|
||||
size_t sa_len;
|
||||
|
||||
if (ss->ss_family == AF_INET)
|
||||
sa_len = sizeof(struct sockaddr_in);
|
||||
else if (ss->ss_family == AF_INET6)
|
||||
sa_len = sizeof(struct sockaddr_in6);
|
||||
else
|
||||
return ERR_PTR(-EAFNOSUPPORT);
|
||||
|
||||
bind = kzalloc(sizeof(*bind), GFP_ATOMIC);
|
||||
if (unlikely(!bind))
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
memcpy(&bind->remote, ss, sa_len);
|
||||
|
||||
return bind;
|
||||
}
|
||||
|
||||
/**
|
||||
* ovpn_bind_reset - assign new binding to peer
|
||||
* @peer: the peer whose binding has to be replaced
|
||||
* @new: the new bind to assign
|
||||
*/
|
||||
void ovpn_bind_reset(struct ovpn_peer *peer, struct ovpn_bind *new)
|
||||
{
|
||||
struct ovpn_bind *old;
|
||||
|
||||
spin_lock_bh(&peer->lock);
|
||||
old = rcu_replace_pointer(peer->bind, new, true);
|
||||
spin_unlock_bh(&peer->lock);
|
||||
|
||||
kfree_rcu(old, rcu);
|
||||
}
|
||||
101
drivers/net/ovpn/bind.h
Normal file
101
drivers/net/ovpn/bind.h
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/* OpenVPN data channel offload
|
||||
*
|
||||
* Copyright (C) 2012-2025 OpenVPN, Inc.
|
||||
*
|
||||
* Author: James Yonan <james@openvpn.net>
|
||||
* Antonio Quartulli <antonio@openvpn.net>
|
||||
*/
|
||||
|
||||
#ifndef _NET_OVPN_OVPNBIND_H_
|
||||
#define _NET_OVPN_OVPNBIND_H_
|
||||
|
||||
#include <net/ip.h>
|
||||
#include <linux/in.h>
|
||||
#include <linux/in6.h>
|
||||
#include <linux/rcupdate.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
struct ovpn_peer;
|
||||
|
||||
/**
|
||||
* union ovpn_sockaddr - basic transport layer address
|
||||
* @in4: IPv4 address
|
||||
* @in6: IPv6 address
|
||||
*/
|
||||
union ovpn_sockaddr {
|
||||
struct sockaddr_in in4;
|
||||
struct sockaddr_in6 in6;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ovpn_bind - remote peer binding
|
||||
* @remote: the remote peer sockaddress
|
||||
* @local: local endpoint used to talk to the peer
|
||||
* @local.ipv4: local IPv4 used to talk to the peer
|
||||
* @local.ipv6: local IPv6 used to talk to the peer
|
||||
* @rcu: used to schedule RCU cleanup job
|
||||
*/
|
||||
struct ovpn_bind {
|
||||
union ovpn_sockaddr remote; /* remote sockaddr */
|
||||
|
||||
union {
|
||||
struct in_addr ipv4;
|
||||
struct in6_addr ipv6;
|
||||
} local;
|
||||
|
||||
struct rcu_head rcu;
|
||||
};
|
||||
|
||||
/**
|
||||
* ovpn_bind_skb_src_match - match packet source with binding
|
||||
* @bind: the binding to match
|
||||
* @skb: the packet to match
|
||||
*
|
||||
* Return: true if the packet source matches the remote peer sockaddr
|
||||
* in the binding
|
||||
*/
|
||||
static inline bool ovpn_bind_skb_src_match(const struct ovpn_bind *bind,
|
||||
const struct sk_buff *skb)
|
||||
{
|
||||
const union ovpn_sockaddr *remote;
|
||||
|
||||
if (unlikely(!bind))
|
||||
return false;
|
||||
|
||||
remote = &bind->remote;
|
||||
|
||||
switch (skb->protocol) {
|
||||
case htons(ETH_P_IP):
|
||||
if (unlikely(remote->in4.sin_family != AF_INET))
|
||||
return false;
|
||||
|
||||
if (unlikely(remote->in4.sin_addr.s_addr != ip_hdr(skb)->saddr))
|
||||
return false;
|
||||
|
||||
if (unlikely(remote->in4.sin_port != udp_hdr(skb)->source))
|
||||
return false;
|
||||
break;
|
||||
case htons(ETH_P_IPV6):
|
||||
if (unlikely(remote->in6.sin6_family != AF_INET6))
|
||||
return false;
|
||||
|
||||
if (unlikely(!ipv6_addr_equal(&remote->in6.sin6_addr,
|
||||
&ipv6_hdr(skb)->saddr)))
|
||||
return false;
|
||||
|
||||
if (unlikely(remote->in6.sin6_port != udp_hdr(skb)->source))
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct ovpn_bind *ovpn_bind_from_sockaddr(const struct sockaddr_storage *sa);
|
||||
void ovpn_bind_reset(struct ovpn_peer *peer, struct ovpn_bind *bind);
|
||||
|
||||
#endif /* _NET_OVPN_OVPNBIND_H_ */
|
||||
|
|
@ -19,6 +19,7 @@
|
|||
#include "main.h"
|
||||
#include "netlink.h"
|
||||
#include "io.h"
|
||||
#include "peer.h"
|
||||
#include "proto.h"
|
||||
|
||||
static const struct net_device_ops ovpn_netdev_ops = {
|
||||
|
|
@ -92,6 +93,7 @@ static int ovpn_newlink(struct net_device *dev,
|
|||
|
||||
ovpn->dev = dev;
|
||||
ovpn->mode = mode;
|
||||
spin_lock_init(&ovpn->lock);
|
||||
|
||||
/* Set carrier explicitly after registration, this way state is
|
||||
* clearly defined.
|
||||
|
|
@ -109,6 +111,16 @@ static int ovpn_newlink(struct net_device *dev,
|
|||
return register_netdevice(dev);
|
||||
}
|
||||
|
||||
static void ovpn_dellink(struct net_device *dev, struct list_head *head)
|
||||
{
|
||||
struct ovpn_priv *ovpn = netdev_priv(dev);
|
||||
|
||||
if (ovpn->mode == OVPN_MODE_P2P)
|
||||
ovpn_peer_release_p2p(ovpn, OVPN_DEL_PEER_REASON_TEARDOWN);
|
||||
|
||||
unregister_netdevice_queue(dev, head);
|
||||
}
|
||||
|
||||
static int ovpn_fill_info(struct sk_buff *skb, const struct net_device *dev)
|
||||
{
|
||||
struct ovpn_priv *ovpn = netdev_priv(dev);
|
||||
|
|
@ -127,7 +139,7 @@ static struct rtnl_link_ops ovpn_link_ops = {
|
|||
.policy = ovpn_policy,
|
||||
.maxtype = IFLA_OVPN_MAX,
|
||||
.newlink = ovpn_newlink,
|
||||
.dellink = unregister_netdevice_queue,
|
||||
.dellink = ovpn_dellink,
|
||||
.fill_info = ovpn_fill_info,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -17,10 +17,14 @@
|
|||
* struct ovpn_priv - per ovpn interface state
|
||||
* @dev: the actual netdev representing the tunnel
|
||||
* @mode: device operation mode (i.e. p2p, mp, ..)
|
||||
* @lock: protect this object
|
||||
* @peer: in P2P mode, this is the only remote peer
|
||||
*/
|
||||
struct ovpn_priv {
|
||||
struct net_device *dev;
|
||||
enum ovpn_mode mode;
|
||||
spinlock_t lock; /* protect writing to the ovpn_priv object */
|
||||
struct ovpn_peer __rcu *peer;
|
||||
};
|
||||
|
||||
#endif /* _NET_OVPN_OVPNSTRUCT_H_ */
|
||||
|
|
|
|||
411
drivers/net/ovpn/peer.c
Normal file
411
drivers/net/ovpn/peer.c
Normal file
|
|
@ -0,0 +1,411 @@
|
|||
// 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/skbuff.h>
|
||||
#include <linux/list.h>
|
||||
|
||||
#include "ovpnpriv.h"
|
||||
#include "bind.h"
|
||||
#include "io.h"
|
||||
#include "main.h"
|
||||
#include "netlink.h"
|
||||
#include "peer.h"
|
||||
|
||||
static void unlock_ovpn(struct ovpn_priv *ovpn,
|
||||
struct llist_head *release_list)
|
||||
__releases(&ovpn->lock)
|
||||
{
|
||||
struct ovpn_peer *peer;
|
||||
|
||||
spin_unlock_bh(&ovpn->lock);
|
||||
|
||||
llist_for_each_entry(peer, release_list->first, release_entry)
|
||||
ovpn_peer_put(peer);
|
||||
}
|
||||
|
||||
/**
|
||||
* ovpn_peer_new - allocate and initialize a new peer object
|
||||
* @ovpn: the openvpn instance inside which the peer should be created
|
||||
* @id: the ID assigned to this peer
|
||||
*
|
||||
* Return: a pointer to the new peer on success or an error code otherwise
|
||||
*/
|
||||
struct ovpn_peer *ovpn_peer_new(struct ovpn_priv *ovpn, u32 id)
|
||||
{
|
||||
struct ovpn_peer *peer;
|
||||
int ret;
|
||||
|
||||
/* alloc and init peer object */
|
||||
peer = kzalloc(sizeof(*peer), GFP_KERNEL);
|
||||
if (!peer)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
peer->id = id;
|
||||
peer->ovpn = ovpn;
|
||||
|
||||
peer->vpn_addrs.ipv4.s_addr = htonl(INADDR_ANY);
|
||||
peer->vpn_addrs.ipv6 = in6addr_any;
|
||||
|
||||
RCU_INIT_POINTER(peer->bind, NULL);
|
||||
spin_lock_init(&peer->lock);
|
||||
kref_init(&peer->refcount);
|
||||
|
||||
ret = dst_cache_init(&peer->dst_cache, GFP_KERNEL);
|
||||
if (ret < 0) {
|
||||
netdev_err(ovpn->dev,
|
||||
"cannot initialize dst cache for peer %u\n",
|
||||
peer->id);
|
||||
kfree(peer);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
netdev_hold(ovpn->dev, &peer->dev_tracker, GFP_KERNEL);
|
||||
|
||||
return peer;
|
||||
}
|
||||
|
||||
/**
|
||||
* ovpn_peer_release_rcu - RCU callback performing last peer release steps
|
||||
* @head: RCU member of the ovpn_peer
|
||||
*/
|
||||
static void ovpn_peer_release_rcu(struct rcu_head *head)
|
||||
{
|
||||
struct ovpn_peer *peer = container_of(head, struct ovpn_peer, rcu);
|
||||
|
||||
/* this call will immediately free the dst_cache, therefore we
|
||||
* perform it in the RCU callback, when all contexts are done
|
||||
*/
|
||||
dst_cache_destroy(&peer->dst_cache);
|
||||
kfree(peer);
|
||||
}
|
||||
|
||||
/**
|
||||
* ovpn_peer_release - release peer private members
|
||||
* @peer: the peer to release
|
||||
*/
|
||||
static void ovpn_peer_release(struct ovpn_peer *peer)
|
||||
{
|
||||
ovpn_bind_reset(peer, NULL);
|
||||
call_rcu(&peer->rcu, ovpn_peer_release_rcu);
|
||||
netdev_put(peer->ovpn->dev, &peer->dev_tracker);
|
||||
}
|
||||
|
||||
/**
|
||||
* ovpn_peer_release_kref - callback for kref_put
|
||||
* @kref: the kref object belonging to the peer
|
||||
*/
|
||||
void ovpn_peer_release_kref(struct kref *kref)
|
||||
{
|
||||
struct ovpn_peer *peer = container_of(kref, struct ovpn_peer, refcount);
|
||||
|
||||
ovpn_peer_release(peer);
|
||||
}
|
||||
|
||||
/**
|
||||
* ovpn_peer_skb_to_sockaddr - fill sockaddr with skb source address
|
||||
* @skb: the packet to extract data from
|
||||
* @ss: the sockaddr to fill
|
||||
*
|
||||
* Return: sockaddr length on success or -1 otherwise
|
||||
*/
|
||||
static int ovpn_peer_skb_to_sockaddr(struct sk_buff *skb,
|
||||
struct sockaddr_storage *ss)
|
||||
{
|
||||
struct sockaddr_in6 *sa6;
|
||||
struct sockaddr_in *sa4;
|
||||
|
||||
switch (skb->protocol) {
|
||||
case htons(ETH_P_IP):
|
||||
sa4 = (struct sockaddr_in *)ss;
|
||||
sa4->sin_family = AF_INET;
|
||||
sa4->sin_addr.s_addr = ip_hdr(skb)->saddr;
|
||||
sa4->sin_port = udp_hdr(skb)->source;
|
||||
return sizeof(*sa4);
|
||||
case htons(ETH_P_IPV6):
|
||||
sa6 = (struct sockaddr_in6 *)ss;
|
||||
sa6->sin6_family = AF_INET6;
|
||||
sa6->sin6_addr = ipv6_hdr(skb)->saddr;
|
||||
sa6->sin6_port = udp_hdr(skb)->source;
|
||||
return sizeof(*sa6);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* ovpn_peer_transp_match - check if sockaddr and peer binding match
|
||||
* @peer: the peer to get the binding from
|
||||
* @ss: the sockaddr to match
|
||||
*
|
||||
* Return: true if sockaddr and binding match or false otherwise
|
||||
*/
|
||||
static bool ovpn_peer_transp_match(const struct ovpn_peer *peer,
|
||||
const struct sockaddr_storage *ss)
|
||||
{
|
||||
struct ovpn_bind *bind = rcu_dereference(peer->bind);
|
||||
struct sockaddr_in6 *sa6;
|
||||
struct sockaddr_in *sa4;
|
||||
|
||||
if (unlikely(!bind))
|
||||
return false;
|
||||
|
||||
if (ss->ss_family != bind->remote.in4.sin_family)
|
||||
return false;
|
||||
|
||||
switch (ss->ss_family) {
|
||||
case AF_INET:
|
||||
sa4 = (struct sockaddr_in *)ss;
|
||||
if (sa4->sin_addr.s_addr != bind->remote.in4.sin_addr.s_addr)
|
||||
return false;
|
||||
if (sa4->sin_port != bind->remote.in4.sin_port)
|
||||
return false;
|
||||
break;
|
||||
case AF_INET6:
|
||||
sa6 = (struct sockaddr_in6 *)ss;
|
||||
if (!ipv6_addr_equal(&sa6->sin6_addr,
|
||||
&bind->remote.in6.sin6_addr))
|
||||
return false;
|
||||
if (sa6->sin6_port != bind->remote.in6.sin6_port)
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* ovpn_peer_get_by_transp_addr_p2p - get peer by transport address in a P2P
|
||||
* instance
|
||||
* @ovpn: the openvpn instance to search
|
||||
* @ss: the transport socket address
|
||||
*
|
||||
* Return: the peer if found or NULL otherwise
|
||||
*/
|
||||
static struct ovpn_peer *
|
||||
ovpn_peer_get_by_transp_addr_p2p(struct ovpn_priv *ovpn,
|
||||
struct sockaddr_storage *ss)
|
||||
{
|
||||
struct ovpn_peer *tmp, *peer = NULL;
|
||||
|
||||
rcu_read_lock();
|
||||
tmp = rcu_dereference(ovpn->peer);
|
||||
if (likely(tmp && ovpn_peer_transp_match(tmp, ss) &&
|
||||
ovpn_peer_hold(tmp)))
|
||||
peer = tmp;
|
||||
rcu_read_unlock();
|
||||
|
||||
return peer;
|
||||
}
|
||||
|
||||
/**
|
||||
* ovpn_peer_get_by_transp_addr - retrieve peer by transport address
|
||||
* @ovpn: the openvpn instance to search
|
||||
* @skb: the skb to retrieve the source transport address from
|
||||
*
|
||||
* Return: a pointer to the peer if found or NULL otherwise
|
||||
*/
|
||||
struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_priv *ovpn,
|
||||
struct sk_buff *skb)
|
||||
{
|
||||
struct ovpn_peer *peer = NULL;
|
||||
struct sockaddr_storage ss = { 0 };
|
||||
|
||||
if (unlikely(!ovpn_peer_skb_to_sockaddr(skb, &ss)))
|
||||
return NULL;
|
||||
|
||||
if (ovpn->mode == OVPN_MODE_P2P)
|
||||
peer = ovpn_peer_get_by_transp_addr_p2p(ovpn, &ss);
|
||||
|
||||
return peer;
|
||||
}
|
||||
|
||||
/**
|
||||
* ovpn_peer_get_by_id_p2p - get peer by ID in a P2P instance
|
||||
* @ovpn: the openvpn instance to search
|
||||
* @peer_id: the ID of the peer to find
|
||||
*
|
||||
* Return: the peer if found or NULL otherwise
|
||||
*/
|
||||
static struct ovpn_peer *ovpn_peer_get_by_id_p2p(struct ovpn_priv *ovpn,
|
||||
u32 peer_id)
|
||||
{
|
||||
struct ovpn_peer *tmp, *peer = NULL;
|
||||
|
||||
rcu_read_lock();
|
||||
tmp = rcu_dereference(ovpn->peer);
|
||||
if (likely(tmp && tmp->id == peer_id && ovpn_peer_hold(tmp)))
|
||||
peer = tmp;
|
||||
rcu_read_unlock();
|
||||
|
||||
return peer;
|
||||
}
|
||||
|
||||
/**
|
||||
* ovpn_peer_get_by_id - retrieve peer by ID
|
||||
* @ovpn: the openvpn instance to search
|
||||
* @peer_id: the unique peer identifier to match
|
||||
*
|
||||
* Return: a pointer to the peer if found or NULL otherwise
|
||||
*/
|
||||
struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_priv *ovpn, u32 peer_id)
|
||||
{
|
||||
struct ovpn_peer *peer = NULL;
|
||||
|
||||
if (ovpn->mode == OVPN_MODE_P2P)
|
||||
peer = ovpn_peer_get_by_id_p2p(ovpn, peer_id);
|
||||
|
||||
return peer;
|
||||
}
|
||||
|
||||
static void ovpn_peer_remove(struct ovpn_peer *peer,
|
||||
enum ovpn_del_peer_reason reason,
|
||||
struct llist_head *release_list)
|
||||
{
|
||||
switch (peer->ovpn->mode) {
|
||||
case OVPN_MODE_P2P:
|
||||
/* prevent double remove */
|
||||
if (peer != rcu_access_pointer(peer->ovpn->peer))
|
||||
return;
|
||||
|
||||
RCU_INIT_POINTER(peer->ovpn->peer, NULL);
|
||||
/* in P2P mode the carrier is switched off when the peer is
|
||||
* deleted so that third party protocols can react accordingly
|
||||
*/
|
||||
netif_carrier_off(peer->ovpn->dev);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
peer->delete_reason = reason;
|
||||
|
||||
/* append to provided list for later socket release and ref drop */
|
||||
llist_add(&peer->release_entry, release_list);
|
||||
}
|
||||
|
||||
/**
|
||||
* ovpn_peer_add_p2p - add peer to related tables in a P2P instance
|
||||
* @ovpn: the instance to add the peer to
|
||||
* @peer: the peer to add
|
||||
*
|
||||
* Return: 0 on success or a negative error code otherwise
|
||||
*/
|
||||
static int ovpn_peer_add_p2p(struct ovpn_priv *ovpn, struct ovpn_peer *peer)
|
||||
{
|
||||
LLIST_HEAD(release_list);
|
||||
struct ovpn_peer *tmp;
|
||||
|
||||
spin_lock_bh(&ovpn->lock);
|
||||
/* in p2p mode it is possible to have a single peer only, therefore the
|
||||
* old one is released and substituted by the new one
|
||||
*/
|
||||
tmp = rcu_dereference_protected(ovpn->peer,
|
||||
lockdep_is_held(&ovpn->lock));
|
||||
if (tmp)
|
||||
ovpn_peer_remove(tmp, OVPN_DEL_PEER_REASON_TEARDOWN,
|
||||
&release_list);
|
||||
|
||||
rcu_assign_pointer(ovpn->peer, peer);
|
||||
/* in P2P mode the carrier is switched on when the peer is added */
|
||||
netif_carrier_on(ovpn->dev);
|
||||
unlock_ovpn(ovpn, &release_list);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* ovpn_peer_add - add peer to the related tables
|
||||
* @ovpn: the openvpn instance the peer belongs to
|
||||
* @peer: the peer object to add
|
||||
*
|
||||
* Assume refcounter was increased by caller
|
||||
*
|
||||
* Return: 0 on success or a negative error code otherwise
|
||||
*/
|
||||
int ovpn_peer_add(struct ovpn_priv *ovpn, struct ovpn_peer *peer)
|
||||
{
|
||||
switch (ovpn->mode) {
|
||||
case OVPN_MODE_P2P:
|
||||
return ovpn_peer_add_p2p(ovpn, peer);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ovpn_peer_del_p2p - delete peer from related tables in a P2P instance
|
||||
* @peer: the peer to delete
|
||||
* @reason: reason why the peer was deleted (sent to userspace)
|
||||
* @release_list: list where delete peer should be appended
|
||||
*
|
||||
* Return: 0 on success or a negative error code otherwise
|
||||
*/
|
||||
static int ovpn_peer_del_p2p(struct ovpn_peer *peer,
|
||||
enum ovpn_del_peer_reason reason,
|
||||
struct llist_head *release_list)
|
||||
{
|
||||
struct ovpn_peer *tmp;
|
||||
|
||||
lockdep_assert_held(&peer->ovpn->lock);
|
||||
|
||||
tmp = rcu_dereference_protected(peer->ovpn->peer,
|
||||
lockdep_is_held(&peer->ovpn->lock));
|
||||
if (tmp != peer)
|
||||
return -ENOENT;
|
||||
|
||||
ovpn_peer_remove(peer, reason, release_list);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* ovpn_peer_del - delete peer from related tables
|
||||
* @peer: the peer object to delete
|
||||
* @reason: reason for deleting peer (will be sent to userspace)
|
||||
*
|
||||
* Return: 0 on success or a negative error code otherwise
|
||||
*/
|
||||
int ovpn_peer_del(struct ovpn_peer *peer, enum ovpn_del_peer_reason reason)
|
||||
{
|
||||
LLIST_HEAD(release_list);
|
||||
int ret = -EOPNOTSUPP;
|
||||
|
||||
spin_lock_bh(&peer->ovpn->lock);
|
||||
switch (peer->ovpn->mode) {
|
||||
case OVPN_MODE_P2P:
|
||||
ret = ovpn_peer_del_p2p(peer, reason, &release_list);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
unlock_ovpn(peer->ovpn, &release_list);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* ovpn_peer_release_p2p - release peer upon P2P device teardown
|
||||
* @ovpn: the instance being torn down
|
||||
* @reason: the reason for releasing the peer
|
||||
*/
|
||||
void ovpn_peer_release_p2p(struct ovpn_priv *ovpn,
|
||||
enum ovpn_del_peer_reason reason)
|
||||
{
|
||||
LLIST_HEAD(release_list);
|
||||
struct ovpn_peer *peer;
|
||||
|
||||
spin_lock_bh(&ovpn->lock);
|
||||
peer = rcu_dereference_protected(ovpn->peer,
|
||||
lockdep_is_held(&ovpn->lock));
|
||||
if (peer)
|
||||
ovpn_peer_remove(peer, reason, &release_list);
|
||||
unlock_ovpn(ovpn, &release_list);
|
||||
}
|
||||
80
drivers/net/ovpn/peer.h
Normal file
80
drivers/net/ovpn/peer.h
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/* OpenVPN data channel offload
|
||||
*
|
||||
* Copyright (C) 2020-2025 OpenVPN, Inc.
|
||||
*
|
||||
* Author: James Yonan <james@openvpn.net>
|
||||
* Antonio Quartulli <antonio@openvpn.net>
|
||||
*/
|
||||
|
||||
#ifndef _NET_OVPN_OVPNPEER_H_
|
||||
#define _NET_OVPN_OVPNPEER_H_
|
||||
|
||||
#include <net/dst_cache.h>
|
||||
|
||||
/**
|
||||
* struct ovpn_peer - the main remote peer object
|
||||
* @ovpn: main openvpn instance this peer belongs to
|
||||
* @dev_tracker: reference tracker for associated dev
|
||||
* @id: unique identifier
|
||||
* @vpn_addrs: IP addresses assigned over the tunnel
|
||||
* @vpn_addrs.ipv4: IPv4 assigned to peer on the tunnel
|
||||
* @vpn_addrs.ipv6: IPv6 assigned to peer on the tunnel
|
||||
* @dst_cache: cache for dst_entry used to send to peer
|
||||
* @bind: remote peer binding
|
||||
* @delete_reason: why peer was deleted (i.e. timeout, transport error, ..)
|
||||
* @lock: protects binding to peer (bind)
|
||||
* @refcount: reference counter
|
||||
* @rcu: used to free peer in an RCU safe way
|
||||
* @release_entry: entry for the socket release list
|
||||
*/
|
||||
struct ovpn_peer {
|
||||
struct ovpn_priv *ovpn;
|
||||
netdevice_tracker dev_tracker;
|
||||
u32 id;
|
||||
struct {
|
||||
struct in_addr ipv4;
|
||||
struct in6_addr ipv6;
|
||||
} vpn_addrs;
|
||||
struct dst_cache dst_cache;
|
||||
struct ovpn_bind __rcu *bind;
|
||||
enum ovpn_del_peer_reason delete_reason;
|
||||
spinlock_t lock; /* protects bind */
|
||||
struct kref refcount;
|
||||
struct rcu_head rcu;
|
||||
struct llist_node release_entry;
|
||||
};
|
||||
|
||||
/**
|
||||
* ovpn_peer_hold - increase reference counter
|
||||
* @peer: the peer whose counter should be increased
|
||||
*
|
||||
* Return: true if the counter was increased or false if it was zero already
|
||||
*/
|
||||
static inline bool ovpn_peer_hold(struct ovpn_peer *peer)
|
||||
{
|
||||
return kref_get_unless_zero(&peer->refcount);
|
||||
}
|
||||
|
||||
void ovpn_peer_release_kref(struct kref *kref);
|
||||
|
||||
/**
|
||||
* ovpn_peer_put - decrease reference counter
|
||||
* @peer: the peer whose counter should be decreased
|
||||
*/
|
||||
static inline void ovpn_peer_put(struct ovpn_peer *peer)
|
||||
{
|
||||
kref_put(&peer->refcount, ovpn_peer_release_kref);
|
||||
}
|
||||
|
||||
struct ovpn_peer *ovpn_peer_new(struct ovpn_priv *ovpn, u32 id);
|
||||
int ovpn_peer_add(struct ovpn_priv *ovpn, struct ovpn_peer *peer);
|
||||
int ovpn_peer_del(struct ovpn_peer *peer, enum ovpn_del_peer_reason reason);
|
||||
void ovpn_peer_release_p2p(struct ovpn_priv *ovpn,
|
||||
enum ovpn_del_peer_reason reason);
|
||||
|
||||
struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_priv *ovpn,
|
||||
struct sk_buff *skb);
|
||||
struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_priv *ovpn, u32 peer_id);
|
||||
|
||||
#endif /* _NET_OVPN_OVPNPEER_H_ */
|
||||
Loading…
Add table
Add a link
Reference in a new issue