From patchwork Wed Oct 14 05:43:10 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marco Baffo X-Patchwork-Id: 5468 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:5249:b0:8d7:ebbe:b250 with SMTP id e9csp1478185mag; Tue, 13 Oct 2026 22:43:37 -0700 (PDT) X-Forwarded-Encrypted: i=2; AKwUvBxmOLwUZU73cQl0lGZ8SkjBNL1tH/R74zWAnFe00mPAZhcMnCQSr/H9x/Vl8S3+vPINoo1XoCznFb4=@openvpn.net X-Received: by 2002:a05:6808:338b:b0:4f4:3790:65fd with SMTP id 5614622812f47-50c516e7ff6mr13510079b6e.29.1791956617735; Tue, 13 Oct 2026 22:43:37 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1791956617; cv=none; d=google.com; s=arc-20260327; b=ghyIKFKtB3N8mCVfWrCoEvLxnzSnCaEyEw/eBkHEORvYQcoAJV2gBP/VIuIKjujXbu 76RbTUglg6ymrfG1JxZNYId0ja5jf0ViywanSCl2sk7WIg4I64ALOoVBrd5ccVPfq0Yt UjD8GHUTHHybwfYCeisb6+vxquSD0z3KdWCrjHNpmSa5nIjvJUHSf9SZCmYzmw/ih3GL yU06YJ1qovuapTZ+FcOWXEz4J6XuVgMvE2fmxkWMUp+N3eljASkk8fLI7UCUcIEe20SH ayXzP/JNp7vUGfYtQuvQtvx3r574mAqSf1CAFROIUIoCwlO7tn7P9GDHfPEaKtrY6A/+ G4ww== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=errors-to:content-transfer-encoding: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=K6M0y2o6KL4yiwH1mD4QJyHuz6FQ0g9viAjBB7zMBZc=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=gDJRQ9MDeW7aqeZg+ssfc374K1nyOajEuxybYXmHwm2jQMl+XPcHNI973ZgGRfmUmC W+WOSnOwXzVnO1t/lux3aS6BMeWqforY/QhZWELCuNKOz7MSWZ4zgf/MygXyH+zygaUo gFirJ67DIGv20WMBw3iImE5y9JtLMcN7KH4hfcH4XJ5JWJSQVckIYUTM1D07BVw67zRa dhUaefZ8hxDkz40c1hO+S8RLwljdV+sW796QwdSG7udBmo0qDiBpJA4EmxAXL9WJjqpD Hjxm7SB3phaixnb+Ndf/hKciD3aK6jlagJpcazWCfKaWxE4odwa5Ajyv+SuRPxW9eCbl 2QwA==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b="iGNE8BZ/"; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=DomJwo9V; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=OhYKzdH7; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=hE30Wra5; 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 5614622812f47-5167f3ca9d3si14450301b6e.32.2026.10.13.22.43.37 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Tue, 13 Oct 2026 22:43:37 -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="iGNE8BZ/"; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=DomJwo9V; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=OhYKzdH7; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=hE30Wra5; 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: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:In-Reply-To:References:List-Owner; bh=K6M0y2o6KL4yiwH1mD4QJyHuz6FQ0g9viAjBB7zMBZc=; b=iGNE8BZ/2I4PdQxU/UakOoPMK1 pj9MO8oSgZxFC1X4xwuHffLukth1idGBETcw6aQTFxwjn5j+ntB90vFotaBB/tPkorlLmj4NNh0Ta PTETxWDPl09ObSerOxeqmXPd+sMwgaMv8sakg4b56IlZjOHKj6mvPt2ZLZPnhMNYlZLY=; Received: from [127.0.0.1] (helo=sfs-ml-3.v29.lw.sourceforge.com) by sfs-ml-3.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1xGrlw-0000sm-H4; Wed, 14 Oct 2026 05:43:29 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-3.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1xGrlt-0000sd-2k for openvpn-devel@lists.sourceforge.net; Wed, 14 Oct 2026 05:43:27 +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=rh9nPKwQojgQpBLTGqeMN66oy21VdG6/KdPdL8F5V08=; b=DomJwo9VuYBUQgFFovrLJmrzZv FwntIuJoziWg+XMOTfNzvhES8TjWoA8N3lUTP+bLRk2Dk6o1Gc82PYpx9/TLpM491bP/VRgyHvLMc RzzbwGlZhVNlq3Ntxo16Isq3DxwpbnbpDvQkYwXvSF+lZFdZ1ZpW9/etDdWvNUDBvfC0=; 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=rh9nPKwQojgQpBLTGqeMN66oy21VdG6/KdPdL8F5V08=; b=O hYKzdH7HFY7U8pWn0stcNyZ81BL9puJNjURFtfjLZrnAd38jQTiQxKnsNQ9c06fOKYOjJETAzbMKV wqM72mnsncS5/VgSwl9Yp0uObtS10VXEksXs8LpXpr71BGhgsTjuq6HrIlKB/yPu9nNUFgppyaI2K w3pkxDm4HxS2Q2rY=; Received: from mout-b-206.mailbox.org ([195.10.208.51]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1xGrlq-00069q-CC for openvpn-devel@lists.sourceforge.net; Wed, 14 Oct 2026 05:43:25 +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 X25519MLKEM768 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-b-206.mailbox.org (Postfix) with ESMTPS id 4j4Kqk214CzYn; Wed, 14 Oct 2026 07:43:14 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1791956594; 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=rh9nPKwQojgQpBLTGqeMN66oy21VdG6/KdPdL8F5V08=; b=hE30Wra5aubSxA4bQFilpjdPDSzakO1rEpIjcfyaqc6yUXEqCkIvjubg69KtLTNDGrzU8A kxz8VhxsrbNthAczDVjZuOqWKkcIfqcehWxWNDVBW1VUNK6R5I9Mz2aAaZ3n291QzUSEuR ZTTup0WkV1VOX1y+Fs1s84yMeuVL1aHERjhkOg8msCrDWX5Bm9SqGJ1a1Q5I+gmyGzK1z4 kX6xYkgzKNIkzZH8WH3CbToVq/bmxCKqZatZYDxZ8886R9d5YAle/scP3TYv5CdoEVrFLg NfSKdSqr0RTqfEpUPIDT4AJ7KjYzFUqvzI7QsvMJHQaGmgEyFOmJFpTf1yZ1kQ== 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: Wed, 14 Oct 2026 07:43:10 +0200 Message-ID: <20261014054311.352210-1-marco@mandelbit.com> X-Mailer: git-send-email 2.43.0 MIME-Version: 1.0 X-Rspamd-Queue-Id: 4j4Kqk214CzYn 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 always performs a unicast peer lookup in ovpn_net_xmit(). The interface also does not set IFF_MULTICAST on the netdevice [...] Content analysis details: (-0.2 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URI: common.sh] [URI: mandelbit.com] -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 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 X-Headers-End: 1xGrlq-00069q-CC Subject: [Openvpn-devel] [PATCH ovpn net-next 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: 1879002702233410704 X-GMAIL-MSGID: 1879002702233410704 The ovpn DCO driver currently drops all multicast/broadcast packets because it always performs a unicast peer lookup in ovpn_net_xmit(). The interface also does not set IFF_MULTICAST on the netdevice so it does not advertise multicast support. 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 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/multicast 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. Only server-to-client flooding is supported. Client-originated multicast data remains subject to RPF. selftests: in common.sh, disable automatic IPv6 link-local address generation during namespace setup to prevent background IPv6 traffic from affecting the mark test's packet counts. Signed-off-by: Marco Baffo --- drivers/net/ovpn/io.c | 267 ++++++++++++++++++++- drivers/net/ovpn/io.h | 2 + drivers/net/ovpn/main.c | 7 +- drivers/net/ovpn/ovpnpriv.h | 17 ++ drivers/net/ovpn/peer.c | 32 ++- drivers/net/ovpn/peer.h | 6 +- tools/testing/selftests/net/ovpn/common.sh | 3 + 7 files changed, 319 insertions(+), 15 deletions(-) diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index 9526f8096da60..65cb078a2493b 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,161 @@ 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: + /* IGMPv2 Leave has no specified zero-source exception. + * Leave messages remain subject to RPF. + */ + 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 +339,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_peer_check_by_src(peer->ovpn, skb, peer) && + !ovpn_mcast_is_control(skb))) { 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), @@ -332,7 +493,9 @@ static bool ovpn_encrypt_one(struct ovpn_peer *peer, struct sk_buff *skb) return true; } -/* send skb to connected peer, if any */ +/* send skb to connected peer, if any. The caller must hold and + * release its peer reference + */ static void ovpn_send(struct ovpn_priv *ovpn, struct sk_buff *skb, struct ovpn_peer *peer) { @@ -347,8 +510,70 @@ static void ovpn_send(struct ovpn_priv *ovpn, struct sk_buff *skb, kfree_skb(curr); } } +} - ovpn_peer_put(peer); +/** + * ovpn_bcast_work - drain broadcast/multicast queue and transmit to all peers + * @work: broadcast work item + * + * Context: Process context. May sleep. + */ +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 ovpn_peer *peer, *next; + struct sk_buff_head skb_list; + LLIST_HEAD(peer_list); + int bkt; + + __skb_queue_head_init(&skb_list); + spin_lock_bh(&ovpn->bcast.queue.lock); + skb_queue_splice_init(&ovpn->bcast.queue, &skb_list); + spin_unlock_bh(&ovpn->bcast.queue.lock); + + if (skb_queue_empty(&skb_list)) + return; + + /* hold one peer_list snapshot for this batch. Packets queued meanwhile + * trigger another run, which picks up peer membership changes + */ + 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(); + + while ((skb = __skb_dequeue(&skb_list))) { + 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_entry(peer, peer_list.first, bcast_entry) { + if (likely(peer->bcast_entry.next)) + 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); + } + cond_resched(); + } + } + + llist_for_each_entry_safe(peer, next, peer_list.first, bcast_entry) + ovpn_peer_put(peer); } /* Send user data to the network @@ -362,6 +587,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 +598,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 +644,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 */ @@ -434,11 +680,13 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev) ovpn_peer_stats_increment_tx(&peer->vpn_stats, tx_bytes); ovpn_send(ovpn, skb_list.next, peer); + ovpn_peer_put(peer); 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); @@ -452,8 +700,8 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev) * @data: message content * @len: message length * - * Assumes that caller holds a reference to peer, which will be - * passed to ovpn_send() + * Assumes that caller holds a reference to peer, which + * is released here after ovpn_send() */ void ovpn_xmit_special(struct ovpn_peer *peer, const void *data, const unsigned int len) @@ -478,4 +726,5 @@ void ovpn_xmit_special(struct ovpn_peer *peer, const void *data, __skb_put_data(skb, data, len); ovpn_send(ovpn, skb, peer); + ovpn_peer_put(peer); } 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..c353c724960d8 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; 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..9cd979754a894 100644 --- a/drivers/net/ovpn/ovpnpriv.h +++ b/drivers/net/ovpn/ovpnpriv.h @@ -36,6 +36,21 @@ struct ovpn_peer_collection { struct hlist_nulls_head by_transp_addr[1 << 12]; }; +/** + * define OVPN_BCAST_MAX_QLEN - per-interface broadcast/multicast queue limit in skbs + */ +#define OVPN_BCAST_MAX_QLEN 1000 + +/** + * struct ovpn_bcast - per-interface broadcast/multicast transmit state + * @queue: packets pending transmission to all peers + * @work: work item draining the queue + */ +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 +60,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 +70,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..a0faa82a3e392 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: set to true if the packet is a broadcast or a multicast + * and flooding to all peers is required, 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 @@ -730,15 +732,20 @@ static void ovpn_peer_remove(struct ovpn_peer *peer, * * Assume that the IP header is accessible in the skb data. * - * Return: the peer if found or NULL otherwise. + * Return: if found, a peer with a reference held. Otherwise NULL, with *@bcast + * set to true to flood all peers or false if no peer was found */ -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; + const struct rtable *rt; + 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,10 +763,29 @@ 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; + + /* Reuse the route's classification when available. Otherwise, use + * inet_dev_addr_type() to also detect subnet-directed broadcasts, + * which ipv4_is_lbcast() and ipv4_is_multicast() cannot identify. + */ + rt = skb_rtable(skb); + addr_type = rt ? rt->rt_type : + inet_dev_addr_type(dev_net(ovpn->dev), + ovpn->dev, addr4); + *bcast = addr_type == RTN_BROADCAST || addr_type == RTN_MULTICAST; break; case htons(ETH_P_IPV6): addr6 = ovpn_nexthop_from_skb6(skb); peer = ovpn_peer_get_by_vpn_addr6(ovpn, &addr6); + + if (peer) + break; + + if (ipv6_addr_is_multicast(&addr6)) + *bcast = true; break; } 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, diff --git a/tools/testing/selftests/net/ovpn/common.sh b/tools/testing/selftests/net/ovpn/common.sh index 2d844eb3aa6e3..ec1b7c0cde07b 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 + # Disable automatic IPv6 link-local address generation + # to keep packet counts predictable. + ip -n "${peer}" link set dev tun${1} addrgenmode none ip -n "${peer}" link set tun${1} up } From patchwork Wed Oct 14 05:43:11 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marco Baffo X-Patchwork-Id: 5469 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:5249:b0:8d7:ebbe:b250 with SMTP id e9csp1478204mag; Tue, 13 Oct 2026 22:43:39 -0700 (PDT) X-Forwarded-Encrypted: i=2; AKwUvByvmiVMmNxRbIG/mWFsmdXLv9G155kMFliqV4GyxaP2TOGLxk2/g1dD/Temxewu1mWBKiGqq5PB8Dw=@openvpn.net X-Received: by 2002:a05:6820:809:b0:6f6:847f:9271 with SMTP id 006d021491bc7-6f6847f9b37mr6249587eaf.61.1791956619307; Tue, 13 Oct 2026 22:43:39 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1791956619; cv=none; d=google.com; s=arc-20260327; b=YknCuOKsA0NCEFH8//Jf4VWoRFtPuQyUScy2mLw1yO5+a9BLlDzVgqgA3kurbl3JF1 dmKTXF4wglmmFEU8lP7cLXM/l5nXqBkm64R7ch5Mv8OIrXbRAEe8MyjjRHq8sND75ClP 6i2hOFQG83RK1VASoOuqGHxkNEHGcYaziPbQz8kPkulzBqqI+eBX57Nz21z2YsvdZE59 iWjrROnxa3D/Cw9aWvlBs1BA6oBqR5C3BkaaopI5mRTd08zwf/NqwPF16TK/41O8mxYm OwzfhYEvPgrvIY9cbdS9zAvUxmXbHLqZXCsppCs/nsSxhOFJ7tQSQuZ6KTAqgP4yrqBC tuRA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; 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=FySI0BTdG81M3sUc2bKGlSwqIHjMTaz8BrO3oL/KqK8=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=NbcN1LxEDoAKNHDwt+bqZRaAlKmx0PjHWv4ff47SJ2VQjxHKV/rKyeadI3tDnGGI+/ J+mXbAud1Xtr80eGWdflJZ80z6wL4d5rTlAvWC/UalQVjN1uX3hL6P/8dRmbwYvgSCZ+ xLFqvvAt5EtK3Imj6+QfWQyObqaayhs2jZNe6cNVkCRA72Fl/5qhdrKobRmuou52FrAZ R0bZEWIwgygWzwBdLcejuiVFTBK8TiXo1sNWdFbAxyD8J86YaPq2bPzjayaPCLHDG9SI aIh+A1BUmNM6/FlWikr5zzWLv3XU0Qpi8HZ461WidUw9tUZ4N4ucFasfnM17L7XIzqRP Y+Pw==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=Pr7V9CMh; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=V4Y1EODw; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=RBw07dkT; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b="I37iby/X"; 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 006d021491bc7-6f565d0da5dsi10923194eaf.5.2026.10.13.22.43.38 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Tue, 13 Oct 2026 22:43:39 -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=Pr7V9CMh; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=V4Y1EODw; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=RBw07dkT; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b="I37iby/X"; 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=FySI0BTdG81M3sUc2bKGlSwqIHjMTaz8BrO3oL/KqK8=; b=Pr7V9CMhiB4vrsKsFvd2Nc+6CS gn9Na71OV3efeE+g1wBwx8kEME0sTNM2Z1z41YuJqsm1Sv+DTHthO0u/7HYALADF28JFwePh1iMz8 QLvF7b1KDezqD7n7+JBs5rdbJoKyb4eOndVsCbwgpEOPYY9eb50F8bF6/FdBRW7XJJHo=; Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1xGrly-0007x4-AL; Wed, 14 Oct 2026 05:43:30 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1xGrlw-0007wv-2s for openvpn-devel@lists.sourceforge.net; Wed, 14 Oct 2026 05:43:28 +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=yllz3wSfJ88c9dRyDVXP8ClZXjNFqeN3nV5Pkpjh57A=; b=V4Y1EODwCC+CfhhYmoQ2E07qD8 jZ0KxFl0sm0DFHnKeoNq6hJzqoAXDJgOMyFrZASvTS0u1TDb2PcWteQq8QhA0w8hJC3vkUrqJKiWP IeqhV8ftn4sn22oZtUs93Jjev7CsAPAdHZ21a65LNQlb77C5Y0v7epTW9SmxXkr10ZzA=; 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=yllz3wSfJ88c9dRyDVXP8ClZXjNFqeN3nV5Pkpjh57A=; b=RBw07dkTTVKu9rmSBZOHeqDtM3 MskwS72ZvbLnKB6njjjwcL6P7F+A6cwm9Tm5nUy76FEDPO5vkRmX1129/36eBTo0nw1rrlw2AdNJW hrgpUaHKPHr14FMf+2lA6VC0B8sZ85PPpTuKHNP/2LHc6BqIXFcw8pAeLe70cKdDvND0=; 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 1xGrlr-0000tS-Sg for openvpn-devel@lists.sourceforge.net; Wed, 14 Oct 2026 05:43:28 +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 X25519MLKEM768 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-b-201.mailbox.org (Postfix) with ESMTPS id 4j4Kql5M9kzLmHV; Wed, 14 Oct 2026 07:43:15 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1791956595; 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=yllz3wSfJ88c9dRyDVXP8ClZXjNFqeN3nV5Pkpjh57A=; b=I37iby/XPgc+hYxtzZr0exte5dFzf9jMqSpcYB2HPo0Iow804rJgoKO2LfuWXLTsaGZrjJ K7JDEKo8s54ZY+eqBQ1oFZk2rLY1Dknjhjeahk9H8JzUbBgOvYGonG5hiQBy7JYNheJf+d 4A7FRGoR7nlbsKq5FR59g4T4aAPTuFlWdxMquuEEC4UtBcEYJgC3aKGiGCpJSDjDlpA7+5 3bp+OtJ3N9oRJT2EjWb6TXPqEL+3LbmCk24qr6CRQZKwy7u5QUqS7dAtskKZT+2/fqucYn KFoV7+eXJqJTdHaAXb20C5NAYA22dn1qTKTrV/t9EUSUPS/mvbb0lRRyvVoumw== 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: Wed, 14 Oct 2026 07:43:11 +0200 Message-ID: <20261014054311.352210-2-marco@mandelbit.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20261014054311.352210-1-marco@mandelbit.com> References: <20261014054311.352210-1-marco@mandelbit.com> MIME-Version: 1.0 X-Rspamd-Queue-Id: 4j4Kql5M9kzLmHV 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.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URI: mandelbit.com] [URI: test.sh] -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 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 X-Headers-End: 1xGrlr-0000tS-Sg Subject: [Openvpn-devel] [PATCH ovpn net-next 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: 1879002704423073196 X-GMAIL-MSGID: 1879002704423073196 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. 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 --- tools/testing/selftests/net/ovpn/test.sh | 88 +++++++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/net/ovpn/test.sh b/tools/testing/selftests/net/ovpn/test.sh index 9b5610837032f..8614a12a5f4a0 100755 --- a/tools/testing/selftests/net/ovpn/test.sh +++ b/tools/testing/selftests/net/ovpn/test.sh @@ -56,6 +56,89 @@ 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 -Q in -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 filter4='icmp and src host 5.5.5.1 and icmp[icmptype] == icmp-echo' + local p + local ret=0 + + ovpn_run_mbcast_test "broadcast" 5.5.5.255 \ + "${filter4} and dst host 5.5.5.255" -b || return 1 + ovpn_run_mbcast_test "IPv4 multicast" 224.0.0.1 \ + "${filter4} 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 nodad + done + # ping sends echo requests without IPv6 extension headers. + ovpn_run_mbcast_test "IPv6 multicast" ff02::1 \ + 'icmp6 and src host fe80::1 and dst host ff02::1 and ip6[40] == 128' -6 || ret=1 + for p in $(seq 0 "${OVPN_NUM_PEERS}"); do + ovpn_cmd_ok "remove IPv6 address on peer${p}" \ + ip -n "ovpn_peer${p}" addr del fe80::$((p + 1))/64 \ + dev tun${p} + done + + return "${ret}" +} + ovpn_run_basic_traffic() { local p local header1 @@ -293,9 +376,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 +386,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