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:
Antonio Quartulli 2025-04-15 13:17:22 +02:00 committed by Paolo Abeni
parent 8327a3baa9
commit 80747caef3
8 changed files with 670 additions and 1 deletions

View File

@ -119,6 +119,7 @@ config OVPN
tristate "OpenVPN data channel offload" tristate "OpenVPN data channel offload"
depends on NET && INET depends on NET && INET
depends on IPV6 || !IPV6 depends on IPV6 || !IPV6
select DST_CACHE
help help
This module enhances the performance of the OpenVPN userspace software This module enhances the performance of the OpenVPN userspace software
by offloading the data channel processing to kernelspace. by offloading the data channel processing to kernelspace.

View File

@ -7,7 +7,9 @@
# Author: Antonio Quartulli <antonio@openvpn.net> # Author: Antonio Quartulli <antonio@openvpn.net>
obj-$(CONFIG_OVPN) := ovpn.o obj-$(CONFIG_OVPN) := ovpn.o
ovpn-y += bind.o
ovpn-y += main.o ovpn-y += main.o
ovpn-y += io.o ovpn-y += io.o
ovpn-y += netlink.o ovpn-y += netlink.o
ovpn-y += netlink-gen.o ovpn-y += netlink-gen.o
ovpn-y += peer.o

58
drivers/net/ovpn/bind.c Normal file
View 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
View 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_ */

View File

@ -19,6 +19,7 @@
#include "main.h" #include "main.h"
#include "netlink.h" #include "netlink.h"
#include "io.h" #include "io.h"
#include "peer.h"
#include "proto.h" #include "proto.h"
static const struct net_device_ops ovpn_netdev_ops = { 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->dev = dev;
ovpn->mode = mode; ovpn->mode = mode;
spin_lock_init(&ovpn->lock);
/* Set carrier explicitly after registration, this way state is /* Set carrier explicitly after registration, this way state is
* clearly defined. * clearly defined.
@ -109,6 +111,16 @@ static int ovpn_newlink(struct net_device *dev,
return register_netdevice(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) static int ovpn_fill_info(struct sk_buff *skb, const struct net_device *dev)
{ {
struct ovpn_priv *ovpn = netdev_priv(dev); struct ovpn_priv *ovpn = netdev_priv(dev);
@ -127,7 +139,7 @@ static struct rtnl_link_ops ovpn_link_ops = {
.policy = ovpn_policy, .policy = ovpn_policy,
.maxtype = IFLA_OVPN_MAX, .maxtype = IFLA_OVPN_MAX,
.newlink = ovpn_newlink, .newlink = ovpn_newlink,
.dellink = unregister_netdevice_queue, .dellink = ovpn_dellink,
.fill_info = ovpn_fill_info, .fill_info = ovpn_fill_info,
}; };

View File

@ -17,10 +17,14 @@
* struct ovpn_priv - per ovpn interface state * struct ovpn_priv - per ovpn interface state
* @dev: the actual netdev representing the tunnel * @dev: the actual netdev representing the tunnel
* @mode: device operation mode (i.e. p2p, mp, ..) * @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 ovpn_priv {
struct net_device *dev; struct net_device *dev;
enum ovpn_mode mode; enum ovpn_mode mode;
spinlock_t lock; /* protect writing to the ovpn_priv object */
struct ovpn_peer __rcu *peer;
}; };
#endif /* _NET_OVPN_OVPNSTRUCT_H_ */ #endif /* _NET_OVPN_OVPNSTRUCT_H_ */

411
drivers/net/ovpn/peer.c Normal file
View 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
View 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_ */