Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
# SPDX-License-Identifier: GPL-2.0
#
# ovpn -- OpenVPN data channel offload in kernel space
#
# Copyright (C) 2020-2025 OpenVPN, Inc.
#
# Author: Antonio Quartulli <antonio@openvpn.net>
obj-$(CONFIG_OVPN) := ovpn.o
ovpn-y += bind.o
ovpn-y += crypto.o
ovpn-y += crypto_aead.o
ovpn-y += main.o
ovpn-y += io.o
ovpn-y += netlink.o
ovpn-y += netlink-gen.o
ovpn-y += peer.o
ovpn-y += pktid.o
ovpn-y += socket.o
ovpn-y += stats.o
ovpn-y += tcp.o
ovpn-y += udp.o
+55
View File
@@ -0,0 +1,55 @@
// 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_obj(*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)
{
lockdep_assert_held(&peer->lock);
kfree_rcu(rcu_replace_pointer(peer->bind, new,
lockdep_is_held(&peer->lock)), rcu);
}
+101
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_ */
+206
View File
@@ -0,0 +1,206 @@
// 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/types.h>
#include <linux/net.h>
#include <linux/netdevice.h>
#include <uapi/linux/ovpn.h>
#include "ovpnpriv.h"
#include "main.h"
#include "pktid.h"
#include "crypto_aead.h"
#include "crypto.h"
void ovpn_crypto_key_slot_release(struct kref *kref)
{
struct ovpn_crypto_key_slot *ks;
ks = container_of(kref, struct ovpn_crypto_key_slot, refcount);
queue_rcu_work(ovpn_wq, &ks->free_work);
}
/* can only be invoked when all peer references have been dropped (i.e. RCU
* release routine)
*/
void ovpn_crypto_state_release(struct ovpn_crypto_state *cs)
{
struct ovpn_crypto_key_slot *ks;
ks = rcu_access_pointer(cs->slots[0]);
if (ks) {
RCU_INIT_POINTER(cs->slots[0], NULL);
ovpn_crypto_key_slot_put(ks);
}
ks = rcu_access_pointer(cs->slots[1]);
if (ks) {
RCU_INIT_POINTER(cs->slots[1], NULL);
ovpn_crypto_key_slot_put(ks);
}
}
/* removes the key matching the specified id from the crypto context */
bool ovpn_crypto_kill_key(struct ovpn_crypto_state *cs, u8 key_id)
{
struct ovpn_crypto_key_slot *ks = NULL;
struct ovpn_crypto_key_slot *tmp;
int slot = 0;
spin_lock_bh(&cs->lock);
tmp = rcu_access_pointer(cs->slots[slot]);
if (!tmp || tmp->key_id != key_id) {
slot = 1;
tmp = rcu_access_pointer(cs->slots[slot]);
}
if (tmp && tmp->key_id == key_id)
ks = rcu_replace_pointer(cs->slots[slot], NULL,
lockdep_is_held(&cs->lock));
spin_unlock_bh(&cs->lock);
if (ks)
ovpn_crypto_key_slot_put(ks);
/* let the caller know if a key was actually killed */
return ks;
}
/* Reset the ovpn_crypto_state object in a way that is atomic
* to RCU readers.
*/
int ovpn_crypto_state_reset(struct ovpn_crypto_state *cs,
const struct ovpn_peer_key_reset *pkr)
{
struct ovpn_crypto_key_slot *old = NULL, *new;
u8 idx;
if (pkr->slot != OVPN_KEY_SLOT_PRIMARY &&
pkr->slot != OVPN_KEY_SLOT_SECONDARY)
return -EINVAL;
new = ovpn_aead_crypto_key_slot_new(&pkr->key);
if (IS_ERR(new))
return PTR_ERR(new);
spin_lock_bh(&cs->lock);
idx = cs->primary_idx;
switch (pkr->slot) {
case OVPN_KEY_SLOT_PRIMARY:
old = rcu_replace_pointer(cs->slots[idx], new,
lockdep_is_held(&cs->lock));
break;
case OVPN_KEY_SLOT_SECONDARY:
old = rcu_replace_pointer(cs->slots[!idx], new,
lockdep_is_held(&cs->lock));
break;
}
spin_unlock_bh(&cs->lock);
if (old)
ovpn_crypto_key_slot_put(old);
return 0;
}
void ovpn_crypto_key_slot_delete(struct ovpn_crypto_state *cs,
enum ovpn_key_slot slot)
{
struct ovpn_crypto_key_slot *ks = NULL;
u8 idx;
if (slot != OVPN_KEY_SLOT_PRIMARY &&
slot != OVPN_KEY_SLOT_SECONDARY) {
pr_warn("Invalid slot to release: %u\n", slot);
return;
}
spin_lock_bh(&cs->lock);
idx = cs->primary_idx;
switch (slot) {
case OVPN_KEY_SLOT_PRIMARY:
ks = rcu_replace_pointer(cs->slots[idx], NULL,
lockdep_is_held(&cs->lock));
break;
case OVPN_KEY_SLOT_SECONDARY:
ks = rcu_replace_pointer(cs->slots[!idx], NULL,
lockdep_is_held(&cs->lock));
break;
}
spin_unlock_bh(&cs->lock);
if (!ks) {
pr_debug("Key slot already released: %u\n", slot);
return;
}
pr_debug("deleting key slot %u, key_id=%u\n", slot, ks->key_id);
ovpn_crypto_key_slot_put(ks);
}
void ovpn_crypto_key_slots_swap(struct ovpn_crypto_state *cs)
{
const struct ovpn_crypto_key_slot *old_primary, *old_secondary;
u8 idx;
spin_lock_bh(&cs->lock);
idx = cs->primary_idx;
old_primary = rcu_dereference_protected(cs->slots[idx],
lockdep_is_held(&cs->lock));
old_secondary = rcu_dereference_protected(cs->slots[!idx],
lockdep_is_held(&cs->lock));
/* perform real swap by switching the index of the primary key */
WRITE_ONCE(cs->primary_idx, !cs->primary_idx);
pr_debug("key swapped: (old primary) %d <-> (new primary) %d\n",
old_primary ? old_primary->key_id : -1,
old_secondary ? old_secondary->key_id : -1);
spin_unlock_bh(&cs->lock);
}
/**
* ovpn_crypto_config_get - populate keyconf object with non-sensible key data
* @cs: the crypto state to extract the key data from
* @slot: the specific slot to inspect
* @keyconf: the output object to populate
*
* Return: 0 on success or a negative error code otherwise
*/
int ovpn_crypto_config_get(struct ovpn_crypto_state *cs,
enum ovpn_key_slot slot,
struct ovpn_key_config *keyconf)
{
struct ovpn_crypto_key_slot *ks;
int idx;
switch (slot) {
case OVPN_KEY_SLOT_PRIMARY:
idx = cs->primary_idx;
break;
case OVPN_KEY_SLOT_SECONDARY:
idx = !cs->primary_idx;
break;
default:
return -EINVAL;
}
rcu_read_lock();
ks = rcu_dereference(cs->slots[idx]);
if (!ks) {
rcu_read_unlock();
return -ENOENT;
}
keyconf->cipher_alg = ovpn_aead_crypto_alg(ks);
keyconf->key_id = ks->key_id;
rcu_read_unlock();
return 0;
}
+147
View File
@@ -0,0 +1,147 @@
/* 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_OVPNCRYPTO_H_
#define _NET_OVPN_OVPNCRYPTO_H_
#include <linux/workqueue.h>
#include "pktid.h"
#include "proto.h"
/* info needed for both encrypt and decrypt directions */
struct ovpn_key_direction {
const u8 *cipher_key;
size_t cipher_key_size;
const u8 *nonce_tail; /* only needed for GCM modes */
size_t nonce_tail_size; /* only needed for GCM modes */
};
/* all info for a particular symmetric key (primary or secondary) */
struct ovpn_key_config {
enum ovpn_cipher_alg cipher_alg;
u8 key_id;
struct ovpn_key_direction encrypt;
struct ovpn_key_direction decrypt;
};
/* used to pass settings from netlink to the crypto engine */
struct ovpn_peer_key_reset {
enum ovpn_key_slot slot;
struct ovpn_key_config key;
};
struct ovpn_crypto_key_slot {
u8 key_id;
struct crypto_aead *encrypt;
struct crypto_aead *decrypt;
u8 nonce_tail_xmit[OVPN_NONCE_TAIL_SIZE];
u8 nonce_tail_recv[OVPN_NONCE_TAIL_SIZE];
struct ovpn_pktid_recv pid_recv ____cacheline_aligned_in_smp;
struct ovpn_pktid_xmit pid_xmit ____cacheline_aligned_in_smp;
struct rcu_work free_work;
struct kref refcount;
};
struct ovpn_crypto_state {
struct ovpn_crypto_key_slot __rcu *slots[2];
u8 primary_idx;
/* protects primary and secondary slots */
spinlock_t lock;
};
static inline bool ovpn_crypto_key_slot_hold(struct ovpn_crypto_key_slot *ks)
{
return kref_get_unless_zero(&ks->refcount);
}
static inline void ovpn_crypto_state_init(struct ovpn_crypto_state *cs)
{
RCU_INIT_POINTER(cs->slots[0], NULL);
RCU_INIT_POINTER(cs->slots[1], NULL);
cs->primary_idx = 0;
spin_lock_init(&cs->lock);
}
static inline struct ovpn_crypto_key_slot *
ovpn_crypto_key_id_to_slot(const struct ovpn_crypto_state *cs, u8 key_id)
{
struct ovpn_crypto_key_slot *ks;
u8 idx;
if (unlikely(!cs))
return NULL;
rcu_read_lock();
idx = READ_ONCE(cs->primary_idx);
ks = rcu_dereference(cs->slots[idx]);
if (ks && ks->key_id == key_id) {
if (unlikely(!ovpn_crypto_key_slot_hold(ks)))
ks = NULL;
goto out;
}
ks = rcu_dereference(cs->slots[!idx]);
if (ks && ks->key_id == key_id) {
if (unlikely(!ovpn_crypto_key_slot_hold(ks)))
ks = NULL;
goto out;
}
/* when both key slots are occupied but no matching key ID is found, ks
* has to be reset to NULL to avoid carrying a stale pointer
*/
ks = NULL;
out:
rcu_read_unlock();
return ks;
}
static inline struct ovpn_crypto_key_slot *
ovpn_crypto_key_slot_primary(const struct ovpn_crypto_state *cs)
{
struct ovpn_crypto_key_slot *ks;
rcu_read_lock();
ks = rcu_dereference(cs->slots[cs->primary_idx]);
if (unlikely(ks && !ovpn_crypto_key_slot_hold(ks)))
ks = NULL;
rcu_read_unlock();
return ks;
}
void ovpn_crypto_key_slot_release(struct kref *kref);
static inline void ovpn_crypto_key_slot_put(struct ovpn_crypto_key_slot *ks)
{
kref_put(&ks->refcount, ovpn_crypto_key_slot_release);
}
int ovpn_crypto_state_reset(struct ovpn_crypto_state *cs,
const struct ovpn_peer_key_reset *pkr);
void ovpn_crypto_key_slot_delete(struct ovpn_crypto_state *cs,
enum ovpn_key_slot slot);
void ovpn_crypto_state_release(struct ovpn_crypto_state *cs);
void ovpn_crypto_key_slots_swap(struct ovpn_crypto_state *cs);
int ovpn_crypto_config_get(struct ovpn_crypto_state *cs,
enum ovpn_key_slot slot,
struct ovpn_key_config *keyconf);
bool ovpn_crypto_kill_key(struct ovpn_crypto_state *cs, u8 key_id);
#endif /* _NET_OVPN_OVPNCRYPTO_H_ */
+484
View File
@@ -0,0 +1,484 @@
// 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 <crypto/aead.h>
#include <linux/skbuff.h>
#include <linux/workqueue.h>
#include <net/ip.h>
#include <net/ipv6.h>
#include <net/udp.h>
#include "ovpnpriv.h"
#include "main.h"
#include "io.h"
#include "pktid.h"
#include "crypto_aead.h"
#include "crypto.h"
#include "peer.h"
#include "proto.h"
#include "skb.h"
#define OVPN_AUTH_TAG_SIZE 16
#define OVPN_AAD_SIZE (OVPN_OPCODE_SIZE + OVPN_NONCE_WIRE_SIZE)
#define ALG_NAME_AES "gcm(aes)"
#define ALG_NAME_CHACHAPOLY "rfc7539(chacha20,poly1305)"
static int ovpn_aead_encap_overhead(const struct ovpn_crypto_key_slot *ks)
{
return OVPN_OPCODE_SIZE + /* OP header size */
sizeof(u32) + /* Packet ID */
crypto_aead_authsize(ks->encrypt); /* Auth Tag */
}
/**
* ovpn_aead_crypto_tmp_size - compute the size of a temporary object containing
* an AEAD request structure with extra space for SG
* and IV.
* @tfm: the AEAD cipher handle
* @nfrags: the number of fragments in the skb
*
* This function calculates the size of a contiguous memory block that includes
* the initialization vector (IV), the AEAD request, and an array of scatterlist
* entries. For alignment considerations, the IV is placed first, followed by
* the request, and then the scatterlist.
* Additional alignment is applied according to the requirements of the
* underlying structures.
*
* Return: the size of the temporary memory that needs to be allocated
*/
static unsigned int ovpn_aead_crypto_tmp_size(struct crypto_aead *tfm,
const unsigned int nfrags)
{
unsigned int len = OVPN_NONCE_SIZE;
DEBUG_NET_WARN_ON_ONCE(crypto_aead_ivsize(tfm) != OVPN_NONCE_SIZE);
/* min size for a buffer of ivsize, aligned to alignmask */
len += crypto_aead_alignmask(tfm) & ~(crypto_tfm_ctx_alignment() - 1);
/* round up to the next multiple of the crypto ctx alignment */
len = ALIGN(len, crypto_tfm_ctx_alignment());
/* reserve space for the AEAD request */
len += sizeof(struct aead_request) + crypto_aead_reqsize(tfm);
/* round up to the next multiple of the scatterlist alignment */
len = ALIGN(len, __alignof__(struct scatterlist));
/* add enough space for nfrags + 2 scatterlist entries */
len += array_size(sizeof(struct scatterlist), nfrags + 2);
return len;
}
/**
* ovpn_aead_crypto_tmp_iv - retrieve the pointer to the IV within a temporary
* buffer allocated using ovpn_aead_crypto_tmp_size
* @aead: the AEAD cipher handle
* @tmp: a pointer to the beginning of the temporary buffer
*
* This function retrieves a pointer to the initialization vector (IV) in the
* temporary buffer. If the AEAD cipher specifies an IV size, the pointer is
* adjusted using the AEAD's alignment mask to ensure proper alignment.
*
* Returns: a pointer to the IV within the temporary buffer
*/
static u8 *ovpn_aead_crypto_tmp_iv(struct crypto_aead *aead, void *tmp)
{
return likely(crypto_aead_ivsize(aead)) ?
PTR_ALIGN((u8 *)tmp, crypto_aead_alignmask(aead) + 1) :
tmp;
}
/**
* ovpn_aead_crypto_tmp_req - retrieve the pointer to the AEAD request structure
* within a temporary buffer allocated using
* ovpn_aead_crypto_tmp_size
* @aead: the AEAD cipher handle
* @iv: a pointer to the initialization vector in the temporary buffer
*
* This function computes the location of the AEAD request structure that
* immediately follows the IV in the temporary buffer and it ensures the request
* is aligned to the crypto transform context alignment.
*
* Returns: a pointer to the AEAD request structure
*/
static struct aead_request *ovpn_aead_crypto_tmp_req(struct crypto_aead *aead,
const u8 *iv)
{
return (void *)PTR_ALIGN(iv + crypto_aead_ivsize(aead),
crypto_tfm_ctx_alignment());
}
/**
* ovpn_aead_crypto_req_sg - locate the scatterlist following the AEAD request
* within a temporary buffer allocated using
* ovpn_aead_crypto_tmp_size
* @aead: the AEAD cipher handle
* @req: a pointer to the AEAD request structure in the temporary buffer
*
* This function computes the starting address of the scatterlist that is
* allocated immediately after the AEAD request structure. It aligns the pointer
* based on the alignment requirements of the scatterlist structure.
*
* Returns: a pointer to the scatterlist
*/
static struct scatterlist *ovpn_aead_crypto_req_sg(struct crypto_aead *aead,
struct aead_request *req)
{
return (void *)ALIGN((unsigned long)(req + 1) +
crypto_aead_reqsize(aead),
__alignof__(struct scatterlist));
}
int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
struct sk_buff *skb)
{
const unsigned int tag_size = crypto_aead_authsize(ks->encrypt);
struct aead_request *req;
struct sk_buff *trailer;
struct scatterlist *sg;
int nfrags, ret;
u32 pktid, op;
void *tmp;
u8 *iv;
ovpn_skb_cb(skb)->peer = peer;
ovpn_skb_cb(skb)->ks = ks;
/* Sample AEAD header format:
* 48000001 00000005 7e7046bd 444a7e28 cc6387b1 64a4d6c1 380275a...
* [ OP32 ] [seq # ] [ auth tag ] [ payload ... ]
* [4-byte
* IV head]
*/
/* check that there's enough headroom in the skb for packet
* encapsulation
*/
if (unlikely(skb_cow_head(skb, OVPN_HEAD_ROOM)))
return -ENOBUFS;
/* get number of skb frags and ensure that packet data is writable */
nfrags = skb_cow_data(skb, 0, &trailer);
if (unlikely(nfrags < 0))
return nfrags;
if (unlikely(nfrags + 2 > (MAX_SKB_FRAGS + 2)))
return -ENOSPC;
/* allocate temporary memory for iv, sg and req */
tmp = kmalloc(ovpn_aead_crypto_tmp_size(ks->encrypt, nfrags),
GFP_ATOMIC);
if (unlikely(!tmp))
return -ENOMEM;
ovpn_skb_cb(skb)->crypto_tmp = tmp;
iv = ovpn_aead_crypto_tmp_iv(ks->encrypt, tmp);
req = ovpn_aead_crypto_tmp_req(ks->encrypt, iv);
sg = ovpn_aead_crypto_req_sg(ks->encrypt, req);
/* sg table:
* 0: op, wire nonce (AD, len=OVPN_OP_SIZE_V2+OVPN_NONCE_WIRE_SIZE),
* 1, 2, 3, ..., n: payload,
* n+1: auth_tag (len=tag_size)
*/
sg_init_table(sg, nfrags + 2);
/* build scatterlist to encrypt packet payload */
ret = skb_to_sgvec_nomark(skb, sg + 1, 0, skb->len);
if (unlikely(ret < 0)) {
netdev_err(peer->ovpn->dev,
"encrypt: cannot map skb to sg: %d\n", ret);
return ret;
}
/* append auth_tag onto scatterlist */
__skb_push(skb, tag_size);
sg_set_buf(sg + ret + 1, skb->data, tag_size);
/* obtain packet ID, which is used both as a first
* 4 bytes of nonce and last 4 bytes of associated data.
*/
ret = ovpn_pktid_xmit_next(&ks->pid_xmit, &pktid);
if (unlikely(ret < 0))
return ret;
/* concat 4 bytes packet id and 8 bytes nonce tail into 12 bytes
* nonce
*/
ovpn_pktid_aead_write(pktid, ks->nonce_tail_xmit, iv);
/* make space for packet id and push it to the front */
__skb_push(skb, OVPN_NONCE_WIRE_SIZE);
memcpy(skb->data, iv, OVPN_NONCE_WIRE_SIZE);
/* add packet op as head of additional data */
op = ovpn_opcode_compose(OVPN_DATA_V2, ks->key_id, peer->tx_id);
__skb_push(skb, OVPN_OPCODE_SIZE);
BUILD_BUG_ON(sizeof(op) != OVPN_OPCODE_SIZE);
*((__force __be32 *)skb->data) = htonl(op);
/* AEAD Additional data */
sg_set_buf(sg, skb->data, OVPN_AAD_SIZE);
/* setup async crypto operation */
aead_request_set_tfm(req, ks->encrypt);
aead_request_set_callback(req, 0, ovpn_encrypt_post, skb);
aead_request_set_crypt(req, sg, sg,
skb->len - ovpn_aead_encap_overhead(ks), iv);
aead_request_set_ad(req, OVPN_AAD_SIZE);
/* encrypt it */
return crypto_aead_encrypt(req);
}
int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
struct sk_buff *skb)
{
const unsigned int tag_size = crypto_aead_authsize(ks->decrypt);
int ret, payload_len, nfrags;
unsigned int payload_offset;
struct aead_request *req;
struct sk_buff *trailer;
struct scatterlist *sg;
void *tmp;
u8 *iv;
payload_offset = OVPN_AAD_SIZE + tag_size;
payload_len = skb->len - payload_offset;
ovpn_skb_cb(skb)->payload_offset = payload_offset;
ovpn_skb_cb(skb)->peer = peer;
ovpn_skb_cb(skb)->ks = ks;
/* sanity check on packet size, payload size must be >= 0 */
if (unlikely(payload_len < 0))
return -EINVAL;
/* Prepare the skb data buffer to be accessed up until the auth tag.
* This is required because this area is directly mapped into the sg
* list.
*/
if (unlikely(!pskb_may_pull(skb, payload_offset)))
return -ENODATA;
/* get number of skb frags and ensure that packet data is writable */
nfrags = skb_cow_data(skb, 0, &trailer);
if (unlikely(nfrags < 0))
return nfrags;
if (unlikely(nfrags + 2 > (MAX_SKB_FRAGS + 2)))
return -ENOSPC;
/* allocate temporary memory for iv, sg and req */
tmp = kmalloc(ovpn_aead_crypto_tmp_size(ks->decrypt, nfrags),
GFP_ATOMIC);
if (unlikely(!tmp))
return -ENOMEM;
ovpn_skb_cb(skb)->crypto_tmp = tmp;
iv = ovpn_aead_crypto_tmp_iv(ks->decrypt, tmp);
req = ovpn_aead_crypto_tmp_req(ks->decrypt, iv);
sg = ovpn_aead_crypto_req_sg(ks->decrypt, req);
/* sg table:
* 0: op, wire nonce (AD, len=OVPN_OPCODE_SIZE+OVPN_NONCE_WIRE_SIZE),
* 1, 2, 3, ..., n: payload,
* n+1: auth_tag (len=tag_size)
*/
sg_init_table(sg, nfrags + 2);
/* packet op is head of additional data */
sg_set_buf(sg, skb->data, OVPN_AAD_SIZE);
/* build scatterlist to decrypt packet payload */
ret = skb_to_sgvec_nomark(skb, sg + 1, payload_offset, payload_len);
if (unlikely(ret < 0)) {
netdev_err(peer->ovpn->dev,
"decrypt: cannot map skb to sg: %d\n", ret);
return ret;
}
/* append auth_tag onto scatterlist */
sg_set_buf(sg + ret + 1, skb->data + OVPN_AAD_SIZE, tag_size);
/* copy nonce into IV buffer */
memcpy(iv, skb->data + OVPN_OPCODE_SIZE, OVPN_NONCE_WIRE_SIZE);
memcpy(iv + OVPN_NONCE_WIRE_SIZE, ks->nonce_tail_recv,
OVPN_NONCE_TAIL_SIZE);
/* setup async crypto operation */
aead_request_set_tfm(req, ks->decrypt);
aead_request_set_callback(req, 0, ovpn_decrypt_post, skb);
aead_request_set_crypt(req, sg, sg, payload_len + tag_size, iv);
aead_request_set_ad(req, OVPN_AAD_SIZE);
/* decrypt it */
return crypto_aead_decrypt(req);
}
/* Initialize a struct crypto_aead object */
static struct crypto_aead *ovpn_aead_init(const char *title,
const char *alg_name,
const unsigned char *key,
unsigned int keylen)
{
struct crypto_aead *aead;
int ret;
aead = crypto_alloc_aead(alg_name, 0, 0);
if (IS_ERR(aead)) {
ret = PTR_ERR(aead);
pr_err("%s crypto_alloc_aead failed, err=%d\n", title, ret);
aead = NULL;
goto error;
}
ret = crypto_aead_setkey(aead, key, keylen);
if (ret) {
pr_err("%s crypto_aead_setkey size=%u failed, err=%d\n", title,
keylen, ret);
goto error;
}
ret = crypto_aead_setauthsize(aead, OVPN_AUTH_TAG_SIZE);
if (ret) {
pr_err("%s crypto_aead_setauthsize failed, err=%d\n", title,
ret);
goto error;
}
/* basic AEAD assumption
* all current algorithms use OVPN_NONCE_SIZE.
* ovpn_aead_crypto_tmp_size and ovpn_aead_encrypt/decrypt
* expect this.
*/
if (crypto_aead_ivsize(aead) != OVPN_NONCE_SIZE) {
pr_err("%s IV size must be %d\n", title, OVPN_NONCE_SIZE);
ret = -EINVAL;
goto error;
}
pr_debug("********* Cipher %s (%s)\n", alg_name, title);
pr_debug("*** IV size=%u\n", crypto_aead_ivsize(aead));
pr_debug("*** req size=%u\n", crypto_aead_reqsize(aead));
pr_debug("*** block size=%u\n", crypto_aead_blocksize(aead));
pr_debug("*** auth size=%u\n", crypto_aead_authsize(aead));
pr_debug("*** alignmask=0x%x\n", crypto_aead_alignmask(aead));
return aead;
error:
crypto_free_aead(aead);
return ERR_PTR(ret);
}
static void ovpn_aead_crypto_key_slot_free(struct ovpn_crypto_key_slot *ks)
{
crypto_free_aead(ks->encrypt);
crypto_free_aead(ks->decrypt);
}
static void ovpn_aead_crypto_key_slot_free_work(struct work_struct *work)
{
struct ovpn_crypto_key_slot *ks;
ks = container_of(to_rcu_work(work), struct ovpn_crypto_key_slot,
free_work);
ovpn_aead_crypto_key_slot_free(ks);
kfree(ks);
}
struct ovpn_crypto_key_slot *
ovpn_aead_crypto_key_slot_new(const struct ovpn_key_config *kc)
{
struct ovpn_crypto_key_slot *ks = NULL;
const char *alg_name;
int ret;
/* validate crypto alg */
switch (kc->cipher_alg) {
case OVPN_CIPHER_ALG_AES_GCM:
alg_name = ALG_NAME_AES;
break;
case OVPN_CIPHER_ALG_CHACHA20_POLY1305:
alg_name = ALG_NAME_CHACHAPOLY;
break;
default:
return ERR_PTR(-EOPNOTSUPP);
}
if (kc->encrypt.nonce_tail_size != OVPN_NONCE_TAIL_SIZE ||
kc->decrypt.nonce_tail_size != OVPN_NONCE_TAIL_SIZE)
return ERR_PTR(-EINVAL);
/* build the key slot */
ks = kmalloc_obj(*ks);
if (!ks)
return ERR_PTR(-ENOMEM);
ks->encrypt = NULL;
ks->decrypt = NULL;
INIT_RCU_WORK(&ks->free_work, ovpn_aead_crypto_key_slot_free_work);
kref_init(&ks->refcount);
ks->key_id = kc->key_id;
ks->encrypt = ovpn_aead_init("encrypt", alg_name,
kc->encrypt.cipher_key,
kc->encrypt.cipher_key_size);
if (IS_ERR(ks->encrypt)) {
ret = PTR_ERR(ks->encrypt);
ks->encrypt = NULL;
goto destroy_ks;
}
ks->decrypt = ovpn_aead_init("decrypt", alg_name,
kc->decrypt.cipher_key,
kc->decrypt.cipher_key_size);
if (IS_ERR(ks->decrypt)) {
ret = PTR_ERR(ks->decrypt);
ks->decrypt = NULL;
goto destroy_ks;
}
memcpy(ks->nonce_tail_xmit, kc->encrypt.nonce_tail,
OVPN_NONCE_TAIL_SIZE);
memcpy(ks->nonce_tail_recv, kc->decrypt.nonce_tail,
OVPN_NONCE_TAIL_SIZE);
/* init packet ID generation/validation */
ovpn_pktid_xmit_init(&ks->pid_xmit);
ovpn_pktid_recv_init(&ks->pid_recv);
return ks;
destroy_ks:
ovpn_aead_crypto_key_slot_free(ks);
kfree(ks);
return ERR_PTR(ret);
}
enum ovpn_cipher_alg ovpn_aead_crypto_alg(struct ovpn_crypto_key_slot *ks)
{
const char *alg_name;
if (!ks->encrypt)
return OVPN_CIPHER_ALG_NONE;
alg_name = crypto_tfm_alg_name(crypto_aead_tfm(ks->encrypt));
if (!strcmp(alg_name, ALG_NAME_AES))
return OVPN_CIPHER_ALG_AES_GCM;
else if (!strcmp(alg_name, ALG_NAME_CHACHAPOLY))
return OVPN_CIPHER_ALG_CHACHA20_POLY1305;
else
return OVPN_CIPHER_ALG_NONE;
}
@@ -0,0 +1,28 @@
/* 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_OVPNAEAD_H_
#define _NET_OVPN_OVPNAEAD_H_
#include "crypto.h"
#include <asm/types.h>
#include <linux/skbuff.h>
int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
struct sk_buff *skb);
int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
struct sk_buff *skb);
struct ovpn_crypto_key_slot *
ovpn_aead_crypto_key_slot_new(const struct ovpn_key_config *kc);
enum ovpn_cipher_alg ovpn_aead_crypto_alg(struct ovpn_crypto_key_slot *ks);
#endif /* _NET_OVPN_OVPNAEAD_H_ */
+481
View File
@@ -0,0 +1,481 @@
// SPDX-License-Identifier: GPL-2.0
/* OpenVPN data channel offload
*
* Copyright (C) 2019-2025 OpenVPN, Inc.
*
* Author: James Yonan <james@openvpn.net>
* Antonio Quartulli <antonio@openvpn.net>
*/
#include <crypto/aead.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <net/gro_cells.h>
#include <net/gso.h>
#include <net/ip.h>
#include "ovpnpriv.h"
#include "peer.h"
#include "io.h"
#include "bind.h"
#include "crypto.h"
#include "crypto_aead.h"
#include "netlink.h"
#include "proto.h"
#include "tcp.h"
#include "udp.h"
#include "skb.h"
#include "socket.h"
const unsigned char ovpn_keepalive_message[OVPN_KEEPALIVE_SIZE] = {
0x2a, 0x18, 0x7b, 0xf3, 0x64, 0x1e, 0xb4, 0xcb,
0x07, 0xed, 0x2d, 0x0a, 0x98, 0x1f, 0xc7, 0x48
};
/**
* ovpn_is_keepalive - check if skb contains a keepalive message
* @skb: packet to check
*
* Assumes that the first byte of skb->data is defined.
*
* Return: true if skb contains a keepalive or false otherwise
*/
static bool ovpn_is_keepalive(struct sk_buff *skb)
{
if (*skb->data != ovpn_keepalive_message[0])
return false;
if (skb->len != OVPN_KEEPALIVE_SIZE)
return false;
if (!pskb_may_pull(skb, OVPN_KEEPALIVE_SIZE))
return false;
return !memcmp(skb->data, ovpn_keepalive_message, OVPN_KEEPALIVE_SIZE);
}
/* Called after decrypt to write the IP packet to the device.
* This method is expected to manage/free the skb.
*/
static void ovpn_netdev_write(struct ovpn_peer *peer, struct sk_buff *skb)
{
unsigned int pkt_len;
int ret;
/*
* GSO state from the transport layer is not valid for the tunnel/data
* path. Reset all GSO fields to prevent any further GSO processing
* from entering an inconsistent state.
*/
skb_gso_reset(skb);
/* we can't guarantee the packet wasn't corrupted before entering the
* VPN, therefore we give other layers a chance to check that
*/
skb->ip_summed = CHECKSUM_NONE;
/* skb hash for transport packet no longer valid after decapsulation */
skb_clear_hash(skb);
/* post-decrypt scrub -- prepare to inject encapsulated packet onto the
* interface, based on __skb_tunnel_rx() in dst.h
*/
skb->dev = peer->ovpn->dev;
skb_set_queue_mapping(skb, 0);
skb_scrub_packet(skb, true);
/* network header reset in ovpn_decrypt_post() */
skb_reset_mac_header(skb);
skb_reset_transport_header(skb);
skb_reset_inner_headers(skb);
/* cause packet to be "received" by the interface */
pkt_len = skb->len;
/* we may get here in process context in case of TCP connections,
* therefore we have to disable BHs to ensure gro_cells_receive()
* and dev_dstats_rx_add() do not get corrupted or enter deadlock
*/
local_bh_disable();
ret = gro_cells_receive(&peer->ovpn->gro_cells, skb);
if (likely(ret == NET_RX_SUCCESS)) {
/* update RX stats with the size of decrypted packet */
ovpn_peer_stats_increment_rx(&peer->vpn_stats, pkt_len);
dev_dstats_rx_add(peer->ovpn->dev, pkt_len);
}
local_bh_enable();
}
void ovpn_decrypt_post(void *data, int ret)
{
struct ovpn_crypto_key_slot *ks;
unsigned int payload_offset = 0;
struct sk_buff *skb = data;
struct ovpn_socket *sock;
struct ovpn_peer *peer;
__be16 proto;
__be32 *pid;
/* crypto is happening asynchronously. this function will be called
* again later by the crypto callback with a proper return code
*/
if (unlikely(ret == -EINPROGRESS))
return;
payload_offset = ovpn_skb_cb(skb)->payload_offset;
ks = ovpn_skb_cb(skb)->ks;
peer = ovpn_skb_cb(skb)->peer;
/* crypto is done, cleanup skb CB and its members */
kfree(ovpn_skb_cb(skb)->crypto_tmp);
if (unlikely(ret < 0))
goto drop;
/* PID sits after the op */
pid = (__force __be32 *)(skb->data + OVPN_OPCODE_SIZE);
ret = ovpn_pktid_recv(&ks->pid_recv, ntohl(*pid), 0);
if (unlikely(ret < 0)) {
net_err_ratelimited("%s: PKT ID RX error for peer %u: %d\n",
netdev_name(peer->ovpn->dev), peer->id,
ret);
goto drop;
}
/* keep track of last received authenticated packet for keepalive */
WRITE_ONCE(peer->last_recv, ktime_get_boottime_seconds());
rcu_read_lock();
sock = rcu_dereference(peer->sock);
if (sock && sock->sk->sk_protocol == IPPROTO_UDP)
/* check if this peer changed local or remote endpoint */
ovpn_peer_endpoints_update(peer, skb);
rcu_read_unlock();
/* point to encapsulated IP packet */
__skb_pull(skb, payload_offset);
/* check if this is a valid datapacket that has to be delivered to the
* ovpn interface
*/
skb_reset_network_header(skb);
proto = ovpn_ip_check_protocol(skb);
if (unlikely(!proto)) {
/* check if null packet */
if (unlikely(!pskb_may_pull(skb, 1))) {
net_info_ratelimited("%s: NULL packet received from peer %u\n",
netdev_name(peer->ovpn->dev),
peer->id);
goto drop;
}
if (ovpn_is_keepalive(skb)) {
net_dbg_ratelimited("%s: ping received from peer %u\n",
netdev_name(peer->ovpn->dev),
peer->id);
/* we drop the packet, but this is not a failure */
consume_skb(skb);
goto drop_nocount;
}
net_info_ratelimited("%s: unsupported protocol received from peer %u\n",
netdev_name(peer->ovpn->dev), peer->id);
goto drop;
}
skb->protocol = proto;
/* perform Reverse Path Filtering (RPF) */
if (unlikely(!ovpn_peer_check_by_src(peer->ovpn, skb, peer))) {
if (skb->protocol == htons(ETH_P_IPV6))
net_dbg_ratelimited("%s: RPF dropped packet from peer %u, src: %pI6c\n",
netdev_name(peer->ovpn->dev),
peer->id, &ipv6_hdr(skb)->saddr);
else
net_dbg_ratelimited("%s: RPF dropped packet from peer %u, src: %pI4\n",
netdev_name(peer->ovpn->dev),
peer->id, &ip_hdr(skb)->saddr);
goto drop;
}
ovpn_netdev_write(peer, skb);
/* skb is passed to upper layer - don't free it */
skb = NULL;
drop:
if (unlikely(skb))
ovpn_dev_dstats_rx_dropped(peer->ovpn->dev);
kfree_skb(skb);
drop_nocount:
if (likely(ks))
ovpn_crypto_key_slot_put(ks);
if (likely(peer))
ovpn_peer_put(peer);
}
/* RX path entry point: decrypt packet and forward it to the device */
void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb)
{
struct ovpn_crypto_key_slot *ks;
u8 key_id;
ovpn_peer_stats_increment_rx(&peer->link_stats, skb->len);
/* get the key slot matching the key ID in the received packet */
key_id = ovpn_key_id_from_skb(skb);
ks = ovpn_crypto_key_id_to_slot(&peer->crypto, key_id);
if (unlikely(!ks)) {
net_info_ratelimited("%s: no available key for peer %u, key-id: %u\n",
netdev_name(peer->ovpn->dev), peer->id,
key_id);
ovpn_dev_dstats_rx_dropped(peer->ovpn->dev);
kfree_skb(skb);
ovpn_peer_put(peer);
return;
}
memset(ovpn_skb_cb(skb), 0, sizeof(struct ovpn_cb));
ovpn_decrypt_post(skb, ovpn_aead_decrypt(peer, ks, skb));
}
void ovpn_encrypt_post(void *data, int ret)
{
struct ovpn_crypto_key_slot *ks;
struct sk_buff *skb = data;
struct ovpn_socket *sock;
struct ovpn_peer *peer;
unsigned int orig_len;
/* encryption is happening asynchronously. This function will be
* called later by the crypto callback with a proper return value
*/
if (unlikely(ret == -EINPROGRESS))
return;
ks = ovpn_skb_cb(skb)->ks;
peer = ovpn_skb_cb(skb)->peer;
/* crypto is done, cleanup skb CB and its members */
kfree(ovpn_skb_cb(skb)->crypto_tmp);
if (unlikely(ret == -ERANGE)) {
/* we ran out of IVs and we must kill the key as it can't be
* use anymore
*/
netdev_warn(peer->ovpn->dev,
"killing key %u for peer %u\n", ks->key_id,
peer->id);
if (ovpn_crypto_kill_key(&peer->crypto, ks->key_id))
/* let userspace know so that a new key must be negotiated */
ovpn_nl_key_swap_notify(peer, ks->key_id);
goto err;
}
if (unlikely(ret < 0))
goto err;
skb_mark_not_on_list(skb);
orig_len = skb->len;
rcu_read_lock();
sock = rcu_dereference(peer->sock);
if (unlikely(!sock))
goto err_unlock;
switch (sock->sk->sk_protocol) {
case IPPROTO_UDP:
ovpn_udp_send_skb(peer, sock->sk, skb);
break;
case IPPROTO_TCP:
ovpn_tcp_send_skb(peer, sock->sk, skb);
break;
default:
/* no transport configured yet */
goto err_unlock;
}
ovpn_peer_stats_increment_tx(&peer->link_stats, orig_len);
/* keep track of last sent packet for keepalive */
WRITE_ONCE(peer->last_sent, ktime_get_boottime_seconds());
/* skb passed down the stack - don't free it */
skb = NULL;
err_unlock:
rcu_read_unlock();
err:
if (unlikely(skb))
ovpn_dev_dstats_tx_dropped(peer->ovpn->dev);
kfree_skb(skb);
if (likely(ks))
ovpn_crypto_key_slot_put(ks);
if (likely(peer))
ovpn_peer_put(peer);
}
static bool ovpn_encrypt_one(struct ovpn_peer *peer, struct sk_buff *skb)
{
struct ovpn_crypto_key_slot *ks;
/* get primary key to be used for encrypting data */
ks = ovpn_crypto_key_slot_primary(&peer->crypto);
if (unlikely(!ks))
return false;
/* take a reference to the peer because the crypto code may run async.
* ovpn_encrypt_post() will release it upon completion
*/
if (unlikely(!ovpn_peer_hold(peer))) {
DEBUG_NET_WARN_ON_ONCE(1);
ovpn_crypto_key_slot_put(ks);
return false;
}
memset(ovpn_skb_cb(skb), 0, sizeof(struct ovpn_cb));
ovpn_encrypt_post(skb, ovpn_aead_encrypt(peer, ks, skb));
return true;
}
/* send skb to connected peer, if any */
static void ovpn_send(struct ovpn_priv *ovpn, struct sk_buff *skb,
struct ovpn_peer *peer)
{
struct sk_buff *curr, *next;
/* this might be a GSO-segmented skb list: process each skb
* independently
*/
skb_list_walk_safe(skb, curr, next) {
if (unlikely(!ovpn_encrypt_one(peer, curr))) {
ovpn_dev_dstats_tx_dropped(ovpn->dev);
kfree_skb(curr);
}
}
ovpn_peer_put(peer);
}
/* Send user data to the network
*/
netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct ovpn_priv *ovpn = netdev_priv(dev);
struct sk_buff *segments, *curr, *next;
struct sk_buff_head skb_list;
unsigned int tx_bytes = 0;
struct ovpn_peer *peer;
__be16 proto;
int ret;
/* reset netfilter state */
nf_reset_ct(skb);
/* verify IP header size in network packet */
proto = ovpn_ip_check_protocol(skb);
if (unlikely(!proto || skb->protocol != proto))
goto drop_no_peer;
/* retrieve peer serving the destination IP of this packet */
peer = ovpn_peer_get_by_dst(ovpn, skb);
if (unlikely(!peer)) {
switch (skb->protocol) {
case htons(ETH_P_IP):
net_dbg_ratelimited("%s: no peer to send data to dst=%pI4\n",
netdev_name(ovpn->dev),
&ip_hdr(skb)->daddr);
break;
case htons(ETH_P_IPV6):
net_dbg_ratelimited("%s: no peer to send data to dst=%pI6c\n",
netdev_name(ovpn->dev),
&ipv6_hdr(skb)->daddr);
break;
}
goto drop_no_peer;
}
/* dst was needed for peer selection - it can now be dropped */
skb_dst_drop(skb);
if (skb_is_gso(skb)) {
segments = skb_gso_segment(skb, 0);
if (IS_ERR(segments)) {
ret = PTR_ERR(segments);
net_err_ratelimited("%s: cannot segment payload packet: %d\n",
netdev_name(dev), ret);
goto drop;
}
consume_skb(skb);
skb = segments;
}
/* from this moment on, "skb" might be a list */
__skb_queue_head_init(&skb_list);
skb_list_walk_safe(skb, curr, next) {
skb_mark_not_on_list(curr);
curr = skb_share_check(curr, GFP_ATOMIC);
if (unlikely(!curr)) {
net_err_ratelimited("%s: skb_share_check failed for payload packet\n",
netdev_name(dev));
ovpn_dev_dstats_tx_dropped(ovpn->dev);
continue;
}
/* only count what we actually send */
tx_bytes += curr->len;
__skb_queue_tail(&skb_list, curr);
}
/* no segments survived: don't jump to 'drop' because we already
* incremented the counter for each failure in the loop
*/
if (unlikely(skb_queue_empty(&skb_list))) {
ovpn_peer_put(peer);
return NETDEV_TX_OK;
}
skb_list.prev->next = NULL;
ovpn_peer_stats_increment_tx(&peer->vpn_stats, tx_bytes);
ovpn_send(ovpn, skb_list.next, peer);
return NETDEV_TX_OK;
drop:
ovpn_peer_put(peer);
drop_no_peer:
ovpn_dev_dstats_tx_dropped(ovpn->dev);
skb_tx_error(skb);
kfree_skb_list(skb);
return NETDEV_TX_OK;
}
/**
* ovpn_xmit_special - encrypt and transmit an out-of-band message to peer
* @peer: peer to send the message to
* @data: message content
* @len: message length
*
* Assumes that caller holds a reference to peer, which will be
* passed to ovpn_send()
*/
void ovpn_xmit_special(struct ovpn_peer *peer, const void *data,
const unsigned int len)
{
struct ovpn_priv *ovpn;
struct sk_buff *skb;
ovpn = peer->ovpn;
if (unlikely(!ovpn)) {
ovpn_peer_put(peer);
return;
}
skb = alloc_skb(256 + len, GFP_ATOMIC);
if (unlikely(!skb)) {
ovpn_peer_put(peer);
return;
}
skb_reserve(skb, 128);
skb->priority = TC_PRIO_BESTEFFORT;
__skb_put_data(skb, data, len);
ovpn_send(ovpn, skb, peer);
}
+34
View File
@@ -0,0 +1,34 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* OpenVPN data channel offload
*
* Copyright (C) 2019-2025 OpenVPN, Inc.
*
* Author: James Yonan <james@openvpn.net>
* Antonio Quartulli <antonio@openvpn.net>
*/
#ifndef _NET_OVPN_OVPN_H_
#define _NET_OVPN_OVPN_H_
/* DATA_V2 header size with AEAD encryption */
#define OVPN_HEAD_ROOM (OVPN_OPCODE_SIZE + OVPN_NONCE_WIRE_SIZE + \
16 /* AEAD TAG length */ + \
max(sizeof(struct udphdr), sizeof(struct tcphdr)) +\
max(sizeof(struct ipv6hdr), sizeof(struct iphdr)))
/* max padding required by encryption */
#define OVPN_MAX_PADDING 16
#define OVPN_KEEPALIVE_SIZE 16
extern const unsigned char ovpn_keepalive_message[OVPN_KEEPALIVE_SIZE];
netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev);
void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb);
void ovpn_xmit_special(struct ovpn_peer *peer, const void *data,
const unsigned int len);
void ovpn_encrypt_post(void *data, int ret);
void ovpn_decrypt_post(void *data, int ret);
#endif /* _NET_OVPN_OVPN_H_ */
+316
View File
@@ -0,0 +1,316 @@
// SPDX-License-Identifier: GPL-2.0
/* OpenVPN data channel offload
*
* Copyright (C) 2020-2025 OpenVPN, Inc.
*
* Author: Antonio Quartulli <antonio@openvpn.net>
* James Yonan <james@openvpn.net>
*/
#include <linux/ethtool.h>
#include <linux/genetlink.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/inetdevice.h>
#include <linux/workqueue.h>
#include <net/gro_cells.h>
#include <net/ip.h>
#include <net/rtnetlink.h>
#include <uapi/linux/if_arp.h>
#include "ovpnpriv.h"
#include "main.h"
#include "netlink.h"
#include "io.h"
#include "peer.h"
#include "proto.h"
#include "tcp.h"
#include "udp.h"
/* module-owned workqueue on which all ovpn-specific work is queued */
struct workqueue_struct *ovpn_wq;
static void ovpn_priv_free(struct net_device *net)
{
struct ovpn_priv *ovpn = netdev_priv(net);
kfree(ovpn->peers);
}
static int ovpn_mp_alloc(struct ovpn_priv *ovpn)
{
int i;
if (ovpn->mode != OVPN_MODE_MP)
return 0;
/* the peer container is fairly large, therefore we allocate it only in
* MP mode
*/
ovpn->peers = kzalloc_obj(*ovpn->peers);
if (!ovpn->peers)
return -ENOMEM;
for (i = 0; i < ARRAY_SIZE(ovpn->peers->by_id); i++) {
INIT_HLIST_HEAD(&ovpn->peers->by_id[i]);
INIT_HLIST_NULLS_HEAD(&ovpn->peers->by_vpn_addr4[i], i);
INIT_HLIST_NULLS_HEAD(&ovpn->peers->by_vpn_addr6[i], i);
INIT_HLIST_NULLS_HEAD(&ovpn->peers->by_transp_addr[i], i);
}
return 0;
}
static int ovpn_net_init(struct net_device *dev)
{
struct ovpn_priv *ovpn = netdev_priv(dev);
int err = gro_cells_init(&ovpn->gro_cells, dev);
if (err < 0)
return err;
err = ovpn_mp_alloc(ovpn);
if (err < 0) {
gro_cells_destroy(&ovpn->gro_cells);
return err;
}
return 0;
}
static void ovpn_net_uninit(struct net_device *dev)
{
struct ovpn_priv *ovpn = netdev_priv(dev);
disable_delayed_work_sync(&ovpn->keepalive_work);
ovpn_peers_free(ovpn, NULL, OVPN_DEL_PEER_REASON_TEARDOWN);
gro_cells_destroy(&ovpn->gro_cells);
}
static int ovpn_net_open(struct net_device *dev)
{
struct ovpn_priv *ovpn = netdev_priv(dev);
struct in_device *dev_v4;
/* the IPv4 in_device (and thus its config) is recreated whenever the
* interface is moved to a new netns, so redirects must be disabled on
* every bring-up rather than once at creation time, otherwise the
* setting is silently lost after such a move
*/
if (ovpn->mode == OVPN_MODE_MP) {
dev_v4 = __in_dev_get_rtnl(dev);
if (dev_v4) {
/* disable redirects as Linux gets confused by ovpn
* handling same-LAN routing.
* This happens because a multipeer interface is used as
* relay point between hosts in the same subnet, while
* in a classic LAN this would not be needed because the
* two hosts would be able to talk directly.
*/
IN_DEV_CONF_SET(dev_v4, SEND_REDIRECTS, false);
IPV4_DEVCONF_ALL(dev_net(dev), SEND_REDIRECTS) = false;
}
}
return 0;
}
static const struct net_device_ops ovpn_netdev_ops = {
.ndo_init = ovpn_net_init,
.ndo_uninit = ovpn_net_uninit,
.ndo_open = ovpn_net_open,
.ndo_start_xmit = ovpn_net_xmit,
};
static const struct device_type ovpn_type = {
.name = OVPN_FAMILY_NAME,
};
static const struct nla_policy ovpn_policy[IFLA_OVPN_MAX + 1] = {
[IFLA_OVPN_MODE] = NLA_POLICY_RANGE(NLA_U8, OVPN_MODE_P2P,
OVPN_MODE_MP),
};
/**
* ovpn_dev_is_valid - check if the netdevice is of type 'ovpn'
* @dev: the interface to check
*
* Return: whether the netdevice is of type 'ovpn'
*/
bool ovpn_dev_is_valid(const struct net_device *dev)
{
return dev->netdev_ops == &ovpn_netdev_ops;
}
static void ovpn_get_drvinfo(struct net_device *dev,
struct ethtool_drvinfo *info)
{
strscpy(info->driver, "ovpn", sizeof(info->driver));
strscpy(info->bus_info, "ovpn", sizeof(info->bus_info));
}
static const struct ethtool_ops ovpn_ethtool_ops = {
.get_drvinfo = ovpn_get_drvinfo,
.get_link = ethtool_op_get_link,
.get_ts_info = ethtool_op_get_ts_info,
};
static void ovpn_setup(struct net_device *dev)
{
netdev_features_t feat = NETIF_F_SG | NETIF_F_GSO |
NETIF_F_GSO_SOFTWARE | NETIF_F_HIGHDMA;
dev->needs_free_netdev = true;
dev->pcpu_stat_type = NETDEV_PCPU_STAT_DSTATS;
dev->ethtool_ops = &ovpn_ethtool_ops;
dev->netdev_ops = &ovpn_netdev_ops;
dev->priv_destructor = ovpn_priv_free;
dev->hard_header_len = 0;
dev->addr_len = 0;
dev->mtu = ETH_DATA_LEN - OVPN_HEAD_ROOM;
dev->min_mtu = IPV4_MIN_MTU;
dev->max_mtu = IP_MAX_MTU - OVPN_HEAD_ROOM;
dev->type = ARPHRD_NONE;
dev->flags = IFF_POINTOPOINT | IFF_NOARP;
dev->priv_flags |= IFF_NO_QUEUE;
/* when routing packets to a LAN behind a client, we rely on the
* route entry that originally brought the packet into ovpn, so
* don't release it
*/
netif_keep_dst(dev);
dev->lltx = true;
dev->features |= feat;
dev->hw_features |= feat;
dev->hw_enc_features |= feat;
dev->needed_headroom = ALIGN(OVPN_HEAD_ROOM, 4);
dev->needed_tailroom = OVPN_MAX_PADDING;
SET_NETDEV_DEVTYPE(dev, &ovpn_type);
}
static int ovpn_newlink(struct net_device *dev,
struct rtnl_newlink_params *params,
struct netlink_ext_ack *extack)
{
struct ovpn_priv *ovpn = netdev_priv(dev);
struct nlattr **data = params->data;
enum ovpn_mode mode = OVPN_MODE_P2P;
int ret;
if (data && data[IFLA_OVPN_MODE]) {
mode = nla_get_u8(data[IFLA_OVPN_MODE]);
netdev_dbg(dev, "setting device mode: %u\n", mode);
}
ovpn->dev = dev;
ovpn->mode = mode;
spin_lock_init(&ovpn->lock);
INIT_DELAYED_WORK(&ovpn->keepalive_work, ovpn_peer_keepalive_work);
/* Set carrier explicitly after registration, this way state is
* clearly defined.
*
* In case of MP interfaces we keep the carrier always on.
*
* Carrier for P2P interfaces is initially off and it is then
* switched on and off when the remote peer is added or deleted.
*/
if (ovpn->mode == OVPN_MODE_MP)
netif_carrier_on(dev);
else
netif_carrier_off(dev);
ret = register_netdevice(dev);
if (ret < 0)
return ret;
return 0;
}
static size_t ovpn_get_size(const struct net_device *dev)
{
/* IFLA_OVPN_MODE */
return nla_total_size(sizeof(u8));
}
static int ovpn_fill_info(struct sk_buff *skb, const struct net_device *dev)
{
struct ovpn_priv *ovpn = netdev_priv(dev);
if (nla_put_u8(skb, IFLA_OVPN_MODE, ovpn->mode))
return -EMSGSIZE;
return 0;
}
static struct rtnl_link_ops ovpn_link_ops = {
.kind = "ovpn",
.netns_refund = false,
.priv_size = sizeof(struct ovpn_priv),
.setup = ovpn_setup,
.policy = ovpn_policy,
.maxtype = IFLA_OVPN_MAX,
.newlink = ovpn_newlink,
.get_size = ovpn_get_size,
.fill_info = ovpn_fill_info,
};
static int __init ovpn_init(void)
{
int err;
ovpn_tcp_init();
ovpn_wq = alloc_workqueue("ovpn", WQ_PERCPU, 0);
if (!ovpn_wq) {
pr_err("ovpn: cannot allocate workqueue\n");
return -ENOMEM;
}
err = rtnl_link_register(&ovpn_link_ops);
if (err) {
pr_err("ovpn: can't register rtnl link ops: %d\n", err);
goto destroy_wq;
}
err = ovpn_nl_register();
if (err) {
pr_err("ovpn: can't register netlink family: %d\n", err);
goto unreg_rtnl;
}
return 0;
unreg_rtnl:
rtnl_link_unregister(&ovpn_link_ops);
destroy_wq:
destroy_workqueue(ovpn_wq);
ovpn_wq = NULL;
return err;
}
static __exit void ovpn_cleanup(void)
{
ovpn_nl_unregister();
rtnl_link_unregister(&ovpn_link_ops);
flush_workqueue(ovpn_wq);
rcu_barrier();
destroy_workqueue(ovpn_wq);
ovpn_wq = NULL;
}
module_init(ovpn_init);
module_exit(ovpn_cleanup);
MODULE_DESCRIPTION("OpenVPN data channel offload (ovpn)");
MODULE_AUTHOR("Antonio Quartulli <antonio@openvpn.net>");
MODULE_LICENSE("GPL");
+14
View File
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* OpenVPN data channel offload
*
* Copyright (C) 2020-2025 OpenVPN, Inc.
*
* Author: Antonio Quartulli <antonio@openvpn.net>
*/
#ifndef _NET_OVPN_MAIN_H_
#define _NET_OVPN_MAIN_H_
bool ovpn_dev_is_valid(const struct net_device *dev);
#endif /* _NET_OVPN_MAIN_H_ */
+278
View File
@@ -0,0 +1,278 @@
// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
/* Do not edit directly, auto-generated from: */
/* Documentation/netlink/specs/ovpn.yaml */
/* YNL-GEN kernel source */
/* To regenerate run: tools/net/ynl/ynl-regen.sh */
#include <net/netlink.h>
#include <net/genetlink.h>
#include "netlink-gen.h"
#include <uapi/linux/ovpn.h>
/* Integer value ranges */
static const struct netlink_range_validation ovpn_a_peer_id_range = {
.max = 16777215ULL,
};
static const struct netlink_range_validation ovpn_a_peer_keepalive_interval_range = {
.max = 86400ULL,
};
static const struct netlink_range_validation ovpn_a_peer_keepalive_timeout_range = {
.max = 86400ULL,
};
static const struct netlink_range_validation ovpn_a_peer_tx_id_range = {
.max = 16777215ULL,
};
static const struct netlink_range_validation ovpn_a_keyconf_peer_id_range = {
.max = 16777215ULL,
};
/* Common nested types */
const struct nla_policy ovpn_keyconf_nl_policy[OVPN_A_KEYCONF_DECRYPT_DIR + 1] = {
[OVPN_A_KEYCONF_PEER_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_keyconf_peer_id_range),
[OVPN_A_KEYCONF_SLOT] = NLA_POLICY_MAX(NLA_U32, 1),
[OVPN_A_KEYCONF_KEY_ID] = NLA_POLICY_MAX(NLA_U32, 7),
[OVPN_A_KEYCONF_CIPHER_ALG] = NLA_POLICY_MAX(NLA_U32, 2),
[OVPN_A_KEYCONF_ENCRYPT_DIR] = NLA_POLICY_NESTED(ovpn_keydir_nl_policy),
[OVPN_A_KEYCONF_DECRYPT_DIR] = NLA_POLICY_NESTED(ovpn_keydir_nl_policy),
};
const struct nla_policy ovpn_keyconf_del_input_nl_policy[OVPN_A_KEYCONF_SLOT + 1] = {
[OVPN_A_KEYCONF_PEER_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_keyconf_peer_id_range),
[OVPN_A_KEYCONF_SLOT] = NLA_POLICY_MAX(NLA_U32, 1),
};
const struct nla_policy ovpn_keyconf_get_nl_policy[OVPN_A_KEYCONF_CIPHER_ALG + 1] = {
[OVPN_A_KEYCONF_PEER_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_keyconf_peer_id_range),
[OVPN_A_KEYCONF_SLOT] = NLA_POLICY_MAX(NLA_U32, 1),
[OVPN_A_KEYCONF_KEY_ID] = NLA_POLICY_MAX(NLA_U32, 7),
[OVPN_A_KEYCONF_CIPHER_ALG] = NLA_POLICY_MAX(NLA_U32, 2),
};
const struct nla_policy ovpn_keyconf_swap_input_nl_policy[OVPN_A_KEYCONF_PEER_ID + 1] = {
[OVPN_A_KEYCONF_PEER_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_keyconf_peer_id_range),
};
const struct nla_policy ovpn_keydir_nl_policy[OVPN_A_KEYDIR_NONCE_TAIL + 1] = {
[OVPN_A_KEYDIR_CIPHER_KEY] = NLA_POLICY_MAX_LEN(256),
[OVPN_A_KEYDIR_NONCE_TAIL] = NLA_POLICY_EXACT_LEN(OVPN_NONCE_TAIL_SIZE),
};
const struct nla_policy ovpn_peer_nl_policy[OVPN_A_PEER_TX_ID + 1] = {
[OVPN_A_PEER_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_id_range),
[OVPN_A_PEER_REMOTE_IPV4] = { .type = NLA_BE32, },
[OVPN_A_PEER_REMOTE_IPV6] = NLA_POLICY_EXACT_LEN(16),
[OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID] = { .type = NLA_U32, },
[OVPN_A_PEER_REMOTE_PORT] = NLA_POLICY_MIN(NLA_BE16, 1),
[OVPN_A_PEER_SOCKET] = { .type = NLA_U32, },
[OVPN_A_PEER_SOCKET_NETNSID] = { .type = NLA_S32, },
[OVPN_A_PEER_VPN_IPV4] = { .type = NLA_BE32, },
[OVPN_A_PEER_VPN_IPV6] = NLA_POLICY_EXACT_LEN(16),
[OVPN_A_PEER_LOCAL_IPV4] = { .type = NLA_BE32, },
[OVPN_A_PEER_LOCAL_IPV6] = NLA_POLICY_EXACT_LEN(16),
[OVPN_A_PEER_LOCAL_PORT] = NLA_POLICY_MIN(NLA_BE16, 1),
[OVPN_A_PEER_KEEPALIVE_INTERVAL] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_keepalive_interval_range),
[OVPN_A_PEER_KEEPALIVE_TIMEOUT] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_keepalive_timeout_range),
[OVPN_A_PEER_DEL_REASON] = NLA_POLICY_MAX(NLA_U32, 4),
[OVPN_A_PEER_VPN_RX_BYTES] = { .type = NLA_UINT, },
[OVPN_A_PEER_VPN_TX_BYTES] = { .type = NLA_UINT, },
[OVPN_A_PEER_VPN_RX_PACKETS] = { .type = NLA_UINT, },
[OVPN_A_PEER_VPN_TX_PACKETS] = { .type = NLA_UINT, },
[OVPN_A_PEER_LINK_RX_BYTES] = { .type = NLA_UINT, },
[OVPN_A_PEER_LINK_TX_BYTES] = { .type = NLA_UINT, },
[OVPN_A_PEER_LINK_RX_PACKETS] = { .type = NLA_UINT, },
[OVPN_A_PEER_LINK_TX_PACKETS] = { .type = NLA_UINT, },
[OVPN_A_PEER_TX_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_tx_id_range),
};
const struct nla_policy ovpn_peer_del_input_nl_policy[OVPN_A_PEER_ID + 1] = {
[OVPN_A_PEER_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_id_range),
};
const struct nla_policy ovpn_peer_new_input_nl_policy[OVPN_A_PEER_TX_ID + 1] = {
[OVPN_A_PEER_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_id_range),
[OVPN_A_PEER_REMOTE_IPV4] = { .type = NLA_BE32, },
[OVPN_A_PEER_REMOTE_IPV6] = NLA_POLICY_EXACT_LEN(16),
[OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID] = { .type = NLA_U32, },
[OVPN_A_PEER_REMOTE_PORT] = NLA_POLICY_MIN(NLA_BE16, 1),
[OVPN_A_PEER_SOCKET] = { .type = NLA_U32, },
[OVPN_A_PEER_VPN_IPV4] = { .type = NLA_BE32, },
[OVPN_A_PEER_VPN_IPV6] = NLA_POLICY_EXACT_LEN(16),
[OVPN_A_PEER_LOCAL_IPV4] = { .type = NLA_BE32, },
[OVPN_A_PEER_LOCAL_IPV6] = NLA_POLICY_EXACT_LEN(16),
[OVPN_A_PEER_KEEPALIVE_INTERVAL] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_keepalive_interval_range),
[OVPN_A_PEER_KEEPALIVE_TIMEOUT] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_keepalive_timeout_range),
[OVPN_A_PEER_TX_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_tx_id_range),
};
const struct nla_policy ovpn_peer_set_input_nl_policy[OVPN_A_PEER_TX_ID + 1] = {
[OVPN_A_PEER_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_id_range),
[OVPN_A_PEER_REMOTE_IPV4] = { .type = NLA_BE32, },
[OVPN_A_PEER_REMOTE_IPV6] = NLA_POLICY_EXACT_LEN(16),
[OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID] = { .type = NLA_U32, },
[OVPN_A_PEER_REMOTE_PORT] = NLA_POLICY_MIN(NLA_BE16, 1),
[OVPN_A_PEER_VPN_IPV4] = { .type = NLA_BE32, },
[OVPN_A_PEER_VPN_IPV6] = NLA_POLICY_EXACT_LEN(16),
[OVPN_A_PEER_LOCAL_IPV4] = { .type = NLA_BE32, },
[OVPN_A_PEER_LOCAL_IPV6] = NLA_POLICY_EXACT_LEN(16),
[OVPN_A_PEER_KEEPALIVE_INTERVAL] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_keepalive_interval_range),
[OVPN_A_PEER_KEEPALIVE_TIMEOUT] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_keepalive_timeout_range),
[OVPN_A_PEER_TX_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_tx_id_range),
};
/* OVPN_CMD_PEER_NEW - do */
static const struct nla_policy ovpn_peer_new_nl_policy[OVPN_A_PEER + 1] = {
[OVPN_A_IFINDEX] = { .type = NLA_U32, },
[OVPN_A_PEER] = NLA_POLICY_NESTED(ovpn_peer_new_input_nl_policy),
};
/* OVPN_CMD_PEER_SET - do */
static const struct nla_policy ovpn_peer_set_nl_policy[OVPN_A_PEER + 1] = {
[OVPN_A_IFINDEX] = { .type = NLA_U32, },
[OVPN_A_PEER] = NLA_POLICY_NESTED(ovpn_peer_set_input_nl_policy),
};
/* OVPN_CMD_PEER_GET - do */
static const struct nla_policy ovpn_peer_get_do_nl_policy[OVPN_A_PEER + 1] = {
[OVPN_A_IFINDEX] = { .type = NLA_U32, },
[OVPN_A_PEER] = NLA_POLICY_NESTED(ovpn_peer_nl_policy),
};
/* OVPN_CMD_PEER_GET - dump */
static const struct nla_policy ovpn_peer_get_dump_nl_policy[OVPN_A_IFINDEX + 1] = {
[OVPN_A_IFINDEX] = { .type = NLA_U32, },
};
/* OVPN_CMD_PEER_DEL - do */
static const struct nla_policy ovpn_peer_del_nl_policy[OVPN_A_PEER + 1] = {
[OVPN_A_IFINDEX] = { .type = NLA_U32, },
[OVPN_A_PEER] = NLA_POLICY_NESTED(ovpn_peer_del_input_nl_policy),
};
/* OVPN_CMD_KEY_NEW - do */
static const struct nla_policy ovpn_key_new_nl_policy[OVPN_A_KEYCONF + 1] = {
[OVPN_A_IFINDEX] = { .type = NLA_U32, },
[OVPN_A_KEYCONF] = NLA_POLICY_NESTED(ovpn_keyconf_nl_policy),
};
/* OVPN_CMD_KEY_GET - do */
static const struct nla_policy ovpn_key_get_nl_policy[OVPN_A_KEYCONF + 1] = {
[OVPN_A_IFINDEX] = { .type = NLA_U32, },
[OVPN_A_KEYCONF] = NLA_POLICY_NESTED(ovpn_keyconf_get_nl_policy),
};
/* OVPN_CMD_KEY_SWAP - do */
static const struct nla_policy ovpn_key_swap_nl_policy[OVPN_A_KEYCONF + 1] = {
[OVPN_A_IFINDEX] = { .type = NLA_U32, },
[OVPN_A_KEYCONF] = NLA_POLICY_NESTED(ovpn_keyconf_swap_input_nl_policy),
};
/* OVPN_CMD_KEY_DEL - do */
static const struct nla_policy ovpn_key_del_nl_policy[OVPN_A_KEYCONF + 1] = {
[OVPN_A_IFINDEX] = { .type = NLA_U32, },
[OVPN_A_KEYCONF] = NLA_POLICY_NESTED(ovpn_keyconf_del_input_nl_policy),
};
/* Ops table for ovpn */
static const struct genl_split_ops ovpn_nl_ops[] = {
{
.cmd = OVPN_CMD_PEER_NEW,
.pre_doit = ovpn_nl_pre_doit,
.doit = ovpn_nl_peer_new_doit,
.post_doit = ovpn_nl_post_doit,
.policy = ovpn_peer_new_nl_policy,
.maxattr = OVPN_A_PEER,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = OVPN_CMD_PEER_SET,
.pre_doit = ovpn_nl_pre_doit,
.doit = ovpn_nl_peer_set_doit,
.post_doit = ovpn_nl_post_doit,
.policy = ovpn_peer_set_nl_policy,
.maxattr = OVPN_A_PEER,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = OVPN_CMD_PEER_GET,
.pre_doit = ovpn_nl_pre_doit,
.doit = ovpn_nl_peer_get_doit,
.post_doit = ovpn_nl_post_doit,
.policy = ovpn_peer_get_do_nl_policy,
.maxattr = OVPN_A_PEER,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = OVPN_CMD_PEER_GET,
.dumpit = ovpn_nl_peer_get_dumpit,
.policy = ovpn_peer_get_dump_nl_policy,
.maxattr = OVPN_A_IFINDEX,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP,
},
{
.cmd = OVPN_CMD_PEER_DEL,
.pre_doit = ovpn_nl_pre_doit,
.doit = ovpn_nl_peer_del_doit,
.post_doit = ovpn_nl_post_doit,
.policy = ovpn_peer_del_nl_policy,
.maxattr = OVPN_A_PEER,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = OVPN_CMD_KEY_NEW,
.pre_doit = ovpn_nl_pre_doit,
.doit = ovpn_nl_key_new_doit,
.post_doit = ovpn_nl_post_doit,
.policy = ovpn_key_new_nl_policy,
.maxattr = OVPN_A_KEYCONF,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = OVPN_CMD_KEY_GET,
.pre_doit = ovpn_nl_pre_doit,
.doit = ovpn_nl_key_get_doit,
.post_doit = ovpn_nl_post_doit,
.policy = ovpn_key_get_nl_policy,
.maxattr = OVPN_A_KEYCONF,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = OVPN_CMD_KEY_SWAP,
.pre_doit = ovpn_nl_pre_doit,
.doit = ovpn_nl_key_swap_doit,
.post_doit = ovpn_nl_post_doit,
.policy = ovpn_key_swap_nl_policy,
.maxattr = OVPN_A_KEYCONF,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = OVPN_CMD_KEY_DEL,
.pre_doit = ovpn_nl_pre_doit,
.doit = ovpn_nl_key_del_doit,
.post_doit = ovpn_nl_post_doit,
.policy = ovpn_key_del_nl_policy,
.maxattr = OVPN_A_KEYCONF,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
};
static const struct genl_multicast_group ovpn_nl_mcgrps[] = {
[OVPN_NLGRP_PEERS] = { "peers", },
};
struct genl_family ovpn_nl_family __ro_after_init = {
.name = OVPN_FAMILY_NAME,
.version = OVPN_FAMILY_VERSION,
.netnsok = true,
.parallel_ops = true,
.module = THIS_MODULE,
.split_ops = ovpn_nl_ops,
.n_split_ops = ARRAY_SIZE(ovpn_nl_ops),
.mcgrps = ovpn_nl_mcgrps,
.n_mcgrps = ARRAY_SIZE(ovpn_nl_mcgrps),
};
@@ -0,0 +1,48 @@
/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
/* Do not edit directly, auto-generated from: */
/* Documentation/netlink/specs/ovpn.yaml */
/* YNL-GEN kernel header */
/* To regenerate run: tools/net/ynl/ynl-regen.sh */
#ifndef _LINUX_OVPN_GEN_H
#define _LINUX_OVPN_GEN_H
#include <net/netlink.h>
#include <net/genetlink.h>
#include <uapi/linux/ovpn.h>
/* Common nested types */
extern const struct nla_policy ovpn_keyconf_nl_policy[OVPN_A_KEYCONF_DECRYPT_DIR + 1];
extern const struct nla_policy ovpn_keyconf_del_input_nl_policy[OVPN_A_KEYCONF_SLOT + 1];
extern const struct nla_policy ovpn_keyconf_get_nl_policy[OVPN_A_KEYCONF_CIPHER_ALG + 1];
extern const struct nla_policy ovpn_keyconf_swap_input_nl_policy[OVPN_A_KEYCONF_PEER_ID + 1];
extern const struct nla_policy ovpn_keydir_nl_policy[OVPN_A_KEYDIR_NONCE_TAIL + 1];
extern const struct nla_policy ovpn_peer_nl_policy[OVPN_A_PEER_TX_ID + 1];
extern const struct nla_policy ovpn_peer_del_input_nl_policy[OVPN_A_PEER_ID + 1];
extern const struct nla_policy ovpn_peer_new_input_nl_policy[OVPN_A_PEER_TX_ID + 1];
extern const struct nla_policy ovpn_peer_set_input_nl_policy[OVPN_A_PEER_TX_ID + 1];
int ovpn_nl_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb,
struct genl_info *info);
void
ovpn_nl_post_doit(const struct genl_split_ops *ops, struct sk_buff *skb,
struct genl_info *info);
int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info);
int ovpn_nl_peer_set_doit(struct sk_buff *skb, struct genl_info *info);
int ovpn_nl_peer_get_doit(struct sk_buff *skb, struct genl_info *info);
int ovpn_nl_peer_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
int ovpn_nl_peer_del_doit(struct sk_buff *skb, struct genl_info *info);
int ovpn_nl_key_new_doit(struct sk_buff *skb, struct genl_info *info);
int ovpn_nl_key_get_doit(struct sk_buff *skb, struct genl_info *info);
int ovpn_nl_key_swap_doit(struct sk_buff *skb, struct genl_info *info);
int ovpn_nl_key_del_doit(struct sk_buff *skb, struct genl_info *info);
enum {
OVPN_NLGRP_PEERS,
};
extern struct genl_family ovpn_nl_family;
#endif /* _LINUX_OVPN_GEN_H */
File diff suppressed because it is too large Load Diff
+20
View File
@@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* OpenVPN data channel offload
*
* Copyright (C) 2020-2025 OpenVPN, Inc.
*
* Author: Antonio Quartulli <antonio@openvpn.net>
*/
#ifndef _NET_OVPN_NETLINK_H_
#define _NET_OVPN_NETLINK_H_
int ovpn_nl_register(void);
void ovpn_nl_unregister(void);
int ovpn_nl_peer_del_notify(struct ovpn_peer *peer);
int ovpn_nl_peer_float_notify(struct ovpn_peer *peer,
const struct sockaddr_storage *ss);
int ovpn_nl_key_swap_notify(struct ovpn_peer *peer, u8 key_id);
#endif /* _NET_OVPN_NETLINK_H_ */
+59
View File
@@ -0,0 +1,59 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* OpenVPN data channel offload
*
* Copyright (C) 2019-2025 OpenVPN, Inc.
*
* Author: James Yonan <james@openvpn.net>
* Antonio Quartulli <antonio@openvpn.net>
*/
#ifndef _NET_OVPN_OVPNSTRUCT_H_
#define _NET_OVPN_OVPNSTRUCT_H_
#include <linux/workqueue.h>
#include <net/gro_cells.h>
#include <uapi/linux/if_link.h>
#include <uapi/linux/ovpn.h>
struct workqueue_struct;
extern struct workqueue_struct *ovpn_wq;
/**
* struct ovpn_peer_collection - container of peers for MultiPeer mode
* @by_id: table of peers index by ID
* @by_vpn_addr4: table of peers indexed by VPN IPv4 address (items can be
* rehashed on the fly due to peer IP change)
* @by_vpn_addr6: table of peers indexed by VPN IPv6 address (items can be
* rehashed on the fly due to peer IP change)
* @by_transp_addr: table of peers indexed by transport address (items can be
* rehashed on the fly due to peer IP change)
*/
struct ovpn_peer_collection {
DECLARE_HASHTABLE(by_id, 12);
struct hlist_nulls_head by_vpn_addr4[1 << 12];
struct hlist_nulls_head by_vpn_addr6[1 << 12];
struct hlist_nulls_head by_transp_addr[1 << 12];
};
/**
* 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
* @peers: data structures holding multi-peer references
* @peer: in P2P mode, this is the only remote peer
* @gro_cells: pointer to the Generic Receive Offload cell
* @keepalive_work: struct used to schedule keepalive periodic job
*/
struct ovpn_priv {
struct net_device *dev;
enum ovpn_mode mode;
spinlock_t lock; /* protect writing to the ovpn_priv object */
struct ovpn_peer_collection *peers;
struct ovpn_peer __rcu *peer;
struct gro_cells gro_cells;
struct delayed_work keepalive_work;
};
#endif /* _NET_OVPN_OVPNSTRUCT_H_ */
File diff suppressed because it is too large Load Diff
+165
View File
@@ -0,0 +1,165 @@
/* 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>
#include <net/strparser.h>
#include "crypto.h"
#include "socket.h"
#include "stats.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, used to match incoming packets
* @tx_id: identifier to be used in TX packets
* @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
* @hash_entry_id: entry in the peer ID hashtable
* @hash_entry_addr4: entry in the peer IPv4 hashtable
* @hash_entry_addr6: entry in the peer IPv6 hashtable
* @hash_entry_transp_addr: entry in the peer transport address hashtable
* @sock: the socket being used to talk to this peer
* @tcp: keeps track of TCP specific state
* @tcp.strp: stream parser context (TCP only)
* @tcp.user_queue: received packets that have to go to userspace (TCP only)
* @tcp.out_queue: packets on hold while socket is taken by user (TCP only)
* @tcp.tx_in_progress: true if TX is already ongoing (TCP only)
* @tcp.out_msg.skb: packet scheduled for sending (TCP only)
* @tcp.out_msg.offset: offset where next send should start (TCP only)
* @tcp.out_msg.len: remaining data to send within packet (TCP only)
* @tcp.sk_cb.sk_data_ready: pointer to original cb (TCP only)
* @tcp.sk_cb.sk_write_space: pointer to original cb (TCP only)
* @tcp.sk_cb.prot: pointer to original prot object (TCP only)
* @tcp.sk_cb.ops: pointer to the original prot_ops object (TCP only)
* @crypto: the crypto configuration (ciphers, keys, etc..)
* @dst_cache: cache for dst_entry used to send to peer
* @bind: remote peer binding
* @keepalive_interval: seconds after which a new keepalive should be sent
* @keepalive_xmit_exp: future timestamp when next keepalive should be sent
* @last_sent: timestamp of the last successfully sent packet
* @keepalive_timeout: seconds after which an inactive peer is considered dead
* @keepalive_recv_exp: future timestamp when the peer should expire
* @last_recv: timestamp of the last authenticated received packet
* @vpn_stats: per-peer in-VPN TX/RX stats
* @link_stats: per-peer link/transport TX/RX stats
* @delete_reason: why peer was deleted (i.e. timeout, transport error, ..)
* @lock: protects binding to peer (bind) and keepalive* fields
* @refcount: reference counter
* @rcu: used to free peer in an RCU safe way
* @release_entry: entry for the socket release list
* @keepalive_work: used to schedule keepalive sending
*/
struct ovpn_peer {
struct ovpn_priv *ovpn;
netdevice_tracker dev_tracker;
u32 id;
u32 tx_id;
struct {
struct in_addr ipv4;
struct in6_addr ipv6;
} vpn_addrs;
struct hlist_node hash_entry_id;
struct hlist_nulls_node hash_entry_addr4;
struct hlist_nulls_node hash_entry_addr6;
struct hlist_nulls_node hash_entry_transp_addr;
struct ovpn_socket __rcu *sock;
struct {
struct strparser strp;
struct sk_buff_head user_queue;
struct sk_buff_head out_queue;
bool tx_in_progress;
struct {
struct sk_buff *skb;
int offset;
int len;
} out_msg;
struct {
void (*sk_data_ready)(struct sock *sk);
void (*sk_write_space)(struct sock *sk);
struct proto *prot;
const struct proto_ops *ops;
} sk_cb;
struct work_struct defer_del_work;
} tcp;
struct ovpn_crypto_state crypto;
struct dst_cache dst_cache;
struct ovpn_bind __rcu *bind;
unsigned long keepalive_interval;
unsigned long keepalive_xmit_exp;
time64_t last_sent;
unsigned long keepalive_timeout;
unsigned long keepalive_recv_exp;
time64_t last_recv;
struct ovpn_peer_stats vpn_stats;
struct ovpn_peer_stats link_stats;
enum ovpn_del_peer_reason delete_reason;
spinlock_t lock; /* protects bind and keepalive* */
struct kref refcount;
struct rcu_head rcu;
struct llist_node release_entry;
struct work_struct keepalive_work;
};
/**
* 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_peers_free(struct ovpn_priv *ovpn, struct sock *sock,
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);
struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_priv *ovpn,
struct sk_buff *skb);
void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer);
void ovpn_peer_hash_transp_addr(struct ovpn_peer *peer);
bool ovpn_peer_check_by_src(struct ovpn_priv *ovpn, struct sk_buff *skb,
struct ovpn_peer *peer);
void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u32 interval, u32 timeout);
void ovpn_peer_keepalive_work(struct work_struct *work);
void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb);
int ovpn_peer_reset_sockaddr(struct ovpn_peer *peer,
const struct sockaddr_storage *ss,
const void *local_ip);
#endif /* _NET_OVPN_OVPNPEER_H_ */
+126
View File
@@ -0,0 +1,126 @@
// SPDX-License-Identifier: GPL-2.0
/* OpenVPN data channel offload
*
* Copyright (C) 2020-2025 OpenVPN, Inc.
*
* Author: Antonio Quartulli <antonio@openvpn.net>
* James Yonan <james@openvpn.net>
*/
#include <linux/atomic.h>
#include <linux/jiffies.h>
#include <linux/net.h>
#include <linux/netdevice.h>
#include <linux/types.h>
#include "ovpnpriv.h"
#include "main.h"
#include "pktid.h"
void ovpn_pktid_xmit_init(struct ovpn_pktid_xmit *pid)
{
atomic_set(&pid->seq_num, 1);
}
void ovpn_pktid_recv_init(struct ovpn_pktid_recv *pr)
{
memset(pr, 0, sizeof(*pr));
spin_lock_init(&pr->lock);
}
/* Packet replay detection.
* Allows ID backtrack of up to REPLAY_WINDOW_SIZE - 1.
*/
int ovpn_pktid_recv(struct ovpn_pktid_recv *pr, u32 pkt_id, u32 pkt_time)
{
const unsigned long now = jiffies;
int ret;
/* ID must not be zero */
if (unlikely(pkt_id == 0))
return -EINVAL;
spin_lock_bh(&pr->lock);
/* expire backtracks at or below pr->id after PKTID_RECV_EXPIRE time */
if (unlikely(time_after_eq(now, pr->expire)))
pr->id_floor = pr->id;
/* time changed? */
if (unlikely(pkt_time != pr->time)) {
if (pkt_time > pr->time) {
/* time moved forward, accept */
pr->base = 0;
pr->extent = 0;
pr->id = 0;
pr->time = pkt_time;
pr->id_floor = 0;
} else {
/* time moved backward, reject */
ret = -ETIME;
goto out;
}
}
if (likely(pkt_id == pr->id + 1)) {
/* well-formed ID sequence (incremented by 1) */
pr->base = REPLAY_INDEX(pr->base, -1);
__set_bit(pr->base, pr->history);
if (pr->extent < REPLAY_WINDOW_SIZE)
++pr->extent;
pr->id = pkt_id;
} else if (pkt_id > pr->id) {
/* ID jumped forward by more than one */
const unsigned int delta = pkt_id - pr->id;
if (delta < REPLAY_WINDOW_SIZE) {
unsigned int i;
pr->base = REPLAY_INDEX(pr->base, -delta);
__set_bit(pr->base, pr->history);
pr->extent += delta;
if (pr->extent > REPLAY_WINDOW_SIZE)
pr->extent = REPLAY_WINDOW_SIZE;
for (i = 1; i < delta; ++i) {
unsigned int newb = REPLAY_INDEX(pr->base, i);
__clear_bit(newb, pr->history);
}
} else {
pr->base = 0;
pr->extent = REPLAY_WINDOW_SIZE;
memset(pr->history, 0, sizeof(pr->history));
pr->history[0] = 1;
}
pr->id = pkt_id;
} else {
/* ID backtrack */
const unsigned int delta = pr->id - pkt_id;
if (delta > pr->max_backtrack)
pr->max_backtrack = delta;
if (delta < pr->extent) {
if (pkt_id > pr->id_floor) {
const unsigned int ri = REPLAY_INDEX(pr->base,
delta);
if (__test_and_set_bit(ri, pr->history)) {
ret = -EINVAL;
goto out;
}
} else {
ret = -EINVAL;
goto out;
}
} else {
ret = -EINVAL;
goto out;
}
}
pr->expire = now + PKTID_RECV_EXPIRE;
ret = 0;
out:
spin_unlock_bh(&pr->lock);
return ret;
}
+86
View File
@@ -0,0 +1,86 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* OpenVPN data channel offload
*
* Copyright (C) 2020-2025 OpenVPN, Inc.
*
* Author: Antonio Quartulli <antonio@openvpn.net>
* James Yonan <james@openvpn.net>
*/
#ifndef _NET_OVPN_OVPNPKTID_H_
#define _NET_OVPN_OVPNPKTID_H_
#include "proto.h"
/* If no packets received for this length of time, set a backtrack floor
* at highest received packet ID thus far.
*/
#define PKTID_RECV_EXPIRE (30 * HZ)
/* Packet-ID state for transmitter */
struct ovpn_pktid_xmit {
atomic_t seq_num;
};
/* replay window sizing in bytes = 2^REPLAY_WINDOW_ORDER */
#define REPLAY_WINDOW_ORDER 8
#define REPLAY_WINDOW_BYTES BIT(REPLAY_WINDOW_ORDER)
#define REPLAY_WINDOW_SIZE (REPLAY_WINDOW_BYTES * 8)
#define REPLAY_INDEX(base, i) (((base) + (i)) & (REPLAY_WINDOW_SIZE - 1))
/* Packet-ID state for receiver.
* Other than lock member, can be zeroed to initialize.
*/
struct ovpn_pktid_recv {
/* "sliding window" bitmask of recent packet IDs received */
DECLARE_BITMAP(history, REPLAY_WINDOW_SIZE);
/* bit position of deque base in history */
unsigned int base;
/* extent (in bits) of deque in history */
unsigned int extent;
/* expiration of history in jiffies */
unsigned long expire;
/* highest sequence number received */
u32 id;
/* highest time stamp received */
u32 time;
/* we will only accept backtrack IDs > id_floor */
u32 id_floor;
unsigned int max_backtrack;
/* protects entire pktd ID state */
spinlock_t lock;
};
/* Get the next packet ID for xmit */
static inline int ovpn_pktid_xmit_next(struct ovpn_pktid_xmit *pid, u32 *pktid)
{
const u32 seq_num = atomic_fetch_add_unless(&pid->seq_num, 1, 0);
/* when the 32bit space is over, we return an error because the packet
* ID is used to create the cipher IV and we do not want to reuse the
* same value more than once
*/
if (unlikely(!seq_num))
return -ERANGE;
*pktid = seq_num;
return 0;
}
/* Write 12-byte AEAD IV to dest */
static inline void ovpn_pktid_aead_write(const u32 pktid,
const u8 nt[],
unsigned char *dest)
{
*(__force __be32 *)(dest) = htonl(pktid);
BUILD_BUG_ON(4 + OVPN_NONCE_TAIL_SIZE != OVPN_NONCE_SIZE);
memcpy(dest + 4, nt, OVPN_NONCE_TAIL_SIZE);
}
void ovpn_pktid_xmit_init(struct ovpn_pktid_xmit *pid);
void ovpn_pktid_recv_init(struct ovpn_pktid_recv *pr);
int ovpn_pktid_recv(struct ovpn_pktid_recv *pr, u32 pkt_id, u32 pkt_time);
#endif /* _NET_OVPN_OVPNPKTID_H_ */
+118
View File
@@ -0,0 +1,118 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* OpenVPN data channel offload
*
* Copyright (C) 2020-2025 OpenVPN, Inc.
*
* Author: Antonio Quartulli <antonio@openvpn.net>
* James Yonan <james@openvpn.net>
*/
#ifndef _NET_OVPN_PROTO_H_
#define _NET_OVPN_PROTO_H_
#include "main.h"
#include <linux/bitfield.h>
#include <linux/skbuff.h>
/* When the OpenVPN protocol is ran in AEAD mode, use
* the OpenVPN packet ID as the AEAD nonce:
*
* 00000005 521c3b01 4308c041
* [seq # ] [ nonce_tail ]
* [ 12-byte full IV ] -> OVPN_NONCE_SIZE
* [4-bytes -> OVPN_NONCE_WIRE_SIZE
* on wire]
*/
/* nonce size (96bits) as required by AEAD ciphers */
#define OVPN_NONCE_SIZE 12
/* last 8 bytes of AEAD nonce: provided by userspace and usually derived
* from key material generated during TLS handshake
*/
#define OVPN_NONCE_TAIL_SIZE 8
/* OpenVPN nonce size reduced by 8-byte nonce tail -- this is the
* size of the AEAD Associated Data (AD) sent over the wire
* and is normally the head of the IV
*/
#define OVPN_NONCE_WIRE_SIZE (OVPN_NONCE_SIZE - OVPN_NONCE_TAIL_SIZE)
#define OVPN_OPCODE_SIZE 4 /* DATA_V2 opcode size */
#define OVPN_OPCODE_KEYID_MASK 0x07000000
#define OVPN_OPCODE_PKTTYPE_MASK 0xF8000000
#define OVPN_OPCODE_PEERID_MASK 0x00FFFFFF
/* packet opcodes of interest to us */
#define OVPN_DATA_V1 6 /* data channel v1 packet */
#define OVPN_DATA_V2 9 /* data channel v2 packet */
#define OVPN_PEER_ID_UNDEF 0x00FFFFFF
/**
* ovpn_opcode_from_skb - extract OP code from skb at specified offset
* @skb: the packet to extract the OP code from
* @offset: the offset in the data buffer where the OP code is located
*
* Note: this function assumes that the skb head was pulled enough
* to access the first 4 bytes.
*
* Return: the OP code
*/
static inline u8 ovpn_opcode_from_skb(const struct sk_buff *skb, u16 offset)
{
u32 opcode = be32_to_cpu(*(__be32 *)(skb->data + offset));
return FIELD_GET(OVPN_OPCODE_PKTTYPE_MASK, opcode);
}
/**
* ovpn_peer_id_from_skb - extract peer ID from skb at specified offset
* @skb: the packet to extract the OP code from
* @offset: the offset in the data buffer where the OP code is located
*
* Note: this function assumes that the skb head was pulled enough
* to access the first 4 bytes.
*
* Return: the peer ID
*/
static inline u32 ovpn_peer_id_from_skb(const struct sk_buff *skb, u16 offset)
{
u32 opcode = be32_to_cpu(*(__be32 *)(skb->data + offset));
return FIELD_GET(OVPN_OPCODE_PEERID_MASK, opcode);
}
/**
* ovpn_key_id_from_skb - extract key ID from the skb head
* @skb: the packet to extract the key ID code from
*
* Note: this function assumes that the skb head was pulled enough
* to access the first 4 bytes.
*
* Return: the key ID
*/
static inline u8 ovpn_key_id_from_skb(const struct sk_buff *skb)
{
u32 opcode = be32_to_cpu(*(__be32 *)skb->data);
return FIELD_GET(OVPN_OPCODE_KEYID_MASK, opcode);
}
/**
* ovpn_opcode_compose - combine OP code, key ID and peer ID to wire format
* @opcode: the OP code
* @key_id: the key ID
* @peer_id: the peer ID
*
* Return: a 4 bytes integer obtained combining all input values following the
* OpenVPN wire format. This integer can then be written to the packet header.
*/
static inline u32 ovpn_opcode_compose(u8 opcode, u8 key_id, u32 peer_id)
{
return FIELD_PREP(OVPN_OPCODE_PKTTYPE_MASK, opcode) |
FIELD_PREP(OVPN_OPCODE_KEYID_MASK, key_id) |
FIELD_PREP(OVPN_OPCODE_PEERID_MASK, peer_id);
}
#endif /* _NET_OVPN_OVPNPROTO_H_ */
+68
View File
@@ -0,0 +1,68 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* OpenVPN data channel offload
*
* Copyright (C) 2020-2025 OpenVPN, Inc.
*
* Author: Antonio Quartulli <antonio@openvpn.net>
* James Yonan <james@openvpn.net>
*/
#ifndef _NET_OVPN_SKB_H_
#define _NET_OVPN_SKB_H_
#include <linux/in.h>
#include <linux/in6.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/skbuff.h>
#include <linux/socket.h>
#include <linux/types.h>
/**
* struct ovpn_cb - ovpn skb control block
* @peer: the peer this skb was received from/sent to
* @ks: the crypto key slot used to encrypt/decrypt this skb
* @crypto_tmp: pointer to temporary memory used for crypto operations
* containing the IV, the scatter gather list and the aead request
* @payload_offset: offset in the skb where the payload starts
* @nosignal: whether this skb should be sent with the MSG_NOSIGNAL flag (TCP)
*/
struct ovpn_cb {
struct ovpn_peer *peer;
struct ovpn_crypto_key_slot *ks;
void *crypto_tmp;
unsigned int payload_offset;
bool nosignal;
};
static inline struct ovpn_cb *ovpn_skb_cb(struct sk_buff *skb)
{
BUILD_BUG_ON(sizeof(struct ovpn_cb) > sizeof(skb->cb));
return (struct ovpn_cb *)skb->cb;
}
/* Return IP protocol version from skb header.
* Return 0 if protocol is not IPv4/IPv6 or cannot be read.
*/
static inline __be16 ovpn_ip_check_protocol(struct sk_buff *skb)
{
__be16 proto = 0;
/* skb could be non-linear,
* make sure IP header is in non-fragmented part
*/
if (!pskb_network_may_pull(skb, sizeof(struct iphdr)))
return 0;
if (ip_hdr(skb)->version == 4) {
proto = htons(ETH_P_IP);
} else if (ip_hdr(skb)->version == 6) {
if (!pskb_network_may_pull(skb, sizeof(struct ipv6hdr)))
return 0;
proto = htons(ETH_P_IPV6);
}
return proto;
}
#endif /* _NET_OVPN_SKB_H_ */
+253
View File
@@ -0,0 +1,253 @@
// 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;
}
+49
View File
@@ -0,0 +1,49 @@
/* 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_SOCK_H_
#define _NET_OVPN_SOCK_H_
#include <linux/net.h>
#include <linux/kref.h>
#include <net/sock.h>
struct ovpn_priv;
struct ovpn_peer;
/**
* struct ovpn_socket - a kernel socket referenced in the ovpn code
* @ovpn: ovpn instance owning this socket (UDP only)
* @dev_tracker: reference tracker for associated dev (UDP only)
* @peer: unique peer transmitting over this socket (TCP only)
* @sk: the low level sock object
* @refcount: amount of contexts currently referencing this object
* @work: member used to schedule release routine (it may block)
* @tcp_tx_work: work for deferring outgoing packet processing (TCP only)
*/
struct ovpn_socket {
union {
struct {
struct ovpn_priv *ovpn;
netdevice_tracker dev_tracker;
};
struct ovpn_peer *peer;
};
struct sock *sk;
struct kref refcount;
struct work_struct work;
struct work_struct tcp_tx_work;
};
struct ovpn_socket *ovpn_socket_new(struct socket *sock,
struct ovpn_peer *peer);
void ovpn_socket_release(struct ovpn_peer *peer);
#endif /* _NET_OVPN_SOCK_H_ */
+21
View File
@@ -0,0 +1,21 @@
// 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/atomic.h>
#include "stats.h"
void ovpn_peer_stats_init(struct ovpn_peer_stats *ps)
{
atomic64_set(&ps->rx.bytes, 0);
atomic64_set(&ps->rx.packets, 0);
atomic64_set(&ps->tx.bytes, 0);
atomic64_set(&ps->tx.packets, 0);
}
+63
View File
@@ -0,0 +1,63 @@
/* 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>
* Lev Stipakov <lev@openvpn.net>
*/
#ifndef _NET_OVPN_OVPNSTATS_H_
#define _NET_OVPN_OVPNSTATS_H_
#include <linux/netdevice.h>
/* one stat */
struct ovpn_peer_stat {
atomic64_t bytes;
atomic64_t packets;
};
/* rx and tx stats combined */
struct ovpn_peer_stats {
struct ovpn_peer_stat rx;
struct ovpn_peer_stat tx;
};
void ovpn_peer_stats_init(struct ovpn_peer_stats *ps);
static inline void ovpn_peer_stats_increment(struct ovpn_peer_stat *stat,
const unsigned int n)
{
atomic64_add(n, &stat->bytes);
atomic64_inc(&stat->packets);
}
static inline void ovpn_peer_stats_increment_rx(struct ovpn_peer_stats *stats,
const unsigned int n)
{
ovpn_peer_stats_increment(&stats->rx, n);
}
static inline void ovpn_peer_stats_increment_tx(struct ovpn_peer_stats *stats,
const unsigned int n)
{
ovpn_peer_stats_increment(&stats->tx, n);
}
static inline void ovpn_dev_dstats_tx_dropped(struct net_device *dev)
{
local_bh_disable();
dev_dstats_tx_dropped(dev);
local_bh_enable();
}
static inline void ovpn_dev_dstats_rx_dropped(struct net_device *dev)
{
local_bh_disable();
dev_dstats_rx_dropped(dev);
local_bh_enable();
}
#endif /* _NET_OVPN_OVPNSTATS_H_ */
+661
View File
@@ -0,0 +1,661 @@
// SPDX-License-Identifier: GPL-2.0
/* OpenVPN data channel offload
*
* Copyright (C) 2019-2025 OpenVPN, Inc.
*
* Author: Antonio Quartulli <antonio@openvpn.net>
*/
#include <linux/skbuff.h>
#include <net/hotdata.h>
#include <net/inet_common.h>
#include <net/ipv6.h>
#include <net/tcp.h>
#include <net/transp_v6.h>
#include <net/route.h>
#include <trace/events/sock.h>
#include "ovpnpriv.h"
#include "main.h"
#include "io.h"
#include "peer.h"
#include "proto.h"
#include "skb.h"
#include "tcp.h"
#define OVPN_TCP_DEPTH_NESTING 2
#if OVPN_TCP_DEPTH_NESTING == SINGLE_DEPTH_NESTING
#error "OVPN TCP requires its own lockdep subclass"
#endif
static struct proto ovpn_tcp_prot __ro_after_init;
static struct proto_ops ovpn_tcp_ops __ro_after_init;
static struct proto ovpn_tcp6_prot __ro_after_init;
static struct proto_ops ovpn_tcp6_ops __ro_after_init;
static int ovpn_tcp_parse(struct strparser *strp, struct sk_buff *skb)
{
struct strp_msg *rxm = strp_msg(skb);
__be16 blen;
u16 len;
int err;
/* when packets are written to the TCP stream, they are prepended with
* two bytes indicating the actual packet size.
* Parse accordingly and return the actual size (including the size
* header)
*/
if (skb->len < rxm->offset + 2)
return 0;
err = skb_copy_bits(skb, rxm->offset, &blen, sizeof(blen));
if (err < 0)
return err;
len = be16_to_cpu(blen);
if (len < 2)
return -EINVAL;
return len + 2;
}
/* queue skb for sending to userspace via recvmsg on the socket */
static void ovpn_tcp_to_userspace(struct ovpn_peer *peer, struct sock *sk,
struct sk_buff *skb)
{
skb_set_owner_r(skb, sk);
memset(skb->cb, 0, sizeof(skb->cb));
skb_queue_tail(&peer->tcp.user_queue, skb);
peer->tcp.sk_cb.sk_data_ready(sk);
}
static struct sk_buff *ovpn_tcp_skb_packet(const struct ovpn_peer *peer,
struct sk_buff *orig_skb,
const int pkt_len, const int pkt_off)
{
struct sk_buff *ovpn_skb;
int err;
/* create a new skb with only the content of the current packet */
ovpn_skb = netdev_alloc_skb(peer->ovpn->dev, pkt_len);
if (unlikely(!ovpn_skb))
goto err;
skb_copy_header(ovpn_skb, orig_skb);
err = skb_copy_bits(orig_skb, pkt_off, skb_put(ovpn_skb, pkt_len),
pkt_len);
if (unlikely(err)) {
net_warn_ratelimited("%s: skb_copy_bits failed for peer %u\n",
netdev_name(peer->ovpn->dev), peer->id);
kfree_skb(ovpn_skb);
goto err;
}
consume_skb(orig_skb);
return ovpn_skb;
err:
kfree_skb(orig_skb);
return NULL;
}
static void ovpn_tcp_rcv(struct strparser *strp, struct sk_buff *skb)
{
struct ovpn_peer *peer = container_of(strp, struct ovpn_peer, tcp.strp);
struct strp_msg *msg = strp_msg(skb);
int pkt_len = msg->full_len - 2;
u8 opcode;
/* we need at least 4 bytes of data in the packet
* to extract the opcode and the key ID later on
*/
if (unlikely(pkt_len < OVPN_OPCODE_SIZE)) {
net_warn_ratelimited("%s: packet too small to fetch opcode for peer %u\n",
netdev_name(peer->ovpn->dev), peer->id);
goto err;
}
/* extract the packet into a new skb */
skb = ovpn_tcp_skb_packet(peer, skb, pkt_len, msg->offset + 2);
if (unlikely(!skb))
goto err;
/* DATA_V2 packets are handled in kernel, the rest goes to user space */
opcode = ovpn_opcode_from_skb(skb, 0);
if (unlikely(opcode != OVPN_DATA_V2)) {
if (opcode == OVPN_DATA_V1) {
net_warn_ratelimited("%s: DATA_V1 detected on the TCP stream\n",
netdev_name(peer->ovpn->dev));
goto err;
}
/* The packet size header must be there when sending the packet
* to userspace, therefore we put it back
*/
*(__be16 *)__skb_push(skb, sizeof(u16)) = htons(pkt_len);
ovpn_tcp_to_userspace(peer, strp->sk, skb);
return;
}
/* hold reference to peer as required by ovpn_recv().
*
* NOTE: in this context we should already be holding a reference to
* this peer, therefore ovpn_peer_hold() is not expected to fail
*/
if (WARN_ON(!ovpn_peer_hold(peer)))
goto err_nopeer;
ovpn_recv(peer, skb);
return;
err:
/* take reference for deferred peer deletion. should never fail */
if (WARN_ON(!ovpn_peer_hold(peer)))
goto err_nopeer;
if (!queue_work(ovpn_wq, &peer->tcp.defer_del_work))
ovpn_peer_put(peer);
ovpn_dev_dstats_rx_dropped(peer->ovpn->dev);
err_nopeer:
kfree_skb(skb);
}
static int ovpn_tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
int flags)
{
int err = 0, off, copied = 0, ret;
struct ovpn_socket *sock;
struct ovpn_peer *peer;
struct sk_buff *skb;
rcu_read_lock();
sock = rcu_dereference_sk_user_data(sk);
if (unlikely(!sock || !sock->peer || !ovpn_peer_hold(sock->peer))) {
rcu_read_unlock();
return -EBADF;
}
peer = sock->peer;
rcu_read_unlock();
skb = __skb_recv_datagram(sk, &peer->tcp.user_queue, flags, &off, &err);
if (!skb) {
if (err == -EAGAIN && sk->sk_shutdown & RCV_SHUTDOWN) {
ret = 0;
goto out;
}
ret = err;
goto out;
}
copied = len;
if (copied > skb->len)
copied = skb->len;
else if (copied < skb->len)
msg->msg_flags |= MSG_TRUNC;
err = skb_copy_datagram_msg(skb, 0, msg, copied);
if (unlikely(err)) {
kfree_skb(skb);
ret = err;
goto out;
}
if (flags & MSG_TRUNC)
copied = skb->len;
kfree_skb(skb);
ret = copied;
out:
ovpn_peer_put(peer);
return ret;
}
void ovpn_tcp_socket_detach(struct ovpn_socket *ovpn_sock)
{
struct ovpn_peer *peer = ovpn_sock->peer;
struct sock *sk = ovpn_sock->sk;
strp_stop(&peer->tcp.strp);
skb_queue_purge(&peer->tcp.user_queue);
/* restore CBs that were saved in ovpn_sock_set_tcp_cb() */
sk->sk_data_ready = peer->tcp.sk_cb.sk_data_ready;
sk->sk_write_space = peer->tcp.sk_cb.sk_write_space;
sk->sk_prot = peer->tcp.sk_cb.prot;
/* tcp_close() may race this function and could set
* sk->sk_socket to NULL. It does so by invoking
* sock_orphan(), which holds sk_callback_lock before
* doing the assignment.
*
* For this reason we acquire the same lock to avoid
* sk_socket to disappear under our feet
*/
write_lock_bh(&sk->sk_callback_lock);
if (sk->sk_socket)
sk->sk_socket->ops = peer->tcp.sk_cb.ops;
write_unlock_bh(&sk->sk_callback_lock);
rcu_assign_sk_user_data(sk, NULL);
}
void ovpn_tcp_socket_wait_finish(struct ovpn_socket *sock)
{
struct ovpn_peer *peer = sock->peer;
/* NOTE: we don't wait for peer->tcp.defer_del_work to finish:
* either the worker is not running or this function
* was invoked by that worker.
*/
cancel_work_sync(&sock->tcp_tx_work);
strp_done(&peer->tcp.strp);
skb_queue_purge(&peer->tcp.out_queue);
kfree_skb(peer->tcp.out_msg.skb);
peer->tcp.out_msg.skb = NULL;
}
static void ovpn_tcp_send_sock(struct ovpn_peer *peer, struct sock *sk)
{
struct sk_buff *skb = peer->tcp.out_msg.skb;
int ret, flags;
if (!skb)
return;
if (peer->tcp.tx_in_progress)
return;
peer->tcp.tx_in_progress = true;
do {
flags = ovpn_skb_cb(skb)->nosignal ? MSG_NOSIGNAL : 0;
ret = skb_send_sock_locked_with_flags(sk, skb,
peer->tcp.out_msg.offset,
peer->tcp.out_msg.len,
flags);
if (unlikely(ret < 0)) {
if (ret == -EAGAIN)
goto out;
net_warn_ratelimited("%s: TCP error to peer %u: %d\n",
netdev_name(peer->ovpn->dev),
peer->id, ret);
/* in case of TCP error we can't recover the VPN
* stream therefore we abort the connection
*/
ovpn_peer_hold(peer);
if (!queue_work(ovpn_wq, &peer->tcp.defer_del_work))
ovpn_peer_put(peer);
/* we bail out immediately and keep tx_in_progress set
* to true. This way we prevent more TX attempts
* which would lead to more invocations of queue_work()
*/
return;
}
peer->tcp.out_msg.len -= ret;
peer->tcp.out_msg.offset += ret;
} while (peer->tcp.out_msg.len > 0);
if (!peer->tcp.out_msg.len) {
local_bh_disable();
dev_dstats_tx_add(peer->ovpn->dev, skb->len);
local_bh_enable();
}
kfree_skb(peer->tcp.out_msg.skb);
peer->tcp.out_msg.skb = NULL;
peer->tcp.out_msg.len = 0;
peer->tcp.out_msg.offset = 0;
out:
peer->tcp.tx_in_progress = false;
}
void ovpn_tcp_tx_work(struct work_struct *work)
{
struct ovpn_socket *sock;
sock = container_of(work, struct ovpn_socket, tcp_tx_work);
lock_sock(sock->sk);
if (sock->peer)
ovpn_tcp_send_sock(sock->peer, sock->sk);
release_sock(sock->sk);
}
static void ovpn_tcp_send_sock_skb(struct ovpn_peer *peer, struct sock *sk,
struct sk_buff *skb)
{
if (peer->tcp.out_msg.skb)
ovpn_tcp_send_sock(peer, sk);
if (peer->tcp.out_msg.skb) {
ovpn_dev_dstats_tx_dropped(peer->ovpn->dev);
kfree_skb(skb);
return;
}
peer->tcp.out_msg.skb = skb;
peer->tcp.out_msg.len = skb->len;
peer->tcp.out_msg.offset = 0;
ovpn_tcp_send_sock(peer, sk);
}
void ovpn_tcp_send_skb(struct ovpn_peer *peer, struct sock *sk,
struct sk_buff *skb)
{
u16 len = skb->len;
*(__be16 *)__skb_push(skb, sizeof(u16)) = htons(len);
spin_lock_nested(&sk->sk_lock.slock, OVPN_TCP_DEPTH_NESTING);
if (sock_owned_by_user(sk)) {
if (skb_queue_len(&peer->tcp.out_queue) >=
READ_ONCE(net_hotdata.max_backlog)) {
ovpn_dev_dstats_tx_dropped(peer->ovpn->dev);
kfree_skb(skb);
goto unlock;
}
__skb_queue_tail(&peer->tcp.out_queue, skb);
} else {
ovpn_tcp_send_sock_skb(peer, sk, skb);
}
unlock:
spin_unlock(&sk->sk_lock.slock);
}
static void ovpn_tcp_release(struct sock *sk)
{
struct sk_buff_head queue;
struct ovpn_socket *sock;
struct ovpn_peer *peer;
struct sk_buff *skb;
rcu_read_lock();
sock = rcu_dereference_sk_user_data(sk);
if (!sock) {
rcu_read_unlock();
return;
}
peer = sock->peer;
/* during initialization this function is called before
* assigning sock->peer
*/
if (unlikely(!peer || !ovpn_peer_hold(peer))) {
rcu_read_unlock();
return;
}
rcu_read_unlock();
__skb_queue_head_init(&queue);
skb_queue_splice_init(&peer->tcp.out_queue, &queue);
while ((skb = __skb_dequeue(&queue)))
ovpn_tcp_send_sock_skb(peer, sk, skb);
peer->tcp.sk_cb.prot->release_cb(sk);
ovpn_peer_put(peer);
}
static int ovpn_tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
{
struct ovpn_socket *sock;
int ret, linear = PAGE_SIZE;
struct ovpn_peer *peer;
struct sk_buff *skb;
lock_sock(sk);
rcu_read_lock();
sock = rcu_dereference_sk_user_data(sk);
if (unlikely(!sock || !sock->peer || !ovpn_peer_hold(sock->peer))) {
rcu_read_unlock();
release_sock(sk);
return -EIO;
}
rcu_read_unlock();
peer = sock->peer;
if (msg->msg_flags & ~(MSG_DONTWAIT | MSG_NOSIGNAL)) {
ret = -EOPNOTSUPP;
goto peer_free;
}
if (peer->tcp.out_msg.skb) {
ret = -EAGAIN;
goto peer_free;
}
if (size < linear)
linear = size;
skb = sock_alloc_send_pskb(sk, linear, size - linear,
msg->msg_flags & MSG_DONTWAIT, &ret, 0);
if (!skb) {
net_err_ratelimited("%s: skb alloc failed: %d\n",
netdev_name(peer->ovpn->dev), ret);
goto peer_free;
}
skb_put(skb, linear);
skb->len = size;
skb->data_len = size - linear;
ret = skb_copy_datagram_from_iter(skb, 0, &msg->msg_iter, size);
if (ret) {
kfree_skb(skb);
net_err_ratelimited("%s: skb copy from iter failed: %d\n",
netdev_name(peer->ovpn->dev), ret);
goto peer_free;
}
ovpn_skb_cb(skb)->nosignal = msg->msg_flags & MSG_NOSIGNAL;
ovpn_tcp_send_sock_skb(peer, sk, skb);
ret = size;
peer_free:
release_sock(sk);
ovpn_peer_put(peer);
return ret;
}
static int ovpn_tcp_disconnect(struct sock *sk, int flags)
{
return -EBUSY;
}
static void ovpn_tcp_data_ready(struct sock *sk)
{
struct ovpn_socket *sock;
trace_sk_data_ready(sk);
rcu_read_lock();
sock = rcu_dereference_sk_user_data(sk);
if (likely(sock && sock->peer))
strp_data_ready(&sock->peer->tcp.strp);
rcu_read_unlock();
}
static void ovpn_tcp_write_space(struct sock *sk)
{
struct ovpn_socket *sock;
rcu_read_lock();
sock = rcu_dereference_sk_user_data(sk);
if (likely(sock && sock->peer)) {
queue_work(ovpn_wq, &sock->tcp_tx_work);
sock->peer->tcp.sk_cb.sk_write_space(sk);
}
rcu_read_unlock();
}
static void ovpn_tcp_build_protos(struct proto *new_prot,
struct proto_ops *new_ops,
const struct proto *orig_prot,
const struct proto_ops *orig_ops);
static void ovpn_tcp_peer_del_work(struct work_struct *work)
{
struct ovpn_peer *peer = container_of(work, struct ovpn_peer,
tcp.defer_del_work);
ovpn_peer_del(peer, OVPN_DEL_PEER_REASON_TRANSPORT_ERROR);
ovpn_peer_put(peer);
}
/* Set TCP encapsulation callbacks */
int ovpn_tcp_socket_attach(struct ovpn_socket *ovpn_sock,
struct ovpn_peer *peer)
{
struct strp_callbacks cb = {
.rcv_msg = ovpn_tcp_rcv,
.parse_msg = ovpn_tcp_parse,
};
int ret;
/* make sure no pre-existing encapsulation handler exists */
if (ovpn_sock->sk->sk_user_data)
return -EBUSY;
rcu_assign_sk_user_data(ovpn_sock->sk, ovpn_sock);
/* only a fully connected socket is expected. Connection should be
* handled in userspace
*/
if (ovpn_sock->sk->sk_state != TCP_ESTABLISHED) {
net_err_ratelimited("%s: provided TCP socket is not in ESTABLISHED state: %d\n",
netdev_name(peer->ovpn->dev),
ovpn_sock->sk->sk_state);
ret = -EINVAL;
goto err;
}
ret = strp_init(&peer->tcp.strp, ovpn_sock->sk, &cb);
if (ret < 0) {
DEBUG_NET_WARN_ON_ONCE(1);
goto err;
}
INIT_WORK(&peer->tcp.defer_del_work, ovpn_tcp_peer_del_work);
__sk_dst_reset(ovpn_sock->sk);
skb_queue_head_init(&peer->tcp.user_queue);
skb_queue_head_init(&peer->tcp.out_queue);
/* save current CBs so that they can be restored upon socket release */
peer->tcp.sk_cb.sk_data_ready = ovpn_sock->sk->sk_data_ready;
peer->tcp.sk_cb.sk_write_space = ovpn_sock->sk->sk_write_space;
peer->tcp.sk_cb.prot = ovpn_sock->sk->sk_prot;
peer->tcp.sk_cb.ops = ovpn_sock->sk->sk_socket->ops;
/* assign our static CBs and prot/ops */
ovpn_sock->sk->sk_data_ready = ovpn_tcp_data_ready;
ovpn_sock->sk->sk_write_space = ovpn_tcp_write_space;
if (ovpn_sock->sk->sk_family == AF_INET) {
ovpn_sock->sk->sk_prot = &ovpn_tcp_prot;
ovpn_sock->sk->sk_socket->ops = &ovpn_tcp_ops;
} else {
ovpn_sock->sk->sk_prot = &ovpn_tcp6_prot;
ovpn_sock->sk->sk_socket->ops = &ovpn_tcp6_ops;
}
/* avoid using task_frag */
ovpn_sock->sk->sk_allocation = GFP_ATOMIC;
ovpn_sock->sk->sk_use_task_frag = false;
/* enqueue the RX worker */
strp_check_rcv(&peer->tcp.strp);
return 0;
err:
rcu_assign_sk_user_data(ovpn_sock->sk, NULL);
return ret;
}
static void ovpn_tcp_close(struct sock *sk, long timeout)
{
struct ovpn_socket *sock;
struct ovpn_peer *peer;
rcu_read_lock();
sock = rcu_dereference_sk_user_data(sk);
if (!sock) {
rcu_read_unlock();
return;
}
peer = sock->peer;
if (!peer || !ovpn_peer_hold(peer)) {
rcu_read_unlock();
return;
}
rcu_read_unlock();
ovpn_peer_del(peer, OVPN_DEL_PEER_REASON_TRANSPORT_DISCONNECT);
peer->tcp.sk_cb.prot->close(sk, timeout);
ovpn_peer_put(peer);
}
static __poll_t ovpn_tcp_poll(struct file *file, struct socket *sock,
poll_table *wait)
{
struct sk_buff_head *queue = &sock->sk->sk_receive_queue;
struct ovpn_socket *ovpn_sock;
struct ovpn_peer *peer = NULL;
__poll_t mask;
rcu_read_lock();
ovpn_sock = rcu_dereference_sk_user_data(sock->sk);
/* if we landed in this callback, we expect to have a
* meaningful state. The ovpn_socket lifecycle would
* prevent it otherwise.
*/
if (WARN(!ovpn_sock || !ovpn_sock->peer,
"ovpn: null state in ovpn_tcp_poll!")) {
rcu_read_unlock();
return 0;
}
if (ovpn_peer_hold(ovpn_sock->peer)) {
peer = ovpn_sock->peer;
queue = &peer->tcp.user_queue;
}
rcu_read_unlock();
mask = datagram_poll_queue(file, sock, wait, queue);
if (peer)
ovpn_peer_put(peer);
return mask;
}
static void ovpn_tcp_build_protos(struct proto *new_prot,
struct proto_ops *new_ops,
const struct proto *orig_prot,
const struct proto_ops *orig_ops)
{
memcpy(new_prot, orig_prot, sizeof(*new_prot));
memcpy(new_ops, orig_ops, sizeof(*new_ops));
new_prot->recvmsg = ovpn_tcp_recvmsg;
new_prot->sendmsg = ovpn_tcp_sendmsg;
new_prot->disconnect = ovpn_tcp_disconnect;
new_prot->close = ovpn_tcp_close;
new_prot->release_cb = ovpn_tcp_release;
new_ops->poll = ovpn_tcp_poll;
}
/* Initialize TCP static objects */
void __init ovpn_tcp_init(void)
{
ovpn_tcp_build_protos(&ovpn_tcp_prot, &ovpn_tcp_ops, &tcp_prot,
&inet_stream_ops);
#if IS_ENABLED(CONFIG_IPV6)
ovpn_tcp_build_protos(&ovpn_tcp6_prot, &ovpn_tcp6_ops, &tcpv6_prot,
&inet6_stream_ops);
#endif
}
+37
View File
@@ -0,0 +1,37 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* OpenVPN data channel offload
*
* Copyright (C) 2019-2025 OpenVPN, Inc.
*
* Author: Antonio Quartulli <antonio@openvpn.net>
*/
#ifndef _NET_OVPN_TCP_H_
#define _NET_OVPN_TCP_H_
#include <linux/net.h>
#include <linux/skbuff.h>
#include <linux/types.h>
#include "peer.h"
#include "skb.h"
#include "socket.h"
void __init ovpn_tcp_init(void);
int ovpn_tcp_socket_attach(struct ovpn_socket *ovpn_sock,
struct ovpn_peer *peer);
void ovpn_tcp_socket_detach(struct ovpn_socket *ovpn_sock);
void ovpn_tcp_socket_wait_finish(struct ovpn_socket *sock);
/* Prepare skb and enqueue it for sending to peer.
*
* Preparation consist in prepending the skb payload with its size.
* Required by the OpenVPN protocol in order to extract packets from
* the TCP stream on the receiver side.
*/
void ovpn_tcp_send_skb(struct ovpn_peer *peer, struct sock *sk,
struct sk_buff *skb);
void ovpn_tcp_tx_work(struct work_struct *work);
#endif /* _NET_OVPN_TCP_H_ */
+448
View File
@@ -0,0 +1,448 @@
// SPDX-License-Identifier: GPL-2.0
/* OpenVPN data channel offload
*
* Copyright (C) 2019-2025 OpenVPN, Inc.
*
* Author: Antonio Quartulli <antonio@openvpn.net>
*/
#include <linux/netdevice.h>
#include <linux/inetdevice.h>
#include <linux/skbuff.h>
#include <linux/socket.h>
#include <linux/udp.h>
#include <net/addrconf.h>
#include <net/dst_cache.h>
#include <net/route.h>
#include <net/transp_v6.h>
#include <net/udp.h>
#include <net/udp_tunnel.h>
#include "ovpnpriv.h"
#include "main.h"
#include "bind.h"
#include "io.h"
#include "peer.h"
#include "proto.h"
#include "socket.h"
#include "udp.h"
/* Retrieve the corresponding ovpn object from a UDP socket
* rcu_read_lock must be held on entry
*/
static struct ovpn_socket *ovpn_socket_from_udp_sock(struct sock *sk)
{
struct ovpn_socket *ovpn_sock;
if (unlikely(READ_ONCE(udp_sk(sk)->encap_type) != UDP_ENCAP_OVPNINUDP))
return NULL;
ovpn_sock = rcu_dereference_sk_user_data(sk);
if (unlikely(!ovpn_sock))
return NULL;
/* make sure that sk matches our stored transport socket */
if (unlikely(!ovpn_sock->sk || sk != ovpn_sock->sk))
return NULL;
return ovpn_sock;
}
/**
* ovpn_udp_encap_recv - Start processing a received UDP packet.
* @sk: socket over which the packet was received
* @skb: the received packet
*
* If the first byte of the payload is:
* - DATA_V2 the packet is accepted for further processing,
* - DATA_V1 the packet is dropped as not supported,
* - anything else the packet is forwarded to the UDP stack for
* delivery to user space.
*
* Return:
* 0 if skb was consumed or dropped
* >0 if skb should be passed up to userspace as UDP (packet not consumed)
* <0 if skb should be resubmitted as proto -N (packet not consumed)
*/
static int ovpn_udp_encap_recv(struct sock *sk, struct sk_buff *skb)
{
struct ovpn_socket *ovpn_sock;
struct ovpn_priv *ovpn;
struct ovpn_peer *peer;
u32 peer_id;
u8 opcode;
ovpn_sock = ovpn_socket_from_udp_sock(sk);
if (unlikely(!ovpn_sock)) {
net_err_ratelimited("ovpn: %s invoked on non ovpn socket\n",
__func__);
goto drop_noovpn;
}
ovpn = ovpn_sock->ovpn;
if (unlikely(!ovpn)) {
net_err_ratelimited("ovpn: cannot obtain ovpn object from UDP socket\n");
goto drop_noovpn;
}
/* Make sure the first 4 bytes of the skb data buffer after the UDP
* header are accessible.
* They are required to fetch the OP code, the key ID and the peer ID.
*/
if (unlikely(!pskb_may_pull(skb, sizeof(struct udphdr) +
OVPN_OPCODE_SIZE))) {
net_dbg_ratelimited("%s: packet too small from UDP socket\n",
netdev_name(ovpn->dev));
goto drop;
}
opcode = ovpn_opcode_from_skb(skb, sizeof(struct udphdr));
if (unlikely(opcode != OVPN_DATA_V2)) {
/* DATA_V1 is not supported */
if (opcode == OVPN_DATA_V1)
goto drop;
/* unknown or control packet: let it bubble up to userspace */
return 1;
}
peer_id = ovpn_peer_id_from_skb(skb, sizeof(struct udphdr));
/* some OpenVPN server implementations send data packets with the
* peer-id set to UNDEF. In this case we skip the peer lookup by peer-id
* and we try with the transport address
*/
if (peer_id == OVPN_PEER_ID_UNDEF)
peer = ovpn_peer_get_by_transp_addr(ovpn, skb);
else
peer = ovpn_peer_get_by_id(ovpn, peer_id);
if (unlikely(!peer))
goto drop;
/* pop off outer UDP header */
__skb_pull(skb, sizeof(struct udphdr));
ovpn_recv(peer, skb);
return 0;
drop:
ovpn_dev_dstats_rx_dropped(ovpn->dev);
drop_noovpn:
kfree_skb(skb);
return 0;
}
/**
* ovpn_udp4_output - send IPv4 packet over udp socket
* @peer: the destination peer
* @bind: the binding related to the destination peer
* @cache: dst cache
* @sk: the socket to send the packet over
* @skb: the packet to send
*
* Return: 0 on success or a negative error code otherwise
*/
static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind,
struct dst_cache *cache, struct sock *sk,
struct sk_buff *skb)
{
struct rtable *rt;
struct flowi4 fl = {
.saddr = bind->local.ipv4.s_addr,
.daddr = bind->remote.in4.sin_addr.s_addr,
.fl4_sport = inet_sk(sk)->inet_sport,
.fl4_dport = bind->remote.in4.sin_port,
.flowi4_proto = sk->sk_protocol,
.flowi4_mark = sk->sk_mark,
};
int ret;
local_bh_disable();
rt = dst_cache_get_ip4(cache, &fl.saddr);
if (rt)
goto transmit;
if (unlikely(!inet_confirm_addr(sock_net(sk), NULL, 0, fl.saddr,
RT_SCOPE_HOST))) {
/* we may end up here when the cached address is not usable
* anymore. In this case we reset address/cache and perform a
* new look up
*/
fl.saddr = 0;
spin_lock_bh(&peer->lock);
bind->local.ipv4.s_addr = 0;
spin_unlock_bh(&peer->lock);
dst_cache_reset(cache);
}
rt = ip_route_output_flow(sock_net(sk), &fl, sk);
if (IS_ERR(rt) && PTR_ERR(rt) == -EINVAL) {
fl.saddr = 0;
spin_lock_bh(&peer->lock);
bind->local.ipv4.s_addr = 0;
spin_unlock_bh(&peer->lock);
dst_cache_reset(cache);
rt = ip_route_output_flow(sock_net(sk), &fl, sk);
}
if (IS_ERR(rt)) {
ret = PTR_ERR(rt);
net_dbg_ratelimited("%s: no route to host %pISpc: %d\n",
netdev_name(peer->ovpn->dev),
&bind->remote.in4,
ret);
goto err;
}
dst_cache_set_ip4(cache, &rt->dst, fl.saddr);
transmit:
udp_tunnel_xmit_skb(rt, sk, skb, fl.saddr, fl.daddr, 0,
ip4_dst_hoplimit(&rt->dst), 0, fl.fl4_sport,
fl.fl4_dport, false, sk->sk_no_check_tx, 0);
ret = 0;
err:
local_bh_enable();
return ret;
}
#if IS_ENABLED(CONFIG_IPV6)
/**
* ovpn_udp6_output - send IPv6 packet over udp socket
* @peer: the destination peer
* @bind: the binding related to the destination peer
* @cache: dst cache
* @sk: the socket to send the packet over
* @skb: the packet to send
*
* Return: 0 on success or a negative error code otherwise
*/
static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind,
struct dst_cache *cache, struct sock *sk,
struct sk_buff *skb)
{
struct dst_entry *dst;
int ret;
struct flowi6 fl = {
.saddr = bind->local.ipv6,
.daddr = bind->remote.in6.sin6_addr,
.fl6_sport = inet_sk(sk)->inet_sport,
.fl6_dport = bind->remote.in6.sin6_port,
.flowi6_proto = sk->sk_protocol,
.flowi6_mark = sk->sk_mark,
.flowi6_oif = bind->remote.in6.sin6_scope_id,
};
local_bh_disable();
dst = dst_cache_get_ip6(cache, &fl.saddr);
if (dst)
goto transmit;
if (unlikely(!ipv6_chk_addr(sock_net(sk), &fl.saddr, NULL, 0))) {
/* we may end up here when the cached address is not usable
* anymore. In this case we reset address/cache and perform a
* new look up
*/
fl.saddr = in6addr_any;
spin_lock_bh(&peer->lock);
bind->local.ipv6 = in6addr_any;
spin_unlock_bh(&peer->lock);
dst_cache_reset(cache);
}
dst = ip6_dst_lookup_flow(sock_net(sk), sk, &fl, NULL);
if (IS_ERR(dst)) {
ret = PTR_ERR(dst);
net_dbg_ratelimited("%s: no route to host %pISpc: %d\n",
netdev_name(peer->ovpn->dev),
&bind->remote.in6, ret);
goto err;
}
dst_cache_set_ip6(cache, dst, &fl.saddr);
transmit:
/* user IPv6 packets may be larger than the transport interface
* MTU (after encapsulation), however, since they are locally
* generated we should ensure they get fragmented.
* Setting the ignore_df flag to 1 will instruct ip6_fragment() to
* fragment packets if needed.
*
* NOTE: this is not needed for IPv4 because we pass df=0 to
* udp_tunnel_xmit_skb()
*/
skb->ignore_df = 1;
udp_tunnel6_xmit_skb(dst, sk, skb, skb->dev, &fl.saddr, &fl.daddr, 0,
ip6_dst_hoplimit(dst), 0, fl.fl6_sport,
fl.fl6_dport, udp_get_no_check6_tx(sk), 0);
ret = 0;
err:
local_bh_enable();
return ret;
}
#endif
/**
* ovpn_udp_output - transmit skb using udp-tunnel
* @peer: the destination peer
* @cache: dst cache
* @sk: the socket to send the packet over
* @skb: the packet to send
*
* rcu_read_lock should be held on entry.
* On return, the skb is consumed.
*
* Return: 0 on success or a negative error code otherwise
*/
static int ovpn_udp_output(struct ovpn_peer *peer, struct dst_cache *cache,
struct sock *sk, struct sk_buff *skb)
{
struct ovpn_bind *bind;
int ret;
/* set sk to null if skb is already orphaned */
if (!skb->destructor)
skb->sk = NULL;
rcu_read_lock();
bind = rcu_dereference(peer->bind);
if (unlikely(!bind)) {
net_warn_ratelimited("%s: no bind for remote peer %u\n",
netdev_name(peer->ovpn->dev), peer->id);
ret = -ENODEV;
goto out;
}
switch (bind->remote.in4.sin_family) {
case AF_INET:
ret = ovpn_udp4_output(peer, bind, cache, sk, skb);
break;
#if IS_ENABLED(CONFIG_IPV6)
case AF_INET6:
ret = ovpn_udp6_output(peer, bind, cache, sk, skb);
break;
#endif
default:
ret = -EAFNOSUPPORT;
break;
}
out:
rcu_read_unlock();
return ret;
}
/**
* ovpn_udp_send_skb - prepare skb and send it over via UDP
* @peer: the destination peer
* @sk: peer socket
* @skb: the packet to send
*/
void ovpn_udp_send_skb(struct ovpn_peer *peer, struct sock *sk,
struct sk_buff *skb)
{
int ret;
skb->dev = peer->ovpn->dev;
skb->mark = READ_ONCE(sk->sk_mark);
/* no checksum performed at this layer */
skb->ip_summed = CHECKSUM_NONE;
/* crypto layer -> transport (UDP) */
ret = ovpn_udp_output(peer, &peer->dst_cache, sk, skb);
if (unlikely(ret < 0))
kfree_skb(skb);
}
static void ovpn_udp_encap_destroy(struct sock *sk)
{
struct ovpn_socket *sock;
struct ovpn_priv *ovpn;
rcu_read_lock();
sock = rcu_dereference_sk_user_data(sk);
if (!sock || !sock->ovpn) {
rcu_read_unlock();
return;
}
ovpn = sock->ovpn;
rcu_read_unlock();
ovpn_peers_free(ovpn, sk, OVPN_DEL_PEER_REASON_TRANSPORT_DISCONNECT);
}
/**
* ovpn_udp_socket_attach - set udp-tunnel CBs on socket and link it to ovpn
* @ovpn_sock: socket to configure
* @sock: the socket container to be passed to setup_udp_tunnel_sock()
* @ovpn: the openvp instance to link
*
* After invoking this function, the sock will be controlled by ovpn so that
* any incoming packet may be processed by ovpn first.
*
* Return: 0 on success or a negative error code otherwise
*/
int ovpn_udp_socket_attach(struct ovpn_socket *ovpn_sock, struct socket *sock,
struct ovpn_priv *ovpn)
{
struct udp_tunnel_sock_cfg cfg = {
.sk_user_data = ovpn_sock,
.encap_type = UDP_ENCAP_OVPNINUDP,
.encap_rcv = ovpn_udp_encap_recv,
.encap_destroy = ovpn_udp_encap_destroy,
};
struct ovpn_socket *old_data;
int ret;
/* make sure no pre-existing encapsulation handler exists */
rcu_read_lock();
old_data = rcu_dereference_sk_user_data(ovpn_sock->sk);
if (!old_data) {
/* socket is currently unused - we can take it */
rcu_read_unlock();
setup_udp_tunnel_sock(sock_net(ovpn_sock->sk), sock->sk, &cfg);
return 0;
}
/* socket is in use. We need to understand if it's owned by this ovpn
* instance or by something else.
* In the former case, we can increase the refcounter and happily
* use it, because the same UDP socket is expected to be shared among
* different peers.
*
* Unlikely TCP, a single UDP socket can be used to talk to many remote
* hosts and therefore openvpn instantiates one only for all its peers
*/
if ((READ_ONCE(udp_sk(ovpn_sock->sk)->encap_type) == UDP_ENCAP_OVPNINUDP) &&
old_data->ovpn == ovpn) {
netdev_dbg(ovpn->dev,
"provided socket already owned by this interface\n");
ret = -EALREADY;
} else {
netdev_dbg(ovpn->dev,
"provided socket already taken by other user\n");
ret = -EBUSY;
}
rcu_read_unlock();
return ret;
}
/**
* ovpn_udp_socket_detach - clean udp-tunnel status for this socket
* @ovpn_sock: the socket to clean
*/
void ovpn_udp_socket_detach(struct ovpn_socket *ovpn_sock)
{
struct sock *sk = ovpn_sock->sk;
/* Re-enable multicast loopback */
inet_set_bit(MC_LOOP, sk);
/* Disable CHECKSUM_UNNECESSARY to CHECKSUM_COMPLETE conversion */
inet_dec_convert_csum(sk);
WRITE_ONCE(udp_sk(sk)->encap_type, 0);
WRITE_ONCE(udp_sk(sk)->encap_rcv, NULL);
WRITE_ONCE(udp_sk(sk)->encap_destroy, NULL);
rcu_assign_sk_user_data(sk, NULL);
}
+25
View File
@@ -0,0 +1,25 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* OpenVPN data channel offload
*
* Copyright (C) 2019-2025 OpenVPN, Inc.
*
* Author: Antonio Quartulli <antonio@openvpn.net>
*/
#ifndef _NET_OVPN_UDP_H_
#define _NET_OVPN_UDP_H_
#include <net/sock.h>
struct ovpn_peer;
struct ovpn_priv;
struct socket;
int ovpn_udp_socket_attach(struct ovpn_socket *ovpn_sock, struct socket *sock,
struct ovpn_priv *ovpn);
void ovpn_udp_socket_detach(struct ovpn_socket *ovpn_sock);
void ovpn_udp_send_skb(struct ovpn_peer *peer, struct sock *sk,
struct sk_buff *skb);
#endif /* _NET_OVPN_UDP_H_ */