From mboxrd@z Thu Jan 1 00:00:00 1970 From: Patrick Ruddy Subject: [PATCH net-next v2 1/2] netlink: ipv4 igmp join notifications Date: Fri, 31 Aug 2018 12:20:23 +0100 Message-ID: <20180831112024.30477-1-pruddy@vyatta.att-mail.com> References: <20180830093545.29465-2-pruddy@vyatta.att-mail.com> Cc: roopa@cumulusnetworks.com, jiri@resnulli.us, stephen@networkplumber.org To: netdev@vger.kernel.org Return-path: Received: from mx0b-00191d01.pphosted.com ([67.231.157.136]:59100 "EHLO mx0a-00191d01.pphosted.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726990AbeHaP1n (ORCPT ); Fri, 31 Aug 2018 11:27:43 -0400 In-Reply-To: <20180830093545.29465-2-pruddy@vyatta.att-mail.com> Sender: netdev-owner@vger.kernel.org List-ID: 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_NEWADDR and RTM_DELADDR messages. It also provides the RTM_GETADDR extension to allow multicast join state to be read from the kernel. Signed-off-by: Patrick Ruddy --- v2: fix kbuild warnings. include/linux/igmp.h | 4 ++ net/ipv4/devinet.c | 39 +++++++++++++------ net/ipv4/igmp.c | 90 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 11 deletions(-) diff --git a/include/linux/igmp.h b/include/linux/igmp.h index 119f53941c12..644a548024ed 100644 --- a/include/linux/igmp.h +++ b/include/linux/igmp.h @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include static inline struct igmphdr *igmp_hdr(const struct sk_buff *skb) @@ -130,6 +132,8 @@ extern void ip_mc_unmap(struct in_device *); extern void ip_mc_remap(struct in_device *); extern void ip_mc_dec_group(struct in_device *in_dev, __be32 addr); extern void ip_mc_inc_group(struct in_device *in_dev, __be32 addr); +extern int ip_mc_dump_ifaddr(struct sk_buff *skb, struct netlink_callback *cb, + struct net_device *dev); int ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed); #endif diff --git a/net/ipv4/devinet.c b/net/ipv4/devinet.c index ea4bd8a52422..42f7dcc4fb5e 100644 --- a/net/ipv4/devinet.c +++ b/net/ipv4/devinet.c @@ -57,6 +57,7 @@ #endif #include #include +#include #include #include @@ -1651,6 +1652,7 @@ static int inet_dump_ifaddr(struct sk_buff *skb, struct netlink_callback *cb) int h, s_h; int idx, s_idx; int ip_idx, s_ip_idx; + int multicast, mcast_idx; struct net_device *dev; struct in_device *in_dev; struct in_ifaddr *ifa; @@ -1659,6 +1661,8 @@ static int inet_dump_ifaddr(struct sk_buff *skb, struct netlink_callback *cb) s_h = cb->args[0]; s_idx = idx = cb->args[1]; s_ip_idx = ip_idx = cb->args[2]; + multicast = cb->args[3]; + mcast_idx = cb->args[4]; for (h = s_h; h < NETDEV_HASHENTRIES; h++, s_idx = 0) { idx = 0; @@ -1675,18 +1679,29 @@ static int inet_dump_ifaddr(struct sk_buff *skb, struct netlink_callback *cb) if (!in_dev) goto cont; - for (ifa = in_dev->ifa_list, ip_idx = 0; ifa; - ifa = ifa->ifa_next, ip_idx++) { - if (ip_idx < s_ip_idx) - continue; - if (inet_fill_ifaddr(skb, ifa, - NETLINK_CB(cb->skb).portid, - cb->nlh->nlmsg_seq, - RTM_NEWADDR, NLM_F_MULTI) < 0) { - rcu_read_unlock(); - goto done; + if (!multicast) { + for (ifa = in_dev->ifa_list, ip_idx = 0; ifa; + ifa = ifa->ifa_next, ip_idx++) { + if (ip_idx < s_ip_idx) + continue; + if (inet_fill_ifaddr(skb, ifa, + NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, + RTM_NEWADDR, + NLM_F_MULTI) < 0) { + rcu_read_unlock(); + goto done; + } + nl_dump_check_consistent(cb, + nlmsg_hdr(skb)); } - nl_dump_check_consistent(cb, nlmsg_hdr(skb)); + /* set for multicast loop */ + multicast++; + } + /* loop over multicast addresses */ + if (ip_mc_dump_ifaddr(skb, cb, dev) < 0) { + rcu_read_unlock(); + goto done; } cont: idx++; @@ -1698,6 +1713,8 @@ static int inet_dump_ifaddr(struct sk_buff *skb, struct netlink_callback *cb) cb->args[0] = h; cb->args[1] = idx; cb->args[2] = ip_idx; + cb->args[3] = multicast; + cb->args[4] = mcast_idx; return skb->len; } diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c index cf75f8944b05..c9bbd1d27124 100644 --- a/net/ipv4/igmp.c +++ b/net/ipv4/igmp.c @@ -86,6 +86,7 @@ #include #include #include +#include #include #include #include @@ -1384,6 +1385,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_IPV4_IFADDR, NULL, GFP_ATOMIC); + return; +errout: + if (err < 0) + rtnl_set_sk_err(net, RTNLGRP_LINK, 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[4]; + 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_NEWADDR, + NLM_F_MULTI) < 0) + goto done; + nl_dump_check_consistent(cb, nlmsg_hdr(skb)); + idx++; + } + + done: + cb->args[4] = idx; + + return skb->len; +} + /* * A socket has joined a multicast group on device dev. */ @@ -1433,6 +1519,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_NEWADDR); if (!in_dev->dead) ip_rt_multicast_event(in_dev); out: @@ -1664,6 +1752,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_DELADDR); if (!in_dev->dead) ip_rt_multicast_event(in_dev); -- 2.17.1