diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
index f14eb4e6c46a..57cdea45dce7 100644
--- a/drivers/net/ovpn/io.c
+++ b/drivers/net/ovpn/io.c
@@ -349,6 +349,31 @@ static void ovpn_send(struct ovpn_priv *ovpn, struct sk_buff *skb,
 	ovpn_peer_put(peer);
 }
 
+/**
+ * ovpn_skb_is_looping - check if skb comes from the peer's own socket
+ * @peer: the peer the skb is about to be sent to
+ * @skb: the packet to check
+ *
+ * Return: true if @skb was generated by the transport socket of @peer,
+ * false otherwise
+ */
+static bool ovpn_skb_is_looping(struct ovpn_peer *peer,
+				const struct sk_buff *skb)
+{
+	struct ovpn_socket *sock;
+	bool ret;
+
+	if (!skb->sk)
+		return false;
+
+	rcu_read_lock();
+	sock = rcu_dereference(peer->sock);
+	ret = sock && sock->sk == skb->sk;
+	rcu_read_unlock();
+
+	return ret;
+}
+
 /* Send user data to the network
  */
 netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
@@ -386,6 +411,13 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
 		}
 		goto drop_no_peer;
 	}
+
+	if (unlikely(ovpn_skb_is_looping(peer, skb))) {
+		net_warn_ratelimited("%s: recursive routing detected, dropping TCP packet to peer %u\n",
+				     netdev_name(ovpn->dev), peer->id);
+		goto drop;
+	}
+
 	/* dst was needed for peer selection - it can now be dropped */
 	skb_dst_drop(skb);
 
