mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-05-04 08:04:24 -04:00
Merge branch 'introducing-openvpn-data-channel-offload'
Antonio Quartulli says: ==================== Introducing OpenVPN Data Channel Offload Notable changes since v25: * removed netdev notifier (was only used for our own devices) * added .dellink implementation to address what was previously done in notifier * removed .ndo_open and moved netif_carrier_off() call to .ndo_init * fixed author in MODULE_AUTHOR() * properly indented checks in ovpn.yaml * switched from TSTATS to DSTATS * removed obsolete comment in ovpn_socket_new() * removed unrelated hunk in ovpn_socket_new() The latest code can also be found at: https://github.com/OpenVPN/ovpn-net-next Reviewed-by: Sabrina Dubroca <sd@queasysnail.net> Tested-by: Oleksandr Natalenko <oleksandr@natalenko.name> ==================== Link: https://patch.msgid.link/20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
367
Documentation/netlink/specs/ovpn.yaml
Normal file
367
Documentation/netlink/specs/ovpn.yaml
Normal file
@@ -0,0 +1,367 @@
|
||||
# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
|
||||
#
|
||||
# Author: Antonio Quartulli <antonio@openvpn.net>
|
||||
#
|
||||
# Copyright (c) 2024-2025, OpenVPN Inc.
|
||||
#
|
||||
|
||||
name: ovpn
|
||||
|
||||
protocol: genetlink
|
||||
|
||||
doc: Netlink protocol to control OpenVPN network devices
|
||||
|
||||
definitions:
|
||||
-
|
||||
type: const
|
||||
name: nonce-tail-size
|
||||
value: 8
|
||||
-
|
||||
type: enum
|
||||
name: cipher-alg
|
||||
entries: [ none, aes-gcm, chacha20-poly1305 ]
|
||||
-
|
||||
type: enum
|
||||
name: del-peer-reason
|
||||
entries:
|
||||
- teardown
|
||||
- userspace
|
||||
- expired
|
||||
- transport-error
|
||||
- transport-disconnect
|
||||
-
|
||||
type: enum
|
||||
name: key-slot
|
||||
entries: [ primary, secondary ]
|
||||
|
||||
attribute-sets:
|
||||
-
|
||||
name: peer
|
||||
attributes:
|
||||
-
|
||||
name: id
|
||||
type: u32
|
||||
doc: >-
|
||||
The unique ID of the peer in the device context. To be used to identify
|
||||
peers during operations for a specific device
|
||||
checks:
|
||||
max: 0xFFFFFF
|
||||
-
|
||||
name: remote-ipv4
|
||||
type: u32
|
||||
doc: The remote IPv4 address of the peer
|
||||
byte-order: big-endian
|
||||
display-hint: ipv4
|
||||
-
|
||||
name: remote-ipv6
|
||||
type: binary
|
||||
doc: The remote IPv6 address of the peer
|
||||
display-hint: ipv6
|
||||
checks:
|
||||
exact-len: 16
|
||||
-
|
||||
name: remote-ipv6-scope-id
|
||||
type: u32
|
||||
doc: The scope id of the remote IPv6 address of the peer (RFC2553)
|
||||
-
|
||||
name: remote-port
|
||||
type: u16
|
||||
doc: The remote port of the peer
|
||||
byte-order: big-endian
|
||||
checks:
|
||||
min: 1
|
||||
-
|
||||
name: socket
|
||||
type: u32
|
||||
doc: The socket to be used to communicate with the peer
|
||||
-
|
||||
name: socket-netnsid
|
||||
type: s32
|
||||
doc: The ID of the netns the socket assigned to this peer lives in
|
||||
-
|
||||
name: vpn-ipv4
|
||||
type: u32
|
||||
doc: The IPv4 address assigned to the peer by the server
|
||||
byte-order: big-endian
|
||||
display-hint: ipv4
|
||||
-
|
||||
name: vpn-ipv6
|
||||
type: binary
|
||||
doc: The IPv6 address assigned to the peer by the server
|
||||
display-hint: ipv6
|
||||
checks:
|
||||
exact-len: 16
|
||||
-
|
||||
name: local-ipv4
|
||||
type: u32
|
||||
doc: The local IPv4 to be used to send packets to the peer (UDP only)
|
||||
byte-order: big-endian
|
||||
display-hint: ipv4
|
||||
-
|
||||
name: local-ipv6
|
||||
type: binary
|
||||
doc: The local IPv6 to be used to send packets to the peer (UDP only)
|
||||
display-hint: ipv6
|
||||
checks:
|
||||
exact-len: 16
|
||||
-
|
||||
name: local-port
|
||||
type: u16
|
||||
doc: The local port to be used to send packets to the peer (UDP only)
|
||||
byte-order: big-endian
|
||||
checks:
|
||||
min: 1
|
||||
-
|
||||
name: keepalive-interval
|
||||
type: u32
|
||||
doc: >-
|
||||
The number of seconds after which a keep alive message is sent to the
|
||||
peer
|
||||
-
|
||||
name: keepalive-timeout
|
||||
type: u32
|
||||
doc: >-
|
||||
The number of seconds from the last activity after which the peer is
|
||||
assumed dead
|
||||
-
|
||||
name: del-reason
|
||||
type: u32
|
||||
doc: The reason why a peer was deleted
|
||||
enum: del-peer-reason
|
||||
-
|
||||
name: vpn-rx-bytes
|
||||
type: uint
|
||||
doc: Number of bytes received over the tunnel
|
||||
-
|
||||
name: vpn-tx-bytes
|
||||
type: uint
|
||||
doc: Number of bytes transmitted over the tunnel
|
||||
-
|
||||
name: vpn-rx-packets
|
||||
type: uint
|
||||
doc: Number of packets received over the tunnel
|
||||
-
|
||||
name: vpn-tx-packets
|
||||
type: uint
|
||||
doc: Number of packets transmitted over the tunnel
|
||||
-
|
||||
name: link-rx-bytes
|
||||
type: uint
|
||||
doc: Number of bytes received at the transport level
|
||||
-
|
||||
name: link-tx-bytes
|
||||
type: uint
|
||||
doc: Number of bytes transmitted at the transport level
|
||||
-
|
||||
name: link-rx-packets
|
||||
type: uint
|
||||
doc: Number of packets received at the transport level
|
||||
-
|
||||
name: link-tx-packets
|
||||
type: uint
|
||||
doc: Number of packets transmitted at the transport level
|
||||
-
|
||||
name: keyconf
|
||||
attributes:
|
||||
-
|
||||
name: peer-id
|
||||
type: u32
|
||||
doc: >-
|
||||
The unique ID of the peer in the device context. To be used to
|
||||
identify peers during key operations
|
||||
checks:
|
||||
max: 0xFFFFFF
|
||||
-
|
||||
name: slot
|
||||
type: u32
|
||||
doc: The slot where the key should be stored
|
||||
enum: key-slot
|
||||
-
|
||||
name: key-id
|
||||
doc: >-
|
||||
The unique ID of the key in the peer context. Used to fetch the
|
||||
correct key upon decryption
|
||||
type: u32
|
||||
checks:
|
||||
max: 7
|
||||
-
|
||||
name: cipher-alg
|
||||
type: u32
|
||||
doc: The cipher to be used when communicating with the peer
|
||||
enum: cipher-alg
|
||||
-
|
||||
name: encrypt-dir
|
||||
type: nest
|
||||
doc: Key material for encrypt direction
|
||||
nested-attributes: keydir
|
||||
-
|
||||
name: decrypt-dir
|
||||
type: nest
|
||||
doc: Key material for decrypt direction
|
||||
nested-attributes: keydir
|
||||
-
|
||||
name: keydir
|
||||
attributes:
|
||||
-
|
||||
name: cipher-key
|
||||
type: binary
|
||||
doc: The actual key to be used by the cipher
|
||||
checks:
|
||||
max-len: 256
|
||||
-
|
||||
name: nonce-tail
|
||||
type: binary
|
||||
doc: >-
|
||||
Random nonce to be concatenated to the packet ID, in order to
|
||||
obtain the actual cipher IV
|
||||
checks:
|
||||
exact-len: nonce-tail-size
|
||||
-
|
||||
name: ovpn
|
||||
attributes:
|
||||
-
|
||||
name: ifindex
|
||||
type: u32
|
||||
doc: Index of the ovpn interface to operate on
|
||||
-
|
||||
name: peer
|
||||
type: nest
|
||||
doc: >-
|
||||
The peer object containing the attributed of interest for the specific
|
||||
operation
|
||||
nested-attributes: peer
|
||||
-
|
||||
name: keyconf
|
||||
type: nest
|
||||
doc: Peer specific cipher configuration
|
||||
nested-attributes: keyconf
|
||||
|
||||
operations:
|
||||
list:
|
||||
-
|
||||
name: peer-new
|
||||
attribute-set: ovpn
|
||||
flags: [ admin-perm ]
|
||||
doc: Add a remote peer
|
||||
do:
|
||||
pre: ovpn-nl-pre-doit
|
||||
post: ovpn-nl-post-doit
|
||||
request:
|
||||
attributes:
|
||||
- ifindex
|
||||
- peer
|
||||
-
|
||||
name: peer-set
|
||||
attribute-set: ovpn
|
||||
flags: [ admin-perm ]
|
||||
doc: modify a remote peer
|
||||
do:
|
||||
pre: ovpn-nl-pre-doit
|
||||
post: ovpn-nl-post-doit
|
||||
request:
|
||||
attributes:
|
||||
- ifindex
|
||||
- peer
|
||||
-
|
||||
name: peer-get
|
||||
attribute-set: ovpn
|
||||
flags: [ admin-perm ]
|
||||
doc: Retrieve data about existing remote peers (or a specific one)
|
||||
do:
|
||||
pre: ovpn-nl-pre-doit
|
||||
post: ovpn-nl-post-doit
|
||||
request:
|
||||
attributes:
|
||||
- ifindex
|
||||
- peer
|
||||
reply:
|
||||
attributes:
|
||||
- peer
|
||||
dump:
|
||||
request:
|
||||
attributes:
|
||||
- ifindex
|
||||
reply:
|
||||
attributes:
|
||||
- peer
|
||||
-
|
||||
name: peer-del
|
||||
attribute-set: ovpn
|
||||
flags: [ admin-perm ]
|
||||
doc: Delete existing remote peer
|
||||
do:
|
||||
pre: ovpn-nl-pre-doit
|
||||
post: ovpn-nl-post-doit
|
||||
request:
|
||||
attributes:
|
||||
- ifindex
|
||||
- peer
|
||||
-
|
||||
name: peer-del-ntf
|
||||
doc: Notification about a peer being deleted
|
||||
notify: peer-get
|
||||
mcgrp: peers
|
||||
|
||||
-
|
||||
name: key-new
|
||||
attribute-set: ovpn
|
||||
flags: [ admin-perm ]
|
||||
doc: Add a cipher key for a specific peer
|
||||
do:
|
||||
pre: ovpn-nl-pre-doit
|
||||
post: ovpn-nl-post-doit
|
||||
request:
|
||||
attributes:
|
||||
- ifindex
|
||||
- keyconf
|
||||
-
|
||||
name: key-get
|
||||
attribute-set: ovpn
|
||||
flags: [ admin-perm ]
|
||||
doc: Retrieve non-sensitive data about peer key and cipher
|
||||
do:
|
||||
pre: ovpn-nl-pre-doit
|
||||
post: ovpn-nl-post-doit
|
||||
request:
|
||||
attributes:
|
||||
- ifindex
|
||||
- keyconf
|
||||
reply:
|
||||
attributes:
|
||||
- keyconf
|
||||
-
|
||||
name: key-swap
|
||||
attribute-set: ovpn
|
||||
flags: [ admin-perm ]
|
||||
doc: Swap primary and secondary session keys for a specific peer
|
||||
do:
|
||||
pre: ovpn-nl-pre-doit
|
||||
post: ovpn-nl-post-doit
|
||||
request:
|
||||
attributes:
|
||||
- ifindex
|
||||
- keyconf
|
||||
-
|
||||
name: key-swap-ntf
|
||||
notify: key-get
|
||||
doc: >-
|
||||
Notification about key having exhausted its IV space and requiring
|
||||
renegotiation
|
||||
mcgrp: peers
|
||||
-
|
||||
name: key-del
|
||||
attribute-set: ovpn
|
||||
flags: [ admin-perm ]
|
||||
doc: Delete cipher key for a specific peer
|
||||
do:
|
||||
pre: ovpn-nl-pre-doit
|
||||
post: ovpn-nl-post-doit
|
||||
request:
|
||||
attributes:
|
||||
- ifindex
|
||||
- keyconf
|
||||
|
||||
mcast-groups:
|
||||
list:
|
||||
-
|
||||
name: peers
|
||||
@@ -938,6 +938,12 @@ definitions:
|
||||
entries:
|
||||
- name: none
|
||||
- name: default
|
||||
-
|
||||
name: ovpn-mode
|
||||
type: enum
|
||||
entries:
|
||||
- p2p
|
||||
- mp
|
||||
|
||||
attribute-sets:
|
||||
-
|
||||
@@ -2272,6 +2278,13 @@ attribute-sets:
|
||||
-
|
||||
name: tailroom
|
||||
type: u16
|
||||
-
|
||||
name: linkinfo-ovpn-attrs
|
||||
attributes:
|
||||
-
|
||||
name: mode
|
||||
type: u8
|
||||
enum: ovpn-mode
|
||||
|
||||
sub-messages:
|
||||
-
|
||||
@@ -2322,6 +2335,9 @@ sub-messages:
|
||||
-
|
||||
value: netkit
|
||||
attribute-set: linkinfo-netkit-attrs
|
||||
-
|
||||
value: ovpn
|
||||
attribute-set: linkinfo-ovpn-attrs
|
||||
-
|
||||
name: linkinfo-member-data-msg
|
||||
formats:
|
||||
|
||||
11
MAINTAINERS
11
MAINTAINERS
@@ -18125,6 +18125,17 @@ F: arch/openrisc/
|
||||
F: drivers/irqchip/irq-ompic.c
|
||||
F: drivers/irqchip/irq-or1k-*
|
||||
|
||||
OPENVPN DATA CHANNEL OFFLOAD
|
||||
M: Antonio Quartulli <antonio@openvpn.net>
|
||||
L: openvpn-devel@lists.sourceforge.net (subscribers-only)
|
||||
L: netdev@vger.kernel.org
|
||||
S: Supported
|
||||
T: git https://github.com/OpenVPN/linux-kernel-ovpn.git
|
||||
F: Documentation/netlink/specs/ovpn.yaml
|
||||
F: drivers/net/ovpn/
|
||||
F: include/uapi/linux/ovpn.h
|
||||
F: tools/testing/selftests/net/ovpn/
|
||||
|
||||
OPENVSWITCH
|
||||
M: Aaron Conole <aconole@redhat.com>
|
||||
M: Eelco Chaudron <echaudro@redhat.com>
|
||||
|
||||
@@ -115,6 +115,21 @@ config WIREGUARD_DEBUG
|
||||
|
||||
Say N here unless you know what you're doing.
|
||||
|
||||
config OVPN
|
||||
tristate "OpenVPN data channel offload"
|
||||
depends on NET && INET
|
||||
depends on IPV6 || !IPV6
|
||||
select DST_CACHE
|
||||
select NET_UDP_TUNNEL
|
||||
select CRYPTO
|
||||
select CRYPTO_AES
|
||||
select CRYPTO_GCM
|
||||
select CRYPTO_CHACHA20POLY1305
|
||||
select STREAM_PARSER
|
||||
help
|
||||
This module enhances the performance of the OpenVPN userspace software
|
||||
by offloading the data channel processing to kernelspace.
|
||||
|
||||
config EQUALIZER
|
||||
tristate "EQL (serial line load balancing) support"
|
||||
help
|
||||
|
||||
@@ -11,6 +11,7 @@ obj-$(CONFIG_IPVLAN) += ipvlan/
|
||||
obj-$(CONFIG_IPVTAP) += ipvlan/
|
||||
obj-$(CONFIG_DUMMY) += dummy.o
|
||||
obj-$(CONFIG_WIREGUARD) += wireguard/
|
||||
obj-$(CONFIG_OVPN) += ovpn/
|
||||
obj-$(CONFIG_EQUALIZER) += eql.o
|
||||
obj-$(CONFIG_IFB) += ifb.o
|
||||
obj-$(CONFIG_MACSEC) += macsec.o
|
||||
|
||||
22
drivers/net/ovpn/Makefile
Normal file
22
drivers/net/ovpn/Makefile
Normal 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
drivers/net/ovpn/bind.c
Normal file
55
drivers/net/ovpn/bind.c
Normal 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(sizeof(*bind), GFP_ATOMIC);
|
||||
if (unlikely(!bind))
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
memcpy(&bind->remote, ss, sa_len);
|
||||
|
||||
return bind;
|
||||
}
|
||||
|
||||
/**
|
||||
* ovpn_bind_reset - assign new binding to peer
|
||||
* @peer: the peer whose binding has to be replaced
|
||||
* @new: the new bind to assign
|
||||
*/
|
||||
void ovpn_bind_reset(struct ovpn_peer *peer, struct ovpn_bind *new)
|
||||
{
|
||||
lockdep_assert_held(&peer->lock);
|
||||
|
||||
kfree_rcu(rcu_replace_pointer(peer->bind, new,
|
||||
lockdep_is_held(&peer->lock)), rcu);
|
||||
}
|
||||
101
drivers/net/ovpn/bind.h
Normal file
101
drivers/net/ovpn/bind.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/* OpenVPN data channel offload
|
||||
*
|
||||
* Copyright (C) 2012-2025 OpenVPN, Inc.
|
||||
*
|
||||
* Author: James Yonan <james@openvpn.net>
|
||||
* Antonio Quartulli <antonio@openvpn.net>
|
||||
*/
|
||||
|
||||
#ifndef _NET_OVPN_OVPNBIND_H_
|
||||
#define _NET_OVPN_OVPNBIND_H_
|
||||
|
||||
#include <net/ip.h>
|
||||
#include <linux/in.h>
|
||||
#include <linux/in6.h>
|
||||
#include <linux/rcupdate.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
struct ovpn_peer;
|
||||
|
||||
/**
|
||||
* union ovpn_sockaddr - basic transport layer address
|
||||
* @in4: IPv4 address
|
||||
* @in6: IPv6 address
|
||||
*/
|
||||
union ovpn_sockaddr {
|
||||
struct sockaddr_in in4;
|
||||
struct sockaddr_in6 in6;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ovpn_bind - remote peer binding
|
||||
* @remote: the remote peer sockaddress
|
||||
* @local: local endpoint used to talk to the peer
|
||||
* @local.ipv4: local IPv4 used to talk to the peer
|
||||
* @local.ipv6: local IPv6 used to talk to the peer
|
||||
* @rcu: used to schedule RCU cleanup job
|
||||
*/
|
||||
struct ovpn_bind {
|
||||
union ovpn_sockaddr remote; /* remote sockaddr */
|
||||
|
||||
union {
|
||||
struct in_addr ipv4;
|
||||
struct in6_addr ipv6;
|
||||
} local;
|
||||
|
||||
struct rcu_head rcu;
|
||||
};
|
||||
|
||||
/**
|
||||
* ovpn_bind_skb_src_match - match packet source with binding
|
||||
* @bind: the binding to match
|
||||
* @skb: the packet to match
|
||||
*
|
||||
* Return: true if the packet source matches the remote peer sockaddr
|
||||
* in the binding
|
||||
*/
|
||||
static inline bool ovpn_bind_skb_src_match(const struct ovpn_bind *bind,
|
||||
const struct sk_buff *skb)
|
||||
{
|
||||
const union ovpn_sockaddr *remote;
|
||||
|
||||
if (unlikely(!bind))
|
||||
return false;
|
||||
|
||||
remote = &bind->remote;
|
||||
|
||||
switch (skb->protocol) {
|
||||
case htons(ETH_P_IP):
|
||||
if (unlikely(remote->in4.sin_family != AF_INET))
|
||||
return false;
|
||||
|
||||
if (unlikely(remote->in4.sin_addr.s_addr != ip_hdr(skb)->saddr))
|
||||
return false;
|
||||
|
||||
if (unlikely(remote->in4.sin_port != udp_hdr(skb)->source))
|
||||
return false;
|
||||
break;
|
||||
case htons(ETH_P_IPV6):
|
||||
if (unlikely(remote->in6.sin6_family != AF_INET6))
|
||||
return false;
|
||||
|
||||
if (unlikely(!ipv6_addr_equal(&remote->in6.sin6_addr,
|
||||
&ipv6_hdr(skb)->saddr)))
|
||||
return false;
|
||||
|
||||
if (unlikely(remote->in6.sin6_port != udp_hdr(skb)->source))
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct ovpn_bind *ovpn_bind_from_sockaddr(const struct sockaddr_storage *sa);
|
||||
void ovpn_bind_reset(struct ovpn_peer *peer, struct ovpn_bind *bind);
|
||||
|
||||
#endif /* _NET_OVPN_OVPNBIND_H_ */
|
||||
210
drivers/net/ovpn/crypto.c
Normal file
210
drivers/net/ovpn/crypto.c
Normal file
@@ -0,0 +1,210 @@
|
||||
// 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"
|
||||
|
||||
static void ovpn_ks_destroy_rcu(struct rcu_head *head)
|
||||
{
|
||||
struct ovpn_crypto_key_slot *ks;
|
||||
|
||||
ks = container_of(head, struct ovpn_crypto_key_slot, rcu);
|
||||
ovpn_aead_crypto_key_slot_destroy(ks);
|
||||
}
|
||||
|
||||
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);
|
||||
call_rcu(&ks->rcu, ovpn_ks_destroy_rcu);
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
spin_lock_bh(&cs->lock);
|
||||
if (rcu_access_pointer(cs->slots[0])->key_id == key_id) {
|
||||
ks = rcu_replace_pointer(cs->slots[0], NULL,
|
||||
lockdep_is_held(&cs->lock));
|
||||
} else if (rcu_access_pointer(cs->slots[1])->key_id == key_id) {
|
||||
ks = rcu_replace_pointer(cs->slots[1], 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;
|
||||
}
|
||||
145
drivers/net/ovpn/crypto.h
Normal file
145
drivers/net/ovpn/crypto.h
Normal file
@@ -0,0 +1,145 @@
|
||||
/* 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 "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 kref refcount;
|
||||
struct rcu_head rcu;
|
||||
};
|
||||
|
||||
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_ */
|
||||
383
drivers/net/ovpn/crypto_aead.c
Normal file
383
drivers/net/ovpn/crypto_aead.c
Normal file
@@ -0,0 +1,383 @@
|
||||
// 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 <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 */
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
|
||||
/* sg may be required by async crypto */
|
||||
ovpn_skb_cb(skb)->sg = kmalloc(sizeof(*ovpn_skb_cb(skb)->sg) *
|
||||
(nfrags + 2), GFP_ATOMIC);
|
||||
if (unlikely(!ovpn_skb_cb(skb)->sg))
|
||||
return -ENOMEM;
|
||||
|
||||
sg = ovpn_skb_cb(skb)->sg;
|
||||
|
||||
/* 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(nfrags != ret))
|
||||
return -EINVAL;
|
||||
|
||||
/* append auth_tag onto scatterlist */
|
||||
__skb_push(skb, tag_size);
|
||||
sg_set_buf(sg + nfrags + 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;
|
||||
|
||||
/* iv may be required by async crypto */
|
||||
ovpn_skb_cb(skb)->iv = kmalloc(OVPN_NONCE_SIZE, GFP_ATOMIC);
|
||||
if (unlikely(!ovpn_skb_cb(skb)->iv))
|
||||
return -ENOMEM;
|
||||
|
||||
iv = ovpn_skb_cb(skb)->iv;
|
||||
|
||||
/* 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->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);
|
||||
|
||||
req = aead_request_alloc(ks->encrypt, GFP_ATOMIC);
|
||||
if (unlikely(!req))
|
||||
return -ENOMEM;
|
||||
|
||||
ovpn_skb_cb(skb)->req = req;
|
||||
|
||||
/* 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;
|
||||
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;
|
||||
|
||||
/* sg may be required by async crypto */
|
||||
ovpn_skb_cb(skb)->sg = kmalloc(sizeof(*ovpn_skb_cb(skb)->sg) *
|
||||
(nfrags + 2), GFP_ATOMIC);
|
||||
if (unlikely(!ovpn_skb_cb(skb)->sg))
|
||||
return -ENOMEM;
|
||||
|
||||
sg = ovpn_skb_cb(skb)->sg;
|
||||
|
||||
/* 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(nfrags != ret))
|
||||
return -EINVAL;
|
||||
|
||||
/* append auth_tag onto scatterlist */
|
||||
sg_set_buf(sg + nfrags + 1, skb->data + OVPN_AAD_SIZE, tag_size);
|
||||
|
||||
/* iv may be required by async crypto */
|
||||
ovpn_skb_cb(skb)->iv = kmalloc(OVPN_NONCE_SIZE, GFP_ATOMIC);
|
||||
if (unlikely(!ovpn_skb_cb(skb)->iv))
|
||||
return -ENOMEM;
|
||||
|
||||
iv = ovpn_skb_cb(skb)->iv;
|
||||
|
||||
/* 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);
|
||||
|
||||
req = aead_request_alloc(ks->decrypt, GFP_ATOMIC);
|
||||
if (unlikely(!req))
|
||||
return -ENOMEM;
|
||||
|
||||
ovpn_skb_cb(skb)->req = req;
|
||||
|
||||
/* 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 */
|
||||
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);
|
||||
}
|
||||
|
||||
void ovpn_aead_crypto_key_slot_destroy(struct ovpn_crypto_key_slot *ks)
|
||||
{
|
||||
if (!ks)
|
||||
return;
|
||||
|
||||
crypto_free_aead(ks->encrypt);
|
||||
crypto_free_aead(ks->decrypt);
|
||||
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(sizeof(*ks), GFP_KERNEL);
|
||||
if (!ks)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
ks->encrypt = NULL;
|
||||
ks->decrypt = NULL;
|
||||
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_destroy(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;
|
||||
}
|
||||
29
drivers/net/ovpn/crypto_aead.h
Normal file
29
drivers/net/ovpn/crypto_aead.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/* 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);
|
||||
void ovpn_aead_crypto_key_slot_destroy(struct ovpn_crypto_key_slot *ks);
|
||||
|
||||
enum ovpn_cipher_alg ovpn_aead_crypto_alg(struct ovpn_crypto_key_slot *ks);
|
||||
|
||||
#endif /* _NET_OVPN_OVPNAEAD_H_ */
|
||||
446
drivers/net/ovpn/io.c
Normal file
446
drivers/net/ovpn/io.c
Normal file
@@ -0,0 +1,446 @@
|
||||
// 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;
|
||||
|
||||
/* 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_transport_header(skb);
|
||||
skb_reset_inner_headers(skb);
|
||||
|
||||
/* cause packet to be "received" by the interface */
|
||||
pkt_len = skb->len;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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)->iv);
|
||||
kfree(ovpn_skb_cb(skb)->sg);
|
||||
aead_request_free(ovpn_skb_cb(skb)->req);
|
||||
|
||||
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_real_seconds());
|
||||
|
||||
rcu_read_lock();
|
||||
sock = rcu_dereference(peer->sock);
|
||||
if (sock && 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))
|
||||
dev_dstats_rx_dropped(peer->ovpn->dev);
|
||||
kfree_skb(skb);
|
||||
drop_nocount:
|
||||
if (likely(peer))
|
||||
ovpn_peer_put(peer);
|
||||
if (likely(ks))
|
||||
ovpn_crypto_key_slot_put(ks);
|
||||
}
|
||||
|
||||
/* 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);
|
||||
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)->iv);
|
||||
kfree(ovpn_skb_cb(skb)->sg);
|
||||
aead_request_free(ovpn_skb_cb(skb)->req);
|
||||
|
||||
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->sock->sk->sk_protocol) {
|
||||
case IPPROTO_UDP:
|
||||
ovpn_udp_send_skb(peer, sock->sock, skb);
|
||||
break;
|
||||
case IPPROTO_TCP:
|
||||
ovpn_tcp_send_skb(peer, sock->sock, 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_real_seconds());
|
||||
/* skb passed down the stack - don't free it */
|
||||
skb = NULL;
|
||||
err_unlock:
|
||||
rcu_read_unlock();
|
||||
err:
|
||||
if (unlikely(skb))
|
||||
dev_dstats_tx_dropped(peer->ovpn->dev);
|
||||
if (likely(peer))
|
||||
ovpn_peer_put(peer);
|
||||
if (likely(ks))
|
||||
ovpn_crypto_key_slot_put(ks);
|
||||
kfree_skb(skb);
|
||||
}
|
||||
|
||||
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))) {
|
||||
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;
|
||||
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;
|
||||
|
||||
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));
|
||||
dev_dstats_tx_dropped(ovpn->dev);
|
||||
continue;
|
||||
}
|
||||
|
||||
__skb_queue_tail(&skb_list, curr);
|
||||
}
|
||||
skb_list.prev->next = NULL;
|
||||
|
||||
/* retrieve peer serving the destination IP of this packet */
|
||||
peer = ovpn_peer_get_by_dst(ovpn, skb);
|
||||
if (unlikely(!peer)) {
|
||||
net_dbg_ratelimited("%s: no peer to send data to\n",
|
||||
netdev_name(ovpn->dev));
|
||||
goto drop;
|
||||
}
|
||||
|
||||
ovpn_peer_stats_increment_tx(&peer->vpn_stats, skb->len);
|
||||
ovpn_send(ovpn, skb_list.next, peer);
|
||||
|
||||
return NETDEV_TX_OK;
|
||||
|
||||
drop:
|
||||
dev_dstats_tx_dropped(ovpn->dev);
|
||||
skb_tx_error(skb);
|
||||
kfree_skb_list(skb);
|
||||
return NET_XMIT_DROP;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
drivers/net/ovpn/io.h
Normal file
34
drivers/net/ovpn/io.h
Normal 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_ */
|
||||
274
drivers/net/ovpn/main.c
Normal file
274
drivers/net/ovpn/main.c
Normal file
@@ -0,0 +1,274 @@
|
||||
// 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 <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"
|
||||
|
||||
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)
|
||||
{
|
||||
struct in_device *dev_v4;
|
||||
int i;
|
||||
|
||||
if (ovpn->mode != OVPN_MODE_MP)
|
||||
return 0;
|
||||
|
||||
dev_v4 = __in_dev_get_rtnl(ovpn->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(ovpn->dev), SEND_REDIRECTS) = false;
|
||||
}
|
||||
|
||||
/* the peer container is fairly large, therefore we allocate it only in
|
||||
* MP mode
|
||||
*/
|
||||
ovpn->peers = kzalloc(sizeof(*ovpn->peers), GFP_KERNEL);
|
||||
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);
|
||||
|
||||
gro_cells_destroy(&ovpn->gro_cells);
|
||||
}
|
||||
|
||||
static const struct net_device_ops ovpn_netdev_ops = {
|
||||
.ndo_init = ovpn_net_init,
|
||||
.ndo_uninit = ovpn_net_uninit,
|
||||
.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;
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
return register_netdevice(dev);
|
||||
}
|
||||
|
||||
static void ovpn_dellink(struct net_device *dev, struct list_head *head)
|
||||
{
|
||||
struct ovpn_priv *ovpn = netdev_priv(dev);
|
||||
|
||||
cancel_delayed_work_sync(&ovpn->keepalive_work);
|
||||
ovpn_peers_free(ovpn, NULL, OVPN_DEL_PEER_REASON_TEARDOWN);
|
||||
unregister_netdevice_queue(dev, head);
|
||||
}
|
||||
|
||||
static int ovpn_fill_info(struct sk_buff *skb, const struct net_device *dev)
|
||||
{
|
||||
struct ovpn_priv *ovpn = netdev_priv(dev);
|
||||
|
||||
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,
|
||||
.dellink = ovpn_dellink,
|
||||
.fill_info = ovpn_fill_info,
|
||||
};
|
||||
|
||||
static int __init ovpn_init(void)
|
||||
{
|
||||
int err = rtnl_link_register(&ovpn_link_ops);
|
||||
|
||||
if (err) {
|
||||
pr_err("ovpn: can't register rtnl link ops: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = ovpn_nl_register();
|
||||
if (err) {
|
||||
pr_err("ovpn: can't register netlink family: %d\n", err);
|
||||
goto unreg_rtnl;
|
||||
}
|
||||
|
||||
ovpn_tcp_init();
|
||||
|
||||
return 0;
|
||||
|
||||
unreg_rtnl:
|
||||
rtnl_link_unregister(&ovpn_link_ops);
|
||||
return err;
|
||||
}
|
||||
|
||||
static __exit void ovpn_cleanup(void)
|
||||
{
|
||||
ovpn_nl_unregister();
|
||||
rtnl_link_unregister(&ovpn_link_ops);
|
||||
|
||||
rcu_barrier();
|
||||
}
|
||||
|
||||
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
drivers/net/ovpn/main.h
Normal file
14
drivers/net/ovpn/main.h
Normal 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_ */
|
||||
213
drivers/net/ovpn/netlink-gen.c
Normal file
213
drivers/net/ovpn/netlink-gen.c
Normal file
@@ -0,0 +1,213 @@
|
||||
// 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 */
|
||||
|
||||
#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_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_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_LINK_TX_PACKETS + 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] = { .type = NLA_U32, },
|
||||
[OVPN_A_PEER_KEEPALIVE_TIMEOUT] = { .type = NLA_U32, },
|
||||
[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_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_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_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_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_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_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_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),
|
||||
};
|
||||
41
drivers/net/ovpn/netlink-gen.h
Normal file
41
drivers/net/ovpn/netlink-gen.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/* 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 */
|
||||
|
||||
#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_keydir_nl_policy[OVPN_A_KEYDIR_NONCE_TAIL + 1];
|
||||
extern const struct nla_policy ovpn_peer_nl_policy[OVPN_A_PEER_LINK_TX_PACKETS + 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 */
|
||||
1258
drivers/net/ovpn/netlink.c
Normal file
1258
drivers/net/ovpn/netlink.c
Normal file
File diff suppressed because it is too large
Load Diff
18
drivers/net/ovpn/netlink.h
Normal file
18
drivers/net/ovpn/netlink.h
Normal file
@@ -0,0 +1,18 @@
|
||||
/* 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_key_swap_notify(struct ovpn_peer *peer, u8 key_id);
|
||||
|
||||
#endif /* _NET_OVPN_NETLINK_H_ */
|
||||
55
drivers/net/ovpn/ovpnpriv.h
Normal file
55
drivers/net/ovpn/ovpnpriv.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/* 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 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_ */
|
||||
1365
drivers/net/ovpn/peer.c
Normal file
1365
drivers/net/ovpn/peer.c
Normal file
File diff suppressed because it is too large
Load Diff
163
drivers/net/ovpn/peer.h
Normal file
163
drivers/net/ovpn/peer.h
Normal file
@@ -0,0 +1,163 @@
|
||||
/* 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
|
||||
* @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;
|
||||
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(struct ovpn_peer *peer);
|
||||
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);
|
||||
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_ */
|
||||
129
drivers/net/ovpn/pktid.c
Normal file
129
drivers/net/ovpn/pktid.c
Normal file
@@ -0,0 +1,129 @@
|
||||
// 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);
|
||||
pr->history[pr->base / 8] |= (1 << (pr->base % 8));
|
||||
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);
|
||||
pr->history[pr->base / 8] |= (1 << (pr->base % 8));
|
||||
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);
|
||||
|
||||
pr->history[newb / 8] &= ~BIT(newb % 8);
|
||||
}
|
||||
} 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);
|
||||
u8 *p = &pr->history[ri / 8];
|
||||
const u8 mask = (1 << (ri % 8));
|
||||
|
||||
if (*p & mask) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
*p |= mask;
|
||||
} 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
drivers/net/ovpn/pktid.h
Normal file
86
drivers/net/ovpn/pktid.h
Normal 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 */
|
||||
u8 history[REPLAY_WINDOW_BYTES];
|
||||
/* 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
drivers/net/ovpn/proto.h
Normal file
118
drivers/net/ovpn/proto.h
Normal 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_ */
|
||||
61
drivers/net/ovpn/skb.h
Normal file
61
drivers/net/ovpn/skb.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/* 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 {
|
||||
struct ovpn_peer *peer;
|
||||
struct ovpn_crypto_key_slot *ks;
|
||||
struct aead_request *req;
|
||||
struct scatterlist *sg;
|
||||
u8 *iv;
|
||||
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_ */
|
||||
233
drivers/net/ovpn/socket.c
Normal file
233
drivers/net/ovpn/socket.c
Normal file
@@ -0,0 +1,233 @@
|
||||
// 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->sock->sk->sk_protocol == IPPROTO_UDP)
|
||||
ovpn_udp_socket_detach(sock);
|
||||
else if (sock->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;
|
||||
|
||||
/* sanity check: we should not end up here if the socket
|
||||
* was already closed
|
||||
*/
|
||||
if (!sock->sock->sk) {
|
||||
DEBUG_NET_WARN_ON_ONCE(1);
|
||||
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->sock->sk);
|
||||
released = ovpn_socket_put(peer, sock);
|
||||
release_sock(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->sock->sk->sk_protocol == IPPROTO_UDP) {
|
||||
netdev_put(sock->ovpn->dev, &sock->dev_tracker);
|
||||
} else if (sock->sock->sk->sk_protocol == IPPROTO_TCP) {
|
||||
/* wait for TCP jobs to terminate */
|
||||
ovpn_tcp_socket_wait_finish(sock);
|
||||
ovpn_peer_put(sock->peer);
|
||||
}
|
||||
/* 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 *sock, struct ovpn_peer *peer)
|
||||
{
|
||||
if (sock->sock->sk->sk_protocol == IPPROTO_UDP)
|
||||
return ovpn_udp_socket_attach(sock, peer->ovpn);
|
||||
else if (sock->sock->sk->sk_protocol == IPPROTO_TCP)
|
||||
return ovpn_tcp_socket_attach(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;
|
||||
int ret;
|
||||
|
||||
lock_sock(sock->sk);
|
||||
|
||||
/* a TCP socket can only be owned by a single peer, therefore there
|
||||
* can't be any other user
|
||||
*/
|
||||
if (sock->sk->sk_protocol == IPPROTO_TCP && sock->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 (sock->sk->sk_protocol == IPPROTO_UDP) {
|
||||
u8 type = READ_ONCE(udp_sk(sock->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(sock->sk);
|
||||
if (ovpn_sock) {
|
||||
/* 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(sizeof(*ovpn_sock), GFP_KERNEL);
|
||||
if (!ovpn_sock) {
|
||||
ovpn_sock = ERR_PTR(-ENOMEM);
|
||||
goto sock_release;
|
||||
}
|
||||
|
||||
ovpn_sock->sock = sock;
|
||||
kref_init(&ovpn_sock->refcount);
|
||||
|
||||
ret = ovpn_socket_attach(ovpn_sock, peer);
|
||||
if (ret < 0) {
|
||||
kfree(ovpn_sock);
|
||||
ovpn_sock = ERR_PTR(ret);
|
||||
goto sock_release;
|
||||
}
|
||||
|
||||
/* TCP sockets are per-peer, therefore they are linked to their unique
|
||||
* peer
|
||||
*/
|
||||
if (sock->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 (sock->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);
|
||||
}
|
||||
|
||||
rcu_assign_sk_user_data(sock->sk, ovpn_sock);
|
||||
sock_release:
|
||||
release_sock(sock->sk);
|
||||
return ovpn_sock;
|
||||
}
|
||||
49
drivers/net/ovpn/socket.h
Normal file
49
drivers/net/ovpn/socket.h
Normal 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)
|
||||
* @sock: 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 socket *sock;
|
||||
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
drivers/net/ovpn/stats.c
Normal file
21
drivers/net/ovpn/stats.c
Normal 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);
|
||||
}
|
||||
47
drivers/net/ovpn/stats.h
Normal file
47
drivers/net/ovpn/stats.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/* 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_
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
#endif /* _NET_OVPN_OVPNSTATS_H_ */
|
||||
598
drivers/net/ovpn/tcp.c
Normal file
598
drivers/net/ovpn/tcp.c
Normal file
@@ -0,0 +1,598 @@
|
||||
// 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 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);
|
||||
size_t pkt_len = msg->full_len - 2;
|
||||
size_t off = msg->offset + 2;
|
||||
u8 opcode;
|
||||
|
||||
/* ensure skb->data points to the beginning of the openvpn packet */
|
||||
if (!pskb_pull(skb, off)) {
|
||||
net_warn_ratelimited("%s: packet too small for peer %u\n",
|
||||
netdev_name(peer->ovpn->dev), peer->id);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* strparser does not trim the skb for us, therefore we do it now */
|
||||
if (pskb_trim(skb, pkt_len) != 0) {
|
||||
net_warn_ratelimited("%s: trimming skb failed for peer %u\n",
|
||||
netdev_name(peer->ovpn->dev), peer->id);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* we need the first 4 bytes of data to be accessible
|
||||
* to extract the opcode and the key ID later on
|
||||
*/
|
||||
if (!pskb_may_pull(skb, 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;
|
||||
}
|
||||
|
||||
/* 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
|
||||
*/
|
||||
skb_push(skb, 2);
|
||||
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;
|
||||
|
||||
ovpn_recv(peer, skb);
|
||||
return;
|
||||
err:
|
||||
dev_dstats_rx_dropped(peer->ovpn->dev);
|
||||
kfree_skb(skb);
|
||||
ovpn_peer_del(peer, OVPN_DEL_PEER_REASON_TRANSPORT_ERROR);
|
||||
}
|
||||
|
||||
static int ovpn_tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
|
||||
int flags, int *addr_len)
|
||||
{
|
||||
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 socket *sock = ovpn_sock->sock;
|
||||
|
||||
strp_stop(&peer->tcp.strp);
|
||||
skb_queue_purge(&peer->tcp.user_queue);
|
||||
|
||||
/* restore CBs that were saved in ovpn_sock_set_tcp_cb() */
|
||||
sock->sk->sk_data_ready = peer->tcp.sk_cb.sk_data_ready;
|
||||
sock->sk->sk_write_space = peer->tcp.sk_cb.sk_write_space;
|
||||
sock->sk->sk_prot = peer->tcp.sk_cb.prot;
|
||||
sock->sk->sk_socket->ops = peer->tcp.sk_cb.ops;
|
||||
|
||||
rcu_assign_sk_user_data(sock->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);
|
||||
schedule_work(&peer->tcp.defer_del_work);
|
||||
|
||||
/* 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
|
||||
* schedule_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) {
|
||||
preempt_disable();
|
||||
dev_dstats_tx_add(peer->ovpn->dev, skb->len);
|
||||
preempt_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->sock->sk);
|
||||
if (sock->peer)
|
||||
ovpn_tcp_send_sock(sock->peer, sock->sock->sk);
|
||||
release_sock(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) {
|
||||
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 socket *sock,
|
||||
struct sk_buff *skb)
|
||||
{
|
||||
u16 len = skb->len;
|
||||
|
||||
*(__be16 *)__skb_push(skb, sizeof(u16)) = htons(len);
|
||||
|
||||
spin_lock_nested(&sock->sk->sk_lock.slock, OVPN_TCP_DEPTH_NESTING);
|
||||
if (sock_owned_by_user(sock->sk)) {
|
||||
if (skb_queue_len(&peer->tcp.out_queue) >=
|
||||
READ_ONCE(net_hotdata.max_backlog)) {
|
||||
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, sock->sk, skb);
|
||||
}
|
||||
unlock:
|
||||
spin_unlock(&sock->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)) {
|
||||
schedule_work(&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 socket *sock = ovpn_sock->sock;
|
||||
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 (sock->sk->sk_user_data)
|
||||
return -EBUSY;
|
||||
|
||||
/* only a fully connected socket is expected. Connection should be
|
||||
* handled in userspace
|
||||
*/
|
||||
if (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),
|
||||
sock->sk->sk_state);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = strp_init(&peer->tcp.strp, sock->sk, &cb);
|
||||
if (ret < 0) {
|
||||
DEBUG_NET_WARN_ON_ONCE(1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
INIT_WORK(&peer->tcp.defer_del_work, ovpn_tcp_peer_del_work);
|
||||
|
||||
__sk_dst_reset(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 = sock->sk->sk_data_ready;
|
||||
peer->tcp.sk_cb.sk_write_space = sock->sk->sk_write_space;
|
||||
peer->tcp.sk_cb.prot = sock->sk->sk_prot;
|
||||
peer->tcp.sk_cb.ops = sock->sk->sk_socket->ops;
|
||||
|
||||
/* assign our static CBs and prot/ops */
|
||||
sock->sk->sk_data_ready = ovpn_tcp_data_ready;
|
||||
sock->sk->sk_write_space = ovpn_tcp_write_space;
|
||||
|
||||
if (sock->sk->sk_family == AF_INET) {
|
||||
sock->sk->sk_prot = &ovpn_tcp_prot;
|
||||
sock->sk->sk_socket->ops = &ovpn_tcp_ops;
|
||||
} else {
|
||||
sock->sk->sk_prot = &ovpn_tcp6_prot;
|
||||
sock->sk->sk_socket->ops = &ovpn_tcp6_ops;
|
||||
}
|
||||
|
||||
/* avoid using task_frag */
|
||||
sock->sk->sk_allocation = GFP_ATOMIC;
|
||||
sock->sk->sk_use_task_frag = false;
|
||||
|
||||
/* enqueue the RX worker */
|
||||
strp_check_rcv(&peer->tcp.strp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 || !sock->peer || !ovpn_peer_hold(sock->peer)) {
|
||||
rcu_read_unlock();
|
||||
return;
|
||||
}
|
||||
peer = sock->peer;
|
||||
rcu_read_unlock();
|
||||
|
||||
ovpn_peer_del(sock->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)
|
||||
{
|
||||
__poll_t mask = datagram_poll(file, sock, wait);
|
||||
struct ovpn_socket *ovpn_sock;
|
||||
|
||||
rcu_read_lock();
|
||||
ovpn_sock = rcu_dereference_sk_user_data(sock->sk);
|
||||
if (ovpn_sock && ovpn_sock->peer &&
|
||||
!skb_queue_empty(&ovpn_sock->peer->tcp.user_queue))
|
||||
mask |= EPOLLIN | EPOLLRDNORM;
|
||||
rcu_read_unlock();
|
||||
|
||||
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
|
||||
}
|
||||
36
drivers/net/ovpn/tcp.h
Normal file
36
drivers/net/ovpn/tcp.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/* 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 socket *sock, struct sk_buff *skb);
|
||||
void ovpn_tcp_tx_work(struct work_struct *work);
|
||||
|
||||
#endif /* _NET_OVPN_TCP_H_ */
|
||||
439
drivers/net/ovpn/udp.c
Normal file
439
drivers/net/ovpn/udp.c
Normal file
@@ -0,0 +1,439 @@
|
||||
// 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/ipv6_stubs.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->sock || sk != ovpn_sock->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:
|
||||
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);
|
||||
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 = ipv6_stub->ipv6_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:
|
||||
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));
|
||||
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
|
||||
* @sock: the RCU protected peer socket
|
||||
* @skb: the packet to send
|
||||
*/
|
||||
void ovpn_udp_send_skb(struct ovpn_peer *peer, struct socket *sock,
|
||||
struct sk_buff *skb)
|
||||
{
|
||||
int ret = -1;
|
||||
|
||||
skb->dev = peer->ovpn->dev;
|
||||
/* no checksum performed at this layer */
|
||||
skb->ip_summed = CHECKSUM_NONE;
|
||||
|
||||
/* get socket info */
|
||||
if (unlikely(!sock)) {
|
||||
net_warn_ratelimited("%s: no sock for remote peer %u\n",
|
||||
netdev_name(peer->ovpn->dev), peer->id);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* crypto layer -> transport (UDP) */
|
||||
ret = ovpn_udp_output(peer, &peer->dst_cache, sock->sk, skb);
|
||||
out:
|
||||
if (unlikely(ret < 0)) {
|
||||
kfree_skb(skb);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
* @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 ovpn_priv *ovpn)
|
||||
{
|
||||
struct udp_tunnel_sock_cfg cfg = {
|
||||
.encap_type = UDP_ENCAP_OVPNINUDP,
|
||||
.encap_rcv = ovpn_udp_encap_recv,
|
||||
.encap_destroy = ovpn_udp_encap_destroy,
|
||||
};
|
||||
struct socket *sock = ovpn_sock->sock;
|
||||
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(sock->sk);
|
||||
if (!old_data) {
|
||||
/* socket is currently unused - we can take it */
|
||||
rcu_read_unlock();
|
||||
setup_udp_tunnel_sock(sock_net(sock->sk), sock, &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(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 udp_tunnel_sock_cfg cfg = { };
|
||||
|
||||
setup_udp_tunnel_sock(sock_net(ovpn_sock->sock->sk), ovpn_sock->sock,
|
||||
&cfg);
|
||||
}
|
||||
25
drivers/net/ovpn/udp.h
Normal file
25
drivers/net/ovpn/udp.h
Normal 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 ovpn_priv *ovpn);
|
||||
void ovpn_udp_socket_detach(struct ovpn_socket *ovpn_sock);
|
||||
|
||||
void ovpn_udp_send_skb(struct ovpn_peer *peer, struct socket *sock,
|
||||
struct sk_buff *skb);
|
||||
|
||||
#endif /* _NET_OVPN_UDP_H_ */
|
||||
@@ -4145,6 +4145,8 @@ int skb_splice_bits(struct sk_buff *skb, struct sock *sk, unsigned int offset,
|
||||
unsigned int flags);
|
||||
int skb_send_sock_locked(struct sock *sk, struct sk_buff *skb, int offset,
|
||||
int len);
|
||||
int skb_send_sock_locked_with_flags(struct sock *sk, struct sk_buff *skb,
|
||||
int offset, int len, int flags);
|
||||
int skb_send_sock(struct sock *sk, struct sk_buff *skb, int offset, int len);
|
||||
void skb_copy_and_csum_dev(const struct sk_buff *skb, u8 *to);
|
||||
unsigned int skb_zerocopy_headlen(const struct sk_buff *from);
|
||||
|
||||
@@ -1986,4 +1986,19 @@ enum {
|
||||
|
||||
#define IFLA_DSA_MAX (__IFLA_DSA_MAX - 1)
|
||||
|
||||
/* OVPN section */
|
||||
|
||||
enum ovpn_mode {
|
||||
OVPN_MODE_P2P,
|
||||
OVPN_MODE_MP,
|
||||
};
|
||||
|
||||
enum {
|
||||
IFLA_OVPN_UNSPEC,
|
||||
IFLA_OVPN_MODE,
|
||||
__IFLA_OVPN_MAX,
|
||||
};
|
||||
|
||||
#define IFLA_OVPN_MAX (__IFLA_OVPN_MAX - 1)
|
||||
|
||||
#endif /* _UAPI_LINUX_IF_LINK_H */
|
||||
|
||||
109
include/uapi/linux/ovpn.h
Normal file
109
include/uapi/linux/ovpn.h
Normal file
@@ -0,0 +1,109 @@
|
||||
/* 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 uapi header */
|
||||
|
||||
#ifndef _UAPI_LINUX_OVPN_H
|
||||
#define _UAPI_LINUX_OVPN_H
|
||||
|
||||
#define OVPN_FAMILY_NAME "ovpn"
|
||||
#define OVPN_FAMILY_VERSION 1
|
||||
|
||||
#define OVPN_NONCE_TAIL_SIZE 8
|
||||
|
||||
enum ovpn_cipher_alg {
|
||||
OVPN_CIPHER_ALG_NONE,
|
||||
OVPN_CIPHER_ALG_AES_GCM,
|
||||
OVPN_CIPHER_ALG_CHACHA20_POLY1305,
|
||||
};
|
||||
|
||||
enum ovpn_del_peer_reason {
|
||||
OVPN_DEL_PEER_REASON_TEARDOWN,
|
||||
OVPN_DEL_PEER_REASON_USERSPACE,
|
||||
OVPN_DEL_PEER_REASON_EXPIRED,
|
||||
OVPN_DEL_PEER_REASON_TRANSPORT_ERROR,
|
||||
OVPN_DEL_PEER_REASON_TRANSPORT_DISCONNECT,
|
||||
};
|
||||
|
||||
enum ovpn_key_slot {
|
||||
OVPN_KEY_SLOT_PRIMARY,
|
||||
OVPN_KEY_SLOT_SECONDARY,
|
||||
};
|
||||
|
||||
enum {
|
||||
OVPN_A_PEER_ID = 1,
|
||||
OVPN_A_PEER_REMOTE_IPV4,
|
||||
OVPN_A_PEER_REMOTE_IPV6,
|
||||
OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID,
|
||||
OVPN_A_PEER_REMOTE_PORT,
|
||||
OVPN_A_PEER_SOCKET,
|
||||
OVPN_A_PEER_SOCKET_NETNSID,
|
||||
OVPN_A_PEER_VPN_IPV4,
|
||||
OVPN_A_PEER_VPN_IPV6,
|
||||
OVPN_A_PEER_LOCAL_IPV4,
|
||||
OVPN_A_PEER_LOCAL_IPV6,
|
||||
OVPN_A_PEER_LOCAL_PORT,
|
||||
OVPN_A_PEER_KEEPALIVE_INTERVAL,
|
||||
OVPN_A_PEER_KEEPALIVE_TIMEOUT,
|
||||
OVPN_A_PEER_DEL_REASON,
|
||||
OVPN_A_PEER_VPN_RX_BYTES,
|
||||
OVPN_A_PEER_VPN_TX_BYTES,
|
||||
OVPN_A_PEER_VPN_RX_PACKETS,
|
||||
OVPN_A_PEER_VPN_TX_PACKETS,
|
||||
OVPN_A_PEER_LINK_RX_BYTES,
|
||||
OVPN_A_PEER_LINK_TX_BYTES,
|
||||
OVPN_A_PEER_LINK_RX_PACKETS,
|
||||
OVPN_A_PEER_LINK_TX_PACKETS,
|
||||
|
||||
__OVPN_A_PEER_MAX,
|
||||
OVPN_A_PEER_MAX = (__OVPN_A_PEER_MAX - 1)
|
||||
};
|
||||
|
||||
enum {
|
||||
OVPN_A_KEYCONF_PEER_ID = 1,
|
||||
OVPN_A_KEYCONF_SLOT,
|
||||
OVPN_A_KEYCONF_KEY_ID,
|
||||
OVPN_A_KEYCONF_CIPHER_ALG,
|
||||
OVPN_A_KEYCONF_ENCRYPT_DIR,
|
||||
OVPN_A_KEYCONF_DECRYPT_DIR,
|
||||
|
||||
__OVPN_A_KEYCONF_MAX,
|
||||
OVPN_A_KEYCONF_MAX = (__OVPN_A_KEYCONF_MAX - 1)
|
||||
};
|
||||
|
||||
enum {
|
||||
OVPN_A_KEYDIR_CIPHER_KEY = 1,
|
||||
OVPN_A_KEYDIR_NONCE_TAIL,
|
||||
|
||||
__OVPN_A_KEYDIR_MAX,
|
||||
OVPN_A_KEYDIR_MAX = (__OVPN_A_KEYDIR_MAX - 1)
|
||||
};
|
||||
|
||||
enum {
|
||||
OVPN_A_IFINDEX = 1,
|
||||
OVPN_A_PEER,
|
||||
OVPN_A_KEYCONF,
|
||||
|
||||
__OVPN_A_MAX,
|
||||
OVPN_A_MAX = (__OVPN_A_MAX - 1)
|
||||
};
|
||||
|
||||
enum {
|
||||
OVPN_CMD_PEER_NEW = 1,
|
||||
OVPN_CMD_PEER_SET,
|
||||
OVPN_CMD_PEER_GET,
|
||||
OVPN_CMD_PEER_DEL,
|
||||
OVPN_CMD_PEER_DEL_NTF,
|
||||
OVPN_CMD_KEY_NEW,
|
||||
OVPN_CMD_KEY_GET,
|
||||
OVPN_CMD_KEY_SWAP,
|
||||
OVPN_CMD_KEY_SWAP_NTF,
|
||||
OVPN_CMD_KEY_DEL,
|
||||
|
||||
__OVPN_CMD_MAX,
|
||||
OVPN_CMD_MAX = (__OVPN_CMD_MAX - 1)
|
||||
};
|
||||
|
||||
#define OVPN_MCGRP_PEERS "peers"
|
||||
|
||||
#endif /* _UAPI_LINUX_OVPN_H */
|
||||
@@ -43,5 +43,6 @@ struct udphdr {
|
||||
#define UDP_ENCAP_GTP1U 5 /* 3GPP TS 29.060 */
|
||||
#define UDP_ENCAP_RXRPC 6
|
||||
#define TCP_ENCAP_ESPINTCP 7 /* Yikes, this is really xfrm encap types. */
|
||||
#define UDP_ENCAP_OVPNINUDP 8 /* OpenVPN traffic */
|
||||
|
||||
#endif /* _UAPI_LINUX_UDP_H */
|
||||
|
||||
@@ -3227,7 +3227,7 @@ static int sendmsg_unlocked(struct sock *sk, struct msghdr *msg)
|
||||
|
||||
typedef int (*sendmsg_func)(struct sock *sk, struct msghdr *msg);
|
||||
static int __skb_send_sock(struct sock *sk, struct sk_buff *skb, int offset,
|
||||
int len, sendmsg_func sendmsg)
|
||||
int len, sendmsg_func sendmsg, int flags)
|
||||
{
|
||||
unsigned int orig_len = len;
|
||||
struct sk_buff *head = skb;
|
||||
@@ -3245,7 +3245,7 @@ static int __skb_send_sock(struct sock *sk, struct sk_buff *skb, int offset,
|
||||
kv.iov_base = skb->data + offset;
|
||||
kv.iov_len = slen;
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.msg_flags = MSG_DONTWAIT;
|
||||
msg.msg_flags = MSG_DONTWAIT | flags;
|
||||
|
||||
iov_iter_kvec(&msg.msg_iter, ITER_SOURCE, &kv, 1, slen);
|
||||
ret = INDIRECT_CALL_2(sendmsg, sendmsg_locked,
|
||||
@@ -3282,7 +3282,8 @@ static int __skb_send_sock(struct sock *sk, struct sk_buff *skb, int offset,
|
||||
while (slen) {
|
||||
struct bio_vec bvec;
|
||||
struct msghdr msg = {
|
||||
.msg_flags = MSG_SPLICE_PAGES | MSG_DONTWAIT,
|
||||
.msg_flags = MSG_SPLICE_PAGES | MSG_DONTWAIT |
|
||||
flags,
|
||||
};
|
||||
|
||||
bvec_set_page(&bvec, skb_frag_page(frag), slen,
|
||||
@@ -3328,14 +3329,21 @@ static int __skb_send_sock(struct sock *sk, struct sk_buff *skb, int offset,
|
||||
int skb_send_sock_locked(struct sock *sk, struct sk_buff *skb, int offset,
|
||||
int len)
|
||||
{
|
||||
return __skb_send_sock(sk, skb, offset, len, sendmsg_locked);
|
||||
return __skb_send_sock(sk, skb, offset, len, sendmsg_locked, 0);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(skb_send_sock_locked);
|
||||
|
||||
int skb_send_sock_locked_with_flags(struct sock *sk, struct sk_buff *skb,
|
||||
int offset, int len, int flags)
|
||||
{
|
||||
return __skb_send_sock(sk, skb, offset, len, sendmsg_locked, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(skb_send_sock_locked_with_flags);
|
||||
|
||||
/* Send skb data on a socket. Socket must be unlocked. */
|
||||
int skb_send_sock(struct sock *sk, struct sk_buff *skb, int offset, int len)
|
||||
{
|
||||
return __skb_send_sock(sk, skb, offset, len, sendmsg_unlocked);
|
||||
return __skb_send_sock(sk, skb, offset, len, sendmsg_unlocked, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -715,6 +715,7 @@ const struct proto_ops inet6_stream_ops = {
|
||||
#endif
|
||||
.set_rcvlowat = tcp_set_rcvlowat,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(inet6_stream_ops);
|
||||
|
||||
const struct proto_ops inet6_dgram_ops = {
|
||||
.family = PF_INET6,
|
||||
|
||||
@@ -71,6 +71,7 @@ TARGETS += net/hsr
|
||||
TARGETS += net/mptcp
|
||||
TARGETS += net/netfilter
|
||||
TARGETS += net/openvswitch
|
||||
TARGETS += net/ovpn
|
||||
TARGETS += net/packetdrill
|
||||
TARGETS += net/rds
|
||||
TARGETS += net/tcp_ao
|
||||
|
||||
2
tools/testing/selftests/net/ovpn/.gitignore
vendored
Normal file
2
tools/testing/selftests/net/ovpn/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
ovpn-cli
|
||||
31
tools/testing/selftests/net/ovpn/Makefile
Normal file
31
tools/testing/selftests/net/ovpn/Makefile
Normal file
@@ -0,0 +1,31 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (C) 2020-2025 OpenVPN, Inc.
|
||||
#
|
||||
CFLAGS = -pedantic -Wextra -Wall -Wl,--no-as-needed -g -O0 -ggdb $(KHDR_INCLUDES)
|
||||
VAR_CFLAGS = $(shell pkg-config --cflags libnl-3.0 libnl-genl-3.0 2>/dev/null)
|
||||
ifeq ($(VAR_CFLAGS),)
|
||||
VAR_CFLAGS = -I/usr/include/libnl3
|
||||
endif
|
||||
CFLAGS += $(VAR_CFLAGS)
|
||||
|
||||
|
||||
LDLIBS = -lmbedtls -lmbedcrypto
|
||||
VAR_LDLIBS = $(shell pkg-config --libs libnl-3.0 libnl-genl-3.0 2>/dev/null)
|
||||
ifeq ($(VAR_LDLIBS),)
|
||||
VAR_LDLIBS = -lnl-genl-3 -lnl-3
|
||||
endif
|
||||
LDLIBS += $(VAR_LDLIBS)
|
||||
|
||||
|
||||
TEST_FILES = common.sh
|
||||
|
||||
TEST_PROGS = test.sh \
|
||||
test-chachapoly.sh \
|
||||
test-tcp.sh \
|
||||
test-float.sh \
|
||||
test-close-socket.sh \
|
||||
test-close-socket-tcp.sh
|
||||
|
||||
TEST_GEN_FILES := ovpn-cli
|
||||
|
||||
include ../../lib.mk
|
||||
92
tools/testing/selftests/net/ovpn/common.sh
Normal file
92
tools/testing/selftests/net/ovpn/common.sh
Normal file
@@ -0,0 +1,92 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (C) 2020-2025 OpenVPN, Inc.
|
||||
#
|
||||
# Author: Antonio Quartulli <antonio@openvpn.net>
|
||||
|
||||
UDP_PEERS_FILE=${UDP_PEERS_FILE:-udp_peers.txt}
|
||||
TCP_PEERS_FILE=${TCP_PEERS_FILE:-tcp_peers.txt}
|
||||
OVPN_CLI=${OVPN_CLI:-./ovpn-cli}
|
||||
ALG=${ALG:-aes}
|
||||
PROTO=${PROTO:-UDP}
|
||||
FLOAT=${FLOAT:-0}
|
||||
|
||||
create_ns() {
|
||||
ip netns add peer${1}
|
||||
}
|
||||
|
||||
setup_ns() {
|
||||
MODE="P2P"
|
||||
|
||||
if [ ${1} -eq 0 ]; then
|
||||
MODE="MP"
|
||||
for p in $(seq 1 ${NUM_PEERS}); do
|
||||
ip link add veth${p} netns peer0 type veth peer name veth${p} netns peer${p}
|
||||
|
||||
ip -n peer0 addr add 10.10.${p}.1/24 dev veth${p}
|
||||
ip -n peer0 link set veth${p} up
|
||||
|
||||
ip -n peer${p} addr add 10.10.${p}.2/24 dev veth${p}
|
||||
ip -n peer${p} link set veth${p} up
|
||||
done
|
||||
fi
|
||||
|
||||
ip netns exec peer${1} ${OVPN_CLI} new_iface tun${1} $MODE
|
||||
ip -n peer${1} addr add ${2} dev tun${1}
|
||||
ip -n peer${1} link set tun${1} up
|
||||
}
|
||||
|
||||
add_peer() {
|
||||
if [ "${PROTO}" == "UDP" ]; then
|
||||
if [ ${1} -eq 0 ]; then
|
||||
ip netns exec peer0 ${OVPN_CLI} new_multi_peer tun0 1 ${UDP_PEERS_FILE}
|
||||
|
||||
for p in $(seq 1 ${NUM_PEERS}); do
|
||||
ip netns exec peer0 ${OVPN_CLI} new_key tun0 ${p} 1 0 ${ALG} 0 \
|
||||
data64.key
|
||||
done
|
||||
else
|
||||
ip netns exec peer${1} ${OVPN_CLI} new_peer tun${1} ${1} 1 10.10.${1}.1 1
|
||||
ip netns exec peer${1} ${OVPN_CLI} new_key tun${1} ${1} 1 0 ${ALG} 1 \
|
||||
data64.key
|
||||
fi
|
||||
else
|
||||
if [ ${1} -eq 0 ]; then
|
||||
(ip netns exec peer0 ${OVPN_CLI} listen tun0 1 ${TCP_PEERS_FILE} && {
|
||||
for p in $(seq 1 ${NUM_PEERS}); do
|
||||
ip netns exec peer0 ${OVPN_CLI} new_key tun0 ${p} 1 0 \
|
||||
${ALG} 0 data64.key
|
||||
done
|
||||
}) &
|
||||
sleep 5
|
||||
else
|
||||
ip netns exec peer${1} ${OVPN_CLI} connect tun${1} ${1} 10.10.${1}.1 1 \
|
||||
data64.key
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
# some ovpn-cli processes sleep in background so they need manual poking
|
||||
killall $(basename ${OVPN_CLI}) 2>/dev/null || true
|
||||
|
||||
# netns peer0 is deleted without erasing ifaces first
|
||||
for p in $(seq 1 10); do
|
||||
ip -n peer${p} link set tun${p} down 2>/dev/null || true
|
||||
ip netns exec peer${p} ${OVPN_CLI} del_iface tun${p} 2>/dev/null || true
|
||||
done
|
||||
for p in $(seq 1 10); do
|
||||
ip -n peer0 link del veth${p} 2>/dev/null || true
|
||||
done
|
||||
for p in $(seq 0 10); do
|
||||
ip netns del peer${p} 2>/dev/null || true
|
||||
done
|
||||
}
|
||||
|
||||
if [ "${PROTO}" == "UDP" ]; then
|
||||
NUM_PEERS=${NUM_PEERS:-$(wc -l ${UDP_PEERS_FILE} | awk '{print $1}')}
|
||||
else
|
||||
NUM_PEERS=${NUM_PEERS:-$(wc -l ${TCP_PEERS_FILE} | awk '{print $1}')}
|
||||
fi
|
||||
|
||||
|
||||
10
tools/testing/selftests/net/ovpn/config
Normal file
10
tools/testing/selftests/net/ovpn/config
Normal file
@@ -0,0 +1,10 @@
|
||||
CONFIG_NET=y
|
||||
CONFIG_INET=y
|
||||
CONFIG_STREAM_PARSER=y
|
||||
CONFIG_NET_UDP_TUNNEL=y
|
||||
CONFIG_DST_CACHE=y
|
||||
CONFIG_CRYPTO=y
|
||||
CONFIG_CRYPTO_AES=y
|
||||
CONFIG_CRYPTO_GCM=y
|
||||
CONFIG_CRYPTO_CHACHA20POLY1305=y
|
||||
CONFIG_OVPN=m
|
||||
5
tools/testing/selftests/net/ovpn/data64.key
Normal file
5
tools/testing/selftests/net/ovpn/data64.key
Normal file
@@ -0,0 +1,5 @@
|
||||
jRqMACN7d7/aFQNT8S7jkrBD8uwrgHbG5OQZP2eu4R1Y7tfpS2bf5RHv06Vi163CGoaIiTX99R3B
|
||||
ia9ycAH8Wz1+9PWv51dnBLur9jbShlgZ2QHLtUc4a/gfT7zZwULXuuxdLnvR21DDeMBaTbkgbai9
|
||||
uvAa7ne1liIgGFzbv+Bas4HDVrygxIxuAnP5Qgc3648IJkZ0QEXPF+O9f0n5+QIvGCxkAUVx+5K6
|
||||
KIs+SoeWXnAopELmoGSjUpFtJbagXK82HfdqpuUxT2Tnuef0/14SzVE/vNleBNu2ZbyrSAaah8tE
|
||||
BofkPJUBFY+YQcfZNM5Dgrw3i+Bpmpq/gpdg5w==
|
||||
2376
tools/testing/selftests/net/ovpn/ovpn-cli.c
Normal file
2376
tools/testing/selftests/net/ovpn/ovpn-cli.c
Normal file
File diff suppressed because it is too large
Load Diff
5
tools/testing/selftests/net/ovpn/tcp_peers.txt
Normal file
5
tools/testing/selftests/net/ovpn/tcp_peers.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
1 5.5.5.2
|
||||
2 5.5.5.3
|
||||
3 5.5.5.4
|
||||
4 5.5.5.5
|
||||
5 5.5.5.6
|
||||
9
tools/testing/selftests/net/ovpn/test-chachapoly.sh
Executable file
9
tools/testing/selftests/net/ovpn/test-chachapoly.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (C) 2025 OpenVPN, Inc.
|
||||
#
|
||||
# Author: Antonio Quartulli <antonio@openvpn.net>
|
||||
|
||||
ALG="chachapoly"
|
||||
|
||||
source test.sh
|
||||
9
tools/testing/selftests/net/ovpn/test-close-socket-tcp.sh
Executable file
9
tools/testing/selftests/net/ovpn/test-close-socket-tcp.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (C) 2025 OpenVPN, Inc.
|
||||
#
|
||||
# Author: Antonio Quartulli <antonio@openvpn.net>
|
||||
|
||||
PROTO="TCP"
|
||||
|
||||
source test-close-socket.sh
|
||||
45
tools/testing/selftests/net/ovpn/test-close-socket.sh
Executable file
45
tools/testing/selftests/net/ovpn/test-close-socket.sh
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (C) 2020-2025 OpenVPN, Inc.
|
||||
#
|
||||
# Author: Antonio Quartulli <antonio@openvpn.net>
|
||||
|
||||
#set -x
|
||||
set -e
|
||||
|
||||
source ./common.sh
|
||||
|
||||
cleanup
|
||||
|
||||
modprobe -q ovpn || true
|
||||
|
||||
for p in $(seq 0 ${NUM_PEERS}); do
|
||||
create_ns ${p}
|
||||
done
|
||||
|
||||
for p in $(seq 0 ${NUM_PEERS}); do
|
||||
setup_ns ${p} 5.5.5.$((${p} + 1))/24
|
||||
done
|
||||
|
||||
for p in $(seq 0 ${NUM_PEERS}); do
|
||||
add_peer ${p}
|
||||
done
|
||||
|
||||
for p in $(seq 1 ${NUM_PEERS}); do
|
||||
ip netns exec peer0 ${OVPN_CLI} set_peer tun0 ${p} 60 120
|
||||
ip netns exec peer${p} ${OVPN_CLI} set_peer tun${p} ${p} 60 120
|
||||
done
|
||||
|
||||
sleep 1
|
||||
|
||||
for p in $(seq 1 ${NUM_PEERS}); do
|
||||
ip netns exec peer0 ping -qfc 500 -w 3 5.5.5.$((${p} + 1))
|
||||
done
|
||||
|
||||
ip netns exec peer0 iperf3 -1 -s &
|
||||
sleep 1
|
||||
ip netns exec peer1 iperf3 -Z -t 3 -c 5.5.5.1
|
||||
|
||||
cleanup
|
||||
|
||||
modprobe -r ovpn || true
|
||||
9
tools/testing/selftests/net/ovpn/test-float.sh
Executable file
9
tools/testing/selftests/net/ovpn/test-float.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (C) 2025 OpenVPN, Inc.
|
||||
#
|
||||
# Author: Antonio Quartulli <antonio@openvpn.net>
|
||||
|
||||
FLOAT="1"
|
||||
|
||||
source test.sh
|
||||
9
tools/testing/selftests/net/ovpn/test-tcp.sh
Executable file
9
tools/testing/selftests/net/ovpn/test-tcp.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (C) 2025 OpenVPN, Inc.
|
||||
#
|
||||
# Author: Antonio Quartulli <antonio@openvpn.net>
|
||||
|
||||
PROTO="TCP"
|
||||
|
||||
source test.sh
|
||||
113
tools/testing/selftests/net/ovpn/test.sh
Executable file
113
tools/testing/selftests/net/ovpn/test.sh
Executable file
@@ -0,0 +1,113 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (C) 2020-2025 OpenVPN, Inc.
|
||||
#
|
||||
# Author: Antonio Quartulli <antonio@openvpn.net>
|
||||
|
||||
#set -x
|
||||
set -e
|
||||
|
||||
source ./common.sh
|
||||
|
||||
cleanup
|
||||
|
||||
modprobe -q ovpn || true
|
||||
|
||||
for p in $(seq 0 ${NUM_PEERS}); do
|
||||
create_ns ${p}
|
||||
done
|
||||
|
||||
for p in $(seq 0 ${NUM_PEERS}); do
|
||||
setup_ns ${p} 5.5.5.$((${p} + 1))/24
|
||||
done
|
||||
|
||||
for p in $(seq 0 ${NUM_PEERS}); do
|
||||
add_peer ${p}
|
||||
done
|
||||
|
||||
for p in $(seq 1 ${NUM_PEERS}); do
|
||||
ip netns exec peer0 ${OVPN_CLI} set_peer tun0 ${p} 60 120
|
||||
ip netns exec peer${p} ${OVPN_CLI} set_peer tun${p} ${p} 60 120
|
||||
done
|
||||
|
||||
sleep 1
|
||||
|
||||
for p in $(seq 1 ${NUM_PEERS}); do
|
||||
ip netns exec peer0 ping -qfc 500 -w 3 5.5.5.$((${p} + 1))
|
||||
done
|
||||
|
||||
if [ "$FLOAT" == "1" ]; then
|
||||
# make clients float..
|
||||
for p in $(seq 1 ${NUM_PEERS}); do
|
||||
ip -n peer${p} addr del 10.10.${p}.2/24 dev veth${p}
|
||||
ip -n peer${p} addr add 10.10.${p}.3/24 dev veth${p}
|
||||
done
|
||||
for p in $(seq 1 ${NUM_PEERS}); do
|
||||
ip netns exec peer${p} ping -qfc 500 -w 3 5.5.5.1
|
||||
done
|
||||
fi
|
||||
|
||||
ip netns exec peer0 iperf3 -1 -s &
|
||||
sleep 1
|
||||
ip netns exec peer1 iperf3 -Z -t 3 -c 5.5.5.1
|
||||
|
||||
echo "Adding secondary key and then swap:"
|
||||
for p in $(seq 1 ${NUM_PEERS}); do
|
||||
ip netns exec peer0 ${OVPN_CLI} new_key tun0 ${p} 2 1 ${ALG} 0 data64.key
|
||||
ip netns exec peer${p} ${OVPN_CLI} new_key tun${p} ${p} 2 1 ${ALG} 1 data64.key
|
||||
ip netns exec peer${p} ${OVPN_CLI} swap_keys tun${p} ${p}
|
||||
done
|
||||
|
||||
sleep 1
|
||||
|
||||
echo "Querying all peers:"
|
||||
ip netns exec peer0 ${OVPN_CLI} get_peer tun0
|
||||
ip netns exec peer1 ${OVPN_CLI} get_peer tun1
|
||||
|
||||
echo "Querying peer 1:"
|
||||
ip netns exec peer0 ${OVPN_CLI} get_peer tun0 1
|
||||
|
||||
echo "Querying non-existent peer 10:"
|
||||
ip netns exec peer0 ${OVPN_CLI} get_peer tun0 10 || true
|
||||
|
||||
echo "Deleting peer 1:"
|
||||
ip netns exec peer0 ${OVPN_CLI} del_peer tun0 1
|
||||
ip netns exec peer1 ${OVPN_CLI} del_peer tun1 1
|
||||
|
||||
echo "Querying keys:"
|
||||
for p in $(seq 2 ${NUM_PEERS}); do
|
||||
ip netns exec peer${p} ${OVPN_CLI} get_key tun${p} ${p} 1
|
||||
ip netns exec peer${p} ${OVPN_CLI} get_key tun${p} ${p} 2
|
||||
done
|
||||
|
||||
echo "Deleting peer while sending traffic:"
|
||||
(ip netns exec peer2 ping -qf -w 4 5.5.5.1)&
|
||||
sleep 2
|
||||
ip netns exec peer0 ${OVPN_CLI} del_peer tun0 2
|
||||
# following command fails in TCP mode
|
||||
# (both ends get conn reset when one peer disconnects)
|
||||
ip netns exec peer2 ${OVPN_CLI} del_peer tun2 2 || true
|
||||
|
||||
echo "Deleting keys:"
|
||||
for p in $(seq 3 ${NUM_PEERS}); do
|
||||
ip netns exec peer${p} ${OVPN_CLI} del_key tun${p} ${p} 1
|
||||
ip netns exec peer${p} ${OVPN_CLI} del_key tun${p} ${p} 2
|
||||
done
|
||||
|
||||
echo "Setting timeout to 3s MP:"
|
||||
for p in $(seq 3 ${NUM_PEERS}); do
|
||||
ip netns exec peer0 ${OVPN_CLI} set_peer tun0 ${p} 3 3 || true
|
||||
ip netns exec peer${p} ${OVPN_CLI} set_peer tun${p} ${p} 0 0
|
||||
done
|
||||
# wait for peers to timeout
|
||||
sleep 5
|
||||
|
||||
echo "Setting timeout to 3s P2P:"
|
||||
for p in $(seq 3 ${NUM_PEERS}); do
|
||||
ip netns exec peer${p} ${OVPN_CLI} set_peer tun${p} ${p} 3 3
|
||||
done
|
||||
sleep 5
|
||||
|
||||
cleanup
|
||||
|
||||
modprobe -r ovpn || true
|
||||
5
tools/testing/selftests/net/ovpn/udp_peers.txt
Normal file
5
tools/testing/selftests/net/ovpn/udp_peers.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
1 10.10.1.2 1 5.5.5.2
|
||||
2 10.10.2.2 1 5.5.5.3
|
||||
3 10.10.3.2 1 5.5.5.4
|
||||
4 10.10.4.2 1 5.5.5.5
|
||||
5 10.10.5.2 1 5.5.5.6
|
||||
Reference in New Issue
Block a user