ipmr: Add FIB notification access functions
authorYotam Gigi <yotamg@mellanox.com>
Wed, 27 Sep 2017 06:23:13 +0000 (08:23 +0200)
committerDavid S. Miller <davem@davemloft.net>
Wed, 27 Sep 2017 18:33:27 +0000 (11:33 -0700)
Make the ipmr module register as a FIB notifier. To do that, implement both
the ipmr_seq_read and ipmr_dump ops.

The ipmr_seq_read op returns a sequence counter that is incremented on
every notification related operation done by the ipmr. To implement that,
add a sequence counter in the netns_ipv4 struct and increment it whenever a
new MFC route or VIF are added or deleted. The sequence operations are
protected by the RTNL lock.

The ipmr_dump iterates the list of MFC routes and the list of VIF entries
and sends notifications about them. The entries dump is done under RCU
where the VIF dump uses the mrt_lock too, as the vif->dev field can change
under RCU.

Signed-off-by: Yotam Gigi <yotamg@mellanox.com>
Reviewed-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: Jiri Pirko <jiri@mellanox.com>
Reviewed-by: Nikolay Aleksandrov <nikolay@cumulusnetworks.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/mroute.h
include/net/netns/ipv4.h
net/ipv4/ipmr.c

index 10028f208efb63de5994d52d63f271c929fcb2c6..54c5cb82ddcb133e8fc8a7d77cfd89581239701f 100644 (file)
@@ -5,6 +5,7 @@
 #include <linux/pim.h>
 #include <linux/rhashtable.h>
 #include <net/sock.h>
+#include <net/fib_notifier.h>
 #include <uapi/linux/mroute.h>
 
 #ifdef CONFIG_IP_MROUTE
@@ -58,6 +59,14 @@ struct vif_device {
        int             link;                   /* Physical interface index     */
 };
 
+struct vif_entry_notifier_info {
+       struct fib_notifier_info info;
+       struct net_device *dev;
+       vifi_t vif_index;
+       unsigned short vif_flags;
+       u32 tb_id;
+};
+
 #define VIFF_STATIC 0x8000
 
 #define VIF_EXISTS(_mrt, _idx) ((_mrt)->vif_table[_idx].dev != NULL)
@@ -146,6 +155,12 @@ struct mfc_cache {
        struct rcu_head rcu;
 };
 
+struct mfc_entry_notifier_info {
+       struct fib_notifier_info info;
+       struct mfc_cache *mfc;
+       u32 tb_id;
+};
+
 struct rtmsg;
 int ipmr_get_route(struct net *net, struct sk_buff *skb,
                   __be32 saddr, __be32 daddr,
index 8387f099115e5d501688ee4fa6bee2a85ec79208..abc84d986da49197850124f4b41527ac2a795772 100644 (file)
@@ -163,6 +163,9 @@ struct netns_ipv4 {
        struct fib_notifier_ops *notifier_ops;
        unsigned int    fib_seq;        /* protected by rtnl_mutex */
 
+       struct fib_notifier_ops *ipmr_notifier_ops;
+       unsigned int    ipmr_seq;       /* protected by rtnl_mutex */
+
        atomic_t        rt_genid;
 };
 #endif
index 86dc5f98c5dd7644b535399f656c05a81a51886b..49879c33835756777967af787694b8f11feefc81 100644 (file)
@@ -264,6 +264,16 @@ static void __net_exit ipmr_rules_exit(struct net *net)
        fib_rules_unregister(net->ipv4.mr_rules_ops);
        rtnl_unlock();
 }
+
+static int ipmr_rules_dump(struct net *net, struct notifier_block *nb)
+{
+       return fib_rules_dump(net, nb, RTNL_FAMILY_IPMR);
+}
+
+static unsigned int ipmr_rules_seq_read(struct net *net)
+{
+       return fib_rules_seq_read(net, RTNL_FAMILY_IPMR);
+}
 #else
 #define ipmr_for_each_table(mrt, net) \
        for (mrt = net->ipv4.mrt; mrt; mrt = NULL)
@@ -298,6 +308,16 @@ static void __net_exit ipmr_rules_exit(struct net *net)
        net->ipv4.mrt = NULL;
        rtnl_unlock();
 }
+
+static int ipmr_rules_dump(struct net *net, struct notifier_block *nb)
+{
+       return 0;
+}
+
+static unsigned int ipmr_rules_seq_read(struct net *net)
+{
+       return 0;
+}
 #endif
 
 static inline int ipmr_hash_cmp(struct rhashtable_compare_arg *arg,
@@ -587,6 +607,43 @@ static struct net_device *ipmr_reg_vif(struct net *net, struct mr_table *mrt)
 }
 #endif
 
+static int call_ipmr_vif_entry_notifier(struct notifier_block *nb,
+                                       struct net *net,
+                                       enum fib_event_type event_type,
+                                       struct vif_device *vif,
+                                       vifi_t vif_index, u32 tb_id)
+{
+       struct vif_entry_notifier_info info = {
+               .info = {
+                       .family = RTNL_FAMILY_IPMR,
+                       .net = net,
+               },
+               .dev = vif->dev,
+               .vif_index = vif_index,
+               .vif_flags = vif->flags,
+               .tb_id = tb_id,
+       };
+
+       return call_fib_notifier(nb, net, event_type, &info.info);
+}
+
+static int call_ipmr_mfc_entry_notifier(struct notifier_block *nb,
+                                       struct net *net,
+                                       enum fib_event_type event_type,
+                                       struct mfc_cache *mfc, u32 tb_id)
+{
+       struct mfc_entry_notifier_info info = {
+               .info = {
+                       .family = RTNL_FAMILY_IPMR,
+                       .net = net,
+               },
+               .mfc = mfc,
+               .tb_id = tb_id
+       };
+
+       return call_fib_notifier(nb, net, event_type, &info.info);
+}
+
 /**
  *     vif_delete - Delete a VIF entry
  *     @notify: Set to 1, if the caller is a notifier_call
@@ -3050,14 +3107,87 @@ static const struct net_protocol pim_protocol = {
 };
 #endif
 
+static unsigned int ipmr_seq_read(struct net *net)
+{
+       ASSERT_RTNL();
+
+       return net->ipv4.ipmr_seq + ipmr_rules_seq_read(net);
+}
+
+static int ipmr_dump(struct net *net, struct notifier_block *nb)
+{
+       struct mr_table *mrt;
+       int err;
+
+       err = ipmr_rules_dump(net, nb);
+       if (err)
+               return err;
+
+       ipmr_for_each_table(mrt, net) {
+               struct vif_device *v = &mrt->vif_table[0];
+               struct mfc_cache *mfc;
+               int vifi;
+
+               /* Notifiy on table VIF entries */
+               read_lock(&mrt_lock);
+               for (vifi = 0; vifi < mrt->maxvif; vifi++, v++) {
+                       if (!v->dev)
+                               continue;
+
+                       call_ipmr_vif_entry_notifier(nb, net, FIB_EVENT_VIF_ADD,
+                                                    v, vifi, mrt->id);
+               }
+               read_unlock(&mrt_lock);
+
+               /* Notify on table MFC entries */
+               list_for_each_entry_rcu(mfc, &mrt->mfc_cache_list, list)
+                       call_ipmr_mfc_entry_notifier(nb, net,
+                                                    FIB_EVENT_ENTRY_ADD, mfc,
+                                                    mrt->id);
+       }
+
+       return 0;
+}
+
+static const struct fib_notifier_ops ipmr_notifier_ops_template = {
+       .family         = RTNL_FAMILY_IPMR,
+       .fib_seq_read   = ipmr_seq_read,
+       .fib_dump       = ipmr_dump,
+       .owner          = THIS_MODULE,
+};
+
+int __net_init ipmr_notifier_init(struct net *net)
+{
+       struct fib_notifier_ops *ops;
+
+       net->ipv4.ipmr_seq = 0;
+
+       ops = fib_notifier_ops_register(&ipmr_notifier_ops_template, net);
+       if (IS_ERR(ops))
+               return PTR_ERR(ops);
+       net->ipv4.ipmr_notifier_ops = ops;
+
+       return 0;
+}
+
+static void __net_exit ipmr_notifier_exit(struct net *net)
+{
+       fib_notifier_ops_unregister(net->ipv4.ipmr_notifier_ops);
+       net->ipv4.ipmr_notifier_ops = NULL;
+}
+
 /* Setup for IP multicast routing */
 static int __net_init ipmr_net_init(struct net *net)
 {
        int err;
 
+       err = ipmr_notifier_init(net);
+       if (err)
+               goto ipmr_notifier_fail;
+
        err = ipmr_rules_init(net);
        if (err < 0)
-               goto fail;
+               goto ipmr_rules_fail;
 
 #ifdef CONFIG_PROC_FS
        err = -ENOMEM;
@@ -3074,7 +3204,9 @@ proc_cache_fail:
 proc_vif_fail:
        ipmr_rules_exit(net);
 #endif
-fail:
+ipmr_rules_fail:
+       ipmr_notifier_exit(net);
+ipmr_notifier_fail:
        return err;
 }
 
@@ -3084,6 +3216,7 @@ static void __net_exit ipmr_net_exit(struct net *net)
        remove_proc_entry("ip_mr_cache", net->proc_net);
        remove_proc_entry("ip_mr_vif", net->proc_net);
 #endif
+       ipmr_notifier_exit(net);
        ipmr_rules_exit(net);
 }