Commit 3ecfd934 authored by Antonio Quartulli's avatar Antonio Quartulli Committed by Paolo Abeni
Browse files

ovpn: implement keepalive mechanism



OpenVPN supports configuring a periodic keepalive packet.
message to allow the remote endpoint detect link failures.

This change implements the keepalive sending and timer expiring logic.

Signed-off-by: default avatarAntonio Quartulli <antonio@openvpn.net>
Link: https://patch.msgid.link/20250415-b4-ovpn-v26-16-577f6097b964@openvpn.net


Reviewed-by: default avatarSabrina Dubroca <sd@queasysnail.net>
Tested-by: default avatarOleksandr Natalenko <oleksandr@natalenko.name>
Signed-off-by: default avatarPaolo Abeni <pabeni@redhat.com>
parent a3aaef8c
Loading
Loading
Loading
Loading
+77 −1
Original line number Diff line number Diff line
@@ -27,6 +27,33 @@
#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.
 */
@@ -101,6 +128,9 @@ void ovpn_decrypt_post(void *data, int ret)
		goto drop;
	}

	/* keep track of last received authenticated packet for keepalive */
	WRITE_ONCE(peer->last_recv, ktime_get_real_seconds());

	/* point to encapsulated IP packet */
	__skb_pull(skb, payload_offset);

@@ -118,6 +148,15 @@ void ovpn_decrypt_post(void *data, int ret)
			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;
@@ -143,11 +182,12 @@ void ovpn_decrypt_post(void *data, int ret)
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);
	kfree_skb(skb);
}

/* RX path entry point: decrypt packet and forward it to the device */
@@ -221,6 +261,8 @@ void ovpn_encrypt_post(void *data, int ret)
	}

	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:
@@ -346,3 +388,37 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
	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);
}
+5 −0
Original line number Diff line number Diff line
@@ -19,9 +19,14 @@
/* 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);
+2 −0
Original line number Diff line number Diff line
@@ -170,6 +170,7 @@ static int ovpn_newlink(struct net_device *dev,
	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.
@@ -191,6 +192,7 @@ 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);
}
+2 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ struct ovpn_peer_collection {
 * @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;
@@ -48,6 +49,7 @@ struct ovpn_priv {
	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_ */
+206 −0
Original line number Diff line number Diff line
@@ -36,6 +36,52 @@ static void unlock_ovpn(struct ovpn_priv *ovpn,
	}
}

/**
 * ovpn_peer_keepalive_set - configure keepalive values for peer
 * @peer: the peer to configure
 * @interval: outgoing keepalive interval
 * @timeout: incoming keepalive timeout
 */
void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u32 interval, u32 timeout)
{
	time64_t now = ktime_get_real_seconds();

	netdev_dbg(peer->ovpn->dev,
		   "scheduling keepalive for peer %u: interval=%u timeout=%u\n",
		   peer->id, interval, timeout);

	peer->keepalive_interval = interval;
	WRITE_ONCE(peer->last_sent, now);
	peer->keepalive_xmit_exp = now + interval;

	peer->keepalive_timeout = timeout;
	WRITE_ONCE(peer->last_recv, now);
	peer->keepalive_recv_exp = now + timeout;

	/* now that interval and timeout have been changed, kick
	 * off the worker so that the next delay can be recomputed
	 */
	mod_delayed_work(system_wq, &peer->ovpn->keepalive_work, 0);
}

/**
 * ovpn_peer_keepalive_send - periodic worker sending keepalive packets
 * @work: pointer to the work member of the related peer object
 *
 * NOTE: the reference to peer is not dropped because it gets inherited
 * by ovpn_xmit_special()
 */
static void ovpn_peer_keepalive_send(struct work_struct *work)
{
	struct ovpn_peer *peer = container_of(work, struct ovpn_peer,
					      keepalive_work);

	local_bh_disable();
	ovpn_xmit_special(peer, ovpn_keepalive_message,
			  sizeof(ovpn_keepalive_message));
	local_bh_enable();
}

/**
 * ovpn_peer_new - allocate and initialize a new peer object
 * @ovpn: the openvpn instance inside which the peer should be created
@@ -65,6 +111,7 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_priv *ovpn, u32 id)
	kref_init(&peer->refcount);
	ovpn_peer_stats_init(&peer->vpn_stats);
	ovpn_peer_stats_init(&peer->link_stats);
	INIT_WORK(&peer->keepalive_work, ovpn_peer_keepalive_send);

	ret = dst_cache_init(&peer->dst_cache, GFP_KERNEL);
	if (ret < 0) {
@@ -948,3 +995,162 @@ void ovpn_peers_free(struct ovpn_priv *ovpn, struct sock *sk,
		break;
	}
}

static time64_t ovpn_peer_keepalive_work_single(struct ovpn_peer *peer,
						time64_t now,
						struct llist_head *release_list)
{
	time64_t last_recv, last_sent, next_run1, next_run2;
	unsigned long timeout, interval;
	bool expired;

	spin_lock_bh(&peer->lock);
	/* we expect both timers to be configured at the same time,
	 * therefore bail out if either is not set
	 */
	if (!peer->keepalive_timeout || !peer->keepalive_interval) {
		spin_unlock_bh(&peer->lock);
		return 0;
	}

	/* check for peer timeout */
	expired = false;
	timeout = peer->keepalive_timeout;
	last_recv = READ_ONCE(peer->last_recv);
	if (now < last_recv + timeout) {
		peer->keepalive_recv_exp = last_recv + timeout;
		next_run1 = peer->keepalive_recv_exp;
	} else if (peer->keepalive_recv_exp > now) {
		next_run1 = peer->keepalive_recv_exp;
	} else {
		expired = true;
	}

	if (expired) {
		/* peer is dead -> kill it and move on */
		spin_unlock_bh(&peer->lock);
		netdev_dbg(peer->ovpn->dev, "peer %u expired\n",
			   peer->id);
		ovpn_peer_remove(peer, OVPN_DEL_PEER_REASON_EXPIRED,
				 release_list);
		return 0;
	}

	/* check for peer keepalive */
	expired = false;
	interval = peer->keepalive_interval;
	last_sent = READ_ONCE(peer->last_sent);
	if (now < last_sent + interval) {
		peer->keepalive_xmit_exp = last_sent + interval;
		next_run2 = peer->keepalive_xmit_exp;
	} else if (peer->keepalive_xmit_exp > now) {
		next_run2 = peer->keepalive_xmit_exp;
	} else {
		expired = true;
		next_run2 = now + interval;
	}
	spin_unlock_bh(&peer->lock);

	if (expired) {
		/* a keepalive packet is required */
		netdev_dbg(peer->ovpn->dev,
			   "sending keepalive to peer %u\n",
			   peer->id);
		if (schedule_work(&peer->keepalive_work))
			ovpn_peer_hold(peer);
	}

	if (next_run1 < next_run2)
		return next_run1;

	return next_run2;
}

static time64_t ovpn_peer_keepalive_work_mp(struct ovpn_priv *ovpn,
					    time64_t now,
					    struct llist_head *release_list)
{
	time64_t tmp_next_run, next_run = 0;
	struct hlist_node *tmp;
	struct ovpn_peer *peer;
	int bkt;

	lockdep_assert_held(&ovpn->lock);

	hash_for_each_safe(ovpn->peers->by_id, bkt, tmp, peer, hash_entry_id) {
		tmp_next_run = ovpn_peer_keepalive_work_single(peer, now,
							       release_list);
		if (!tmp_next_run)
			continue;

		/* the next worker run will be scheduled based on the shortest
		 * required interval across all peers
		 */
		if (!next_run || tmp_next_run < next_run)
			next_run = tmp_next_run;
	}

	return next_run;
}

static time64_t ovpn_peer_keepalive_work_p2p(struct ovpn_priv *ovpn,
					     time64_t now,
					     struct llist_head *release_list)
{
	struct ovpn_peer *peer;
	time64_t next_run = 0;

	lockdep_assert_held(&ovpn->lock);

	peer = rcu_dereference_protected(ovpn->peer,
					 lockdep_is_held(&ovpn->lock));
	if (peer)
		next_run = ovpn_peer_keepalive_work_single(peer, now,
							   release_list);

	return next_run;
}

/**
 * ovpn_peer_keepalive_work - run keepalive logic on each known peer
 * @work: pointer to the work member of the related ovpn object
 *
 * Each peer has two timers (if configured):
 * 1. peer timeout: when no data is received for a certain interval,
 *    the peer is considered dead and it gets killed.
 * 2. peer keepalive: when no data is sent to a certain peer for a
 *    certain interval, a special 'keepalive' packet is explicitly sent.
 *
 * This function iterates across the whole peer collection while
 * checking the timers described above.
 */
void ovpn_peer_keepalive_work(struct work_struct *work)
{
	struct ovpn_priv *ovpn = container_of(work, struct ovpn_priv,
					      keepalive_work.work);
	time64_t next_run = 0, now = ktime_get_real_seconds();
	LLIST_HEAD(release_list);

	spin_lock_bh(&ovpn->lock);
	switch (ovpn->mode) {
	case OVPN_MODE_MP:
		next_run = ovpn_peer_keepalive_work_mp(ovpn, now,
						       &release_list);
		break;
	case OVPN_MODE_P2P:
		next_run = ovpn_peer_keepalive_work_p2p(ovpn, now,
							&release_list);
		break;
	}

	/* prevent rearming if the interface is being destroyed */
	if (next_run > 0 &&
	    READ_ONCE(ovpn->dev->reg_state) == NETREG_REGISTERED) {
		netdev_dbg(ovpn->dev,
			   "scheduling keepalive work: now=%llu next_run=%llu delta=%llu\n",
			   next_run, now, next_run - now);
		schedule_delayed_work(&ovpn->keepalive_work,
				      (next_run - now) * HZ);
	}
	unlock_ovpn(ovpn, &release_list);
}
Loading