From patchwork Fri May 22 16:43:47 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marco Baffo X-Patchwork-Id: 4964 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:788e:b0:861:c897:cb9d with SMTP id d14csp928887max; Fri, 22 May 2026 09:44:19 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ9I3z033LR33fHs2qA6c8EAg88vnLuxEyjpm2Y0aS16wCxyqXKmaKG6Y9Sq3P9pFQeKVQq2JfUAxVo=@openvpn.net X-Received: by 2002:a05:6870:c275:b0:43a:19fb:8b99 with SMTP id 586e51a60fabf-43b5ab561a3mr2568832fac.9.1779468259126; Fri, 22 May 2026 09:44:19 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1779468259; cv=none; d=google.com; s=arc-20240605; b=Im4sEfWST3RNNJzyKwbGta/tyQRmj1cJvwTvaCK5kJKQnKnYPSXD+PstZFgivmDRy5 mpcrKnfULY0O22DRplnBSv7f/ql1+RMnSAI3mgXpkpIAKHehGAgVN+8mGav09IVG8Gq0 LPimuMic6DCeis7ORev5ca3/aU4rrk+yBWZWQ7YsHi5FtoiX+vXdbZpaUAKy7iSiesal ZiHhWC5tAdO3w2aoXDO/kkgc37mmQvr/OxNrIQcOdfFZz1f3UBlbDSddLD00l3ZrXGck s/taS1ZskYgkpqg9ppDPxFQiSPfKLV+9jecUCnouR0Kq9PWdzHKZBsQGp4DyUlCpZWcQ dUCw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature:dkim-signature:dkim-signature; bh=scpBoJ5+Iad3baN+T4oDCDYekofbURU1vtn/Vfij1Os=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=CtTLDdWz52Oz5sD4ZQFMZ/z4RYwM2QiGIcLJTrKvhoCBMUg7nNardQncEvVV8p8Mwb 5NoqAdrRbO9quYpSiL44yIrxxiu59iDo59zZmWrT3B5VDIuTKmLZz57A18KHgMKfrJMq JEjyiYT9FOciFysYTZje3kd0HYNL90t3n+GERCIMTs8jhrSPiIbHofTq+t8RouWn6L2v XXyKZTE0HN6DlMTXUyKWZgPtvsYKFhRBfo0YuXp8y8fsUE49V4LNbnJOyJ1BemTGLPoo 2f8Ls18uBNoJ7qqf2TWb9jqHDeLdExgt+aG0uhHIygsOhVJKz4L7sZ8atAi0agy2xcEr uBeg==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=PFyPUw59; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=K3zt2pWf; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=aM+EREMZ; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=tRkZQbnB; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 586e51a60fabf-43b63c5221bsi1860783fac.270.2026.05.22.09.44.18 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Fri, 22 May 2026 09:44:19 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=PFyPUw59; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=K3zt2pWf; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=aM+EREMZ; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=tRkZQbnB; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.sourceforge.net; s=beta; h=Content-Transfer-Encoding:Content-Type: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:References:In-Reply-To:Message-ID:Date:To:From:Sender: Reply-To:Cc:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=scpBoJ5+Iad3baN+T4oDCDYekofbURU1vtn/Vfij1Os=; b=PFyPUw59LgpowRIPBYGSWwuVcx +xPoa93w1p2n1zr/0zRxGzy/c6Chb6A+xBS/2z7O2M+FNV1ctUNqFbhbTe3iSLa6v6NcDVfJj9QEX PquS5+6dmumpv4Y4SOpJVA//OyLWIf0vTmeek5VetXchueT32MGB4owETkFKo5KsOnIk=; Received: from [127.0.0.1] (helo=sfs-ml-2.v29.lw.sourceforge.com) by sfs-ml-2.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1wQSyr-0002vL-LO; Fri, 22 May 2026 16:44:14 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-2.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1wQSyp-0002v4-He for openvpn-devel@lists.sourceforge.net; Fri, 22 May 2026 16:44:12 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=hwXYZr8BaHJhBWDOE2ZBRArrBPD2paxMxacsjKg/Bvk=; b=K3zt2pWfb8/K7yJz3izEgdArDR 2NPN4q3OUz3cjzJUcovTmcOtZFPnlJP7qOmfT+m7KMB0Wt7QiPmx1fuldEP+RWHTkmREqVuj6La1X Nz9ES5+YgVUo8aI5sc4Uf8hpxmL1NE/97mcgQjek4s4xRTZpEjkD4BY3pVUesJ5NXQJU=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=hwXYZr8BaHJhBWDOE2ZBRArrBPD2paxMxacsjKg/Bvk=; b=aM+EREMZbGEEpcA+uJ18/4x2Sc X1muxCQ/v6++6UBZoE112n10++Bg9tPwpID3CDVE2VvVZty+vilgCpaMzkw3syWQCUWo+f9kCFsvQ evEFU25PntFVEs9kv0K2zIPZj39Lua5AyWHom97ESFwZqPPB+K74kHxkHwCliZtGto/I=; Received: from mout-b-107.mailbox.org ([195.10.208.47]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wQSyo-0004aj-B0 for openvpn-devel@lists.sourceforge.net; Fri, 22 May 2026 16:44:12 +0000 Received: from smtp1.mailbox.org (smtp1.mailbox.org [IPv6:2001:67c:2050:b231:465::1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-b-107.mailbox.org (Postfix) with ESMTPS id 4gMWM13bQQzDrp2; Fri, 22 May 2026 18:43:57 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1779468237; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=hwXYZr8BaHJhBWDOE2ZBRArrBPD2paxMxacsjKg/Bvk=; b=tRkZQbnBYxfm3lcBLOFw9WIDJ6MzI6kzSaFV4hULJIzEslcjyrdcoXu3fNg9Sg9HIPWBTo N16ADYJV0clNUvn1pdLisz2sPosghcUCMQSj8ZqHnxGS1WH317XMM2q1BojDR3A0rra5Vq DlGpJAmYVf+qnn+RV/DLUKHyTIRCpXLgPHBosUE/APG/U0w9hwAYAfqsyaAP+9+Q878inh dC3RM5tFKgCJltsxohWdzdKAO/a+gWvHOpFhSE6ihhiYPNCpJICBqnPJKuN5AYMJIzmYBc spD7h5Q+NPvsNNyQ4dO5zDUlen57ZERXhskN6TQgeXDPQhOYoj3uu39z7KK8Xg== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of marco@mandelbit.com designates 2001:67c:2050:b231:465::1 as permitted sender) smtp.mailfrom=marco@mandelbit.com From: Marco Baffo To: openvpn-devel@lists.sourceforge.net Date: Fri, 22 May 2026 18:43:47 +0200 Message-ID: <20260522164348.1904580-2-marco@mandelbit.com> In-Reply-To: <20260522164348.1904580-1-marco@mandelbit.com> References: <20260522164348.1904580-1-marco@mandelbit.com> MIME-Version: 1.0 X-Rspamd-Queue-Id: 4gMWM13bQQzDrp2 X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-1.hosts.colo.sdot.me", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: The ovpn DCO driver currently drops all multicast/broadcast packets because it does not set IFF_MULTICAST and IFF_BROADCAST on the netdevice and always performs a unicast peer lookup in ovpn_net_xmit( [...] Content analysis details: (-0.2 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain X-Headers-End: 1wQSyo-0004aj-B0 Subject: [Openvpn-devel] [RFC ovpn net-next v5 1/2] ovpn: add multicast/broadcast packet transmission support X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: 1865907709353852832 X-GMAIL-MSGID: 1865907709353852832 The ovpn DCO driver currently drops all multicast/broadcast packets because it does not set IFF_MULTICAST and IFF_BROADCAST on the netdevice and always performs a unicast peer lookup in ovpn_net_xmit(). This prevents multicast routing daemons such as smcroute from using an ovpn interface as a multicast VIF and makes it impossible to forward multicast and broadcast traffic to VPN clients. Add the minimal infrastructure needed to get multicast/broadcast working: - Set IFF_MULTICAST and IFF_BROADCAST in ovpn_setup(). - Detect multicast and broadcast destinations in ovpn_peer_get_by_dst() and set the bcast flag to true. - Introduce ovpn_bcast_work() to transmit enqueued broadcast messages. - Allow all IGMP/MLD packets to bypass the RPF check in the RX path. Multicast traffic is treated as broadcast and flooded to all peers. Signed-off-by: Marco Baffo --- Changes in v5 - Add cond_resched() to the broadcast worker loop so it yields when the queue still has more work. - Switch broadcast drop accounting to the new ovpn_dev_dstats_tx_dropped() helper introduced by: 0c0dddc07d27 ("ovpn: disable BHs when updating device stats") - Wrap ovpn_send() with local_bh_disable()/local_bh_enable() in the broadcast worker, matching the pattern already used by the keepalive worker (ovpn_peer_keepalive_send()) to avoid sends concurrecy. Changes in v4: - Extend the v3 bh-protection for dev_dstats_tx_dropped() to the ovpn_send() failure path. - Refactor ovpn_mcast_mld_offset() and ovpn_mcast_is_control() to use skb_header_pointer() instead of pskb_may_pull() + direct pointer access, avoiding to mutate the skb during read-only inspection. Changes in v3: - Guard dev_dstats_tx_dropped() in ovpn_bcast_work() with local_bh_disable() to avoid seqcount races with softirq updates to dev->dstats, matching ovpn_netdev_write(). Changes in v2: - Replace broadcast path with a deferred workqueue, avoiding GFP_ATOMIC: introduce struct ovpn_bcast (queue, work, wq) embedded in ovpn_priv. - Add struct llist_node bcast_entry to ovpn_peer to build a lockless peer snapshot under RCU without allocating peer list nodes. - Process broadcast packets in an ordered workqueue so the entire send path runs in process context and can use GFP_KERNEL. - Queue broadcast skbs directly to bcast.queue inside the main ovpn_net_xmit() loop instead of building a temporary skb_list. drivers/net/ovpn/io.c | 180 +++++++++++++++++++++++++++++++++++- drivers/net/ovpn/io.h | 3 + drivers/net/ovpn/main.c | 8 +- drivers/net/ovpn/ovpnpriv.h | 10 ++ drivers/net/ovpn/peer.c | 21 ++++- drivers/net/ovpn/peer.h | 6 +- 6 files changed, 217 insertions(+), 11 deletions(-) diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index 22c555dd962e..715794a6e4d2 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -105,6 +105,80 @@ static void ovpn_netdev_write(struct ovpn_peer *peer, struct sk_buff *skb) local_bh_enable(); } +/** + * ovpn_mcast_mld_offset - compute the offset to the MLD payload in an IPv6 packet + * @skb: the packet to inspect + * @offsetp: pointer to store the computed offset + * + * MLD packets may be preceded by a Hop-by-Hop options header containing + * the Router Alert option. Calculate the actual payload offset and + * verify that the next header is ICMPv6. + * + * Caller must ensure that the IPv6 header is linearized. + * + * Return: true if the offset was computed successfully, false otherwise + */ +static bool ovpn_mcast_mld_offset(const struct sk_buff *skb, unsigned int *offsetp) +{ + unsigned int offset = sizeof(struct ipv6hdr); + u8 nexthdr = ipv6_hdr(skb)->nexthdr; + struct ipv6_opt_hdr _hopopt, *hopopt; + + if (nexthdr != IPPROTO_HOPOPTS) + goto check_icmpv6; + + hopopt = skb_header_pointer(skb, offset, sizeof(_hopopt), &_hopopt); + if (!hopopt) + return false; + + nexthdr = hopopt->nexthdr; + offset += ipv6_optlen(hopopt); + +check_icmpv6: + if (nexthdr != IPPROTO_ICMPV6) + return false; + + *offsetp = offset; + return true; +} + +/** + * ovpn_mcast_is_control - determine whether an skb is multicast control traffic + * @skb: the packet to inspect + * + * Caller must ensure that IP/IPv6 headers are linearized. + * + * Return: true if the skb contains IGMP or MLD control traffic, + * false otherwise + */ +static bool ovpn_mcast_is_control(const struct sk_buff *skb) +{ + unsigned int offset; + struct icmp6hdr _ih, *ih; + + if (skb->protocol == htons(ETH_P_IP)) + return ip_hdr(skb)->protocol == IPPROTO_IGMP; + + if (skb->protocol != htons(ETH_P_IPV6)) + return false; + + if (!ovpn_mcast_mld_offset(skb, &offset)) + return false; + + ih = skb_header_pointer(skb, offset, sizeof(_ih), &_ih); + if (!ih) + return false; + switch (ih->icmp6_type) { + case ICMPV6_MGM_QUERY: + case ICMPV6_MGM_REPORT: + case ICMPV6_MGM_REDUCTION: + case ICMPV6_MLD2_REPORT: + return true; + } + + return false; +} + void ovpn_decrypt_post(void *data, int ret) { struct ovpn_crypto_key_slot *ks; @@ -183,8 +257,13 @@ void ovpn_decrypt_post(void *data, int ret) } skb->protocol = proto; - /* perform Reverse Path Filtering (RPF) */ - if (unlikely(!ovpn_peer_check_by_src(peer->ovpn, skb, peer))) { + /* perform Reverse Path Filtering (RPF). + * IGMP/MLD protocols may use source addresses + * that differ from the peer's VPN address + * so we bypass RPF in that case + */ + if (unlikely(!ovpn_mcast_is_control(skb) && + !ovpn_peer_check_by_src(peer->ovpn, skb, peer))) { if (skb->protocol == htons(ETH_P_IPV6)) net_dbg_ratelimited("%s: RPF dropped packet from peer %u, src: %pI6c\n", netdev_name(peer->ovpn->dev), @@ -351,6 +430,75 @@ static void ovpn_send(struct ovpn_priv *ovpn, struct sk_buff *skb, ovpn_peer_put(peer); } +static void ovpn_bcast_work(struct work_struct *work) +{ + struct ovpn_priv *ovpn = container_of_const(work, struct ovpn_priv, bcast.work); + struct sk_buff *skb, *to_send; + struct llist_head peer_list; + struct llist_node *node, *n; + struct ovpn_peer *peer; + int bkt; + + while ((skb = skb_dequeue(&ovpn->bcast.queue))) { + skb_mark_not_on_list(skb); + init_llist_head(&peer_list); + + rcu_read_lock(); + hash_for_each_rcu(ovpn->peers->by_id, bkt, peer, hash_entry_id) { + if (likely(ovpn_peer_hold(peer))) + llist_add(&peer->bcast_entry, &peer_list); + } + rcu_read_unlock(); + + if (unlikely(llist_empty(&peer_list))) { + ovpn_dev_dstats_tx_dropped(ovpn->dev); + skb_tx_error(skb); + kfree_skb(skb); + goto resched; + } + + llist_for_each_safe(node, n, peer_list.first) { + peer = llist_entry(node, struct ovpn_peer, bcast_entry); + + if (likely(n)) + to_send = skb_clone(skb, GFP_KERNEL); + else + to_send = skb; + + if (likely(to_send)) { + ovpn_peer_stats_increment_tx(&peer->vpn_stats, skb->len); + local_bh_disable(); + ovpn_send(ovpn, to_send, peer); + local_bh_enable(); + continue; + } + ovpn_dev_dstats_tx_dropped(ovpn->dev); + ovpn_peer_put(peer); + } +resched: + if (!skb_queue_empty(&ovpn->bcast.queue)) + cond_resched(); + } +} + +int ovpn_bcast_init(struct ovpn_priv *ovpn) +{ + skb_queue_head_init(&ovpn->bcast.queue); + INIT_WORK(&ovpn->bcast.work, ovpn_bcast_work); + ovpn->bcast.wq = alloc_ordered_workqueue("ovpn-bcast-%s", WQ_MEM_RECLAIM, + netdev_name(ovpn->dev)); + if (!ovpn->bcast.wq) + return -ENOMEM; + + return 0; +} + +void ovpn_bcast_exit(struct ovpn_priv *ovpn) +{ + cancel_work_sync(&ovpn->bcast.work); + skb_queue_purge(&ovpn->bcast.queue); +} + /* Send user data to the network */ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev) @@ -362,6 +510,7 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev) struct ovpn_peer *peer; __be16 proto; int ret; + bool bcast = false; /* reset netfilter state */ nf_reset_ct(skb); @@ -372,8 +521,8 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev) goto drop_no_peer; /* retrieve peer serving the destination IP of this packet */ - peer = ovpn_peer_get_by_dst(ovpn, skb); - if (unlikely(!peer)) { + peer = ovpn_peer_get_by_dst(ovpn, skb, &bcast); + if (unlikely(!peer && !bcast)) { switch (skb->protocol) { case htons(ETH_P_IP): net_dbg_ratelimited("%s: no peer to send data to dst=%pI4\n", @@ -418,11 +567,31 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev) continue; } + if (unlikely(bcast)) { + spin_lock_bh(&ovpn->bcast.queue.lock); + if (unlikely(skb_queue_len(&ovpn->bcast.queue) >= OVPN_BCAST_MAX_QLEN)) { + spin_unlock_bh(&ovpn->bcast.queue.lock); + ovpn_dev_dstats_tx_dropped(ovpn->dev); + skb_tx_error(curr); + kfree_skb(curr); + continue; + } + __skb_queue_tail(&ovpn->bcast.queue, curr); + spin_unlock_bh(&ovpn->bcast.queue.lock); + continue; + } + /* only count what we actually send */ tx_bytes += curr->len; __skb_queue_tail(&skb_list, curr); } + if (unlikely(bcast)) { + if (!skb_queue_empty(&ovpn->bcast.queue)) + queue_work(ovpn->bcast.wq, &ovpn->bcast.work); + return NETDEV_TX_OK; + } + /* no segments survived: don't jump to 'drop' because we already * incremented the counter for each failure in the loop */ @@ -438,7 +607,8 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev) return NETDEV_TX_OK; drop: - ovpn_peer_put(peer); + if (peer) + ovpn_peer_put(peer); drop_no_peer: dev_dstats_tx_dropped(ovpn->dev); skb_tx_error(skb); diff --git a/drivers/net/ovpn/io.h b/drivers/net/ovpn/io.h index db9e10f9077c..9519b9a08030 100644 --- a/drivers/net/ovpn/io.h +++ b/drivers/net/ovpn/io.h @@ -31,4 +31,7 @@ void ovpn_xmit_special(struct ovpn_peer *peer, const void *data, void ovpn_encrypt_post(void *data, int ret); void ovpn_decrypt_post(void *data, int ret); +int ovpn_bcast_init(struct ovpn_priv *ovpn); +void ovpn_bcast_exit(struct ovpn_priv *ovpn); + #endif /* _NET_OVPN_OVPN_H_ */ diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index 2e0420febda0..0537b3d22cf6 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -30,6 +30,8 @@ static void ovpn_priv_free(struct net_device *net) { struct ovpn_priv *ovpn = netdev_priv(net); + if (ovpn->bcast.wq) + destroy_workqueue(ovpn->bcast.wq); kfree(ovpn->peers); } @@ -155,7 +157,7 @@ static void ovpn_setup(struct net_device *dev) dev->max_mtu = IP_MAX_MTU - OVPN_HEAD_ROOM; dev->type = ARPHRD_NONE; - dev->flags = IFF_POINTOPOINT | IFF_NOARP; + dev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST | IFF_BROADCAST; dev->priv_flags |= IFF_NO_QUEUE; /* when routing packets to a LAN behind a client, we rely on the * route entry that originally brought the packet into ovpn, so @@ -192,6 +194,9 @@ static int ovpn_newlink(struct net_device *dev, spin_lock_init(&ovpn->lock); INIT_DELAYED_WORK(&ovpn->keepalive_work, ovpn_peer_keepalive_work); + if (ovpn_bcast_init(ovpn)) + return -ENOMEM; + /* Set carrier explicitly after registration, this way state is * clearly defined. * @@ -212,6 +217,7 @@ static void ovpn_dellink(struct net_device *dev, struct list_head *head) { struct ovpn_priv *ovpn = netdev_priv(dev); + ovpn_bcast_exit(ovpn); cancel_delayed_work_sync(&ovpn->keepalive_work); ovpn_peers_free(ovpn, NULL, OVPN_DEL_PEER_REASON_TEARDOWN); unregister_netdevice_queue(dev, head); diff --git a/drivers/net/ovpn/ovpnpriv.h b/drivers/net/ovpn/ovpnpriv.h index 5898f6adada7..5c86b239527b 100644 --- a/drivers/net/ovpn/ovpnpriv.h +++ b/drivers/net/ovpn/ovpnpriv.h @@ -32,6 +32,14 @@ struct ovpn_peer_collection { struct hlist_nulls_head by_transp_addr[1 << 12]; }; +#define OVPN_BCAST_MAX_QLEN 1000 + +struct ovpn_bcast { + struct sk_buff_head queue; + struct work_struct work; + struct workqueue_struct *wq; +}; + /** * struct ovpn_priv - per ovpn interface state * @dev: the actual netdev representing the tunnel @@ -41,6 +49,7 @@ struct ovpn_peer_collection { * @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 + * @bcast: struct used to queue and transmit broadcast messages */ struct ovpn_priv { struct net_device *dev; @@ -50,6 +59,7 @@ struct ovpn_priv { struct ovpn_peer __rcu *peer; struct gro_cells gro_cells; struct delayed_work keepalive_work; + struct ovpn_bcast bcast; }; #endif /* _NET_OVPN_OVPNSTRUCT_H_ */ diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index c02dfab51a6e..d1616e04c0ad 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -722,6 +722,8 @@ static void ovpn_peer_remove(struct ovpn_peer *peer, * ovpn_peer_get_by_dst - Lookup peer to send skb to * @ovpn: the private data representing the current VPN session * @skb: the skb to extract the destination address from + * @bcast: a pointer to a bool. It's set to true if the packet is a + * broadcast or a multicast. * * This function takes a tunnel packet and looks up the peer to send it to * after encapsulation. The skb is expected to be the in-tunnel packet, without @@ -731,10 +733,11 @@ static void ovpn_peer_remove(struct ovpn_peer *peer, * * Return: the peer if found or NULL otherwise. */ -struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_priv *ovpn, - struct sk_buff *skb) +struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_priv *ovpn, struct sk_buff *skb, + bool *bcast) { struct ovpn_peer *peer = NULL; + unsigned int addr_type; struct in6_addr addr6; __be32 addr4; @@ -755,11 +758,23 @@ struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_priv *ovpn, case htons(ETH_P_IP): addr4 = ovpn_nexthop_from_skb4(skb); peer = ovpn_peer_get_by_vpn_addr4(ovpn, addr4); + + if (peer) + break; + + addr_type = inet_dev_addr_type(dev_net(ovpn->dev), ovpn->dev, addr4); + if (addr_type == RTN_MULTICAST || addr_type == RTN_BROADCAST) + *bcast = true; break; case htons(ETH_P_IPV6): addr6 = ovpn_nexthop_from_skb6(skb); peer = ovpn_peer_get_by_vpn_addr6(ovpn, &addr6); - break; + + if (peer) + break; + + if (ipv6_addr_is_multicast(&addr6)) + *bcast = true; } if (unlikely(peer && !ovpn_peer_hold(peer))) diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index 328401570cba..2b5027d0ad01 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -59,6 +59,7 @@ * @refcount: reference counter * @rcu: used to free peer in an RCU safe way * @release_entry: entry for the socket release list + * @bcast_entry: entry for the broadcast peers list * @keepalive_work: used to schedule keepalive sending */ struct ovpn_peer { @@ -113,6 +114,7 @@ struct ovpn_peer { struct kref refcount; struct rcu_head rcu; struct llist_node release_entry; + struct llist_node bcast_entry; struct work_struct keepalive_work; }; @@ -148,8 +150,8 @@ void ovpn_peers_free(struct ovpn_priv *ovpn, struct sock *sock, struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_priv *ovpn, struct sk_buff *skb); struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_priv *ovpn, u32 peer_id); -struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_priv *ovpn, - struct sk_buff *skb); +struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_priv *ovpn, struct sk_buff *skb, + bool *bcast); void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer); bool ovpn_peer_check_by_src(struct ovpn_priv *ovpn, struct sk_buff *skb, struct ovpn_peer *peer); From patchwork Fri May 22 16:43:48 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marco Baffo X-Patchwork-Id: 4965 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:788e:b0:861:c897:cb9d with SMTP id d14csp928899max; Fri, 22 May 2026 09:44:20 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ9PNq1dqp6aHkc2A8oByvR+dBRhfkqVs+q78Z82ECBfEivk0mJmjhDsq6iXiA+vgZmaOk/fLhMjNYo=@openvpn.net X-Received: by 2002:a05:6820:200e:b0:69d:64a3:6320 with SMTP id 006d021491bc7-69d6dfebeefmr3605311eaf.6.1779468260175; Fri, 22 May 2026 09:44:20 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1779468260; cv=none; d=google.com; s=arc-20240605; b=XRrbuBrlEuppuznI3XTnyUrDaBgIVCtm/K/ya4Bkj5bJ65o1x/yMsD7r2ZXk3BAO3e UgPEbHWVggAvAkPG/j3JUcLn5qbT6UgMhdDGf5OKFXrmFqiP0Dr//PLYe5Ecz1k/Yyu4 8vRbkbEIBlFabVr0JbwbfyrbrVVH1KBdhqb1E/06ey83If4x1w/LlrW9Ba5IkM/kIutY K8gFDLAFCVk5n2IeuXFR9TAnN6GPxW39bPsSF/86XpnmVjaSICXNHS4S+yATDAe9Wow5 gPvF1h9QkymvwROzxlPBHf8+Ic31kZolVrCPsUFL/HHKD1/GGw3KmxgULpeyreKtdQ91 ETrQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature:dkim-signature:dkim-signature; bh=SJDMLyXYtp/e7gmQYepnPUUimxs3SgwgrvsjMozNisc=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=XZKypTz91yRLPPNZl4ZaYdXFJtQrd1DfL8Qoqfsw/LpoWrWp+S0LeKYbKfDX/GepAs DJuvFuYTx3jSt2G3cD9pOg2hdm5jX4/Xr65g/5vII2+6CnBYvD2cGoVBI6axSBSmkhsu t1fw+kxSXLtqohQd/Ri/sg6wKUexMq/zolTJha3mE/8e77enZ6Wp7CcG6FThC+gpXhVl YtAvoozzRR+WMEDUAFv1eg4J4CQBspeR3bx1aDFUiQkocmdhG1cud4Gt3uhHJsNSBHup 0nDlesp0T6d2ILbaqEtPeKCaVPDS41aHbmKRks6lUg/sC9vSNVG++7AGt7BhFoeVHfv5 GCQw==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=Q+QV7IQg; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=acSLnZUE; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=XRfhY5ZV; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=DGhVRfAP; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 586e51a60fabf-43b63bd4a54si1838006fac.207.2026.05.22.09.44.19 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Fri, 22 May 2026 09:44:20 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=Q+QV7IQg; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=acSLnZUE; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=XRfhY5ZV; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=DGhVRfAP; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.sourceforge.net; s=beta; h=Content-Transfer-Encoding:Content-Type: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:References:In-Reply-To:Message-ID:Date:To:From:Sender: Reply-To:Cc:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=SJDMLyXYtp/e7gmQYepnPUUimxs3SgwgrvsjMozNisc=; b=Q+QV7IQgOQHy9QsVZJgwAZowiS 25VfSGKVkRI3tMKnl5iObW+Kd0Y7v2erig/nDTRWUPw7v8Bo1LU7PL796yl7Mjob0heeji048nMfI N4AvJ4kl1RPGd7t8TQ5BaA8Q5Du1ctjF+AjtSBubF7vWLt3CV7zSXxkH4b2eKD7kYmDU=; Received: from [127.0.0.1] (helo=sfs-ml-1.v29.lw.sourceforge.com) by sfs-ml-1.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1wQSyr-00004w-QR; Fri, 22 May 2026 16:44:11 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-1.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1wQSyq-0008WU-5g for openvpn-devel@lists.sourceforge.net; Fri, 22 May 2026 16:44:09 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=57jUF+lhhGxOOfHidCQJryhhSbNQrKky/44vvRdq5bs=; b=acSLnZUEQE+uN7qjNSdtu1W7O8 dKsZgrs53IQ9geptFTzlG9L1dBziEKFSjJCZWPiSoU2EtKIQuliIzVctQ6aBLp6G26slfwjvDDqwc YWrbF/UtcfoU8K6xOY3u2ZP3UB8N/U3wT17qV8qPdh3u0IR+UdSC6fAAc3GtEDK1UMV0=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=57jUF+lhhGxOOfHidCQJryhhSbNQrKky/44vvRdq5bs=; b=XRfhY5ZVcVIuQENsmXablOlQG5 bcOTJ1vbFbTZK+gahvAgH/ezqInDTOFunmE65ISFWHLNxuA5Pg9MRvi3CdxfCmdc/+e7w0Xheemwd u2Xqk3YAGXC0EAJxGrTHG3vrbDULxmUY8F8HpNTOj37M/bbXTuzAzsBfLfgdoLNmS3Xg=; Received: from mout-b-106.mailbox.org ([195.10.208.46]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wQSyk-0004aS-IC for openvpn-devel@lists.sourceforge.net; Fri, 22 May 2026 16:44:08 +0000 Received: from smtp1.mailbox.org (smtp1.mailbox.org [10.196.197.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-b-106.mailbox.org (Postfix) with ESMTPS id 4gMWM31ZZFzDsJH; Fri, 22 May 2026 18:43:59 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1779468239; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=57jUF+lhhGxOOfHidCQJryhhSbNQrKky/44vvRdq5bs=; b=DGhVRfAPs2SIzCzQ13Eq+anCo2qulQU+y21w8xCzCHyjSXwlS3uwC5GgrANeF/E5v3LGFD LlWYW3re37pfk8yiN5ppNl0E99yJ6t1E58oCtY+azw9uzyg/sNQpv3t+8GOUknI1EIJd/j jfM51VL8DaKWwCWhNSQhoetzsPZIpeqBiR7FWrAgGfRIxsKV3Ba4Vt1HSs9aFFSSxBiawB KN647AtJ8i1/zwGf3aFlFSOlDYt+rNbwwif+qLZ7dshX4EYGkUWuWuyigAIXne8fbm+hUy jQOAMCLPONKTimp8umBH25awL03oVBgc+/nJWidAfHhro3KR8j3IkI++JyphIw== From: Marco Baffo To: openvpn-devel@lists.sourceforge.net Date: Fri, 22 May 2026 18:43:48 +0200 Message-ID: <20260522164348.1904580-3-marco@mandelbit.com> In-Reply-To: <20260522164348.1904580-1-marco@mandelbit.com> References: <20260522164348.1904580-1-marco@mandelbit.com> MIME-Version: 1.0 X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-1.hosts.colo.sdot.me", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: Add a test stage that verifies the ovpn module forwards broadcast (IPv4) and multicast (IPv4/v6) packets to all active peers. For each mode we start tcpdump on every client peer, send a single ping from peer0 to the broadcast/multicast address, and verify all peers captured the packet. Content analysis details: (-0.2 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain X-Headers-End: 1wQSyk-0004aS-IC Subject: [Openvpn-devel] [RFC ovpn net-next v5 2/2] ovpn: add broadcast and multicast selftests X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: 1865907710422636336 X-GMAIL-MSGID: 1865907710422636336 Add a test stage that verifies the ovpn module forwards broadcast (IPv4) and multicast (IPv4/v6) packets to all active peers. For each mode we start tcpdump on every client peer, send a single ping from peer0 to the broadcast/multicast address, and verify all peers captured the packet. IPv6 link-local addresses are assigned to TUN interfaces so that ping to ff02::1 can select a valid source address. Signed-off-by: Marco Baffo --- tools/testing/selftests/net/ovpn/common.sh | 1 + tools/testing/selftests/net/ovpn/test.sh | 58 +++++++++++++++++++++- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/net/ovpn/common.sh b/tools/testing/selftests/net/ovpn/common.sh index 2d844eb3aa6e..c0ca08171fa1 100644 --- a/tools/testing/selftests/net/ovpn/common.sh +++ b/tools/testing/selftests/net/ovpn/common.sh @@ -174,6 +174,7 @@ ovpn_setup_ns() { ip -n "${peer}" link set mtu ${3} dev tun${1} fi ip -n "${peer}" link set tun${1} up + ip -n "${peer}" addr add fe80::$(( ${1} + 1 ))/64 dev tun${1} scope link } ovpn_build_capture_filter() { diff --git a/tools/testing/selftests/net/ovpn/test.sh b/tools/testing/selftests/net/ovpn/test.sh index c06e3135fbef..e485282025e8 100755 --- a/tools/testing/selftests/net/ovpn/test.sh +++ b/tools/testing/selftests/net/ovpn/test.sh @@ -56,6 +56,59 @@ ovpn_prepare_network() { done } +ovpn_run_mbcast_tests() { + local p + local peer_ns + local -a pids=() + + ovpn_log "Testing broadcast:" + for p in $(seq 1 "${OVPN_NUM_PEERS}"); do + peer_ns="ovpn_peer${p}" + timeout 3 ip netns exec "${peer_ns}" \ + tcpdump --immediate-mode -p -ni "tun${p}" -c 1 \ + 'icmp and dst host 5.5.5.255' >/dev/null 2>&1 & + pids+=($!) + done + sleep 0.5 + ovpn_cmd_mayfail "send broadcast ping from peer0" \ + ip netns exec ovpn_peer0 ping -qbc 1 -w 3 -I tun0 5.5.5.255 + for pid in "${pids[@]}"; do + wait "${pid}" || return 1 + done + pids=() + + ovpn_log "Testing multicast IPv4:" + for p in $(seq 1 "${OVPN_NUM_PEERS}"); do + peer_ns="ovpn_peer${p}" + timeout 3 ip netns exec "${peer_ns}" \ + tcpdump --immediate-mode -p -ni "tun${p}" -c 1 \ + 'icmp and dst host 224.0.0.1' >/dev/null 2>&1 & + pids+=($!) + done + sleep 0.5 + ovpn_cmd_mayfail "send IPv4 multicast ping from peer0" \ + ip netns exec ovpn_peer0 ping -qc 1 -w 3 -I tun0 224.0.0.1 + for pid in "${pids[@]}"; do + wait "${pid}" || return 1 + done + pids=() + + ovpn_log "Testing multicast IPv6:" + for p in $(seq 1 "${OVPN_NUM_PEERS}"); do + peer_ns="ovpn_peer${p}" + timeout 3 ip netns exec "${peer_ns}" \ + tcpdump --immediate-mode -p -ni "tun${p}" -c 1 \ + 'icmp6 and dst host ff02::1' >/dev/null 2>&1 & + pids+=($!) + done + sleep 0.5 + ovpn_cmd_mayfail "send IPv6 multicast ping from peer0" \ + ip netns exec ovpn_peer0 ping -6 -qc 1 -w 3 -I tun0 ff02::1 + for pid in "${pids[@]}"; do + wait "${pid}" || return 1 + done +} + ovpn_run_basic_traffic() { local p local header1 @@ -293,9 +346,9 @@ trap ovpn_stage_err ERR ktap_print_header if [ "${OVPN_FLOAT}" == "1" ]; then - ktap_set_plan 13 + ktap_set_plan 14 else - ktap_set_plan 12 + ktap_set_plan 13 fi ovpn_cleanup @@ -303,6 +356,7 @@ modprobe -q ovpn || true ovpn_run_stage "setup network topology" ovpn_prepare_network ovpn_run_stage "run baseline data traffic" ovpn_run_basic_traffic +ovpn_run_stage "run multi/broadcast traffic" ovpn_run_mbcast_tests ovpn_run_stage "run LAN traffic behind peer1" ovpn_run_lan_traffic [ "${OVPN_FLOAT}" == "1" ] && ovpn_run_stage "run floating peer checks" \ ovpn_run_float_mode