[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20180906091056.21109-1-pruddy@vyatta.att-mail.com>
Date: Thu, 6 Sep 2018 10:10:55 +0100
From: Patrick Ruddy <pruddy@...tta.att-mail.com>
To: netdev@...r.kernel.org
Cc: roopa@...ulusnetworks.com, jiri@...nulli.us,
stephen@...workplumber.org
Subject: [PATCH net-next v3 1/2] netlink: ipv4 igmp join notifications
Some userspace applications need to know about IGMP joins from the
kernel for 2 reasons:
1. To allow the programming of multicast MAC filters in hardware
2. To form a multicast FORUS list for non link-local multicast
groups to be sent to the kernel and from there to the interested
party.
(1) can be fulfilled but simply sending the hardware multicast MAC
address to be programmed but (2) requires the L3 address to be sent
since this cannot be constructed from the MAC address whereas the
reverse translation is a standard library function.
This commit provides addition and deletion of multicast addresses
using the RTM_NEWMDB and RTM_DELMDB messages with AF_INET. It also
provides the RTM_GETMDB extension to allow multicast join state to
be read from the kernel.
Signed-off-by: Patrick Ruddy <pruddy@...tta.att-mail.com>
---
v3 rework to use RTM_***MDB messages as per review comments.
net/ipv4/igmp.c | 139 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 139 insertions(+)
diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c
index 4da39446da2d..aed819e2ea93 100644
--- a/net/ipv4/igmp.c
+++ b/net/ipv4/igmp.c
@@ -86,6 +86,7 @@
#include <linux/inetdevice.h>
#include <linux/igmp.h>
#include <linux/if_arp.h>
+#include <net/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/times.h>
#include <linux/pkt_sched.h>
@@ -1385,6 +1386,91 @@ static void ip_mc_hash_remove(struct in_device *in_dev,
}
+static int fill_addr(struct sk_buff *skb, struct net_device *dev, __be32 addr,
+ int type, unsigned int flags)
+{
+ struct nlmsghdr *nlh;
+ struct ifaddrmsg *ifm;
+
+ nlh = nlmsg_put(skb, 0, 0, type, sizeof(*ifm), flags);
+ if (!nlh)
+ return -EMSGSIZE;
+
+ ifm = nlmsg_data(nlh);
+ ifm->ifa_family = AF_INET;
+ ifm->ifa_prefixlen = 32;
+ ifm->ifa_flags = IFA_F_PERMANENT;
+ ifm->ifa_scope = RT_SCOPE_LINK;
+ ifm->ifa_index = dev->ifindex;
+
+ if (nla_put_in_addr(skb, IFA_ADDRESS, addr))
+ goto nla_put_failure;
+ nlmsg_end(skb, nlh);
+ return 0;
+
+nla_put_failure:
+ nlmsg_cancel(skb, nlh);
+ return -EMSGSIZE;
+}
+
+static inline size_t addr_nlmsg_size(void)
+{
+ return NLMSG_ALIGN(sizeof(struct ifaddrmsg))
+ + nla_total_size(sizeof(__be32));
+}
+
+static void ip_mc_addr_notify(struct net_device *dev, __be32 addr, int type)
+{
+ struct net *net = dev_net(dev);
+ struct sk_buff *skb;
+ int err = -ENOBUFS;
+
+ skb = nlmsg_new(addr_nlmsg_size(), GFP_ATOMIC);
+ if (!skb)
+ goto errout;
+
+ err = fill_addr(skb, dev, addr, type, 0);
+ if (err < 0) {
+ WARN_ON(err == -EMSGSIZE);
+ kfree_skb(skb);
+ goto errout;
+ }
+ rtnl_notify(skb, net, 0, RTNLGRP_MDB, NULL, GFP_ATOMIC);
+ return;
+errout:
+ if (err < 0)
+ rtnl_set_sk_err(net, RTNLGRP_MDB, err);
+}
+
+int ip_mc_dump_ifaddr(struct sk_buff *skb, struct netlink_callback *cb,
+ struct net_device *dev)
+{
+ int s_idx;
+ int idx = 0;
+ struct ip_mc_list *im;
+ struct in_device *in_dev;
+
+ ASSERT_RTNL();
+
+ s_idx = cb->args[2];
+ in_dev = __in_dev_get_rtnl(dev);
+
+ for_each_pmc_rtnl(in_dev, im) {
+ if (idx < s_idx)
+ continue;
+ if (fill_addr(skb, dev, im->multiaddr, RTM_NEWMDB,
+ NLM_F_MULTI) < 0)
+ goto done;
+ nl_dump_check_consistent(cb, nlmsg_hdr(skb));
+ idx++;
+ }
+
+ done:
+ cb->args[2] = idx;
+
+ return skb->len;
+}
+
/*
* A socket has joined a multicast group on device dev.
*/
@@ -1430,6 +1516,8 @@ static void __ip_mc_inc_group(struct in_device *in_dev, __be32 addr,
igmpv3_del_delrec(in_dev, im);
#endif
igmp_group_added(im);
+
+ ip_mc_addr_notify(in_dev->dev, addr, RTM_NEWMDB);
if (!in_dev->dead)
ip_rt_multicast_event(in_dev);
out:
@@ -1661,6 +1749,8 @@ void ip_mc_dec_group(struct in_device *in_dev, __be32 addr)
in_dev->mc_count--;
igmp_group_dropped(i);
ip_mc_clear_src(i);
+ ip_mc_addr_notify(in_dev->dev, addr,
+ RTM_DELMDB);
if (!in_dev->dead)
ip_rt_multicast_event(in_dev);
@@ -3051,6 +3141,53 @@ static struct notifier_block igmp_notifier = {
.notifier_call = igmp_netdev_event,
};
+static int igmp_mc_dump_ifaddrs(struct sk_buff *skb,
+ struct netlink_callback *cb)
+{
+ struct net *net = sock_net(skb->sk);
+ int h, s_h;
+ int idx, s_idx;
+ struct net_device *dev;
+ struct in_device *in_dev;
+ struct hlist_head *head;
+
+ s_h = cb->args[0];
+ idx = cb->args[1];
+ s_idx = idx;
+
+ for (h = s_h; h < NETDEV_HASHENTRIES; h++, s_idx = 0) {
+ idx = 0;
+ head = &net->dev_index_head[h];
+ rcu_read_lock();
+ cb->seq = atomic_read(&net->ipv4.dev_addr_genid) ^
+ net->dev_base_seq;
+ hlist_for_each_entry_rcu(dev, head, index_hlist) {
+ if (idx < s_idx)
+ goto cont;
+ if (h > s_h || idx > s_idx)
+ cb->args[2] = 0;
+ in_dev = __in_dev_get_rcu(dev);
+ if (!in_dev)
+ goto cont;
+
+ /* loop over multicast addresses */
+ if (ip_mc_dump_ifaddr(skb, cb, dev) < 0) {
+ rcu_read_unlock();
+ goto done;
+ }
+cont:
+ idx++;
+ }
+ rcu_read_unlock();
+ }
+
+done:
+ cb->args[0] = h;
+ cb->args[1] = idx;
+
+ return skb->len;
+}
+
int __init igmp_mc_init(void)
{
#if defined(CONFIG_PROC_FS)
@@ -3064,6 +3201,8 @@ int __init igmp_mc_init(void)
goto reg_notif_fail;
return 0;
+ rtnl_register(PF_INET, RTM_GETMDB, NULL, igmp_mc_dump_ifaddrs, 0);
+
reg_notif_fail:
unregister_pernet_subsys(&igmp_net_ops);
return err;
--
2.17.1
Powered by blists - more mailing lists