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 <anto...@openvpn.net>
---
 drivers/net/ovpn/io.c         |  77 +++++++++++++++++
 drivers/net/ovpn/io.h         |   5 ++
 drivers/net/ovpn/main.c       |   3 +
 drivers/net/ovpn/ovpnstruct.h |   2 +
 drivers/net/ovpn/peer.c       | 190 ++++++++++++++++++++++++++++++++++++++++++
 drivers/net/ovpn/peer.h       |  21 ++++-
 6 files changed, 296 insertions(+), 2 deletions(-)

diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
index 
24a6f04b9f1cecf43e845bc948f6b9c09d0a9502..0bab35fab06b4399c57e49732453df2fc12e9334
 100644
--- a/drivers/net/ovpn/io.c
+++ b/drivers/net/ovpn/io.c
@@ -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.
  */
@@ -104,6 +131,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);
 
@@ -121,6 +151,13 @@ 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);
+                       goto drop_nocount;
+               }
+
                net_info_ratelimited("%s: unsupported protocol received from 
peer %u\n",
                                     netdev_name(peer->ovpn->dev), peer->id);
                goto drop;
@@ -146,6 +183,7 @@ void ovpn_decrypt_post(void *data, int ret)
 drop:
        if (unlikely(skb))
                dev_core_stats_rx_dropped_inc(peer->ovpn->dev);
+drop_nocount:
        if (likely(peer))
                ovpn_peer_put(peer);
        if (likely(ks))
@@ -221,6 +259,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:
@@ -350,3 +390,40 @@ 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
+ */
+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))
+               return;
+
+       skb = alloc_skb(256 + len, GFP_ATOMIC);
+       if (unlikely(!skb))
+               return;
+
+       skb_reserve(skb, 128);
+       skb->priority = TC_PRIO_BESTEFFORT;
+       __skb_put_data(skb, data, len);
+
+       /* increase reference counter when passing peer to sending queue */
+       if (!ovpn_peer_hold(peer)) {
+               netdev_dbg(ovpn->dev,
+                          "cannot hold peer reference for sending special 
packet\n");
+               kfree_skb(skb);
+               return;
+       }
+
+       ovpn_send(ovpn, skb, peer);
+}
diff --git a/drivers/net/ovpn/io.h b/drivers/net/ovpn/io.h
index 
5f9c7eba37b132bcf8c0ebad60af9171e46bf3e8..1186bb6a5f1b0411a825a295d0e6e21c32972e84
 100644
--- a/drivers/net/ovpn/io.h
+++ b/drivers/net/ovpn/io.h
@@ -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);
diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c
index 
17cdd5a732132de71b854fe0b76c284bd9f3d918..c7299a4334b6d50fb1596bab0af41323ed09edd0
 100644
--- a/drivers/net/ovpn/main.c
+++ b/drivers/net/ovpn/main.c
@@ -182,6 +182,7 @@ static int ovpn_newlink(struct net *src_net, 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);
 
        err = ovpn_mp_alloc(ovpn);
        if (err < 0)
@@ -246,6 +247,8 @@ static int ovpn_netdev_notifier_call(struct notifier_block 
*nb,
                netif_carrier_off(dev);
                ovpn->registered = false;
 
+               cancel_delayed_work_sync(&ovpn->keepalive_work);
+
                switch (ovpn->mode) {
                case OVPN_MODE_P2P:
                        ovpn_peer_release_p2p(ovpn,
diff --git a/drivers/net/ovpn/ovpnstruct.h b/drivers/net/ovpn/ovpnstruct.h
index 
3ba01159afd237edf5941f3af194be8f292f37d9..276b1aab5beb4a5a7bac967ce96f576b596cbbb6
 100644
--- a/drivers/net/ovpn/ovpnstruct.h
+++ b/drivers/net/ovpn/ovpnstruct.h
@@ -38,6 +38,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;
@@ -47,6 +48,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_ */
diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c
index 
78ef3ff74925331423c3fc4f8055767b05eaa63f..d78726718ec40126c93624310ce627ddbd210816
 100644
--- a/drivers/net/ovpn/peer.c
+++ b/drivers/net/ovpn/peer.c
@@ -22,6 +22,46 @@
 #include "peer.h"
 #include "socket.h"
 
+/**
+ * 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);
+}
+
+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_put(peer);
+}
+
 /**
  * ovpn_peer_new - allocate and initialize a new peer object
  * @ovpn: the openvpn instance inside which the peer should be created
@@ -51,6 +91,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) {
@@ -885,3 +926,152 @@ void ovpn_peers_free(struct ovpn_priv *ovpn,
                ovpn_peer_remove(peer, reason);
        spin_unlock_bh(&ovpn->lock);
 }
+
+static time64_t ovpn_peer_keepalive_work_single(struct ovpn_peer *peer,
+                                               time64_t now)
+{
+       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);
+               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)
+{
+       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);
+               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 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);
+
+       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();
+
+       spin_lock_bh(&ovpn->lock);
+       switch (ovpn->mode) {
+       case OVPN_MODE_MP:
+               next_run = ovpn_peer_keepalive_work_mp(ovpn, now);
+               break;
+       case OVPN_MODE_P2P:
+               next_run = ovpn_peer_keepalive_work_p2p(ovpn, now);
+               break;
+       }
+       spin_unlock_bh(&ovpn->lock);
+
+       /* prevent rearming if the interface is being destroyed */
+       if (next_run > 0 && ovpn->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);
+       }
+}
diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h
index 
52632215bb531b3dec4e0dae358f4e315cf33d7b..c69774877c84f0f335fc7ab8fc5da0c555c0794c
 100644
--- a/drivers/net/ovpn/peer.h
+++ b/drivers/net/ovpn/peer.h
@@ -46,13 +46,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
  * @remove_work: deferred release work, for code that may sleep
+ * @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 work_struct remove_work;
+       struct work_struct keepalive_work;
 };
 
 /**
@@ -137,4 +151,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_ */

-- 
2.45.2


Reply via email to