@@ -22,6 +22,26 @@
#include <net/gso.h>
+static const unsigned char ovpn_keepalive_message[] = {
+ 0x2a, 0x18, 0x7b, 0xf3, 0x64, 0x1e, 0xb4, 0xcb,
+ 0x07, 0xed, 0x2d, 0x0a, 0x98, 0x1f, 0xc7, 0x48
+};
+
+/* Is keepalive message?
+ * Assumes that the first byte of skb->data is defined.
+ */
+static bool ovpn_is_keepalive(struct sk_buff *skb)
+{
+ if (*skb->data != OVPN_KEEPALIVE_FIRST_BYTE)
+ return false;
+
+ if (!pskb_may_pull(skb, sizeof(ovpn_keepalive_message)))
+ return false;
+
+ return !memcmp(skb->data, ovpn_keepalive_message,
+ sizeof(ovpn_keepalive_message));
+}
+
int ovpn_struct_init(struct net_device *dev)
{
struct ovpn_struct *ovpn = netdev_priv(dev);
@@ -186,6 +206,9 @@ static int ovpn_decrypt_one(struct ovpn_peer *peer, struct sk_buff *skb)
goto drop;
}
+ /* note event of authenticated packet received for keepalive */
+ ovpn_peer_keepalive_recv_reset(peer);
+
/* increment RX stats */
ovpn_peer_stats_increment_rx(&peer->vpn_stats, skb->len);
@@ -203,6 +226,17 @@ static int ovpn_decrypt_one(struct ovpn_peer *peer, struct sk_buff *skb)
goto drop;
}
+ /* check if special OpenVPN message */
+ if (ovpn_is_keepalive(skb)) {
+ netdev_dbg(peer->ovpn->dev, "ping received from peero %u\n", peer->id);
+ /* not an error */
+ consume_skb(skb);
+ /* inform the caller that NAPI should not be scheduled
+ * for this packet
+ */
+ return -1;
+ }
+
netdev_dbg(peer->ovpn->dev, "unsupported protocol received from peer %u\n",
peer->id);
@@ -338,6 +372,9 @@ void ovpn_encrypt_work(struct work_struct *work)
break;
}
}
+
+ /* note event of authenticated packet xmit for keepalive */
+ ovpn_peer_keepalive_xmit_reset(peer);
}
/* give a chance to be rescheduled if needed */
@@ -437,3 +474,42 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
kfree_skb_list(skb);
return NET_XMIT_DROP;
}
+
+/* Encrypt and transmit a special message to peer, such as keepalive
+ * or explicit-exit-notify. Called from softirq context.
+ * Assumes that caller holds a reference to peer.
+ */
+static void ovpn_xmit_special(struct ovpn_peer *peer, const void *data,
+ const unsigned int len)
+{
+ struct ovpn_struct *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;
+ memcpy(__skb_put(skb, len), data, len);
+
+ /* increase reference counter when passing peer to sending queue */
+ if (!ovpn_peer_hold(peer)) {
+ netdev_dbg(ovpn->dev, "%s: cannot hold peer reference for sending special packet\n",
+ __func__);
+ kfree_skb(skb);
+ return;
+ }
+
+ ovpn_queue_skb(ovpn, skb, peer);
+}
+
+void ovpn_keepalive_xmit(struct ovpn_peer *peer)
+{
+ ovpn_xmit_special(peer, ovpn_keepalive_message,
+ sizeof(ovpn_keepalive_message));
+}
@@ -19,6 +19,7 @@ struct ovpn_struct;
int ovpn_struct_init(struct net_device *dev);
netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev);
int ovpn_napi_poll(struct napi_struct *napi, int budget);
+void ovpn_keepalive_xmit(struct ovpn_peer *peer);
int ovpn_recv(struct ovpn_struct *ovpn, struct ovpn_peer *peer, struct sk_buff *skb);
@@ -23,6 +23,48 @@
#include <linux/workqueue.h>
+static void ovpn_peer_ping(struct timer_list *t)
+{
+ struct ovpn_peer *peer = from_timer(peer, t, keepalive_xmit);
+
+ netdev_dbg(peer->ovpn->dev, "%s: sending ping to peer %u\n", __func__, peer->id);
+ ovpn_keepalive_xmit(peer);
+}
+
+static void ovpn_peer_expire(struct timer_list *t)
+{
+ struct ovpn_peer *peer = from_timer(peer, t, keepalive_recv);
+
+ netdev_dbg(peer->ovpn->dev, "%s: peer %u expired\n", __func__, peer->id);
+ ovpn_peer_del(peer, OVPN_DEL_PEER_REASON_EXPIRED);
+}
+
+/* Configure keepalive parameters */
+void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u32 interval, u32 timeout)
+{
+ u32 delta;
+
+ netdev_dbg(peer->ovpn->dev,
+ "%s: scheduling keepalive for peer %u: interval=%u timeout=%u\n", __func__,
+ peer->id, interval, timeout);
+
+ peer->keepalive_interval = interval;
+ if (interval > 0) {
+ delta = msecs_to_jiffies(interval * MSEC_PER_SEC);
+ mod_timer(&peer->keepalive_xmit, jiffies + delta);
+ } else {
+ del_timer(&peer->keepalive_xmit);
+ }
+
+ peer->keepalive_timeout = timeout;
+ if (timeout) {
+ delta = msecs_to_jiffies(timeout * MSEC_PER_SEC);
+ mod_timer(&peer->keepalive_recv, jiffies + delta);
+ } else {
+ del_timer(&peer->keepalive_recv);
+ }
+}
+
/* Construct a new peer */
struct ovpn_peer *ovpn_peer_new(struct ovpn_struct *ovpn, u32 id)
{
@@ -82,6 +124,9 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_struct *ovpn, u32 id)
dev_hold(ovpn->dev);
+ timer_setup(&peer->keepalive_xmit, ovpn_peer_ping, 0);
+ timer_setup(&peer->keepalive_recv, ovpn_peer_expire, 0);
+
return peer;
err_rx_ring:
ptr_ring_cleanup(&peer->rx_ring, NULL);
@@ -97,9 +142,16 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_struct *ovpn, u32 id)
#define ovpn_peer_index(_tbl, _key, _key_len) \
(jhash(_key, _key_len, 0) % HASH_SIZE(_tbl)) \
+static void ovpn_peer_timer_delete_all(struct ovpn_peer *peer)
+{
+ del_timer_sync(&peer->keepalive_xmit);
+ del_timer_sync(&peer->keepalive_recv);
+}
+
static void ovpn_peer_free(struct ovpn_peer *peer)
{
ovpn_bind_reset(peer, NULL);
+ ovpn_peer_timer_delete_all(peer);
WARN_ON(!__ptr_ring_empty(&peer->tx_ring));
ptr_ring_cleanup(&peer->tx_ring, NULL);
@@ -77,6 +77,20 @@ struct ovpn_peer {
/* our binding to peer, protected by spinlock */
struct ovpn_bind __rcu *bind;
+ /* timer used to send periodic ping messages to the other peer, if no
+ * other data was sent within the past keepalive_interval seconds
+ */
+ struct timer_list keepalive_xmit;
+ /* keepalive interval in seconds */
+ unsigned long keepalive_interval;
+
+ /* timer used to mark a peer as expired when no data is received for
+ * keepalive_timeout seconds
+ */
+ struct timer_list keepalive_recv;
+ /* keepalive timeout in seconds */
+ unsigned long keepalive_timeout;
+
/* true if ovpn_peer_mark_delete was called */
bool halt;
@@ -117,8 +131,30 @@ static inline void ovpn_peer_put(struct ovpn_peer *peer)
kref_put(&peer->refcount, ovpn_peer_release_kref);
}
+static inline void ovpn_peer_keepalive_recv_reset(struct ovpn_peer *peer)
+{
+ u32 delta = msecs_to_jiffies(peer->keepalive_timeout * MSEC_PER_SEC);
+
+ if (unlikely(!delta))
+ return;
+
+ mod_timer(&peer->keepalive_recv, jiffies + delta);
+}
+
+static inline void ovpn_peer_keepalive_xmit_reset(struct ovpn_peer *peer)
+{
+ u32 delta = msecs_to_jiffies(peer->keepalive_interval * MSEC_PER_SEC);
+
+ if (unlikely(!delta))
+ return;
+
+ mod_timer(&peer->keepalive_xmit, jiffies + delta);
+}
+
struct ovpn_peer *ovpn_peer_new(struct ovpn_struct *ovpn, u32 id);
+void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u32 interval, u32 timeout);
+
int ovpn_peer_add(struct ovpn_struct *ovpn, struct ovpn_peer *peer);
int ovpn_peer_del(struct ovpn_peer *peer, enum ovpn_del_peer_reason reason);
struct ovpn_peer *ovpn_peer_find(struct ovpn_struct *ovpn, u32 peer_id);
OpenVPN supports configuring a periodic keepalive "ping" message to allow the remote endpoint detect link failures. This change implements the ping sending and timer expiring logic. Signed-off-by: Antonio Quartulli <antonio@openvpn.net> --- drivers/net/ovpn/io.c | 76 +++++++++++++++++++++++++++++++++++++++++ drivers/net/ovpn/io.h | 1 + drivers/net/ovpn/peer.c | 52 ++++++++++++++++++++++++++++ drivers/net/ovpn/peer.h | 36 +++++++++++++++++++ 4 files changed, 165 insertions(+)