ipmr: Add FIB notification access functions

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>
This commit is contained in:
Yotam Gigi 2017-09-27 08:23:13 +02:00 committed by David S. Miller
parent 310ebbba3b
commit 4d65b94878
3 changed files with 153 additions and 2 deletions

View 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,

View 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

View 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 @@ static int __net_init ipmr_net_init(struct net *net)
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);
}