Merge tag 'ovpn-net-next-20260317' of https://github.com/OpenVPN/ovpn-net-next

Antonio Quartulli says:

====================
Included features:
* use bitops.h API when possible
* send netlink notification in case of client float event
* implement support for asymmetric peer IDs
* consolidate memory allocations during crypto operations
* add netlink notification check in selftests
* add FW mark check in selftest

* tag 'ovpn-net-next-20260317' of https://github.com/OpenVPN/ovpn-net-next:
  ovpn: consolidate crypto allocations in one chunk
  selftests: ovpn: add test for the FW mark feature
  selftests: ovpn: check asymmetric peer-id
  ovpn: add support for asymmetric peer IDs
  selftests: ovpn: add notification parsing and matching
  ovpn: notify userspace on client float event
  ovpn: pktid: use bitops.h API
  ovpn: use correct array size to parse nested attributes in ovpn_nl_key_swap_doit
  selftests: ovpn: allow compiling ovpn-cli.c with mbedtls3
====================

Link: https://patch.msgid.link/20260317104023.192548-1-antonio@openvpn.net
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Paolo Abeni
2026-03-19 12:50:42 +01:00
53 changed files with 757 additions and 153 deletions
+22 -1
View File
@@ -43,7 +43,8 @@ attribute-sets:
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
identify peers during operations for a specific device.
Also used to match packets received from this peer.
checks:
max: 0xFFFFFF
-
@@ -160,6 +161,16 @@ attribute-sets:
name: link-tx-packets
type: uint
doc: Number of packets transmitted at the transport level
-
name: tx-id
type: u32
doc: >-
The ID value used when transmitting packets to this peer. This
way outgoing packets can have a different ID than incoming ones.
Useful in multipeer-to-multipeer connections, where each peer
will advertise the tx-id to be used on the link.
checks:
max: 0xFFFFFF
-
name: peer-new-input
subset-of: peer
@@ -188,6 +199,8 @@ attribute-sets:
name: keepalive-interval
-
name: keepalive-timeout
-
name: tx-id
-
name: peer-set-input
subset-of: peer
@@ -214,6 +227,8 @@ attribute-sets:
name: keepalive-interval
-
name: keepalive-timeout
-
name: tx-id
-
name: peer-del-input
subset-of: peer
@@ -502,6 +517,12 @@ operations:
- ifindex
- keyconf
-
name: peer-float-ntf
doc: Notification about a peer floating (changing its remote UDP endpoint)
notify: peer-get
mcgrp: peers
mcast-groups:
list:
-
+124 -38
View File
@@ -36,6 +36,104 @@ static int ovpn_aead_encap_overhead(const struct ovpn_crypto_key_slot *ks)
crypto_aead_authsize(ks->encrypt); /* Auth Tag */
}
/**
* ovpn_aead_crypto_tmp_size - compute the size of a temporary object containing
* an AEAD request structure with extra space for SG
* and IV.
* @tfm: the AEAD cipher handle
* @nfrags: the number of fragments in the skb
*
* This function calculates the size of a contiguous memory block that includes
* the initialization vector (IV), the AEAD request, and an array of scatterlist
* entries. For alignment considerations, the IV is placed first, followed by
* the request, and then the scatterlist.
* Additional alignment is applied according to the requirements of the
* underlying structures.
*
* Return: the size of the temporary memory that needs to be allocated
*/
static unsigned int ovpn_aead_crypto_tmp_size(struct crypto_aead *tfm,
const unsigned int nfrags)
{
unsigned int len = OVPN_NONCE_SIZE;
DEBUG_NET_WARN_ON_ONCE(crypto_aead_ivsize(tfm) != OVPN_NONCE_SIZE);
/* min size for a buffer of ivsize, aligned to alignmask */
len += crypto_aead_alignmask(tfm) & ~(crypto_tfm_ctx_alignment() - 1);
/* round up to the next multiple of the crypto ctx alignment */
len = ALIGN(len, crypto_tfm_ctx_alignment());
/* reserve space for the AEAD request */
len += sizeof(struct aead_request) + crypto_aead_reqsize(tfm);
/* round up to the next multiple of the scatterlist alignment */
len = ALIGN(len, __alignof__(struct scatterlist));
/* add enough space for nfrags + 2 scatterlist entries */
len += array_size(sizeof(struct scatterlist), nfrags + 2);
return len;
}
/**
* ovpn_aead_crypto_tmp_iv - retrieve the pointer to the IV within a temporary
* buffer allocated using ovpn_aead_crypto_tmp_size
* @aead: the AEAD cipher handle
* @tmp: a pointer to the beginning of the temporary buffer
*
* This function retrieves a pointer to the initialization vector (IV) in the
* temporary buffer. If the AEAD cipher specifies an IV size, the pointer is
* adjusted using the AEAD's alignment mask to ensure proper alignment.
*
* Returns: a pointer to the IV within the temporary buffer
*/
static u8 *ovpn_aead_crypto_tmp_iv(struct crypto_aead *aead, void *tmp)
{
return likely(crypto_aead_ivsize(aead)) ?
PTR_ALIGN((u8 *)tmp, crypto_aead_alignmask(aead) + 1) :
tmp;
}
/**
* ovpn_aead_crypto_tmp_req - retrieve the pointer to the AEAD request structure
* within a temporary buffer allocated using
* ovpn_aead_crypto_tmp_size
* @aead: the AEAD cipher handle
* @iv: a pointer to the initialization vector in the temporary buffer
*
* This function computes the location of the AEAD request structure that
* immediately follows the IV in the temporary buffer and it ensures the request
* is aligned to the crypto transform context alignment.
*
* Returns: a pointer to the AEAD request structure
*/
static struct aead_request *ovpn_aead_crypto_tmp_req(struct crypto_aead *aead,
const u8 *iv)
{
return (void *)PTR_ALIGN(iv + crypto_aead_ivsize(aead),
crypto_tfm_ctx_alignment());
}
/**
* ovpn_aead_crypto_req_sg - locate the scatterlist following the AEAD request
* within a temporary buffer allocated using
* ovpn_aead_crypto_tmp_size
* @aead: the AEAD cipher handle
* @req: a pointer to the AEAD request structure in the temporary buffer
*
* This function computes the starting address of the scatterlist that is
* allocated immediately after the AEAD request structure. It aligns the pointer
* based on the alignment requirements of the scatterlist structure.
*
* Returns: a pointer to the scatterlist
*/
static struct scatterlist *ovpn_aead_crypto_req_sg(struct crypto_aead *aead,
struct aead_request *req)
{
return (void *)ALIGN((unsigned long)(req + 1) +
crypto_aead_reqsize(aead),
__alignof__(struct scatterlist));
}
int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
struct sk_buff *skb)
{
@@ -45,6 +143,7 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
struct scatterlist *sg;
int nfrags, ret;
u32 pktid, op;
void *tmp;
u8 *iv;
ovpn_skb_cb(skb)->peer = peer;
@@ -71,13 +170,17 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
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))
/* allocate temporary memory for iv, sg and req */
tmp = kmalloc(ovpn_aead_crypto_tmp_size(ks->encrypt, nfrags),
GFP_ATOMIC);
if (unlikely(!tmp))
return -ENOMEM;
sg = ovpn_skb_cb(skb)->sg;
ovpn_skb_cb(skb)->crypto_tmp = tmp;
iv = ovpn_aead_crypto_tmp_iv(ks->encrypt, tmp);
req = ovpn_aead_crypto_tmp_req(ks->encrypt, iv);
sg = ovpn_aead_crypto_req_sg(ks->encrypt, req);
/* sg table:
* 0: op, wire nonce (AD, len=OVPN_OP_SIZE_V2+OVPN_NONCE_WIRE_SIZE),
@@ -105,13 +208,6 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
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
*/
@@ -122,7 +218,7 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
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);
op = ovpn_opcode_compose(OVPN_DATA_V2, ks->key_id, peer->tx_id);
__skb_push(skb, OVPN_OPCODE_SIZE);
BUILD_BUG_ON(sizeof(op) != OVPN_OPCODE_SIZE);
*((__force __be32 *)skb->data) = htonl(op);
@@ -130,12 +226,6 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
/* 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);
@@ -156,6 +246,7 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
struct aead_request *req;
struct sk_buff *trailer;
struct scatterlist *sg;
void *tmp;
u8 *iv;
payload_offset = OVPN_AAD_SIZE + tag_size;
@@ -184,13 +275,17 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
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))
/* allocate temporary memory for iv, sg and req */
tmp = kmalloc(ovpn_aead_crypto_tmp_size(ks->decrypt, nfrags),
GFP_ATOMIC);
if (unlikely(!tmp))
return -ENOMEM;
sg = ovpn_skb_cb(skb)->sg;
ovpn_skb_cb(skb)->crypto_tmp = tmp;
iv = ovpn_aead_crypto_tmp_iv(ks->decrypt, tmp);
req = ovpn_aead_crypto_tmp_req(ks->decrypt, iv);
sg = ovpn_aead_crypto_req_sg(ks->decrypt, req);
/* sg table:
* 0: op, wire nonce (AD, len=OVPN_OPCODE_SIZE+OVPN_NONCE_WIRE_SIZE),
@@ -213,24 +308,11 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks,
/* append auth_tag onto scatterlist */
sg_set_buf(sg + ret + 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);
@@ -273,7 +355,11 @@ static struct crypto_aead *ovpn_aead_init(const char *title,
goto error;
}
/* basic AEAD assumption */
/* basic AEAD assumption
* all current algorithms use OVPN_NONCE_SIZE.
* ovpn_aead_crypto_tmp_size and ovpn_aead_encrypt/decrypt
* expect this.
*/
if (crypto_aead_ivsize(aead) != OVPN_NONCE_SIZE) {
pr_err("%s IV size must be %d\n", title, OVPN_NONCE_SIZE);
ret = -EINVAL;
+2 -6
View File
@@ -119,9 +119,7 @@ void ovpn_decrypt_post(void *data, int ret)
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);
kfree(ovpn_skb_cb(skb)->crypto_tmp);
if (unlikely(ret < 0))
goto drop;
@@ -248,9 +246,7 @@ void ovpn_encrypt_post(void *data, int ret)
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);
kfree(ovpn_skb_cb(skb)->crypto_tmp);
if (unlikely(ret == -ERANGE)) {
/* we ran out of IVs and we must kill the key as it can't be
+10 -3
View File
@@ -16,6 +16,10 @@ static const struct netlink_range_validation ovpn_a_peer_id_range = {
.max = 16777215ULL,
};
static const struct netlink_range_validation ovpn_a_peer_tx_id_range = {
.max = 16777215ULL,
};
static const struct netlink_range_validation ovpn_a_keyconf_peer_id_range = {
.max = 16777215ULL,
};
@@ -51,7 +55,7 @@ const struct nla_policy ovpn_keydir_nl_policy[OVPN_A_KEYDIR_NONCE_TAIL + 1] = {
[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] = {
const struct nla_policy ovpn_peer_nl_policy[OVPN_A_PEER_TX_ID + 1] = {
[OVPN_A_PEER_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_id_range),
[OVPN_A_PEER_REMOTE_IPV4] = { .type = NLA_BE32, },
[OVPN_A_PEER_REMOTE_IPV6] = NLA_POLICY_EXACT_LEN(16),
@@ -75,13 +79,14 @@ const struct nla_policy ovpn_peer_nl_policy[OVPN_A_PEER_LINK_TX_PACKETS + 1] = {
[OVPN_A_PEER_LINK_TX_BYTES] = { .type = NLA_UINT, },
[OVPN_A_PEER_LINK_RX_PACKETS] = { .type = NLA_UINT, },
[OVPN_A_PEER_LINK_TX_PACKETS] = { .type = NLA_UINT, },
[OVPN_A_PEER_TX_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_tx_id_range),
};
const struct nla_policy ovpn_peer_del_input_nl_policy[OVPN_A_PEER_ID + 1] = {
[OVPN_A_PEER_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_id_range),
};
const struct nla_policy ovpn_peer_new_input_nl_policy[OVPN_A_PEER_KEEPALIVE_TIMEOUT + 1] = {
const struct nla_policy ovpn_peer_new_input_nl_policy[OVPN_A_PEER_TX_ID + 1] = {
[OVPN_A_PEER_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_id_range),
[OVPN_A_PEER_REMOTE_IPV4] = { .type = NLA_BE32, },
[OVPN_A_PEER_REMOTE_IPV6] = NLA_POLICY_EXACT_LEN(16),
@@ -94,9 +99,10 @@ const struct nla_policy ovpn_peer_new_input_nl_policy[OVPN_A_PEER_KEEPALIVE_TIME
[OVPN_A_PEER_LOCAL_IPV6] = NLA_POLICY_EXACT_LEN(16),
[OVPN_A_PEER_KEEPALIVE_INTERVAL] = { .type = NLA_U32, },
[OVPN_A_PEER_KEEPALIVE_TIMEOUT] = { .type = NLA_U32, },
[OVPN_A_PEER_TX_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_tx_id_range),
};
const struct nla_policy ovpn_peer_set_input_nl_policy[OVPN_A_PEER_KEEPALIVE_TIMEOUT + 1] = {
const struct nla_policy ovpn_peer_set_input_nl_policy[OVPN_A_PEER_TX_ID + 1] = {
[OVPN_A_PEER_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_id_range),
[OVPN_A_PEER_REMOTE_IPV4] = { .type = NLA_BE32, },
[OVPN_A_PEER_REMOTE_IPV6] = NLA_POLICY_EXACT_LEN(16),
@@ -108,6 +114,7 @@ const struct nla_policy ovpn_peer_set_input_nl_policy[OVPN_A_PEER_KEEPALIVE_TIME
[OVPN_A_PEER_LOCAL_IPV6] = NLA_POLICY_EXACT_LEN(16),
[OVPN_A_PEER_KEEPALIVE_INTERVAL] = { .type = NLA_U32, },
[OVPN_A_PEER_KEEPALIVE_TIMEOUT] = { .type = NLA_U32, },
[OVPN_A_PEER_TX_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_tx_id_range),
};
/* OVPN_CMD_PEER_NEW - do */
+3 -3
View File
@@ -18,10 +18,10 @@ extern const struct nla_policy ovpn_keyconf_del_input_nl_policy[OVPN_A_KEYCONF_S
extern const struct nla_policy ovpn_keyconf_get_nl_policy[OVPN_A_KEYCONF_CIPHER_ALG + 1];
extern const struct nla_policy ovpn_keyconf_swap_input_nl_policy[OVPN_A_KEYCONF_PEER_ID + 1];
extern const struct nla_policy ovpn_keydir_nl_policy[OVPN_A_KEYDIR_NONCE_TAIL + 1];
extern const struct nla_policy ovpn_peer_nl_policy[OVPN_A_PEER_LINK_TX_PACKETS + 1];
extern const struct nla_policy ovpn_peer_nl_policy[OVPN_A_PEER_TX_ID + 1];
extern const struct nla_policy ovpn_peer_del_input_nl_policy[OVPN_A_PEER_ID + 1];
extern const struct nla_policy ovpn_peer_new_input_nl_policy[OVPN_A_PEER_KEEPALIVE_TIMEOUT + 1];
extern const struct nla_policy ovpn_peer_set_input_nl_policy[OVPN_A_PEER_KEEPALIVE_TIMEOUT + 1];
extern const struct nla_policy ovpn_peer_new_input_nl_policy[OVPN_A_PEER_TX_ID + 1];
extern const struct nla_policy ovpn_peer_set_input_nl_policy[OVPN_A_PEER_TX_ID + 1];
int ovpn_nl_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb,
struct genl_info *info);
+95 -3
View File
@@ -305,6 +305,12 @@ static int ovpn_nl_peer_modify(struct ovpn_peer *peer, struct genl_info *info,
dst_cache_reset(&peer->dst_cache);
}
/* In a multipeer-to-multipeer setup we may have asymmetric peer IDs,
* that is peer->id might be different from peer->tx_id.
*/
if (attrs[OVPN_A_PEER_TX_ID])
peer->tx_id = nla_get_u32(attrs[OVPN_A_PEER_TX_ID]);
if (attrs[OVPN_A_PEER_VPN_IPV4]) {
rehash = true;
peer->vpn_addrs.ipv4.s_addr =
@@ -326,8 +332,8 @@ static int ovpn_nl_peer_modify(struct ovpn_peer *peer, struct genl_info *info,
}
netdev_dbg(peer->ovpn->dev,
"modify peer id=%u endpoint=%pIScp VPN-IPv4=%pI4 VPN-IPv6=%pI6c\n",
peer->id, &ss,
"modify peer id=%u tx_id=%u endpoint=%pIScp VPN-IPv4=%pI4 VPN-IPv6=%pI6c\n",
peer->id, peer->tx_id, &ss,
&peer->vpn_addrs.ipv4.s_addr, &peer->vpn_addrs.ipv6);
spin_unlock_bh(&peer->lock);
@@ -373,6 +379,7 @@ int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info)
}
peer_id = nla_get_u32(attrs[OVPN_A_PEER_ID]);
peer = ovpn_peer_new(ovpn, peer_id);
if (IS_ERR(peer)) {
NL_SET_ERR_MSG_FMT_MOD(info->extack,
@@ -572,6 +579,9 @@ static int ovpn_nl_send_peer(struct sk_buff *skb, const struct genl_info *info,
if (nla_put_u32(skb, OVPN_A_PEER_ID, peer->id))
goto err;
if (nla_put_u32(skb, OVPN_A_PEER_TX_ID, peer->tx_id))
goto err;
if (peer->vpn_addrs.ipv4.s_addr != htonl(INADDR_ANY))
if (nla_put_in_addr(skb, OVPN_A_PEER_VPN_IPV4,
peer->vpn_addrs.ipv4.s_addr))
@@ -1061,8 +1071,8 @@ err:
int ovpn_nl_key_swap_doit(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr *attrs[OVPN_A_KEYCONF_MAX + 1];
struct ovpn_priv *ovpn = info->user_ptr[0];
struct nlattr *attrs[OVPN_A_PEER_MAX + 1];
struct ovpn_peer *peer;
u32 peer_id;
int ret;
@@ -1203,6 +1213,88 @@ err_free_msg:
return ret;
}
/**
* ovpn_nl_peer_float_notify - notify userspace about peer floating
* @peer: the floated peer
* @ss: sockaddr representing the new remote endpoint
*
* Return: 0 on success or a negative error code otherwise
*/
int ovpn_nl_peer_float_notify(struct ovpn_peer *peer,
const struct sockaddr_storage *ss)
{
struct ovpn_socket *sock;
struct sockaddr_in6 *sa6;
struct sockaddr_in *sa;
struct sk_buff *msg;
struct nlattr *attr;
int ret = -EMSGSIZE;
void *hdr;
msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
if (!msg)
return -ENOMEM;
hdr = genlmsg_put(msg, 0, 0, &ovpn_nl_family, 0,
OVPN_CMD_PEER_FLOAT_NTF);
if (!hdr) {
ret = -ENOBUFS;
goto err_free_msg;
}
if (nla_put_u32(msg, OVPN_A_IFINDEX, peer->ovpn->dev->ifindex))
goto err_cancel_msg;
attr = nla_nest_start(msg, OVPN_A_PEER);
if (!attr)
goto err_cancel_msg;
if (nla_put_u32(msg, OVPN_A_PEER_ID, peer->id))
goto err_cancel_msg;
if (ss->ss_family == AF_INET) {
sa = (struct sockaddr_in *)ss;
if (nla_put_in_addr(msg, OVPN_A_PEER_REMOTE_IPV4,
sa->sin_addr.s_addr) ||
nla_put_net16(msg, OVPN_A_PEER_REMOTE_PORT, sa->sin_port))
goto err_cancel_msg;
} else if (ss->ss_family == AF_INET6) {
sa6 = (struct sockaddr_in6 *)ss;
if (nla_put_in6_addr(msg, OVPN_A_PEER_REMOTE_IPV6,
&sa6->sin6_addr) ||
nla_put_u32(msg, OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID,
sa6->sin6_scope_id) ||
nla_put_net16(msg, OVPN_A_PEER_REMOTE_PORT, sa6->sin6_port))
goto err_cancel_msg;
} else {
ret = -EAFNOSUPPORT;
goto err_cancel_msg;
}
nla_nest_end(msg, attr);
genlmsg_end(msg, hdr);
rcu_read_lock();
sock = rcu_dereference(peer->sock);
if (!sock) {
ret = -EINVAL;
goto err_unlock;
}
genlmsg_multicast_netns(&ovpn_nl_family, sock_net(sock->sk), msg,
0, OVPN_NLGRP_PEERS, GFP_ATOMIC);
rcu_read_unlock();
return 0;
err_unlock:
rcu_read_unlock();
err_cancel_msg:
genlmsg_cancel(msg, hdr);
err_free_msg:
nlmsg_free(msg);
return ret;
}
/**
* ovpn_nl_key_swap_notify - notify userspace peer's key must be renewed
* @peer: the peer whose key needs to be renewed
+2
View File
@@ -13,6 +13,8 @@ int ovpn_nl_register(void);
void ovpn_nl_unregister(void);
int ovpn_nl_peer_del_notify(struct ovpn_peer *peer);
int ovpn_nl_peer_float_notify(struct ovpn_peer *peer,
const struct sockaddr_storage *ss);
int ovpn_nl_key_swap_notify(struct ovpn_peer *peer, u8 key_id);
#endif /* _NET_OVPN_NETLINK_H_ */
+6
View File
@@ -99,7 +99,11 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_priv *ovpn, u32 id)
if (!peer)
return ERR_PTR(-ENOMEM);
/* in the default case TX and RX IDs are the same.
* the user may set a different TX ID via netlink
*/
peer->id = id;
peer->tx_id = id;
peer->ovpn = ovpn;
peer->vpn_addrs.ipv4.s_addr = htonl(INADDR_ANY);
@@ -287,6 +291,8 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb)
spin_unlock_bh(&peer->lock);
ovpn_nl_peer_float_notify(peer, &ss);
/* rehashing is required only in MP mode as P2P has one peer
* only and thus there is no hashtable
*/
+3 -1
View File
@@ -21,7 +21,8 @@
* 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
* @id: unique identifier, used to match incoming packets
* @tx_id: identifier to be used in TX packets
* @vpn_addrs: IP addresses assigned over the tunnel
* @vpn_addrs.ipv4: IPv4 assigned to peer on the tunnel
* @vpn_addrs.ipv6: IPv6 assigned to peer on the tunnel
@@ -64,6 +65,7 @@ struct ovpn_peer {
struct ovpn_priv *ovpn;
netdevice_tracker dev_tracker;
u32 id;
u32 tx_id;
struct {
struct in_addr ipv4;
struct in6_addr ipv6;
+4 -7
View File
@@ -65,7 +65,7 @@ int ovpn_pktid_recv(struct ovpn_pktid_recv *pr, u32 pkt_id, u32 pkt_time)
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));
__set_bit(pr->base, pr->history);
if (pr->extent < REPLAY_WINDOW_SIZE)
++pr->extent;
pr->id = pkt_id;
@@ -77,14 +77,14 @@ int ovpn_pktid_recv(struct ovpn_pktid_recv *pr, u32 pkt_id, u32 pkt_time)
unsigned int i;
pr->base = REPLAY_INDEX(pr->base, -delta);
pr->history[pr->base / 8] |= (1 << (pr->base % 8));
__set_bit(pr->base, pr->history);
pr->extent += delta;
if (pr->extent > REPLAY_WINDOW_SIZE)
pr->extent = REPLAY_WINDOW_SIZE;
for (i = 1; i < delta; ++i) {
unsigned int newb = REPLAY_INDEX(pr->base, i);
pr->history[newb / 8] &= ~BIT(newb % 8);
__clear_bit(newb, pr->history);
}
} else {
pr->base = 0;
@@ -103,14 +103,11 @@ int ovpn_pktid_recv(struct ovpn_pktid_recv *pr, u32 pkt_id, u32 pkt_time)
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) {
if (__test_and_set_bit(ri, pr->history)) {
ret = -EINVAL;
goto out;
}
*p |= mask;
} else {
ret = -EINVAL;
goto out;
+1 -1
View File
@@ -34,7 +34,7 @@ struct ovpn_pktid_xmit {
*/
struct ovpn_pktid_recv {
/* "sliding window" bitmask of recent packet IDs received */
u8 history[REPLAY_WINDOW_BYTES];
DECLARE_BITMAP(history, REPLAY_WINDOW_SIZE);
/* bit position of deque base in history */
unsigned int base;
/* extent (in bits) of deque in history */
+10 -3
View File
@@ -18,12 +18,19 @@
#include <linux/socket.h>
#include <linux/types.h>
/**
* struct ovpn_cb - ovpn skb control block
* @peer: the peer this skb was received from/sent to
* @ks: the crypto key slot used to encrypt/decrypt this skb
* @crypto_tmp: pointer to temporary memory used for crypto operations
* containing the IV, the scatter gather list and the aead request
* @payload_offset: offset in the skb where the payload starts
* @nosignal: whether this skb should be sent with the MSG_NOSIGNAL flag (TCP)
*/
struct ovpn_cb {
struct ovpn_peer *peer;
struct ovpn_crypto_key_slot *ks;
struct aead_request *req;
struct scatterlist *sg;
u8 *iv;
void *crypto_tmp;
unsigned int payload_offset;
bool nosignal;
};
+2
View File
@@ -55,6 +55,7 @@ enum {
OVPN_A_PEER_LINK_TX_BYTES,
OVPN_A_PEER_LINK_RX_PACKETS,
OVPN_A_PEER_LINK_TX_PACKETS,
OVPN_A_PEER_TX_ID,
__OVPN_A_PEER_MAX,
OVPN_A_PEER_MAX = (__OVPN_A_PEER_MAX - 1)
@@ -100,6 +101,7 @@ enum {
OVPN_CMD_KEY_SWAP,
OVPN_CMD_KEY_SWAP_NTF,
OVPN_CMD_KEY_DEL,
OVPN_CMD_PEER_FLOAT_NTF,
__OVPN_CMD_MAX,
OVPN_CMD_MAX = (__OVPN_CMD_MAX - 1)
+24 -7
View File
@@ -2,22 +2,35 @@
# Copyright (C) 2020-2025 OpenVPN, Inc.
#
CFLAGS = -pedantic -Wextra -Wall -Wl,--no-as-needed -g -O0 -ggdb $(KHDR_INCLUDES)
CFLAGS += $(shell pkg-config --cflags mbedcrypto-3 mbedtls-3 2>/dev/null)
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
MTLS_LDLIBS= $(shell pkg-config --libs mbedcrypto-3 mbedtls-3 2>/dev/null)
ifeq ($(MTLS_LDLIBS),)
MTLS_LDLIBS = -lmbedtls -lmbedcrypto
endif
LDLIBS += $(VAR_LDLIBS)
LDLIBS += $(MTLS_LDLIBS)
NL_LDLIBS = $(shell pkg-config --libs libnl-3.0 libnl-genl-3.0 2>/dev/null)
ifeq ($(NL_LDLIBS),)
NL_LDLIBS = -lnl-genl-3 -lnl-3
endif
LDLIBS += $(NL_LDLIBS)
TEST_FILES = common.sh
TEST_FILES = \
common.sh \
data64.key \
json \
tcp_peers.txt \
udp_peers.txt \
../../../../net/ynl/pyynl/cli.py \
# end of TEST_FILES
TEST_PROGS := \
test-chachapoly.sh \
@@ -25,6 +38,10 @@ TEST_PROGS := \
test-close-socket.sh \
test-float.sh \
test-large-mtu.sh \
test-mark.sh \
test-symmetric-id-float.sh \
test-symmetric-id-tcp.sh \
test-symmetric-id.sh \
test-tcp.sh \
test.sh \
# end of TEST_PROGS
+88 -13
View File
@@ -7,12 +7,21 @@
UDP_PEERS_FILE=${UDP_PEERS_FILE:-udp_peers.txt}
TCP_PEERS_FILE=${TCP_PEERS_FILE:-tcp_peers.txt}
OVPN_CLI=${OVPN_CLI:-./ovpn-cli}
YNL_CLI=${YNL_CLI:-../../../../net/ynl/pyynl/cli.py}
ALG=${ALG:-aes}
PROTO=${PROTO:-UDP}
FLOAT=${FLOAT:-0}
SYMMETRIC_ID=${SYMMETRIC_ID:-0}
export ID_OFFSET=$(( 9 * (SYMMETRIC_ID == 0) ))
JQ_FILTER='map(select(.msg.peer | has("remote-ipv6") | not)) |
map(del(.msg.ifindex)) | sort_by(.msg.peer.id)[]'
LAN_IP="11.11.11.11"
declare -A tmp_jsons=()
declare -A listener_pids=()
create_ns() {
ip netns add peer${1}
}
@@ -48,27 +57,67 @@ setup_ns() {
ip -n peer${1} link set tun${1} up
}
build_capture_filter() {
# match the first four bytes of the openvpn data payload
if [ "${PROTO}" == "UDP" ]; then
# For UDP, libpcap transport indexing only works for IPv4, so
# use an explicit IPv4 or IPv6 expression based on the peer
# address. The IPv6 branch assumes there are no extension
# headers in the outer packet.
if [[ "${2}" == *:* ]]; then
printf "ip6 and ip6[6] = 17 and ip6[48:4] = %s" "${1}"
else
printf "ip and udp[8:4] = %s" "${1}"
fi
else
# openvpn over TCP prepends a 2-byte packet length ahead of the
# DATA_V2 opcode, so skip it before matching the payload header
printf "ip and tcp[(((tcp[12] & 0xf0) >> 2) + 2):4] = %s" "${1}"
fi
}
setup_listener() {
file=$(mktemp)
PYTHONUNBUFFERED=1 ip netns exec peer${p} ${YNL_CLI} --family ovpn \
--subscribe peers --output-json --duration 40 > ${file} &
listener_pids[$1]=$!
tmp_jsons[$1]="${file}"
}
add_peer() {
labels=("ASYMM" "SYMM")
M_ID=${labels[SYMMETRIC_ID]}
if [ "${PROTO}" == "UDP" ]; then
if [ ${1} -eq 0 ]; then
ip netns exec peer0 ${OVPN_CLI} new_multi_peer tun0 1 ${UDP_PEERS_FILE}
ip netns exec peer0 ${OVPN_CLI} new_multi_peer tun0 1 \
${M_ID} ${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
RADDR=$(awk "NR == ${1} {print \$2}" ${UDP_PEERS_FILE})
RPORT=$(awk "NR == ${1} {print \$3}" ${UDP_PEERS_FILE})
LPORT=$(awk "NR == ${1} {print \$5}" ${UDP_PEERS_FILE})
ip netns exec peer${1} ${OVPN_CLI} new_peer tun${1} ${1} ${LPORT} \
${RADDR} ${RPORT}
ip netns exec peer${1} ${OVPN_CLI} new_key tun${1} ${1} 1 0 ${ALG} 1 \
data64.key
if [ "${SYMMETRIC_ID}" -eq 1 ]; then
PEER_ID=${1}
TX_ID="none"
else
PEER_ID=$(awk "NR == ${1} {print \$2}" \
${UDP_PEERS_FILE})
TX_ID=${1}
fi
RADDR=$(awk "NR == ${1} {print \$3}" ${UDP_PEERS_FILE})
RPORT=$(awk "NR == ${1} {print \$4}" ${UDP_PEERS_FILE})
LPORT=$(awk "NR == ${1} {print \$6}" ${UDP_PEERS_FILE})
ip netns exec peer${1} ${OVPN_CLI} new_peer tun${1} \
${PEER_ID} ${TX_ID} ${LPORT} ${RADDR} ${RPORT}
ip netns exec peer${1} ${OVPN_CLI} new_key tun${1} \
${PEER_ID} 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} && {
(ip netns exec peer0 ${OVPN_CLI} listen tun0 1 ${M_ID} \
${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
@@ -76,12 +125,40 @@ add_peer() {
}) &
sleep 5
else
ip netns exec peer${1} ${OVPN_CLI} connect tun${1} ${1} 10.10.${1}.1 1 \
data64.key
if [ "${SYMMETRIC_ID}" -eq 1 ]; then
PEER_ID=${1}
TX_ID="none"
else
PEER_ID=$(awk "NR == ${1} {print \$2}" \
${TCP_PEERS_FILE})
TX_ID=${1}
fi
ip netns exec peer${1} ${OVPN_CLI} connect tun${1} \
${PEER_ID} ${TX_ID} 10.10.${1}.1 1 data64.key
fi
fi
}
compare_ntfs() {
if [ ${#tmp_jsons[@]} -gt 0 ]; then
suffix=""
[ "${SYMMETRIC_ID}" -eq 1 ] && suffix="${suffix}-symm"
[ "$FLOAT" == 1 ] && suffix="${suffix}-float"
expected="json/peer${1}${suffix}.json"
received="${tmp_jsons[$1]}"
kill -TERM ${listener_pids[$1]} || true
wait ${listener_pids[$1]} || true
printf "Checking notifications for peer ${1}... "
if diff <(jq -s "${JQ_FILTER}" ${expected}) \
<(jq -s "${JQ_FILTER}" ${received}); then
echo "OK"
fi
rm -f ${received} || true
fi
}
cleanup() {
# some ovpn-cli processes sleep in background so they need manual poking
killall $(basename ${OVPN_CLI}) 2>/dev/null || true
@@ -104,5 +181,3 @@ if [ "${PROTO}" == "UDP" ]; then
else
NUM_PEERS=${NUM_PEERS:-$(wc -l ${TCP_PEERS_FILE} | awk '{print $1}')}
fi
+1 -5
View File
@@ -1,5 +1 @@
jRqMACN7d7/aFQNT8S7jkrBD8uwrgHbG5OQZP2eu4R1Y7tfpS2bf5RHv06Vi163CGoaIiTX99R3B
ia9ycAH8Wz1+9PWv51dnBLur9jbShlgZ2QHLtUc4a/gfT7zZwULXuuxdLnvR21DDeMBaTbkgbai9
uvAa7ne1liIgGFzbv+Bas4HDVrygxIxuAnP5Qgc3648IJkZ0QEXPF+O9f0n5+QIvGCxkAUVx+5K6
KIs+SoeWXnAopELmoGSjUpFtJbagXK82HfdqpuUxT2Tnuef0/14SzVE/vNleBNu2ZbyrSAaah8tE
BofkPJUBFY+YQcfZNM5Dgrw3i+Bpmpq/gpdg5w==
jRqMACN7d7/aFQNT8S7jkrBD8uwrgHbG5OQZP2eu4R1Y7tfpS2bf5RHv06Vi163CGoaIiTX99R3Bia9ycAH8Wz1+9PWv51dnBLur9jbShlgZ2QHLtUc4a/gfT7zZwULXuuxdLnvR21DDeMBaTbkgbai9uvAa7ne1liIgGFzbv+Bas4HDVrygxIxuAnP5Qgc3648IJkZ0QEXPF+O9f0n5+QIvGCxkAUVx+5K6KIs+SoeWXnAopELmoGSjUpFtJbagXK82HfdqpuUxT2Tnuef0/14SzVE/vNleBNu2ZbyrSAaah8tEBofkPJUBFY+YQcfZNM5Dgrw3i+Bpmpq/gpdg5w==
@@ -0,0 +1,9 @@
{"name": "peer-float-ntf", "msg": {"ifindex": 0, "peer": {"id": 1, "remote-ipv4": "10.10.1.3", "remote-port": 1}}}
{"name": "peer-float-ntf", "msg": {"ifindex": 0, "peer": {"id": 2, "remote-ipv4": "10.10.2.3", "remote-port": 1}}}
{"name": "peer-float-ntf", "msg": {"ifindex": 0, "peer": {"id": 3, "remote-ipv4": "10.10.3.3", "remote-port": 1}}}
{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "userspace", "id": 1}}}
{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "userspace", "id": 2}}}
{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 3}}}
{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 4}}}
{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 5}}}
{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 6}}}
@@ -0,0 +1 @@
peer0-float.json
@@ -0,0 +1 @@
peer0.json
@@ -0,0 +1,6 @@
{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "userspace", "id": 1}}}
{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "userspace", "id": 2}}}
{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 3}}}
{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 4}}}
{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 5}}}
{"name": "peer-del-ntf", "msg": {"ifindex": 0, "peer": {"del-reason": "expired", "id": 6}}}

Some files were not shown because too many files have changed in this diff Show More