mirror of https://github.com/torvalds/linux.git
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: Antonio Quartulli <antonio@openvpn.net> Link: https://patch.msgid.link/20250415-b4-ovpn-v26-16-577f6097b964@openvpn.net Reviewed-by: Sabrina Dubroca <sd@queasysnail.net> Tested-by: Oleksandr Natalenko <oleksandr@natalenko.name> Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
parent
a3aaef8cd1
commit
3ecfd9349f
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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_ */
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,13 +45,20 @@
|
|||
* @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)
|
||||
* @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;
|
||||
|
|
@ -91,13 +98,20 @@ struct ovpn_peer {
|
|||
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 */
|
||||
spinlock_t lock; /* protects bind and keepalive* */
|
||||
struct kref refcount;
|
||||
struct rcu_head rcu;
|
||||
struct llist_node release_entry;
|
||||
struct work_struct keepalive_work;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -136,4 +150,7 @@ struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_priv *ovpn,
|
|||
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);
|
||||
|
||||
#endif /* _NET_OVPN_OVPNPEER_H_ */
|
||||
|
|
|
|||
Loading…
Reference in New Issue