From patchwork Fri Sep 18 06:30:35 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marco Baffo X-Patchwork-Id: 5382 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:6446:b0:8a0:ea1f:253a with SMTP id n6csp8383843mag; Thu, 17 Sep 2026 23:31:03 -0700 (PDT) X-Forwarded-Encrypted: i=2; AKwUvBxGZyvfixQgTHL4II72Dl+IYIxBWWftHjfn60KruasUzV8c9aRiDi9etcF1HBVrD8c6TG/oMtP0HdA=@openvpn.net X-Received: by 2002:a05:6830:81d7:b0:804:d066:b9a5 with SMTP id 46e09a7af769-80de15a19ccmr1741555a34.13.1789713063095; Thu, 17 Sep 2026 23:31:03 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1789713063; cv=none; d=google.com; s=arc-20260327; b=EslCaAOHGkEMaalQWf+b+N0zpOP5y1inGVbuoP8mLPuG7/PwICnOVEEmQV6sEjft+t S97gbDEbPExpAo42LbEL03ah3wLn0mEFLvudzj8fjncFiAeIVyUDmx7G3IB+9JFdkdfF SzFsyqurJjDzS2shkcPZUFhqFgMZvC46i0nb7xXfYMzBV7DWhsloBMU6vfnWMhwOHZW1 TXxWkW47cTBRgtuH0QnxYBBsxfvOnq0gYvu/cosYu/46q1CcQ/w+SszsVKGkzB3Wyrqw ctG3bPrAMT/ZeOxICWEbLyLapvNn8xg1asUByizcSnM+OlCLRJotXqLYHknxSsX4q8Ax CcoQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=errors-to:content-transfer-encoding:cc:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:message-id:date:to:from:dkim-signature:dkim-signature :dkim-signature:dkim-signature; bh=s6JlOPVYBJlyjZhBmdNGz4K6YUxDyBDTxwcLmrfUlN4=; fh=BsMg/B0Yb/hS/rzP5Npz4luh0IleZm8REk1XWiWRt2A=; b=BYPW6THtAR584LnUQY11JRI1KdfeGgCAY4t7B5jb7KDvVkCgn5BUERI30EXysosxQ7 GLwmn86aj5hq8V0qcDaYOQzkJuvuIef84VNOCiac20KwBNaIUXhqHC5FJWLya9CBGfKO 6lrl3F8iF9EmaaoME339L8zA7/wsNNWlqGkQPP4j3bKM5WlndFVpnFS38xMVzD68OsCv Q1wTU3Z7zQdDE38jnXcnFImBWi0T7b6kVnFbRoR55PZrJwd2M+isSkU7juemHhQ1KuVC SZAOfZ7lude5tXhyP2yjQ+7jFVUw+3YavLCw+Rl06Zt6etPsBLrAqIUmrhCmTHfqU9XO hPjg==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=ixgoN+Qe; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b="mkodm/Wt"; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=YC9hsYU7; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=J2Xrl6TA; 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 46e09a7af769-80e51a76e23si1393756a34.80.2026.09.17.23.31.02 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 17 Sep 2026 23:31:03 -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=ixgoN+Qe; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b="mkodm/Wt"; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=YC9hsYU7; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=J2Xrl6TA; 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:Cc: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:Message-ID:Date:To:From:Sender:Reply-To:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:In-Reply-To:References:List-Owner; bh=s6JlOPVYBJlyjZhBmdNGz4K6YUxDyBDTxwcLmrfUlN4=; b=ixgoN+Qe/YkA2ANvhHM2FU59vu CRc6DFLfvWDkOm1s9hClSswZjOju4N8VYBrjFsHlV8WYoYi0bacWzPbqce7urz+M7whwoTkkDkbuu 2InCV2lnG4YIJ73i5CW9jzt2Zfr7/6vfGp65LUA1fEJgJqt4lwItCACrKVxhpK2DraKI=; 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 1x7S7d-0007AW-KD; Fri, 18 Sep 2026 06:30:55 +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 1x7S7a-0007AM-D7 for openvpn-devel@lists.sourceforge.net; Fri, 18 Sep 2026 06:30:52 +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: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:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=nTk0X34JXjoLX0GTax1XVRHsXxJ3soR/3LkFYur4rRA=; b=mkodm/Wt8lu5MZv+LkDHbw4VtV 2l5XuSF3HkRQFX1f2x4rIP53bylE/d21tQ4EIzbFmdHhpRj2SS2pqUv9j/tixZc65WKp7RyQEbwk6 ynQRaFOmvf2ba7wsgOSM1KZza+yluHGh/uxxWosh1gBZ4GEkRZXRb2ZbtRg/mP987kcA=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version: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:In-Reply-To: References:List-Id:List-Help:List-Unsubscribe:List-Subscribe:List-Post: List-Owner:List-Archive; bh=nTk0X34JXjoLX0GTax1XVRHsXxJ3soR/3LkFYur4rRA=; b=Y C9hsYU7DKu5pljrFeNF856nDatDWPSQbEObX6Y0uH6zNkrtv0aHiNLf77YL94agoLMP1wlvs9doTj urba5T/E4i+kql2PLkVjGEBjAh44961VSfIrYRTtFDvIT7wDkYjnIbXbbo2y0A2moWXQWxbo8cVpw 8QvHd2dPywtfvboI=; Received: from mout-b-112.mailbox.org ([195.10.208.42]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1x7S7V-0007zA-Nx for openvpn-devel@lists.sourceforge.net; Fri, 18 Sep 2026 06:30:51 +0000 Received: from smtp2.mailbox.org (smtp2.mailbox.org [IPv6:2001:67c:2050:b231:465::2]) (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-112.mailbox.org (Postfix) with ESMTPS id 4hmN6T1Lf3z5x4q; Fri, 18 Sep 2026 08:30:41 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1789713041; 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; bh=nTk0X34JXjoLX0GTax1XVRHsXxJ3soR/3LkFYur4rRA=; b=J2Xrl6TA8t1cPaw9hXD2kbqHUCXAcycxddIo+/Xz0Itr0JVUz8fWjCjanZq98gtN0SMKzG kjC1LrnkzU+HqqwWEXfrP4hQHyF6WUpP1Ev6Q8cb/Vcr0n9Vhf5zfAc35UnXe4QRozOCEJ iKEzy76qTTMe4P44QkE14ZiufrQ8aNBsdrSw0xhFGW1uOj87iWD2hefAFFJXGDe/QAjm/d dI0SqS6rcUoTJGHa28obo6jrOk1qPQFjhMwnWDEAyO8M4l5V24dmnijD906YFLCXV7UKRI HOOA7zeDodYBQOX+8gyxsk5hrN8CczLIenKg8ZcLjdTgcQVEiH8N9gRCkmY8gw== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of marco@mandelbit.com designates 2001:67c:2050:b231:465::2 as permitted sender) smtp.mailfrom=marco@mandelbit.com From: Marco Baffo To: openvpn-devel@lists.sourceforge.net Date: Fri, 18 Sep 2026 08:30:35 +0200 Message-ID: <20260918063036.1588046-1-marco@mandelbit.com> MIME-Version: 1.0 X-Rspamd-Queue-Id: 4hmN6T1Lf3z5x4q X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-2.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_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain X-Headers-End: 1x7S7V-0007zA-Nx Subject: [Openvpn-devel] [RFC ovpn net-next v7 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: , Cc: Antonio Quartulli Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: 1876650165076080833 X-GMAIL-MSGID: 1876650165076080833 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 IGMP/MLD control 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 v7: - Move broadcast work teardown back to ovpn_net_uninit(), using disable_work_sync() and purging the pending skb queue, since no race conditions with ovpn_net_xmit() is actually possible. - Tighten the IGMP/MLD RPF exemption with source-address, message-type, and header validation. - Call cond_resched() per peer instead of per skb. - Reuse the module-wide ovpn_wq and remove the dedicated broadcast workqueue. - Remove the redundant skb_mark_not_on_list() after skb_dequeue(). - Use spin_lock() instead of spin_lock_bh() in ovpn_net_xmit(), where bottom halves are already disabled. - Initialize *bcast to false in ovpn_peer_get_by_dst(). Changes in v6: - Moved cancel_work_sync(&ovpn->bcast.work) and skb_queue_purge(&ovpn->bcast.queue) to ovp_priv_free() to avoid possibles race conditions with ovpn_net_xmit(). 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 | 239 +++++++++++++++++++++++++++++++++++- drivers/net/ovpn/io.h | 2 + drivers/net/ovpn/main.c | 7 +- drivers/net/ovpn/ovpnpriv.h | 9 ++ drivers/net/ovpn/peer.c | 23 +++- drivers/net/ovpn/peer.h | 6 +- 6 files changed, 275 insertions(+), 11 deletions(-) diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index 9526f8096da60..c3dfeec32a248 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -8,6 +8,7 @@ */ #include +#include #include #include #include @@ -105,6 +106,158 @@ 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 + * + * RFC 9777, section 5, requires a Hop-by-Hop Router Alert option for MLD. + * Verify it and locate the following ICMPv6 header. + * + * 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); + struct ipv6_opt_hdr _hopopt, *hopopt; + unsigned int len; + + if (ipv6_hdr(skb)->nexthdr != IPPROTO_HOPOPTS) + return false; + + hopopt = skb_header_pointer(skb, offset, sizeof(_hopopt), &_hopopt); + if (!hopopt || hopopt->nexthdr != IPPROTO_ICMPV6) + return false; + + len = ipv6_optlen(hopopt); + if (ntohs(ipv6_hdr(skb)->payload_len) < len + sizeof(struct icmp6hdr)) + return false; + + const unsigned int opt_end = offset + len; + + offset += sizeof(_hopopt); + while (offset < opt_end) { + const u8 *opt; + u8 _opt[4]; + + opt = skb_header_pointer(skb, offset, + min_t(unsigned int, sizeof(_opt), opt_end - offset), + _opt); + if (!opt) + return false; + + if (opt[0] == IPV6_TLV_PAD1) { + offset++; + continue; + } + + if (opt_end < offset + 2) + return false; + + len = 2 + opt[1]; + if (len + offset > opt_end) + return false; + + if (opt[0] == IPV6_TLV_ROUTERALERT) { + /* MLD Router Alert: two-byte value, both bytes zero. */ + if (len != sizeof(_opt) || opt[2] || opt[3]) + return false; + *offsetp = opt_end; + return true; + } + offset += len; + } + + return false; +} + +/** + * ovpn_mcast_is_control - identify multicast control traffic exempt from RPF + * @skb: the packet to inspect + * + * Caller must ensure that IP/IPv6 headers are linearized. + * + * Return: true if the skb contains IGMP or MLD traffic exempt from RPF, + * false otherwise + */ +static bool ovpn_mcast_is_control(const struct sk_buff *skb) +{ + unsigned int offset; + struct icmp6hdr _ih, *ih; + int addr_type; + + if (skb->protocol == htons(ETH_P_IP)) { + const struct igmphdr *igmp; + struct igmphdr _igmp; + + /* RFC 9776, section 4.2.14, permits IGMP reports to use + * 0.0.0.0 before acquiring an IP address. RFC 4541, section + * 2.1.1, also permits proxy queries with this source address. + * Unicast sources must pass the normal RPF check. + */ + if (ip_hdr(skb)->protocol != IPPROTO_IGMP || + ip_hdr(skb)->saddr != 0) + return false; + + if (ip_is_fragment(ip_hdr(skb))) + return false; + + offset = ip_hdrlen(skb); + if (offset < sizeof(struct iphdr) || + ntohs(ip_hdr(skb)->tot_len) < offset + sizeof(_igmp)) + return false; + + igmp = skb_header_pointer(skb, offset, sizeof(_igmp), &_igmp); + if (!igmp) + return false; + + switch (igmp->type) { + case IGMP_HOST_MEMBERSHIP_QUERY: + case IGMP_HOST_MEMBERSHIP_REPORT: + case IGMPV2_HOST_MEMBERSHIP_REPORT: + case IGMPV3_HOST_MEMBERSHIP_REPORT: + return true; + default: + return false; + } + } + + /* RFC 9777, section 5, requires a Hop Limit of 1 for MLD. */ + if (skb->protocol != htons(ETH_P_IPV6) || ipv6_hdr(skb)->hop_limit != 1) + return false; + + addr_type = ipv6_addr_type(&ipv6_hdr(skb)->saddr); + if (addr_type != IPV6_ADDR_ANY && + addr_type != (IPV6_ADDR_UNICAST | IPV6_ADDR_LINKLOCAL)) + 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: + /* RFC 3590, section 4, and RFC 9777, section 5.1.14 + * require link-local query sources. + */ + return addr_type != IPV6_ADDR_ANY; + case ICMPV6_MGM_REPORT: + case ICMPV6_MGM_REDUCTION: + case ICMPV6_MLD2_REPORT: + /* RFC 3590, section 4, and RFC 9777, section 5.2.14, also + * permit unspecified sources for reports and Done messages + * before a link-local address is available. + */ + return true; + } + + return false; +} + void ovpn_decrypt_post(void *data, int ret) { struct ovpn_crypto_key_slot *ks; @@ -183,8 +336,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 +509,55 @@ static void ovpn_send(struct ovpn_priv *ovpn, struct sk_buff *skb, ovpn_peer_put(peer); } +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))) { + 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); + cond_resched(); + continue; + } + + 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(); + } else { + ovpn_dev_dstats_tx_dropped(ovpn->dev); + ovpn_peer_put(peer); + } + cond_resched(); + } + } +} + /* Send user data to the network */ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev) @@ -362,6 +569,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 +580,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 +626,31 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev) continue; } + if (unlikely(bcast)) { + spin_lock(&ovpn->bcast.queue.lock); + if (unlikely(skb_queue_len(&ovpn->bcast.queue) >= OVPN_BCAST_MAX_QLEN)) { + spin_unlock(&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(&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_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 +666,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: ovpn_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 db9e10f9077c4..576f425c631af 100644 --- a/drivers/net/ovpn/io.h +++ b/drivers/net/ovpn/io.h @@ -31,4 +31,6 @@ 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); +void ovpn_bcast_work(struct work_struct *work); + #endif /* _NET_OVPN_OVPN_H_ */ diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index 0708249e9607c..a199f7e51050e 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -83,6 +83,8 @@ static void ovpn_net_uninit(struct net_device *dev) struct ovpn_priv *ovpn = netdev_priv(dev); disable_delayed_work_sync(&ovpn->keepalive_work); + disable_work_sync(&ovpn->bcast.work); + skb_queue_purge(&ovpn->bcast.queue); ovpn_peers_free(ovpn, NULL, OVPN_DEL_PEER_REASON_TEARDOWN); gro_cells_destroy(&ovpn->gro_cells); } @@ -176,7 +178,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 @@ -214,6 +216,9 @@ static int ovpn_newlink(struct net_device *dev, spin_lock_init(&ovpn->lock); INIT_DELAYED_WORK(&ovpn->keepalive_work, ovpn_peer_keepalive_work); + skb_queue_head_init(&ovpn->bcast.queue); + INIT_WORK(&ovpn->bcast.work, ovpn_bcast_work); + /* Set carrier explicitly after registration, this way state is * clearly defined. * diff --git a/drivers/net/ovpn/ovpnpriv.h b/drivers/net/ovpn/ovpnpriv.h index 84499140e4bd9..b32b622dbcfaf 100644 --- a/drivers/net/ovpn/ovpnpriv.h +++ b/drivers/net/ovpn/ovpnpriv.h @@ -36,6 +36,13 @@ 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 ovpn_priv - per ovpn interface state * @dev: the actual netdev representing the tunnel @@ -45,6 +52,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; @@ -54,6 +62,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 c95656ca7c357..486aca938394b 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -723,6 +723,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, false otherwise. * * 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 @@ -732,13 +734,16 @@ 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; + *bcast = false; + /* in P2P mode, no matter the destination, packets are always sent to * the single peer listening on the other side */ @@ -756,11 +761,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 dfa5c0037e02b..686148d9b458d 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; }; @@ -147,8 +149,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); void ovpn_peer_hash_transp_addr(struct ovpn_peer *peer); bool ovpn_peer_check_by_src(struct ovpn_priv *ovpn, struct sk_buff *skb, From patchwork Fri Sep 18 06:30:36 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marco Baffo X-Patchwork-Id: 5381 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:6446:b0:8a0:ea1f:253a with SMTP id n6csp8383848mag; Thu, 17 Sep 2026 23:31:03 -0700 (PDT) X-Forwarded-Encrypted: i=2; AKwUvBzl1jGED9S5LEy4RoM1E4VmnMBVTaewmytXr6nEZX/bVrz0S1hSn4zE6CkRC8TNy8illwUJ7x8TMvI=@openvpn.net X-Received: by 2002:a05:6870:831f:b0:439:bf59:554a with SMTP id 586e51a60fabf-4856e7a9f99mr4602298fac.13.1789713063275; Thu, 17 Sep 2026 23:31:03 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1789713063; cv=none; d=google.com; s=arc-20260327; b=cY07yEkvwqPWvqmo4CpzikmDWLWjLowH3IY9aXfCbg94qanYCoHMipOgqc/Ag1gsBB E55ihIjnrqTbcZs1UpaiT2Ld508cDgUrwbuQuirUtUFGdpRCZr56PHiwE3DE6BXkkPWa Jqun4lk5yLKmmqgPVcHFrkY2/+sot3kU1Vsegd6Va9vqOSLDbC0MfZg/neK1RLLocI/U blSX53U92v2I2qqpIa32N7KnnXro3WRXno/ZzVvHswloez1TEYeRKIvCGe7xSrexMq4Z Jyg572l5HgAqEZBdLg7wfQqRp2t8EZdz0ySinlfUwW3oiVvzJZkRKTcjrmbJQjo9uZJ9 F0fA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=errors-to:content-transfer-encoding:cc: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=mY1oDj5AbTTHObOzwezSOkmp24Gd8m9zsp9t+/qBpz0=; fh=BsMg/B0Yb/hS/rzP5Npz4luh0IleZm8REk1XWiWRt2A=; b=fV8q1mGbhTD+jdeJyaezqX1VtbQbvZkbTPQhq/WSQC99wMRbpVjg4Wrkukk2G6QPc5 AZu+YyBM8L0I61IhDYuQ6/+ySuxfKqccZnIfIl0Qhgq2waF9vBtRG04aSQW9IauycpG2 ndg7YzJNrxyONYTEg7ISIWXgQFc1Xs7lfl/A3eU2S9RFTUInPDkdf3VnANCHtzyjdCzj K2IfgDqwa5+Qt3RcKIcM6UyscbfZR75mTwHoV5lFFXluOmui2uvSPNfPYy0XZDRbyT5W tPebxX6m8NRtraHdGC0vWHfYjlH46mM6FOYyycX+hppmRYOP/5Q7l3zDPANnorg3Ybly p1gw==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=RWQBRLFV; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=j6IWDf1G; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=MuYraX79; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b="uCrm/gQP"; 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-48737921f99si756975fac.37.2026.09.17.23.31.02 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 17 Sep 2026 23:31:03 -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=RWQBRLFV; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=j6IWDf1G; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=MuYraX79; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b="uCrm/gQP"; 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:Cc: 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:Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender :Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=mY1oDj5AbTTHObOzwezSOkmp24Gd8m9zsp9t+/qBpz0=; b=RWQBRLFVC1ow659W71bfm9qy8g 1zTVKSwKX6628FdwE4NNGeGOaVanY/L1fZ7pYzUI5qYCpGrieAwUN0T797TgFplzIve2XznxQUt/w f9/bcTD6mK7nj4Q6XKVjilYR/cvkRvGMuX69E0k69kVqh25DilZ+WvHpUxaTshg4ubeU=; 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 1x7S7b-0006HZ-TN; Fri, 18 Sep 2026 06:30:56 +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 1x7S7a-0006HG-Al for openvpn-devel@lists.sourceforge.net; Fri, 18 Sep 2026 06:30:55 +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=TyH5zyjUsvaMUwHKlaz2nQNOySt0PPEdyfJ/nfen30c=; b=j6IWDf1GgK6OpR8uBYXHE6ycJJ vS1oOAuemVlULUV7S+1P/CfH36t6zn/TwnKYPmD6tFoArekiE/vvZhukaSBRe/PAdv6XlelNuWnna 5kKjDAANS2F1mPbyArN4l23mxuFP7CXsDRWja0sVXxNV6NkFZxYewxnSb9ET7U+TH7QA=; 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=TyH5zyjUsvaMUwHKlaz2nQNOySt0PPEdyfJ/nfen30c=; b=MuYraX79ZHXFXOxQSAJu6R9ONZ 6nd5HLlhNRMjxLhQAT/88N3iCtGMwqeFh9sxW3FxRJA36tHxZVpmWOSwV/uj5S/koEtPw582wl/Oz viwic7auNXxcIbNc/uuBkWo1ztCBPs8ggLFsb6nrMOb+50sO9b/qbTthQoWiRpE8gwWk=; Received: from mout-b-201.mailbox.org ([195.10.208.61]) by sfi-mx-1.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1x7S7Y-0000c1-3N for openvpn-devel@lists.sourceforge.net; Fri, 18 Sep 2026 06:30:55 +0000 Received: from smtp2.mailbox.org (smtp2.mailbox.org [IPv6:2001:67c:2050:b231:465::2]) (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-201.mailbox.org (Postfix) with ESMTPS id 4hmN6W68PzzLm3d; Fri, 18 Sep 2026 08:30:43 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1789713043; 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=TyH5zyjUsvaMUwHKlaz2nQNOySt0PPEdyfJ/nfen30c=; b=uCrm/gQPf3FXFtggYGy4LchlRdZcIJMYlBjU3YHmRgvOp7GztNPcQVbXwDbyKslLIE9019 pY3mE1OrO1Z5QptkpG44SDxxiWxxhbcpDwrQXaX9mxS68uWzzdylknWlebaOBgG6kFI4oa czgo6amKuKX2XSaF/NW1KG3NvoX9zsTuQ1UzuAv2WO9AKkIH1gpA51wkAyLI1CS5MZPa0X /tAWQQVPsNtwfQE6zK+YnyV0zung+quKxVtrtulBHXSfrszQjSjwqZd5SU98jJd3MoI3yG 727MWPp8j9545EQ1Y8CiWi8NDjxgEKrSC4mYxHFfCVDrOAqYbZlIGI2fnyExpg== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of marco@mandelbit.com designates 2001:67c:2050:b231:465::2 as permitted sender) smtp.mailfrom=marco@mandelbit.com From: Marco Baffo To: openvpn-devel@lists.sourceforge.net Date: Fri, 18 Sep 2026 08:30:36 +0200 Message-ID: <20260918063036.1588046-2-marco@mandelbit.com> In-Reply-To: <20260918063036.1588046-1-marco@mandelbit.com> References: <20260918063036.1588046-1-marco@mandelbit.com> MIME-Version: 1.0 X-Rspamd-Queue-Id: 4hmN6W68PzzLm3d X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-2.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_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain X-Headers-End: 1x7S7Y-0000c1-3N Subject: [Openvpn-devel] [RFC ovpn net-next v7 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: , Cc: Antonio Quartulli Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: 1876650164787764617 X-GMAIL-MSGID: 1876650164787764617 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. Disable automatic IPv6 link-local address generation during namespace setup to prevent background IPv6 traffic from affecting the mark test's packet counts. Assign link-local addresses explicitly for the IPv6 multicast test so that ping to ff02::1 has a valid source address. Signed-off-by: Marco Baffo --- Changes in v7: - Disable automatic IPv6 link-local address generation and assign addresses only for the IPv6 multicast test, avoiding background traffic that affects the mark test's packet counts. - Replace fixed startup delays with bounded tcpdump readiness checks. - Consolidate capture handling in a shared helper with timeouts, per-peer diagnostics, and process/temporary-file cleanup. tools/testing/selftests/net/ovpn/common.sh | 3 + tools/testing/selftests/net/ovpn/test.sh | 78 +++++++++++++++++++++- 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/net/ovpn/common.sh b/tools/testing/selftests/net/ovpn/common.sh index 2d844eb3aa6e3..be319b3c42660 100644 --- a/tools/testing/selftests/net/ovpn/common.sh +++ b/tools/testing/selftests/net/ovpn/common.sh @@ -173,6 +173,9 @@ ovpn_setup_ns() { if [ -n "${3}" ]; then ip -n "${peer}" link set mtu ${3} dev tun${1} fi + # Configure IPv6 addresses explicitly in the multicast test. Automatic + # link-local addresses would generate traffic that affects packet counts. + ip -n "${peer}" link set dev tun${1} addrgenmode none ip -n "${peer}" link set tun${1} up } diff --git a/tools/testing/selftests/net/ovpn/test.sh b/tools/testing/selftests/net/ovpn/test.sh index 9b5610837032f..11a0bd92c2827 100755 --- a/tools/testing/selftests/net/ovpn/test.sh +++ b/tools/testing/selftests/net/ovpn/test.sh @@ -56,6 +56,79 @@ ovpn_prepare_network() { done } +ovpn_run_mbcast_test() ( + local label="$1" + local destination="$2" + local filter="$3" + local capture_dir + local deadline + local p + local ret=0 + local -a pids=() + + shift 3 + capture_dir=$(mktemp -d) || return 1 + trap 'kill "${pids[@]}" 2>/dev/null || true + wait "${pids[@]}" 2>/dev/null || true + rm -rf "${capture_dir}"' EXIT + trap 'exit 1' INT TERM + + ovpn_log "Testing ${label}:" + # Allow five seconds for startup, plus time to send and capture the ping. + deadline=$((SECONDS + 5)) + for p in $(seq 1 "${OVPN_NUM_PEERS}"); do + : >"${capture_dir}/${p}" || return 1 + LC_ALL=C timeout 10 ip netns exec "ovpn_peer${p}" \ + tcpdump --immediate-mode -p -ni "tun${p}" -c 1 \ + "${filter}" >/dev/null 2>"${capture_dir}/${p}" & + pids[p]=$! + done + + for p in $(seq 1 "${OVPN_NUM_PEERS}"); do + while ! grep -q "listening on tun${p}," "${capture_dir}/${p}"; do + if ! kill -0 "${pids[p]}" 2>/dev/null || + ((SECONDS >= deadline)); then + printf '# %s: capture not ready on peer%s\n' \ + "${label}" "${p}" + cat "${capture_dir}/${p}" + return 1 + fi + sleep 0.1 + done + done + + ovpn_cmd_mayfail "send ${label} ping from peer0" \ + ip netns exec ovpn_peer0 ping "$@" -qc 1 -w 3 -I tun0 \ + "${destination}" + for p in $(seq 1 "${OVPN_NUM_PEERS}"); do + if ! wait "${pids[p]}"; then + printf '# %s: capture failed on peer%s\n' "${label}" "${p}" + cat "${capture_dir}/${p}" + ret=1 + fi + unset 'pids[p]' + done + + return "${ret}" +) + +ovpn_run_mbcast_tests() { + local p + + ovpn_run_mbcast_test "broadcast" 5.5.5.255 \ + 'icmp and dst host 5.5.5.255' -b || return 1 + ovpn_run_mbcast_test "IPv4 multicast" 224.0.0.1 \ + 'icmp and dst host 224.0.0.1' || return 1 + + for p in $(seq 0 "${OVPN_NUM_PEERS}"); do + ovpn_cmd_ok "configure IPv6 address on peer${p}" \ + ip -n "ovpn_peer${p}" addr add fe80::$((p + 1))/64 \ + dev tun${p} scope link + done + ovpn_run_mbcast_test "IPv6 multicast" ff02::1 \ + 'icmp6 and dst host ff02::1' -6 || return 1 +} + ovpn_run_basic_traffic() { local p local header1 @@ -293,9 +366,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 +376,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