From patchwork Thu May 14 09:52:06 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marco Baffo X-Patchwork-Id: 4939 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:a719:b0:84a:48f:a1fd with SMTP id hl25csp3721096mab; Thu, 14 May 2026 02:52:31 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ93ZGrJ525FURfIlt6H9UhP39uQK1bTq6jq/ayzBqPD42a+qbhOFv5T9fB2hDOWfkQBI+ESsEBDg3A=@openvpn.net X-Received: by 2002:a05:6870:e087:b0:417:837:635c with SMTP id 586e51a60fabf-439ce469444mr4010641fac.36.1778752351662; Thu, 14 May 2026 02:52:31 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1778752351; cv=none; d=google.com; s=arc-20240605; b=b1a0igtnrToF5c0E/WLTiHiNEuZFL+/1sG0uC9/tIV0z6sfN/vRzERR76MdDe/ccYQ O+RtRGlP0+LQ8wd3PE3hf4uN7cU+F0nLe/w606Kso4WUSHdDnfw3Y6ZtPX3DuSEp77dK CBk0xUwWiouCRq841DoR92SrMWR+KeNgpPIiGnaaYUWZ3YNKVofsQ2LGBJC5LKJxNZBh L2w5sjT5i1jTquYdbxLJLLBad7mrvJmdhxvQnFMP5M3+478/4dKfO0jZ0zHvcFMRXor3 fkNlrcYh/bDwSTSrZ3Hqviy2s3+Gc6GxzMiXRjJ6SasP19nu1CG0gU0XkHe7QngdYKQt J/Rg== 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:message-id:date:to:from:dkim-signature:dkim-signature :dkim-signature:dkim-signature; bh=nYPfviBhpdars/82kX5Kvjh84ci2Rsf0Lnk+X9DUfX0=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=gaANUJugaW6w13WHgWqoWK5uyUK337jQxd8E4COYylHXxosE+5+EcMcPmeLKe2fPc+ L/Aqfv2eTE9QKnOe2GQJZZAmIi74973VOpon2EJ2ex32sMiUtsVqPLA66PMqxLoDy+aI uxVO6ZG+EkKDmuwCt2w70oLh8ZIqqvVTMqAelaomlOa8qVJaMh0cHatMHkuUOBt23hQv JcJLFvE4908ASjGL6BsYXRqB4S8oZ8ulVoIDQhj5m51+4anbPguUFryminMKKN0HNkHI 7CRnykhtsIPaIGuw9u+ikO2ugtnPu5NQMVl0AwbwujxejGC1ty0Q91Azh/mqfZMD3Hx4 qdsg==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=etc+WUzh; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b="Yl8YC2K/"; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=iGMkiwqP; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=pc8gdPOG; 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-439fc70bdb3si1551534fac.257.2026.05.14.02.52.31 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 14 May 2026 02:52:31 -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=etc+WUzh; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b="Yl8YC2K/"; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=iGMkiwqP; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=pc8gdPOG; 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=nYPfviBhpdars/82kX5Kvjh84ci2Rsf0Lnk+X9DUfX0=; b=etc+WUzhnNCAY3q+Hv5IB0cKS/ Y4KEkLHgOJOhGD9uoP+sgwYlrjVyFNmrlf+K0OnD4pNv1G/x7Kz9JrR67XdPPvGjFx0Q2bkNCBhdL YYsgYBQ+z2HEE9QU428Op+c/bPPGdmDPggPmuASi3aeMg4ML45xBy/Bglfrlc+YVM/Gg=; 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 1wNSk2-00048M-RF; Thu, 14 May 2026 09:52:28 +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 1wNSk1-00048F-Tu for openvpn-devel@lists.sourceforge.net; Thu, 14 May 2026 09:52: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=uzGT369//u6C/Hbi08SGx7199+8q5nnjhOf/9KTiyck=; b=Yl8YC2K/cWARi5YiPhKM+bMuKn R24zUQtkZeovr4OvplYrL81V3EYh11j9Fpm7/ls72tXDDv/qnhM5FoVNkDhbKxvvSom9EzK7cJLbV fGAIDnbLMBm+WuWTwNTm7FDSIf06lBAvgZPbyoW2mZ4I8kTwGLA4ysnLssY9xzWvNUt8=; 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=uzGT369//u6C/Hbi08SGx7199+8q5nnjhOf/9KTiyck=; b=i GMkiwqPa1caQzuTY4UhQPrvhp4Z27uvVfMtyjYHbBm/XN8IW9PUolk7rOIjmL6I4hAvbiPj4929BD Nv0BH2ku0ONOqHDLlAOsDWhknjqjeoF0BOZY/y9GXdpomjzqR5NmhXY8NTCxSBsGvCK/l94GLHvHx gGt50CSanAUGKbr0=; 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 1wNSjx-0004Tl-Eu for openvpn-devel@lists.sourceforge.net; Thu, 14 May 2026 09:52:27 +0000 Received: from smtp102.mailbox.org (smtp102.mailbox.org [10.196.197.102]) (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 4gGQbf1ZVxzDsJT; Thu, 14 May 2026 11:52:14 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1778752334; 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=uzGT369//u6C/Hbi08SGx7199+8q5nnjhOf/9KTiyck=; b=pc8gdPOGk14J7DICjJty653PpmrU/evFOxvZys9vq0e0cBHzIRDG7O9jYoVQGLTdiHFji7 5D5VzhQ8DjW6VWuo+H5a6+fM4lfeJPSSFGj+xLBgKHjPxRDk1CSZbD4Sytkmsvu2FLxeeJ AzirrlUtyPO5Lh95MwZsJyffHmuet7cS59SiSLqmCLq5kZZkAtcmFG6c4CSr4Rg+UMOz8S gf8Vhl6NsJVf5ow16ItVtKeO6BXJjkfjT/usGWSvsWeMgYx7/4vkzXqzdZr5T1rmjAeuH0 fBnhJsQV3nwHdPn7zlP+caj/+82ZCCJGmOlGD1DSjw9k2pl8qei+GiOCCDWl2Q== From: Marco Baffo To: openvpn-devel@lists.sourceforge.net Date: Thu, 14 May 2026 11:52:06 +0200 Message-ID: <20260514095210.288979-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: 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 0.0 RCVD_IN_MSPIKE_H2 RBL: Average reputation (+2) [195.10.208.46 listed in wl.mailspike.net] X-Headers-End: 1wNSjx-0004Tl-Eu Subject: [Openvpn-devel] [RFC ovpn net-next 1/5] ovpn: add multicast/braodcast 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: =?utf-8?q?1865157025538204346?= X-GMAIL-MSGID: =?utf-8?q?1865157025538204346?= 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 multicastand 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_list_get_by_dst() and create a list with the target peers. - Introduce ovpn_skb_list_clone() to clone GSO segment lists and replicate the packet to every connected peer in ovpn_net_xmit(). For now multicast traffic is flooded to all peers. A future enhancement will replace the flood with a subscription table driven by IGMP snooping. Signed-off-by: Marco Baffo --- drivers/net/ovpn/io.c | 57 ++++++++++++++++++++++++++----- drivers/net/ovpn/main.c | 2 +- drivers/net/ovpn/peer.c | 76 ++++++++++++++++++++++++++++++++--------- drivers/net/ovpn/peer.h | 5 +-- 4 files changed, 112 insertions(+), 28 deletions(-) diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index 22c555dd962e..acf0907dd445 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -351,15 +351,39 @@ static void ovpn_send(struct ovpn_priv *ovpn, struct sk_buff *skb, ovpn_peer_put(peer); } +static struct sk_buff *ovpn_skb_list_clone(struct sk_buff *skb) +{ + struct sk_buff *copy, *curr, *next, *head = NULL, *prev = NULL; + + skb_list_walk_safe(skb, curr, next) { + copy = skb_clone(curr, GFP_ATOMIC); + if (unlikely(!copy)) { + kfree_skb_list(head); + return NULL; + } + + if (unlikely(!head)) + head = copy; + else + prev->next = copy; + + prev = copy; + } + + return head; +} + /* Send user data to the network */ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev) { struct ovpn_priv *ovpn = netdev_priv(dev); - struct sk_buff *segments, *curr, *next; + struct sk_buff *segments, *curr, *next, *to_send; struct sk_buff_head skb_list; - unsigned int tx_bytes = 0; + struct llist_head mcast_list; + struct llist_node *node, *n; struct ovpn_peer *peer; + unsigned int tx_bytes = 0; __be16 proto; int ret; @@ -372,8 +396,9 @@ 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)) { + init_llist_head(&mcast_list); + ovpn_peer_list_get_by_dst(ovpn, skb, &mcast_list); + if (unlikely(llist_empty(&mcast_list))) { switch (skb->protocol) { case htons(ETH_P_IP): net_dbg_ratelimited("%s: no peer to send data to dst=%pI4\n", @@ -427,18 +452,34 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev) * incremented the counter for each failure in the loop */ if (unlikely(skb_queue_empty(&skb_list))) { - ovpn_peer_put(peer); + llist_for_each_safe(node, n, mcast_list.first) { + peer = llist_entry(node, struct ovpn_peer, mcast_entry); + ovpn_peer_put(peer); + } return NETDEV_TX_OK; } skb_list.prev->next = NULL; - ovpn_peer_stats_increment_tx(&peer->vpn_stats, tx_bytes); - ovpn_send(ovpn, skb_list.next, peer); + llist_for_each_safe(node, n, mcast_list.first) { + peer = llist_entry(node, struct ovpn_peer, mcast_entry); + + to_send = n ? ovpn_skb_list_clone(skb_list.next) : skb_list.next; + if (likely(to_send)) { + ovpn_peer_stats_increment_tx(&peer->vpn_stats, tx_bytes); + ovpn_send(ovpn, to_send, peer); + } else { + dev_dstats_tx_dropped(ovpn->dev); + ovpn_peer_put(peer); + } + } return NETDEV_TX_OK; drop: - ovpn_peer_put(peer); + llist_for_each_safe(node, n, mcast_list.first) { + peer = llist_entry(node, struct ovpn_peer, mcast_entry); + ovpn_peer_put(peer); + } drop_no_peer: dev_dstats_tx_dropped(ovpn->dev); skb_tx_error(skb); diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index 2e0420febda0..ee9cb61a090f 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -155,7 +155,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 diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index c02dfab51a6e..06d47c468956 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -718,23 +718,49 @@ static void ovpn_peer_remove(struct ovpn_peer *peer, llist_add(&peer->release_entry, release_list); } +static void ovpn_peer_list_get_all(struct ovpn_priv *ovpn, + struct llist_head *list) +{ + struct ovpn_peer *peer; + int bkt; + + rcu_read_lock(); + hash_for_each_rcu(ovpn->peers->by_id, bkt, peer, hash_entry_id) { + if (ovpn_peer_hold(peer)) + llist_add(&peer->mcast_entry, list); + } + rcu_read_unlock(); +} + +/** + * TO DO: At the moment the list contain all the peers, + * after IGMP snooping is implemented we want to select only the peers + * subscribed to a specific multicast group. + */ +static void ovpn_peer_list_get_by_mcast_group(struct ovpn_priv *ovpn, + struct llist_head *list) +{ + ovpn_peer_list_get_all(ovpn, list); +} + /** - * ovpn_peer_get_by_dst - Lookup peer to send skb to + * ovpn_peer_list_get_by_dst - Lookup peers to send skb to * @ovpn: the private data representing the current VPN session * @skb: the skb to extract the destination address from + * @list: the head of the list to fill with the target peers * - * 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 - * any OpenVPN related header. + * This function takes a tunnel packet and looks up the peers to send it to + * after encapsulation and add them to `list'. The skb is expected to be the + * in-tunnel packet, without any OpenVPN related header. * * Assume that the IP header is accessible in the skb data. * - * Return: the peer if found or NULL otherwise. */ -struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_priv *ovpn, - struct sk_buff *skb) +void ovpn_peer_list_get_by_dst(struct ovpn_priv *ovpn, struct sk_buff *skb, + struct llist_head *list) { struct ovpn_peer *peer = NULL; + unsigned int addr_type; struct in6_addr addr6; __be32 addr4; @@ -744,29 +770,45 @@ struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_priv *ovpn, if (ovpn->mode == OVPN_MODE_P2P) { rcu_read_lock(); peer = rcu_dereference(ovpn->peer); - if (unlikely(peer && !ovpn_peer_hold(peer))) - peer = NULL; + if (likely(peer && ovpn_peer_hold(peer))) + llist_add(&peer->mcast_entry, list); rcu_read_unlock(); - return peer; + return; } - rcu_read_lock(); switch (skb->protocol) { case htons(ETH_P_IP): addr4 = ovpn_nexthop_from_skb4(skb); + rcu_read_lock(); peer = ovpn_peer_get_by_vpn_addr4(ovpn, addr4); - break; + + if (peer) + break; + + rcu_read_unlock(); + addr_type = inet_dev_addr_type(dev_net(ovpn->dev), ovpn->dev, addr4); + if (addr_type == RTN_MULTICAST) + ovpn_peer_list_get_by_mcast_group(ovpn, list); + else if (addr_type == RTN_BROADCAST) + ovpn_peer_list_get_all(ovpn, list); + return; case htons(ETH_P_IPV6): addr6 = ovpn_nexthop_from_skb6(skb); + rcu_read_lock(); peer = ovpn_peer_get_by_vpn_addr6(ovpn, &addr6); - break; + + if (peer) + break; + + rcu_read_unlock(); + if (ipv6_addr_is_multicast(&addr6)) + ovpn_peer_list_get_by_mcast_group(ovpn, list); + return; } - if (unlikely(peer && !ovpn_peer_hold(peer))) - peer = NULL; + if (likely(peer && ovpn_peer_hold(peer))) + llist_add(&peer->mcast_entry, list); rcu_read_unlock(); - - return peer; } /** diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index 328401570cba..08a471deb187 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -113,6 +113,7 @@ struct ovpn_peer { struct kref refcount; struct rcu_head rcu; struct llist_node release_entry; + struct llist_node mcast_entry; struct work_struct keepalive_work; }; @@ -148,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); +void ovpn_peer_list_get_by_dst(struct ovpn_priv *ovpn, struct sk_buff *skb, + struct llist_head *list); 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 Thu May 14 09:52:07 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marco Baffo X-Patchwork-Id: 4941 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:a719:b0:84a:48f:a1fd with SMTP id hl25csp3721097mab; Thu, 14 May 2026 02:52:31 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ+iEekXte7otl3yOh7d9igAjKyVe9qKNCfJxlvwB0CEBKa+LiZWifDvNbLbKwfLqDSsHsmY8ajqQqE=@openvpn.net X-Received: by 2002:a05:6820:2084:b0:694:a339:43af with SMTP id 006d021491bc7-69b78d83503mr3827018eaf.28.1778752351637; Thu, 14 May 2026 02:52:31 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1778752351; cv=none; d=google.com; s=arc-20240605; b=bENjEiWgDylPGbdsC7kg2H1leJXdM+u34FdGuADZzPBjaK7hCDnRdXUPhJ+8vzXeOK qR/0CmFM3zr6pY1IKCtk9alJpnzyjINzvKrMHeGWhA3tIA5oOzdahcinFfVS0xxCjxPi goxkTeLzucOWDrbhARF7hIx9yUqydwGR/EalsCAp8t/bumdGJkrNlESbVc4CWjKyraQt aXXoYfaHCLU/Bz701dALXAsFB0zFsIO20Hwt643WOVFKvHYDk1iZUp0AADe35N6/V2wZ mRT1FoTTJr98IR8UYWcW4Jk59c6oi515AbtbCNIzdNwjv0A4QDHm2b1sDsW/bSdxuNW5 sgig== 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=8YLNPn1mmKXQqPM7pvcd+MSgQKDKv9OKDrVtZfed32U=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=X/4sneoGQ/p9oUOPeoxRVyE9We2F4hSj/84qvUYY1qeBdmFywkdLityEJPtR8Ui+Ht wC5Ky43XULH9+rAV3dn7y6zKTFSiagRLFMUauiB9gU7+Xi7gPsHWcdVQywC/F3WzPpOK hd8Ec0t5N47jr93UGHrJn+nVV/IfnNC7YfKgN9Bu+V2DPkIIjc+S4aSVFQKtRUO9WxRF JkbTNn5Y2vTUloKrMUWO0opSho3UJmQG6C3wpE1ZYmgca8Funplok08rElI/3kYSPBsl Av2hbKfMgrf2L10jPIQxXj4XaqaBa3qDrLmbWuLj7Ybj35II2l3cbnqdTcmeS01fvj3M tESQ==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=RGsQLB35; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=KYOV7HV5; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=YjRb0q93; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=BG49+0bl; 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-69b8cce8cacsi836146eaf.55.2026.05.14.02.52.31 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 14 May 2026 02:52:31 -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=RGsQLB35; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=KYOV7HV5; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=YjRb0q93; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=BG49+0bl; 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=8YLNPn1mmKXQqPM7pvcd+MSgQKDKv9OKDrVtZfed32U=; b=RGsQLB35kUFBQoy+NSalQT8g0F 5/fuWmcZBH2LLY2zIyPX6n7ptkquHg55Y4ELlLK9ncsxcCc6c3FVuHaIuVA8wOS9p4e3WnPOM4GMQ PnW5PXN3nsTuViVu2tKHtcZ+aD4DkkUYE6AbiW6P8leg8LY5P3MTlyhMm5+QY0Nb+s+A=; 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 1wNSjy-0002P9-1c; Thu, 14 May 2026 09:52:26 +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 1wNSjv-0002Oz-MK for openvpn-devel@lists.sourceforge.net; Thu, 14 May 2026 09:52:24 +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=65nXXH8nqwy7z+VZHWHoLiOWD39V0QngGnjMekKyQoQ=; b=KYOV7HV59lveYbkRnVLQsvVu5u 3RjKDC+gRVE/vD3SKQPO5K6Pd4GqJ461/m3pFEfjgbJRdw7aLQJohpBqdu/2iZJoC3vEW755nc1Jn XWB8UrV3w1nofcqjubCM+VXbP0LMFZg9TOz52C8kQD13i3ksiTP/4voJmVdPgOS4M4kk=; 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=65nXXH8nqwy7z+VZHWHoLiOWD39V0QngGnjMekKyQoQ=; b=YjRb0q930j98M0T1AyGBVb7vl/ /eEtJlst7M0J/YoS4MLdsDjtfaEgWLdW90LiZ8aWHl6LWNDRheRgN+u9zmmquYKW0Y7VeXdhMPF9v n2rDRM3onWLLYsz7ynynjS+0uyexyjisE1lYfayKTwOtzwGcSOXbWRSg4QSxcccUVWz8=; Received: from mout-b-202.mailbox.org ([195.10.208.62]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wNSjs-0004TX-W3 for openvpn-devel@lists.sourceforge.net; Thu, 14 May 2026 09:52:23 +0000 Received: from smtp102.mailbox.org (smtp102.mailbox.org [IPv6:2001:67c:2050:b231:465::102]) (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-202.mailbox.org (Postfix) with ESMTPS id 4gGQbf6zfLzDrw4; Thu, 14 May 2026 11:52:14 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1778752335; 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=65nXXH8nqwy7z+VZHWHoLiOWD39V0QngGnjMekKyQoQ=; b=BG49+0bl50DUj5ejTzT8qlQsYaaNxuXZaM3nXLbkBF55Pue03gVs0LcUxj7wYmUENjwh78 1LkELlaZraCurUKTlSkhiNzoMflLetY6PpwH6p7MMBv4I4Jam1U9J3pGXH4hSHp2d3o3BA q3NcTh03KxbZXpTgbHRm2UNqpALhPB0JJ6obTe/i5x7GQTuB5PaxkcqhxzPnOnNa4Dp5wC IazyIPvk2CR8h/JVllbSquLPMTcXpr7FosW9axv8hjx/LygL+7AxEZlCaR0IWWxkjD9N3n o/9t8a6LNOIfAVUSVp0ZKB00TD7h8os8FkAnmwYF8PTQfToWpF31szvXhxdC8w== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of marco@mandelbit.com designates 2001:67c:2050:b231:465::102 as permitted sender) smtp.mailfrom=marco@mandelbit.com From: Marco Baffo To: openvpn-devel@lists.sourceforge.net Date: Thu, 14 May 2026 11:52:07 +0200 Message-ID: <20260514095210.288979-2-marco@mandelbit.com> In-Reply-To: <20260514095210.288979-1-marco@mandelbit.com> References: <20260514095210.288979-1-marco@mandelbit.com> MIME-Version: 1.0 X-Rspamd-Queue-Id: 4gGQbf6zfLzDrw4 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 multicast snooping for IGMPv2 and MLDv1 control traffic received from tunnel peers. IGMPv2 membership reports/leaves and MLDv1 reports/done messages are parsed to build a per-peer multicast group [...] 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: 1wNSjs-0004TX-W3 Subject: [Openvpn-devel] [RFC ovpn net-next 2/5] ovpn: implement IGMPv2/MLDv1 snooping 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: =?utf-8?q?1865157025507522252?= X-GMAIL-MSGID: =?utf-8?q?1865157025507522252?= Add multicast snooping for IGMPv2 and MLDv1 control traffic received from tunnel peers. IGMPv2 membership reports/leaves and MLDv1 reports/done messages are parsed to build a per-peer multicast group subscription table. On the TX path, multicast packets are forwarded only to peers subscribed to the destination group instead of flooding all peers. Because IGMP and MLD control packets may use source addresses that do not match the peer's VPN-assigned address (e.g., MLD requires link-local sources per RFC 2710 and RFC 3810), snooping is performed before Reverse Path Filtering. Recognized join/leave messages bypass the RPF check via short-circuit evaluation so they are not dropped. Multicast subscriptions are cleaned up automatically when a peer is deleted or the interface is destroyed. Signed-off-by: Marco Baffo --- drivers/net/ovpn/Makefile | 1 + drivers/net/ovpn/io.c | 9 +- drivers/net/ovpn/main.c | 3 + drivers/net/ovpn/mcast.c | 369 ++++++++++++++++++++++++++++++++++++ drivers/net/ovpn/mcast.h | 27 +++ drivers/net/ovpn/ovpnpriv.h | 1 + drivers/net/ovpn/peer.c | 29 ++- 7 files changed, 421 insertions(+), 18 deletions(-) create mode 100644 drivers/net/ovpn/mcast.c create mode 100644 drivers/net/ovpn/mcast.h diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile index 229be66167e1..740de96ebe38 100644 --- a/drivers/net/ovpn/Makefile +++ b/drivers/net/ovpn/Makefile @@ -12,6 +12,7 @@ ovpn-y += crypto.o ovpn-y += crypto_aead.o ovpn-y += main.o ovpn-y += io.o +ovpn-y += mcast.o ovpn-y += netlink.o ovpn-y += netlink-gen.o ovpn-y += peer.o diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index acf0907dd445..bb691c441d8a 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -17,6 +17,7 @@ #include "ovpnpriv.h" #include "peer.h" #include "io.h" +#include "mcast.h" #include "bind.h" #include "crypto.h" #include "crypto_aead.h" @@ -183,8 +184,12 @@ 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). + * snoop IGMP/MLD before RPF: these control protocols may use source + * addresses that differ from the peer's VPN address + */ + if (likely(!ovpn_mcast_snoop_skb(peer, skb)) && + unlikely(!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), diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index ee9cb61a090f..000e715ec4cc 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -19,6 +19,7 @@ #include "ovpnpriv.h" #include "main.h" +#include "mcast.h" #include "netlink.h" #include "io.h" #include "peer.h" @@ -30,6 +31,7 @@ static void ovpn_priv_free(struct net_device *net) { struct ovpn_priv *ovpn = netdev_priv(net); + ovpn_mcast_cleanup(ovpn); kfree(ovpn->peers); } @@ -190,6 +192,7 @@ static int ovpn_newlink(struct net_device *dev, ovpn->dev = dev; ovpn->mode = mode; spin_lock_init(&ovpn->lock); + hash_init(ovpn->mcast_table); INIT_DELAYED_WORK(&ovpn->keepalive_work, ovpn_peer_keepalive_work); /* Set carrier explicitly after registration, this way state is diff --git a/drivers/net/ovpn/mcast.c b/drivers/net/ovpn/mcast.c new file mode 100644 index 000000000000..c90ef2b8d8b8 --- /dev/null +++ b/drivers/net/ovpn/mcast.c @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2026 OpenVPN, Inc. + */ + +#include +#include + +#include "ovpnpriv.h" +#include "peer.h" +#include "mcast.h" + +struct ovpn_mcast_group { + struct hlist_node hash_entry; + struct in6_addr addr; + struct list_head subs; +}; + +struct ovpn_mcast_sub { + struct list_head list; + struct ovpn_peer *peer; +}; + +static inline u32 ovpn_mcast_hash(const struct in6_addr *group_addr) +{ + return jhash(group_addr, sizeof(*group_addr), 0); +} + +static bool ovpn_mcast_addr_valid(const struct in6_addr *group_addr) +{ + if (ipv6_addr_v4mapped(group_addr)) + return ipv4_is_multicast(group_addr->s6_addr32[3]); + return ipv6_addr_is_multicast(group_addr); +} + +static struct ovpn_mcast_group * +ovpn_mcast_group_find(const struct ovpn_priv *ovpn, const struct in6_addr *group_addr) +{ + struct ovpn_mcast_group *group; + u32 hash = ovpn_mcast_hash(group_addr); + + hash_for_each_possible(ovpn->mcast_table, group, hash_entry, hash) { + if (ipv6_addr_equal(&group->addr, group_addr)) + return group; + } + return NULL; +} + +static struct ovpn_peer *ovpn_mcast_sub_del(struct ovpn_mcast_sub *sub) +{ + struct ovpn_peer *peer = sub->peer; + + list_del(&sub->list); + kfree(sub); + return peer; +} + +static void ovpn_mcast_group_try_del(struct ovpn_mcast_group *group) +{ + if (!list_empty(&group->subs)) + return; + hash_del(&group->hash_entry); + kfree(group); +} + +/** + * ovpn_mcast_cleanup - tear down all multicast state + * @ovpn: the ovpn instance + * + * Walks the multicast hash table and frees every group and subscription. + * Called at instance destruction time. + */ +void ovpn_mcast_cleanup(struct ovpn_priv *ovpn) +{ + struct ovpn_mcast_group *group; + struct hlist_node *tmp; + struct ovpn_mcast_sub *sub, *next; + unsigned int bkt; + + hash_for_each_safe(ovpn->mcast_table, bkt, tmp, group, hash_entry) { + list_for_each_entry_safe(sub, next, &group->subs, list) + ovpn_peer_put(ovpn_mcast_sub_del(sub)); + ovpn_mcast_group_try_del(group); + } +} + +/** + * ovpn_mcast_join - add a peer to a multicast group + * @ovpn: the ovpn instance + * @peer: the peer joining the group + * @group_addr: the multicast group address (IPv4-mapped IPv6 for IPv4 groups) + * + * Creates the group if it does not exist and adds a subscription for @peer. + * If the peer is already subscribed, returns success without doing anything. + */ +void ovpn_mcast_join(struct ovpn_priv *ovpn, struct ovpn_peer *peer, + const struct in6_addr *group_addr) +{ + struct ovpn_mcast_group *group; + struct ovpn_mcast_sub *sub; + + if (!ovpn_mcast_addr_valid(group_addr)) + return; + + spin_lock_bh(&ovpn->lock); + + group = ovpn_mcast_group_find(ovpn, group_addr); + if (!group) { + group = kzalloc_obj(*group, GFP_ATOMIC); + if (unlikely(!group)) + goto end; + group->addr = *group_addr; + INIT_LIST_HEAD(&group->subs); + hash_add(ovpn->mcast_table, &group->hash_entry, + ovpn_mcast_hash(group_addr)); + } + + list_for_each_entry(sub, &group->subs, list) { + if (sub->peer == peer) + goto end; + } + + sub = kzalloc_obj(*sub, GFP_ATOMIC); + if (unlikely(!sub)) + goto end; + + sub->peer = peer; + ovpn_peer_hold(peer); + list_add_tail(&sub->list, &group->subs); +end: + spin_unlock_bh(&ovpn->lock); +} + +/** + * ovpn_mcast_leave - remove a peer from a multicast group + * @ovpn: the ovpn instance + * @peer: the peer leaving the group + * @group_addr: the multicast group address + * + * Removes @peer's subscription for @group_addr. If the group has no remaining + * subscribers it is destroyed. + */ +void ovpn_mcast_leave(struct ovpn_priv *ovpn, struct ovpn_peer *peer, + const struct in6_addr *group_addr) +{ + struct ovpn_mcast_group *group; + struct ovpn_mcast_sub *sub, *next; + struct ovpn_peer *peer_to_put = NULL; + + spin_lock_bh(&ovpn->lock); + + group = ovpn_mcast_group_find(ovpn, group_addr); + if (!group) + goto end; + + list_for_each_entry_safe(sub, next, &group->subs, list) { + if (sub->peer != peer) + continue; + peer_to_put = ovpn_mcast_sub_del(sub); + ovpn_mcast_group_try_del(group); + goto end; + } +end: + spin_unlock_bh(&ovpn->lock); + + if (peer_to_put) + ovpn_peer_put(peer_to_put); +} + +/** + * ovpn_mcast_leave_all - remove a peer from all multicast groups + * @peer: the peer to remove + * + * Called when a peer disconnects. Removes the peer from every group it + * was subscribed to and destroys any groups that become empty. + */ +void ovpn_mcast_leave_all(struct ovpn_peer *peer) +{ + struct ovpn_priv *ovpn = peer->ovpn; + struct ovpn_mcast_group *group; + struct hlist_node *tmp; + struct ovpn_mcast_sub *sub, *next; + unsigned int bkt, nput = 0; + + spin_lock_bh(&ovpn->lock); + + hash_for_each_safe(ovpn->mcast_table, bkt, tmp, group, hash_entry) { + list_for_each_entry_safe(sub, next, &group->subs, list) { + if (sub->peer != peer) + continue; + ovpn_mcast_sub_del(sub); + nput++; + ovpn_mcast_group_try_del(group); + break; + } + } + + spin_unlock_bh(&ovpn->lock); + + while (nput--) + ovpn_peer_put(peer); +} + +/** + * ovpn_peer_list_get_by_mcast_group - retrieve peers subscribed to a multicast group + * @ovpn: the ovpn instance to search + * @group_addr: the multicast group address to look up + * @list: the lockless list to append matching peers to + * + * Searches for the multicast group identified by @group_addr and appends all + * subscribed peers to @list, acquiring a reference on each one. + * + * Return: false if no peer was found, true otherwise + */ +bool ovpn_peer_list_get_by_mcast_group(struct ovpn_priv *ovpn, + const struct in6_addr *group_addr, + struct llist_head *list) +{ + struct ovpn_mcast_group *group; + struct ovpn_mcast_sub *sub; + + spin_lock_bh(&ovpn->lock); + + group = ovpn_mcast_group_find(ovpn, group_addr); + if (group) { + list_for_each_entry(sub, &group->subs, list) { + if (ovpn_peer_hold(sub->peer)) + llist_add(&sub->peer->mcast_entry, list); + } + } + + spin_unlock_bh(&ovpn->lock); + return !(llist_empty(list)); +} + +/** + * 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. + * + * Return: true if the offset was computed successfully, false otherwise + */ +static bool ovpn_mcast_mld_offset(struct sk_buff *skb, unsigned int *offsetp) +{ + unsigned int offset = sizeof(struct ipv6hdr); + u8 nexthdr = ipv6_hdr(skb)->nexthdr; + + if (nexthdr == IPPROTO_HOPOPTS) { + struct ipv6_opt_hdr *hopopt; + + if (!pskb_may_pull(skb, offset + sizeof(*hopopt))) + return false; + + hopopt = (struct ipv6_opt_hdr *)(skb_network_header(skb) + offset); + nexthdr = hopopt->nexthdr; + offset += ipv6_optlen(hopopt); + } + + if (nexthdr != IPPROTO_ICMPV6) + return false; + + *offsetp = offset; + return true; +} + +/** + * ovpn_mcast_snoop_mld - inspect an IPv6 packet for MLD join/leave messages + * @peer: the peer this packet was received from + * @skb: the packet to inspect + * + * Parse the MLD header and update the multicast subscription table on + * MLDv1 reports and done messages. + * + * Return: true if the packet was a recognized MLD join/leave and was + * consumed, false otherwise + */ +static bool ovpn_mcast_snoop_mld(struct ovpn_peer *peer, struct sk_buff *skb) +{ + struct mld_msg *mld; + unsigned int offset; + + if (!ovpn_mcast_mld_offset(skb, &offset)) + return false; + + if (!pskb_may_pull(skb, offset + sizeof(*mld))) + return false; + + mld = (struct mld_msg *)(skb_network_header(skb) + offset); + + switch (mld->mld_type) { + case ICMPV6_MGM_REPORT: + ovpn_mcast_join(peer->ovpn, peer, &mld->mld_mca); + return true; + case ICMPV6_MGM_REDUCTION: + ovpn_mcast_leave(peer->ovpn, peer, &mld->mld_mca); + return true; + case ICMPV6_MGM_QUERY: + return true; + } + return false; +} + +/** + * ovpn_mcast_snoop_igmp - inspect an IPv4 packet for IGMP join/leave messages + * @peer: the peer this packet was received from + * @skb: the packet to inspect + * + * Parse the IGMP header and update the multicast subscription table on + * IGMPv2 membership reports and leave messages. + * + * Return: true if the packet was a recognized IGMP join/leave and was + * consumed, false otherwise + */ +static bool ovpn_mcast_snoop_igmp(struct ovpn_peer *peer, struct sk_buff *skb) +{ + struct igmphdr *ih; + struct in6_addr addr6; + unsigned int ihl; + + ihl = ip_hdr(skb)->ihl * 4; + if (!pskb_may_pull(skb, ihl + sizeof(struct igmphdr))) + return false; + + ih = (struct igmphdr *)(skb_network_header(skb) + ihl); + + switch (ih->type) { + case IGMPV2_HOST_MEMBERSHIP_REPORT: + ipv6_addr_set_v4mapped(ih->group, &addr6); + ovpn_mcast_join(peer->ovpn, peer, &addr6); + return true; + case IGMP_HOST_LEAVE_MESSAGE: + ipv6_addr_set_v4mapped(ih->group, &addr6); + ovpn_mcast_leave(peer->ovpn, peer, &addr6); + return true; + } + return true; +} + +/** + * ovpn_mcast_snoop_skb - snoop IGMP/MLD control packets from a peer + * @peer: the peer this packet was received from + * @skb: the packet to inspect + * + * Check whether @skb contains an IGMP or MLD membership report/leave message. + * If so, update the multicast forwarding table and report that the packet was + * consumed. Snooping is only performed in multi-peer (server) mode; in P2P + * mode the function returns false immediately since there is only one peer. + * + * Return: true if the packet was a recognized IGMP/MLD join/leave and was + * consumed, false otherwise + */ +bool ovpn_mcast_snoop_skb(struct ovpn_peer *peer, struct sk_buff *skb) +{ + if (peer->ovpn->mode != OVPN_MODE_MP) + return false; + if (skb->protocol == htons(ETH_P_IP)) { + if (ip_hdr(skb)->protocol == IPPROTO_IGMP) + return ovpn_mcast_snoop_igmp(peer, skb); + } else if (skb->protocol == htons(ETH_P_IPV6)) { + if (ipv6_hdr(skb)->nexthdr == IPPROTO_ICMPV6) + return ovpn_mcast_snoop_mld(peer, skb); + } + return false; +} diff --git a/drivers/net/ovpn/mcast.h b/drivers/net/ovpn/mcast.h new file mode 100644 index 000000000000..e9e14d807270 --- /dev/null +++ b/drivers/net/ovpn/mcast.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2026 OpenVPN, Inc. + */ + +#ifndef _NET_OVPN_MCAST_H_ +#define _NET_OVPN_MCAST_H_ + +struct ovpn_priv; +struct ovpn_peer; +struct in6_addr; +struct llist_head; +struct sk_buff; + +void ovpn_mcast_cleanup(struct ovpn_priv *ovpn); +void ovpn_mcast_join(struct ovpn_priv *ovpn, struct ovpn_peer *peer, + const struct in6_addr *group_addr); +void ovpn_mcast_leave(struct ovpn_priv *ovpn, struct ovpn_peer *peer, + const struct in6_addr *group_addr); +void ovpn_mcast_leave_all(struct ovpn_peer *peer); +bool ovpn_peer_list_get_by_mcast_group(struct ovpn_priv *ovpn, + const struct in6_addr *group_addr, + struct llist_head *list); +bool ovpn_mcast_snoop_skb(struct ovpn_peer *peer, struct sk_buff *skb); + +#endif /* _NET_OVPN_MCAST_H_ */ diff --git a/drivers/net/ovpn/ovpnpriv.h b/drivers/net/ovpn/ovpnpriv.h index 5898f6adada7..b28849f36ae4 100644 --- a/drivers/net/ovpn/ovpnpriv.h +++ b/drivers/net/ovpn/ovpnpriv.h @@ -50,6 +50,7 @@ struct ovpn_priv { struct ovpn_peer __rcu *peer; struct gro_cells gro_cells; struct delayed_work keepalive_work; + DECLARE_HASHTABLE(mcast_table, 8); }; #endif /* _NET_OVPN_OVPNSTRUCT_H_ */ diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 06d47c468956..5159a8f9dfba 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -18,6 +18,7 @@ #include "crypto.h" #include "io.h" #include "main.h" +#include "mcast.h" #include "netlink.h" #include "peer.h" #include "socket.h" @@ -732,17 +733,6 @@ static void ovpn_peer_list_get_all(struct ovpn_priv *ovpn, rcu_read_unlock(); } -/** - * TO DO: At the moment the list contain all the peers, - * after IGMP snooping is implemented we want to select only the peers - * subscribed to a specific multicast group. - */ -static void ovpn_peer_list_get_by_mcast_group(struct ovpn_priv *ovpn, - struct llist_head *list) -{ - ovpn_peer_list_get_all(ovpn, list); -} - /** * ovpn_peer_list_get_by_dst - Lookup peers to send skb to * @ovpn: the private data representing the current VPN session @@ -787,10 +777,13 @@ void ovpn_peer_list_get_by_dst(struct ovpn_priv *ovpn, struct sk_buff *skb, rcu_read_unlock(); addr_type = inet_dev_addr_type(dev_net(ovpn->dev), ovpn->dev, addr4); - if (addr_type == RTN_MULTICAST) - ovpn_peer_list_get_by_mcast_group(ovpn, list); - else if (addr_type == RTN_BROADCAST) + if (addr_type == RTN_MULTICAST) { + ipv6_addr_set_v4mapped(addr4, &addr6); + if (!ovpn_peer_list_get_by_mcast_group(ovpn, &addr6, list)) + ovpn_peer_list_get_all(ovpn, list); + } else if (addr_type == RTN_BROADCAST) { ovpn_peer_list_get_all(ovpn, list); + } return; case htons(ETH_P_IPV6): addr6 = ovpn_nexthop_from_skb6(skb); @@ -801,8 +794,10 @@ void ovpn_peer_list_get_by_dst(struct ovpn_priv *ovpn, struct sk_buff *skb, break; rcu_read_unlock(); - if (ipv6_addr_is_multicast(&addr6)) - ovpn_peer_list_get_by_mcast_group(ovpn, list); + if (ipv6_addr_is_multicast(&addr6) && + !ovpn_peer_list_get_by_mcast_group(ovpn, &addr6, list)) { + ovpn_peer_list_get_all(ovpn, list); + } return; } @@ -1153,6 +1148,8 @@ int ovpn_peer_del(struct ovpn_peer *peer, enum ovpn_del_peer_reason reason) LLIST_HEAD(release_list); int ret = -EOPNOTSUPP; + ovpn_mcast_leave_all(peer); + spin_lock_bh(&peer->ovpn->lock); switch (peer->ovpn->mode) { case OVPN_MODE_MP: From patchwork Thu May 14 09:52:08 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marco Baffo X-Patchwork-Id: 4942 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:a719:b0:84a:48f:a1fd with SMTP id hl25csp3721127mab; Thu, 14 May 2026 02:52:35 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ/CTB3EfHVW5PO8ChWtNNEWx+TUHDl0st4L/MBh7oVM3uxam4r8+9VOSeRfSVcV+SvC/cN7dtOANlM=@openvpn.net X-Received: by 2002:a05:6808:2e48:b0:479:d605:64a0 with SMTP id 5614622812f47-482b23c3deamr4610770b6e.0.1778752355785; Thu, 14 May 2026 02:52:35 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1778752355; cv=none; d=google.com; s=arc-20240605; b=Ov6dAAo4VVUKGGqEIxPh7aZvw3Jqoebz6+onddxpfW6Ym6Phfvwo+0I3TGyD/IcXYQ 2y7BYVXar2QftSNW2fY2RfXcgF5B3VYS7HxeHgHojotwunfKdslGzF+A443c0qvZAE0b VDcLxCIyTJfI07a9iudazNc5lUAPKB94udxt47ma+skuSOtaKbZtEm9z2XBpo78GDSmk JLreNGqo+LeOyVtLMv3Z64KJAhqb2tQH1devbp+GoocIjMk0R1Uj4eczgFkUjoX0j10W CblYuyev+/6/IiLF8NBYc6qrpVJLsPtUP4SG1tRhDBhrmUv889Mst47SPLa9/svQ46dx bRjQ== 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=N29w3QYbrhlVBhS/5hySSvE9RFz1mDjFTKgZiT/1dIg=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=Th9p5RRq2tuW2GJJ7gxYpEVk2W/Vs8E97KD4z6Jisg8oOTS35x6d7AaDqzrChtU29z YRPwofXwNNN/3Y4vMGrorzW6tz2RUl/eLJpJoHK9pUMdWJQQeTU0TUFOdbKaB/uz8jkH r15qvCXAgj6CowIXFEdaG1Y6Ob6pc0fdRIymuskVsSRxdaiSC2VVvWCOWiZE7v45lTE4 b1RZ1uak1KFtsdjhZTx61rmI5yjf1lf6344OBkcG/dxiQ/Fc6n8A0FCC7ek6FMPzV7eL J+KFEjnxhILtmXDCNiVRio45dk1gngKh/RD6Z+4ww/pWahgj+MUtrC9daeqcLEoUOcwP 1lTQ==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=IkGqd4sa; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=CwaNOh3h; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=TnJNbMsU; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=dOcQ9YWU; 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-482d3df8399si942429b6e.114.2026.05.14.02.52.35 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 14 May 2026 02:52:35 -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=IkGqd4sa; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=CwaNOh3h; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=TnJNbMsU; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=dOcQ9YWU; 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=N29w3QYbrhlVBhS/5hySSvE9RFz1mDjFTKgZiT/1dIg=; b=IkGqd4sadf9boXUCOWkRZIMGXu lw9518mFD9TkbF/PvGD9zBZDK/u/EJotwnmrGLTMvPuHbEoT1edj/nLPptHcXc66t2xKpshz5hiZA sr5q6zSMK/yioichs9eHGET2h2QLcyP10rMAcl7nksX3PFCYZbh+9WoFKaPRojdGWBnc=; 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 1wNSk3-0001el-GG; Thu, 14 May 2026 09:52:32 +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 1wNSk0-0001eP-3H for openvpn-devel@lists.sourceforge.net; Thu, 14 May 2026 09:52:29 +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=P7cGHdZcQ2cb3dm/1mwoHgGW2CiTsiZ51s+HG70/cF0=; b=CwaNOh3hsIdwFezhJpcd3rQO7Z wPuz7Iu3rA9YlODzSHcqKRYUtZr2JNhft2jDow7m6YWvZPMmh/2btDeS4eLh6Y4ez8tYXwazpcVRd fQSVEA8lQQbFi/ecd46GmvNgmDEKCEDT0WKAdjDExmdRDnYjLBhZbXLAj2JPjAaSni2U=; 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=P7cGHdZcQ2cb3dm/1mwoHgGW2CiTsiZ51s+HG70/cF0=; b=TnJNbMsU6IFuwDW5SjQiIJvkX7 jveREUdQDtUCPfy9VkY904xZNW1InYYpjYHKpUpEWaMchOcGTVdMRn6cQdiG/kqTo1Hpp6HmnuE+I 9DnH87mp2VD50fWdgEaJmX7Cc8qzY8OPKPr1vrVlylkRUXmSMWyj2AFH3THlnHjJaxzU=; 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 1wNSjy-0004To-NH for openvpn-devel@lists.sourceforge.net; Thu, 14 May 2026 09:52:28 +0000 Received: from smtp102.mailbox.org (smtp102.mailbox.org [10.196.197.102]) (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 4gGQbg5hnbzDs2B; Thu, 14 May 2026 11:52:15 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1778752335; 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=P7cGHdZcQ2cb3dm/1mwoHgGW2CiTsiZ51s+HG70/cF0=; b=dOcQ9YWUu4jD0Z/ysOGxItyNNWyMH2mpLsp3BRQ6cfZYePIMeEqPcRag3q3IhCtjsNjwwR F+FMe5kQc1tMq82mhIqVPAtpnApkPcaVd0jHkdl7VeSuGlK1GWH6rUuJtKmxqhrWsibZ9w KTH5eQk6RWEdNfcTFKHbmPGbFqClJhO1Fh6T4nivCDG6j17vJ2pt19mUt1JQ6n1lbdjWwz WAP+7jksUefyXWdUoSK3WnezYOJwmqZqPEJinPmJY3vIL7qXpZ9OycQveGrDYflZVaLP71 igJiZKdreMdPifGxbR2AsoUsNlTz1aNME8onMV70gYlJBZS3oAABZgBD6a3Hjg== From: Marco Baffo To: openvpn-devel@lists.sourceforge.net Date: Thu, 14 May 2026 11:52:08 +0200 Message-ID: <20260514095210.288979-3-marco@mandelbit.com> In-Reply-To: <20260514095210.288979-1-marco@mandelbit.com> References: <20260514095210.288979-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: When the Ovpn server receive a multicast data packet that has to be trasmitted to the peers, if its IP destination is a multicast group to which no peer is currently subscribed, the packet is dropped [...] 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 0.0 RCVD_IN_MSPIKE_H2 RBL: Average reputation (+2) [195.10.208.47 listed in wl.mailspike.net] X-Headers-End: 1wNSjy-0004To-NH Subject: [Openvpn-devel] [RFC ovpn net-next 3/5] ovpn: filter incoming multicast data packets 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: =?utf-8?q?1865157029929791097?= X-GMAIL-MSGID: =?utf-8?q?1865157029929791097?= When the Ovpn server receive a multicast data packet that has to be trasmitted to the peers, if its IP destination is a multicast group to which no peer is currently subscribed, the packet is dropped instead of being broadcasted. Multicast control messages (IGMP/MLD) are still broadcasted to all peers. Signed-off-by: Marco Baffo --- drivers/net/ovpn/mcast.c | 41 ++++++++++++++++++++++++++++++++++++++-- drivers/net/ovpn/mcast.h | 1 + drivers/net/ovpn/peer.c | 7 +++++-- 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/drivers/net/ovpn/mcast.c b/drivers/net/ovpn/mcast.c index c90ef2b8d8b8..59b0b62afcde 100644 --- a/drivers/net/ovpn/mcast.c +++ b/drivers/net/ovpn/mcast.c @@ -358,12 +358,49 @@ bool ovpn_mcast_snoop_skb(struct ovpn_peer *peer, struct sk_buff *skb) { if (peer->ovpn->mode != OVPN_MODE_MP) return false; + if (skb->protocol == htons(ETH_P_IP)) { if (ip_hdr(skb)->protocol == IPPROTO_IGMP) return ovpn_mcast_snoop_igmp(peer, skb); } else if (skb->protocol == htons(ETH_P_IPV6)) { - if (ipv6_hdr(skb)->nexthdr == IPPROTO_ICMPV6) - return ovpn_mcast_snoop_mld(peer, skb); + return ovpn_mcast_snoop_mld(peer, skb); + } + + return false; +} + +/** + * ovpn_mcast_is_control - determine whether an skb is multicast control traffic + * @skb: the packet to inspect + * + * Return: true if the skb contains IGMP or MLD control traffic, + * false otherwise + */ +bool ovpn_mcast_is_control(struct sk_buff *skb) +{ + unsigned int offset; + struct icmp6hdr *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; + + if (!pskb_may_pull(skb, offset + sizeof(*ih))) + return false; + + ih = (struct icmp6hdr *)(skb_network_header(skb) + offset); + switch (ih->icmp6_type) { + case ICMPV6_MGM_QUERY: + case ICMPV6_MGM_REPORT: + case ICMPV6_MGM_REDUCTION: + case ICMPV6_MLD2_REPORT: + return true; } + return false; } diff --git a/drivers/net/ovpn/mcast.h b/drivers/net/ovpn/mcast.h index e9e14d807270..9e06e893a355 100644 --- a/drivers/net/ovpn/mcast.h +++ b/drivers/net/ovpn/mcast.h @@ -22,6 +22,7 @@ void ovpn_mcast_leave_all(struct ovpn_peer *peer); bool ovpn_peer_list_get_by_mcast_group(struct ovpn_priv *ovpn, const struct in6_addr *group_addr, struct llist_head *list); +bool ovpn_mcast_is_control(struct sk_buff *skb); bool ovpn_mcast_snoop_skb(struct ovpn_peer *peer, struct sk_buff *skb); #endif /* _NET_OVPN_MCAST_H_ */ diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 5159a8f9dfba..a9728a157210 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -779,8 +779,10 @@ void ovpn_peer_list_get_by_dst(struct ovpn_priv *ovpn, struct sk_buff *skb, addr_type = inet_dev_addr_type(dev_net(ovpn->dev), ovpn->dev, addr4); if (addr_type == RTN_MULTICAST) { ipv6_addr_set_v4mapped(addr4, &addr6); - if (!ovpn_peer_list_get_by_mcast_group(ovpn, &addr6, list)) + if (!ovpn_peer_list_get_by_mcast_group(ovpn, &addr6, list) && + ovpn_mcast_is_control(skb)) { ovpn_peer_list_get_all(ovpn, list); + } } else if (addr_type == RTN_BROADCAST) { ovpn_peer_list_get_all(ovpn, list); } @@ -795,7 +797,8 @@ void ovpn_peer_list_get_by_dst(struct ovpn_priv *ovpn, struct sk_buff *skb, rcu_read_unlock(); if (ipv6_addr_is_multicast(&addr6) && - !ovpn_peer_list_get_by_mcast_group(ovpn, &addr6, list)) { + !ovpn_peer_list_get_by_mcast_group(ovpn, &addr6, list) && + ovpn_mcast_is_control(skb)) { ovpn_peer_list_get_all(ovpn, list); } return; From patchwork Thu May 14 09:52:09 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marco Baffo X-Patchwork-Id: 4940 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:a719:b0:84a:48f:a1fd with SMTP id hl25csp3721101mab; Thu, 14 May 2026 02:52:32 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ+QvsEQsoSABGRkexXrAJ5jNEYH2SGNxqbblJFFNAlIMJ8kLY9Cg6SIO8nVksgCaMnlAQ3a87vsFwI=@openvpn.net X-Received: by 2002:a05:6870:91ca:b0:435:2ae:19bf with SMTP id 586e51a60fabf-439ce02db00mr3873467fac.3.1778752351857; Thu, 14 May 2026 02:52:31 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1778752351; cv=none; d=google.com; s=arc-20240605; b=Pc3yU/VleY+43ujnc/iT9oodp679qhywdZQGXTLpMQ/OdwLz+5w/zkVV94dBcPYMZ/ eYgKfIm/ARbQyQ4La/gk0fxn1aXqcCGJGm+N0w6zU4QFyHxGdrmG3GQVBREHRI7zZ4UX ZZEJWHU+TYrFh5G1M2duJmQ+jiVyoI1KMFzsqDd7b6g6rIxnE2UxVj7fcXTTEcRmU3FS a+F5dwjHapxC2BV9f7/nFV04BidYKWQSnww+khyOVeknFVwjXCCRlflIOnntWsgPQzDp 9NGdQkkPeFuq3Q4N8DxVZV6ljVxxZX61AksngaaYDm9XlIKPxJ/WWUiNAYbvSA22lYc3 ei7A== 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=McmmUC1jxWXX4NZH67by3/8SF1KlmHBhpifOwJRmqPs=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=ApnCaEOJcv4CWh6vxyZxRFNsgKe/JyRugbGFoabJ31wK5PmtmyRl23coptiSJ0mech 5QPpK60fbFwORJFCsEJojDvcWOLlc+ndW6yXL1yq6zMm3ZNsYY7jE8YtzL4W8Y5kAzHU 6+n4FxyjzZQSki6BSmfIhaeJnHN3TTJWyUaP4g8RsW7INYTD7fr41dCbSOpj3IVtqq3Z HMXROlG3pyG7a3/gJfqM1MyJrDVaH5DMsgvfczIKkqWwmUp2zHH4ez+82wLpOVCgMKZP OBQqZ4OBNO83N3bC2o9dLh15ADj3l3DIM48yFS7wKG0wXxXdenF6bmc4HTBzR4lTDQV1 61Gw==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=A2BfQHiT; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b="L/YVFbT/"; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=nB3hZ+Rd; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=15rHgEUJ; 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-439fc780866si1457857fac.288.2026.05.14.02.52.31 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 14 May 2026 02:52:31 -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=A2BfQHiT; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b="L/YVFbT/"; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=nB3hZ+Rd; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=15rHgEUJ; 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=McmmUC1jxWXX4NZH67by3/8SF1KlmHBhpifOwJRmqPs=; b=A2BfQHiTUxBsIncwd0pIzfz3TQ EanGsiTfzPII/DnQQqqXY93Au7RxlrorNt52oHO4ptmRxj2I8Okag1EtRJrSwndv/B5n1QqFopi/t le5iOMNdc6sO/WRcvD5ZQP3OoSLLn2E3ZKlcnrfDiXRd04tSgMbIwxfASHk8LV1g6dbU=; 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 1wNSjx-0005sC-5W; Thu, 14 May 2026 09:52:25 +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 1wNSjv-0005s4-TV for openvpn-devel@lists.sourceforge.net; Thu, 14 May 2026 09:52:24 +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=08qlaEW/4Z8aTVH+fgFqZNPX9Ghxm7aE5V/AVfa3Ru4=; b=L/YVFbT/7SxdQP/LkJn9se87nM co2Z3aD6KPIox/WTaUzIxt9/x9SPtYv/rOQ0SHcw6BcdU20jI6hZSYcccbdrOJ3za+MCi7ha3Doq6 8DgmEFdFEgN3Gk8e5OPhoUMYfjk4XFCC+H8XO3mijQB/NZRQ93j5ShJEKN/kMwlLMxVA=; 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=08qlaEW/4Z8aTVH+fgFqZNPX9Ghxm7aE5V/AVfa3Ru4=; b=nB3hZ+RduBA/vDKD2i4Gisv//5 3kaCnRIaDqw7UnYAz+PuDZrbPDybj7FNxrqe761f3WfMIKH5+nmt1Zn2nfpC6IDsAfynovnB1lnwo knIkbgpDwlsqQErAVVSatyC8EQqW3xEm4+95iBRIt6rV3qOJ7B3q31qUxheGRLK0H4JA=; Received: from mout-b-105.mailbox.org ([195.10.208.50]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wNSju-0004Te-F2 for openvpn-devel@lists.sourceforge.net; Thu, 14 May 2026 09:52:24 +0000 Received: from smtp102.mailbox.org (smtp102.mailbox.org [10.196.197.102]) (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-105.mailbox.org (Postfix) with ESMTPS id 4gGQbh42KVz9xXG; Thu, 14 May 2026 11:52:16 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1778752336; 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=08qlaEW/4Z8aTVH+fgFqZNPX9Ghxm7aE5V/AVfa3Ru4=; b=15rHgEUJVHJG7lr9Ixb9GAACqyDbrbChvpV6/kK/SIFQRoWmSCpKjGd0VA9xXtryDrUxBN rmLPLCsj08H4UBW3aC09SkJHMSNFTmaXzeuCc7vEVFmeIGk2y5TCnlg0nlHUI2o0ekYu9W l7IN7rDS16A47xBXj3eZH7b7Card3Bh5cTbjvG+fTrevyy/DGJygO2nbtVcc9UDlToqBNU /oBIYl096MIx+mJfyH0ANooCgefk3JDHiPkzcXF/0bq6ktaDPJd+NNDlEhf5zuiDUWsrek wEPeCVSuMTgEgRpCuDRws87j9oVw5CbW6M8XXRMWnMhEMbbQGUN+FP9ZHl8BHg== From: Marco Baffo To: openvpn-devel@lists.sourceforge.net Date: Thu, 14 May 2026 11:52:09 +0200 Message-ID: <20260514095210.288979-4-marco@mandelbit.com> In-Reply-To: <20260514095210.288979-1-marco@mandelbit.com> References: <20260514095210.288979-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: Extend multicast snooping to handle IGMPv3 and MLDv2 membership reports received from tunnel peers. These protocols carry multiple group records per report, each with an INCLUDE/EXCLUDE filter mode an [...] 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 0.0 RCVD_IN_MSPIKE_H2 RBL: Average reputation (+2) [195.10.208.50 listed in wl.mailspike.net] X-Headers-End: 1wNSju-0004Te-F2 Subject: [Openvpn-devel] [RFC ovpn net-next 4/5] ovpn: add IGMPv3 and MLDv2 ASM snooping 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: =?utf-8?q?1865157026279346620?= X-GMAIL-MSGID: =?utf-8?q?1865157026279346620?= Extend multicast snooping to handle IGMPv3 and MLDv2 membership reports received from tunnel peers. These protocols carry multiple group records per report, each with an INCLUDE/EXCLUDE filter mode and an optional source list. For ASM (Any Source Multicast), the snooping logic treats EXCLUDE mode with an empty source list as a join and INCLUDE mode with an empty source list as a leave, matching the behavior of IGMPv2 and MLDv1. SSM is not supported yet. Signed-off-by: Marco Baffo --- drivers/net/ovpn/mcast.c | 127 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 125 insertions(+), 2 deletions(-) diff --git a/drivers/net/ovpn/mcast.c b/drivers/net/ovpn/mcast.c index 59b0b62afcde..1e436a6721bb 100644 --- a/drivers/net/ovpn/mcast.c +++ b/drivers/net/ovpn/mcast.c @@ -268,13 +268,68 @@ static bool ovpn_mcast_mld_offset(struct sk_buff *skb, unsigned int *offsetp) return true; } +/** + * ovpn_mcast_snoop_mldv2 - inspect an MLDv2 report message + * @peer: the peer this packet was received from + * @skb: the packet to inspect + * @offset: bytes from the start of the network header to the start of the MLD group records + * @ngrec: number of group records + * + * Parse the MLDv2 report and update the multicast subscription table. + * + * Return: true if the packet was a recognized MLDv2 join/leave and was + * consumed, false otherwise + */ +static bool ovpn_mcast_snoop_mldv2(struct ovpn_peer *peer, struct sk_buff *skb, + unsigned int offset, const int ngrec) +{ + struct mld2_grec *grec; + int i; + __u16 nsrcs; + unsigned int rec_len; + + for (i = 0; i < ngrec; i++) { + if (!pskb_may_pull(skb, offset + sizeof(*grec))) + return true; + + grec = (struct mld2_grec *)(skb_network_header(skb) + offset); + nsrcs = ntohs(grec->grec_nsrcs); + + rec_len = sizeof(*grec) + nsrcs * sizeof(struct in6_addr) + + grec->grec_auxwords * 4; + offset += rec_len; + + if (!pskb_may_pull(skb, offset)) + return true; + + /* recompute grec after potential head reallocation */ + grec = (struct mld2_grec *)(skb_network_header(skb) + offset - rec_len); + + /* In MLDv2 ASM, EXCLUDE mode with an empty source list means + * "exclude nothing, receive everything" -> JOIN. + * INCLUDE mode with an empty source list means + * "include nothing, receive nothing" -> LEAVE. + * See RFC 3810, section 4. + */ + if (nsrcs == 0 && + (grec->grec_type == MLD2_CHANGE_TO_INCLUDE || + grec->grec_type == MLD2_MODE_IS_INCLUDE)) { + ovpn_mcast_leave(peer->ovpn, peer, &grec->grec_mca); + } else { + ovpn_mcast_join(peer->ovpn, peer, &grec->grec_mca); + } + } + + return true; +} + /** * ovpn_mcast_snoop_mld - inspect an IPv6 packet for MLD join/leave messages * @peer: the peer this packet was received from * @skb: the packet to inspect * * Parse the MLD header and update the multicast subscription table on - * MLDv1 reports and done messages. + * MLDv1/v2 reports and done messages. * * Return: true if the packet was a recognized MLD join/leave and was * consumed, false otherwise @@ -293,6 +348,11 @@ static bool ovpn_mcast_snoop_mld(struct ovpn_peer *peer, struct sk_buff *skb) mld = (struct mld_msg *)(skb_network_header(skb) + offset); switch (mld->mld_type) { + case ICMPV6_MLD2_REPORT: + return ovpn_mcast_snoop_mldv2(peer, skb, + offset + sizeof(struct mld2_report), + ntohs(((struct mld2_report *)mld)->mld2r_ngrec) + ); case ICMPV6_MGM_REPORT: ovpn_mcast_join(peer->ovpn, peer, &mld->mld_mca); return true; @@ -305,13 +365,71 @@ static bool ovpn_mcast_snoop_mld(struct ovpn_peer *peer, struct sk_buff *skb) return false; } +/** + * ovpn_mcast_snoop_igmpv3 - inspect an IGMPv3 report message + * @peer: the peer this packet was received from + * @skb: the packet to inspect + * @offset: bytes from the start of the network header to the start of the IGMP group records + * @ngrec: number of group records + * + * Parse the IGMPv3 report and update the multicast subscription table. + * + * Return: true if the packet was a recognized IGMPv3 join/leave and was + * consumed, false otherwise + */ +static bool ovpn_mcast_snoop_igmpv3(struct ovpn_peer *peer, struct sk_buff *skb, + unsigned int offset, const int ngrec) +{ + struct igmpv3_grec *grec; + struct in6_addr addr6; + int i; + unsigned int rec_len; + __u16 nsrcs; + + for (i = 0; i < ngrec; i++) { + if (!pskb_may_pull(skb, offset + sizeof(*grec))) + return true; + + grec = (struct igmpv3_grec *)(skb_network_header(skb) + offset); + nsrcs = ntohs(grec->grec_nsrcs); + + rec_len = sizeof(*grec) + nsrcs * sizeof(__be32) + + grec->grec_auxwords * 4; + offset += rec_len; + + if (!pskb_may_pull(skb, offset)) + return true; + + /* recompute grec after potential head reallocation */ + grec = (struct igmpv3_grec *)(skb_network_header(skb) + offset - rec_len); + + /* In IGMPv3 ASM, EXCLUDE mode with an empty source list means + * "exclude nothing, receive everything" -> JOIN. + * INCLUDE mode with an empty source list means + * "include nothing, receive nothing" -> LEAVE. + * See RFC 3376, section 3. + */ + if (nsrcs == 0 && + (grec->grec_type == IGMPV3_CHANGE_TO_INCLUDE || + grec->grec_type == IGMPV3_MODE_IS_INCLUDE)) { + ipv6_addr_set_v4mapped(grec->grec_mca, &addr6); + ovpn_mcast_leave(peer->ovpn, peer, &addr6); + } else { + ipv6_addr_set_v4mapped(grec->grec_mca, &addr6); + ovpn_mcast_join(peer->ovpn, peer, &addr6); + } + } + + return true; +} + /** * ovpn_mcast_snoop_igmp - inspect an IPv4 packet for IGMP join/leave messages * @peer: the peer this packet was received from * @skb: the packet to inspect * * Parse the IGMP header and update the multicast subscription table on - * IGMPv2 membership reports and leave messages. + * IGMPv2/v3 membership reports and leave messages. * * Return: true if the packet was a recognized IGMP join/leave and was * consumed, false otherwise @@ -329,6 +447,11 @@ static bool ovpn_mcast_snoop_igmp(struct ovpn_peer *peer, struct sk_buff *skb) ih = (struct igmphdr *)(skb_network_header(skb) + ihl); switch (ih->type) { + case IGMPV3_HOST_MEMBERSHIP_REPORT: + return ovpn_mcast_snoop_igmpv3(peer, skb, + ihl + sizeof(struct igmpv3_report), + ntohs(((struct igmpv3_report *)ih)->ngrec) + ); case IGMPV2_HOST_MEMBERSHIP_REPORT: ipv6_addr_set_v4mapped(ih->group, &addr6); ovpn_mcast_join(peer->ovpn, peer, &addr6); From patchwork Thu May 14 09:52: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: 4943 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:a719:b0:84a:48f:a1fd with SMTP id hl25csp3721124mab; Thu, 14 May 2026 02:52:35 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ8RtyTjqm5m6zYEUMTR8dRMrYVcdoEUWeFHEbsNRN/fx3kFpuub4eZadU+Z7Qgn7Z7soV83Szw8ATk=@openvpn.net X-Received: by 2002:a05:6820:6ae7:b0:67d:e7c3:3c6c with SMTP id 006d021491bc7-69b78defe42mr3525667eaf.53.1778752355494; Thu, 14 May 2026 02:52:35 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1778752355; cv=none; d=google.com; s=arc-20240605; b=fUbVrBLhedOYr4xlZpZHmU763H7pJmK+qBZvE8OgCh9jN9Y1N2TQfzdYcNB8d4aJzT EPyVRzqnOh+TsGFuWVs4Wm90n1IVDvNYPmnptMBcZrTO2vQuXubJfVatQHI5a6DgZTy0 kN4lxc7OtAlaXmAWPBSsjclXFpBF8pCgQfzxRUqXWrp1t9lVc/Dy0eXVhWkaiICfKqp2 vbgB+xQ1+03oZmhxKrj7vHZ++GpArANl1BPnPDpc9SAA1e7lT3t/ZVNcQ3KnO/f4QlKL fdSbe5qbLYnb1eKSH7GEHOUjGYZrttKAt7ppEAMmW/p+mccNlfz/uuyC+JxeuvUKNK0t NK3g== 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=lgife2oncXmr4kB8Xqyha7WxgRl5P3Bcq2LYh42LeIM=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=NQMAFvsiJNj6q2nmEcPR5rODJ1m8Y9T60Rs/Si0IH+k+LRt+Vph/IZmV0KcMTKNf06 qQUR1P59iFqcDeBjMqYOQi6jrAegk/uFaVEuh0zgbV7iC/ShDuQVBRyZwxgW7a/Uwg6e hDmW3CU+z6MiiDR5pZqETSQyuZmuikSQ1BaG8ojwNaPAyoUwAGWuaPFWeeLDwOkpTtX/ CVwcCDBWsO9nQ+I0Ff/S9a9FN7ku1JYjHKkhA4areBJxTih6oOzZkzVDlkCPIMIxT5/Q oaHjqx8qpLnDL1qeeif5zg08gxDPo0Ak0IiG9SIqo+Xp5Qn7/8JnivmUdD4GongCvEaZ VlnQ==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=bWqmSmYF; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=NTe6S0hy; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b="f/5cWtyt"; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=NxEtCPa2; 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-439fc148c7bsi1527607fac.58.2026.05.14.02.52.35 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 14 May 2026 02:52:35 -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=bWqmSmYF; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=NTe6S0hy; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b="f/5cWtyt"; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=NxEtCPa2; 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=lgife2oncXmr4kB8Xqyha7WxgRl5P3Bcq2LYh42LeIM=; b=bWqmSmYFW//caz43+RH/SJBdRG So7ZgYY7xvsJ0BlCdCw4MpH8u4MYNES3oz7/VIVHCTX4fxGI2gzrBj/vrdjUfiN4BWjsZ4oGX8Kc7 SzZHScE7ZL7LG3LKdJEVJor+rLhGM5bDtVkjjT+3xKD48eL9bamZOI8uuSgY1KVmp/7I=; 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 1wNSk4-0002Pg-NZ; Thu, 14 May 2026 09:52:32 +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 1wNSk2-0002PT-JI for openvpn-devel@lists.sourceforge.net; Thu, 14 May 2026 09:52:30 +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=m8dgcjqYJGlHpk3F+iZkezX8E9V6vyqEIqSmZpuWvWg=; b=NTe6S0hy1J0NZSXq+0blvKOz5O wgqluyNX7ODdPHZr5JHa67Pf7lKW12TETaE2qEPrf+AKFneW7sL1ia9CuicqkwlmNejG6MePBCn3C bt/QnTuzaTJ+Tu7EwERevm3JB9SzVUSiCNugTADMqFQJsrVN7gK+fVNMdj3AaAMjO8bw=; 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=m8dgcjqYJGlHpk3F+iZkezX8E9V6vyqEIqSmZpuWvWg=; b=f/5cWtytdanW+TQMqEWSmsQs2B m40AGqXbWgr8xLiq3oAMR8kylg1BRmoGmBfm5MEw96ckRrn/rAPxhEJmBOzoGobS3A+MXUIC9eYga rEaYP9Xp+eNuj46FXbHWqBzUy5ke+7uh5WGljgovbpDe0bX9GWgECHXHcIP1T7gwh7xA=; 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 1wNSk0-0004Tt-EI for openvpn-devel@lists.sourceforge.net; Thu, 14 May 2026 09:52:30 +0000 Received: from smtp102.mailbox.org (smtp102.mailbox.org [10.196.197.102]) (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-206.mailbox.org (Postfix) with ESMTPS id 4gGQbj3WsXz9xDj; Thu, 14 May 2026 11:52:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1778752337; 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=m8dgcjqYJGlHpk3F+iZkezX8E9V6vyqEIqSmZpuWvWg=; b=NxEtCPa2CXndvdiIeiduMbjEUzB53QWMU8MPsMGV0pDaux9CJIm2yp8ZL4p5eVlkmhnxCK yFilGYZACGyuSzAZSoZQxYpu4I6vBjCliVd4vCu4H0JRrJdCiAFZlnkpZ5lsUM16sQWrOB mi2mA7/emCPGJn+3ujVggcScKMqeFeFf9Altb4/wMjKGgWxW4CaKzcO5xSZ/jDN2KkxGfe J24+C0CLdfSXb/5Eb4/SYK0bRrct2Wl3hvmynby6Yg+XTz7kP54cIohcZqaKnMz9PW6J/b dOiJxfTdVQmZUZLL8AbOtt1o6mKVHxsz+8UsDJ8snpnS/6UTk9mrsQFPljOdzg== From: Marco Baffo To: openvpn-devel@lists.sourceforge.net Date: Thu, 14 May 2026 11:52:10 +0200 Message-ID: <20260514095210.288979-5-marco@mandelbit.com> In-Reply-To: <20260514095210.288979-1-marco@mandelbit.com> References: <20260514095210.288979-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: Extend the multicast subscription table to track per-source state, enabling SSM (S,G) forwarding instead of group-only ASM forwarding. Data structures: - Add ovpn_mcast_source to track individual source addresses - Extend ovpn_mcast_sub with filter_mode (INCLUDE/EXCLUDE) and a source list 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 0.0 RCVD_IN_MSPIKE_H2 RBL: Average reputation (+2) [195.10.208.51 listed in wl.mailspike.net] X-Headers-End: 1wNSk0-0004Tt-EI Subject: [Openvpn-devel] [RFC ovpn net-next 5/5] ovpn: implement IGMPv3/MLDv2 SSM snooping 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: =?utf-8?q?1865157029768931633?= X-GMAIL-MSGID: =?utf-8?q?1865157029768931633?= Extend the multicast subscription table to track per-source state, enabling SSM (S,G) forwarding instead of group-only ASM forwarding. Data structures: - Add ovpn_mcast_source to track individual source addresses - Extend ovpn_mcast_sub with filter_mode (INCLUDE/EXCLUDE) and a source list Subscription API: - Add ovpn_mcast_sub_update() to create or update a subscription with a full source list and filter mode - ovpn_mcast_join() becomes a thin wrapper around sub_update() (EXCLUDE mode with empty source list = ASM join) - Add ovpn_mcast_srcs_update() for incremental source merging: ALLOW_NEW adds sources in INCLUDE mode and removes them in EXCLUDE mode; BLOCK_OLD does the opposite - Empty INCLUDE subscriptions are automatically deleted when BLOCK_OLD removes the last source RX path (snooping): - IGMPv3 and MLDv2 parsers now extract source lists from reports and pass them to sub_update() - All record types are handled: MODE_IS_*, CHANGE_TO_*, ALLOW_NEW_SOURCES, BLOCK_OLD_SOURCES TX path (forwarding): - Add ovpn_mcast_src_allowed() to evaluate a source against a peer's filter mode and source list - ovpn_peer_list_get_by_mcast_group() now takes a source address and only returns peers whose subscription allows the source - ASM backward compatibility preserved: EXCLUDE with empty source list allows all sources Signed-off-by: Marco Baffo --- drivers/net/ovpn/mcast.c | 334 +++++++++++++++++++++++++++++++++------ drivers/net/ovpn/mcast.h | 14 +- drivers/net/ovpn/peer.c | 7 +- 3 files changed, 305 insertions(+), 50 deletions(-) diff --git a/drivers/net/ovpn/mcast.c b/drivers/net/ovpn/mcast.c index 1e436a6721bb..74b791ad7489 100644 --- a/drivers/net/ovpn/mcast.c +++ b/drivers/net/ovpn/mcast.c @@ -17,9 +17,16 @@ struct ovpn_mcast_group { struct list_head subs; }; +struct ovpn_mcast_source { + struct list_head list; + struct in6_addr addr; +}; + struct ovpn_mcast_sub { struct list_head list; struct ovpn_peer *peer; + enum ovpn_mcast_filter_mode filter_mode; + struct list_head sources; }; static inline u32 ovpn_mcast_hash(const struct in6_addr *group_addr) @@ -47,10 +54,21 @@ ovpn_mcast_group_find(const struct ovpn_priv *ovpn, const struct in6_addr *group return NULL; } +static void ovpn_mcast_srcs_del_all(struct list_head *srcs) +{ + struct ovpn_mcast_source *src, *next; + + list_for_each_entry_safe(src, next, srcs, list) { + list_del(&src->list); + kfree(src); + } +} + static struct ovpn_peer *ovpn_mcast_sub_del(struct ovpn_mcast_sub *sub) { struct ovpn_peer *peer = sub->peer; + ovpn_mcast_srcs_del_all(&sub->sources); list_del(&sub->list); kfree(sub); return peer; @@ -85,20 +103,138 @@ void ovpn_mcast_cleanup(struct ovpn_priv *ovpn) } } +static void ovpn_mcast_srcs_del(struct ovpn_mcast_sub *sub, + const struct in6_addr *sources, + const unsigned int nsrcs) +{ + struct ovpn_mcast_source *src, *next; + unsigned int i; + + for (i = 0; i < nsrcs; i++) { + list_for_each_entry_safe(src, next, &sub->sources, list) { + if (ipv6_addr_equal(&src->addr, &sources[i])) { + list_del(&src->list); + kfree(src); + break; + } + } + } +} + +static bool ovpn_mcast_source_exists(const struct ovpn_mcast_sub *sub, + const struct in6_addr *addr) +{ + struct ovpn_mcast_source *src; + + list_for_each_entry(src, &sub->sources, list) { + if (ipv6_addr_equal(&src->addr, addr)) + return true; + } + return false; +} + +static void ovpn_mcast_srcs_add(struct ovpn_mcast_sub *sub, + const struct in6_addr *sources, + const unsigned int nsrcs) +{ + struct ovpn_mcast_source *src; + unsigned int i; + + for (i = 0; i < nsrcs; i++) { + if (ovpn_mcast_source_exists(sub, &sources[i])) + continue; + + src = kzalloc_obj(*src, GFP_ATOMIC); + if (!src) + break; + src->addr = sources[i]; + list_add_tail(&src->list, &sub->sources); + } +} + +static struct ovpn_peer *ovpn_mcast_srcs_update(struct ovpn_mcast_sub *sub, + const enum ovpn_mcast_filter_mode msg_mode, + const struct in6_addr *sources, + const unsigned int nsrcs) +{ + if (!sources || !nsrcs) + return NULL; + + /* ALLOW_NEW: add in INCLUDE, del in EXCLUDE. + * BLOCK_OLD: del in INCLUDE, add in EXCLUDE. + */ + if (sub->filter_mode == msg_mode) { + ovpn_mcast_srcs_add(sub, sources, nsrcs); + } else { + ovpn_mcast_srcs_del(sub, sources, nsrcs); + if (sub->filter_mode == OVPN_MCAST_INCLUDE && + list_empty(&sub->sources)) + return ovpn_mcast_sub_del(sub); + } + return NULL; +} + +static bool ovpn_mcast_sub_init(struct ovpn_mcast_sub **subp, + struct ovpn_peer *peer, + const enum ovpn_mcast_filter_mode mode, + struct ovpn_mcast_group *group) +{ + struct ovpn_mcast_sub *sub; + + sub = kzalloc_obj(*sub, GFP_ATOMIC); + if (unlikely(!sub)) + return false; + + if (!ovpn_peer_hold(peer)) { + kfree(sub); + return false; + } + + sub->peer = peer; + sub->filter_mode = mode; + INIT_LIST_HEAD(&sub->sources); + list_add_tail(&sub->list, &group->subs); + *subp = sub; + return true; +} + /** - * ovpn_mcast_join - add a peer to a multicast group + * ovpn_mcast_sub_update - create, replace, or incrementally update a multicast subscription * @ovpn: the ovpn instance - * @peer: the peer joining the group - * @group_addr: the multicast group address (IPv4-mapped IPv6 for IPv4 groups) + * @peer: the peer whose subscription is being updated + * @group_addr: the multicast group address + * @mode: the filter mode (INCLUDE or EXCLUDE) + * @sources: array of source addresses to add or remove + * @nsrcs: number of sources in @sources + * @incremental_update: if true, merge sources into existing state; + * if false, replace state entirely * - * Creates the group if it does not exist and adds a subscription for @peer. - * If the peer is already subscribed, returns success without doing anything. + * When @incremental_update is false the subscription is fully replaced with + * the given @mode and @sources. An empty source list with INCLUDE mode is + * equivalent to leaving the group; with EXCLUDE mode it is an ASM join + * (receive all sources). + * + * When @incremental_update is true the sources are merged: they are added + * to the list when @mode matches the current filter mode, or removed when + * it differs. ALLOW_NEW maps to INCLUDE; BLOCK_OLD maps to EXCLUDE. If a + * BLOCK_OLD operation removes the last source from an INCLUDE subscription, + * the subscription is destroyed. + * + * If no subscription exists for @peer on @group_addr one is created. If the + * group does not exist it is created. + * + * All updates are atomic under @ovpn->lock. */ -void ovpn_mcast_join(struct ovpn_priv *ovpn, struct ovpn_peer *peer, - const struct in6_addr *group_addr) +void ovpn_mcast_sub_update(struct ovpn_priv *ovpn, struct ovpn_peer *peer, + const struct in6_addr *group_addr, + const enum ovpn_mcast_filter_mode mode, + const struct in6_addr *sources, + const unsigned int nsrcs, + const bool incremental_update) { struct ovpn_mcast_group *group; struct ovpn_mcast_sub *sub; + struct ovpn_peer *peer_to_put = NULL; if (!ovpn_mcast_addr_valid(group_addr)) return; @@ -117,19 +253,47 @@ void ovpn_mcast_join(struct ovpn_priv *ovpn, struct ovpn_peer *peer, } list_for_each_entry(sub, &group->subs, list) { - if (sub->peer == peer) + if (sub->peer != peer) + continue; + if (incremental_update) { + peer_to_put = ovpn_mcast_srcs_update(sub, mode, sources, nsrcs); + ovpn_mcast_group_try_del(group); goto end; + } else { + sub->filter_mode = mode; + ovpn_mcast_srcs_del_all(&sub->sources); + goto add_sources; + } } - sub = kzalloc_obj(*sub, GFP_ATOMIC); - if (unlikely(!sub)) + if (!ovpn_mcast_sub_init(&sub, peer, mode, group)) { + ovpn_mcast_group_try_del(group); goto end; - - sub->peer = peer; - ovpn_peer_hold(peer); - list_add_tail(&sub->list, &group->subs); + } +add_sources: + if (sources && nsrcs) + ovpn_mcast_srcs_add(sub, sources, nsrcs); end: spin_unlock_bh(&ovpn->lock); + + if (peer_to_put) + ovpn_peer_put(peer_to_put); +} + +/** + * ovpn_mcast_join - add a peer to a multicast group + * @ovpn: the ovpn instance + * @peer: the peer joining the group + * @group_addr: the multicast group address (IPv4-mapped IPv6 for IPv4 groups) + * + * Creates the group if it does not exist and adds a subscription for @peer. + * If the peer is already subscribed, returns without doing anything. + */ +void ovpn_mcast_join(struct ovpn_priv *ovpn, struct ovpn_peer *peer, + const struct in6_addr *group_addr) +{ + ovpn_mcast_sub_update(ovpn, peer, group_addr, OVPN_MCAST_EXCLUDE, + NULL, 0, false); } /** @@ -202,20 +366,36 @@ void ovpn_mcast_leave_all(struct ovpn_peer *peer) ovpn_peer_put(peer); } +static bool ovpn_mcast_src_allowed(const struct ovpn_mcast_sub *sub, + const struct in6_addr *src_addr) +{ + struct ovpn_mcast_source *src; + + list_for_each_entry(src, &sub->sources, list) { + if (ipv6_addr_equal(&src->addr, src_addr)) + return sub->filter_mode == OVPN_MCAST_INCLUDE; + } + return sub->filter_mode == OVPN_MCAST_EXCLUDE; +} + /** * ovpn_peer_list_get_by_mcast_group - retrieve peers subscribed to a multicast group * @ovpn: the ovpn instance to search * @group_addr: the multicast group address to look up * @list: the lockless list to append matching peers to * - * Searches for the multicast group identified by @group_addr and appends all - * subscribed peers to @list, acquiring a reference on each one. + * @src: the source address to match against per-peer source filters + * + * Searches for the multicast group identified by @group_addr and appends + * subscribed peers whose source filter allows @src to @list, acquiring a + * reference on each one. * * Return: false if no peer was found, true otherwise */ bool ovpn_peer_list_get_by_mcast_group(struct ovpn_priv *ovpn, const struct in6_addr *group_addr, - struct llist_head *list) + struct llist_head *list, + const struct in6_addr *src) { struct ovpn_mcast_group *group; struct ovpn_mcast_sub *sub; @@ -225,7 +405,8 @@ bool ovpn_peer_list_get_by_mcast_group(struct ovpn_priv *ovpn, group = ovpn_mcast_group_find(ovpn, group_addr); if (group) { list_for_each_entry(sub, &group->subs, list) { - if (ovpn_peer_hold(sub->peer)) + if (ovpn_mcast_src_allowed(sub, src) && + ovpn_peer_hold(sub->peer)) llist_add(&sub->peer->mcast_entry, list); } } @@ -305,18 +486,47 @@ static bool ovpn_mcast_snoop_mldv2(struct ovpn_peer *peer, struct sk_buff *skb, /* recompute grec after potential head reallocation */ grec = (struct mld2_grec *)(skb_network_header(skb) + offset - rec_len); - /* In MLDv2 ASM, EXCLUDE mode with an empty source list means - * "exclude nothing, receive everything" -> JOIN. - * INCLUDE mode with an empty source list means - * "include nothing, receive nothing" -> LEAVE. - * See RFC 3810, section 4. - */ - if (nsrcs == 0 && - (grec->grec_type == MLD2_CHANGE_TO_INCLUDE || - grec->grec_type == MLD2_MODE_IS_INCLUDE)) { - ovpn_mcast_leave(peer->ovpn, peer, &grec->grec_mca); - } else { - ovpn_mcast_join(peer->ovpn, peer, &grec->grec_mca); + switch (grec->grec_type) { + case MLD2_MODE_IS_INCLUDE: + case MLD2_CHANGE_TO_INCLUDE: + if (nsrcs == 0) + ovpn_mcast_leave(peer->ovpn, peer, + &grec->grec_mca); + else + ovpn_mcast_sub_update(peer->ovpn, peer, + &grec->grec_mca, + OVPN_MCAST_INCLUDE, + grec->grec_src, nsrcs, + false); + break; + case MLD2_MODE_IS_EXCLUDE: + case MLD2_CHANGE_TO_EXCLUDE: + if (nsrcs == 0) + ovpn_mcast_join(peer->ovpn, peer, + &grec->grec_mca); + else + ovpn_mcast_sub_update(peer->ovpn, peer, + &grec->grec_mca, + OVPN_MCAST_EXCLUDE, + grec->grec_src, nsrcs, + false); + break; + case MLD2_ALLOW_NEW_SOURCES: + if (nsrcs) + ovpn_mcast_sub_update(peer->ovpn, peer, + &grec->grec_mca, + OVPN_MCAST_INCLUDE, + grec->grec_src, nsrcs, + true); + break; + case MLD2_BLOCK_OLD_SOURCES: + if (nsrcs) + ovpn_mcast_sub_update(peer->ovpn, peer, + &grec->grec_mca, + OVPN_MCAST_EXCLUDE, + grec->grec_src, nsrcs, + true); + break; } } @@ -381,9 +591,9 @@ static bool ovpn_mcast_snoop_igmpv3(struct ovpn_peer *peer, struct sk_buff *skb, unsigned int offset, const int ngrec) { struct igmpv3_grec *grec; - struct in6_addr addr6; + struct in6_addr addr6, *srcs = NULL; int i; - unsigned int rec_len; + unsigned int j, rec_len; __u16 nsrcs; for (i = 0; i < ngrec; i++) { @@ -403,21 +613,53 @@ static bool ovpn_mcast_snoop_igmpv3(struct ovpn_peer *peer, struct sk_buff *skb, /* recompute grec after potential head reallocation */ grec = (struct igmpv3_grec *)(skb_network_header(skb) + offset - rec_len); - /* In IGMPv3 ASM, EXCLUDE mode with an empty source list means - * "exclude nothing, receive everything" -> JOIN. - * INCLUDE mode with an empty source list means - * "include nothing, receive nothing" -> LEAVE. - * See RFC 3376, section 3. - */ - if (nsrcs == 0 && - (grec->grec_type == IGMPV3_CHANGE_TO_INCLUDE || - grec->grec_type == IGMPV3_MODE_IS_INCLUDE)) { - ipv6_addr_set_v4mapped(grec->grec_mca, &addr6); - ovpn_mcast_leave(peer->ovpn, peer, &addr6); - } else { - ipv6_addr_set_v4mapped(grec->grec_mca, &addr6); - ovpn_mcast_join(peer->ovpn, peer, &addr6); + ipv6_addr_set_v4mapped(grec->grec_mca, &addr6); + + if (nsrcs > 0) { + srcs = kcalloc(nsrcs, sizeof(*srcs), GFP_ATOMIC); + if (!srcs) + return false; + + for (j = 0; j < nsrcs; j++) + ipv6_addr_set_v4mapped(grec->grec_src[j], + &srcs[j]); } + + switch (grec->grec_type) { + case IGMPV3_MODE_IS_INCLUDE: + case IGMPV3_CHANGE_TO_INCLUDE: + if (nsrcs == 0) + ovpn_mcast_leave(peer->ovpn, peer, &addr6); + else + ovpn_mcast_sub_update(peer->ovpn, peer, &addr6, + OVPN_MCAST_INCLUDE, srcs, + nsrcs, false); + break; + case IGMPV3_MODE_IS_EXCLUDE: + case IGMPV3_CHANGE_TO_EXCLUDE: + if (nsrcs == 0) + ovpn_mcast_join(peer->ovpn, peer, &addr6); + else + ovpn_mcast_sub_update(peer->ovpn, peer, &addr6, + OVPN_MCAST_EXCLUDE, srcs, + nsrcs, false); + break; + case IGMPV3_ALLOW_NEW_SOURCES: + if (nsrcs) + ovpn_mcast_sub_update(peer->ovpn, peer, &addr6, + OVPN_MCAST_INCLUDE, srcs, + nsrcs, true); + break; + case IGMPV3_BLOCK_OLD_SOURCES: + if (nsrcs) + ovpn_mcast_sub_update(peer->ovpn, peer, &addr6, + OVPN_MCAST_EXCLUDE, srcs, + nsrcs, true); + break; + } + + kfree(srcs); + srcs = NULL; } return true; diff --git a/drivers/net/ovpn/mcast.h b/drivers/net/ovpn/mcast.h index 9e06e893a355..b41812534d58 100644 --- a/drivers/net/ovpn/mcast.h +++ b/drivers/net/ovpn/mcast.h @@ -13,15 +13,27 @@ struct in6_addr; struct llist_head; struct sk_buff; +enum ovpn_mcast_filter_mode { + OVPN_MCAST_EXCLUDE, + OVPN_MCAST_INCLUDE, +}; + void ovpn_mcast_cleanup(struct ovpn_priv *ovpn); void ovpn_mcast_join(struct ovpn_priv *ovpn, struct ovpn_peer *peer, const struct in6_addr *group_addr); void ovpn_mcast_leave(struct ovpn_priv *ovpn, struct ovpn_peer *peer, const struct in6_addr *group_addr); +void ovpn_mcast_sub_update(struct ovpn_priv *ovpn, struct ovpn_peer *peer, + const struct in6_addr *group_addr, + const enum ovpn_mcast_filter_mode mode, + const struct in6_addr *sources, + const unsigned int nsrcs, + const bool incremental_update); void ovpn_mcast_leave_all(struct ovpn_peer *peer); bool ovpn_peer_list_get_by_mcast_group(struct ovpn_priv *ovpn, const struct in6_addr *group_addr, - struct llist_head *list); + struct llist_head *list, + const struct in6_addr *src); bool ovpn_mcast_is_control(struct sk_buff *skb); bool ovpn_mcast_snoop_skb(struct ovpn_peer *peer, struct sk_buff *skb); diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index a9728a157210..3fc69c3cecc0 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -751,7 +751,7 @@ void ovpn_peer_list_get_by_dst(struct ovpn_priv *ovpn, struct sk_buff *skb, { struct ovpn_peer *peer = NULL; unsigned int addr_type; - struct in6_addr addr6; + struct in6_addr addr6, src; __be32 addr4; /* in P2P mode, no matter the destination, packets are always sent to @@ -779,7 +779,8 @@ void ovpn_peer_list_get_by_dst(struct ovpn_priv *ovpn, struct sk_buff *skb, addr_type = inet_dev_addr_type(dev_net(ovpn->dev), ovpn->dev, addr4); if (addr_type == RTN_MULTICAST) { ipv6_addr_set_v4mapped(addr4, &addr6); - if (!ovpn_peer_list_get_by_mcast_group(ovpn, &addr6, list) && + ipv6_addr_set_v4mapped(ip_hdr(skb)->saddr, &src); + if (!ovpn_peer_list_get_by_mcast_group(ovpn, &addr6, list, &src) && ovpn_mcast_is_control(skb)) { ovpn_peer_list_get_all(ovpn, list); } @@ -797,7 +798,7 @@ void ovpn_peer_list_get_by_dst(struct ovpn_priv *ovpn, struct sk_buff *skb, rcu_read_unlock(); if (ipv6_addr_is_multicast(&addr6) && - !ovpn_peer_list_get_by_mcast_group(ovpn, &addr6, list) && + !ovpn_peer_list_get_by_mcast_group(ovpn, &addr6, list, &ipv6_hdr(skb)->saddr) && ovpn_mcast_is_control(skb)) { ovpn_peer_list_get_all(ovpn, list); }