Commit d7c31cbd authored by David Lamparter's avatar David Lamparter Committed by David S. Miller
Browse files

net: ip6mr: add RTM_GETROUTE netlink op



The IPv6 multicast routing code previously implemented only the dump
variant of RTM_GETROUTE.  Implement single MFC item retrieval by copying
and adapting the respective IPv4 code.

Tested against FRRouting's IPv6 PIM stack.

Signed-off-by: default avatarDavid Lamparter <equinox@diac24.net>
Reviewed-by: default avatarNikolay Aleksandrov <razor@blackwall.org>
Reviewed-by: default avatarDavid Ahern <dsahern@kernel.org>
Cc: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 00cf1fb3
Loading
Loading
Loading
Loading
+92 −1
Original line number Diff line number Diff line
@@ -95,6 +95,8 @@ static int ip6mr_cache_report(const struct mr_table *mrt, struct sk_buff *pkt,
static void mr6_netlink_event(struct mr_table *mrt, struct mfc6_cache *mfc,
			      int cmd);
static void mrt6msg_netlink_event(const struct mr_table *mrt, struct sk_buff *pkt);
static int ip6mr_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,
			      struct netlink_ext_ack *extack);
static int ip6mr_rtm_dumproute(struct sk_buff *skb,
			       struct netlink_callback *cb);
static void mroute_clean_tables(struct mr_table *mrt, int flags);
@@ -1390,7 +1392,7 @@ int __init ip6_mr_init(void)
	}
#endif
	err = rtnl_register_module(THIS_MODULE, RTNL_FAMILY_IP6MR, RTM_GETROUTE,
				   NULL, ip6mr_rtm_dumproute, 0);
				   ip6mr_rtm_getroute, ip6mr_rtm_dumproute, 0);
	if (err == 0)
		return 0;

@@ -2510,6 +2512,95 @@ static void mrt6msg_netlink_event(const struct mr_table *mrt, struct sk_buff *pk
	rtnl_set_sk_err(net, RTNLGRP_IPV6_MROUTE_R, -ENOBUFS);
}

static const struct nla_policy ip6mr_getroute_policy[RTA_MAX + 1] = {
	[RTA_SRC]		= NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)),
	[RTA_DST]		= NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)),
	[RTA_TABLE]		= { .type = NLA_U32 },
};

static int ip6mr_rtm_valid_getroute_req(struct sk_buff *skb,
					const struct nlmsghdr *nlh,
					struct nlattr **tb,
					struct netlink_ext_ack *extack)
{
	struct rtmsg *rtm;
	int err;

	err = nlmsg_parse(nlh, sizeof(*rtm), tb, RTA_MAX, ip6mr_getroute_policy,
			  extack);
	if (err)
		return err;

	rtm = nlmsg_data(nlh);
	if ((rtm->rtm_src_len && rtm->rtm_src_len != 128) ||
	    (rtm->rtm_dst_len && rtm->rtm_dst_len != 128) ||
	    rtm->rtm_tos || rtm->rtm_table || rtm->rtm_protocol ||
	    rtm->rtm_scope || rtm->rtm_type || rtm->rtm_flags) {
		NL_SET_ERR_MSG_MOD(extack,
				   "Invalid values in header for multicast route get request");
		return -EINVAL;
	}

	if ((tb[RTA_SRC] && !rtm->rtm_src_len) ||
	    (tb[RTA_DST] && !rtm->rtm_dst_len)) {
		NL_SET_ERR_MSG_MOD(extack, "rtm_src_len and rtm_dst_len must be 128 for IPv6");
		return -EINVAL;
	}

	return 0;
}

static int ip6mr_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,
			      struct netlink_ext_ack *extack)
{
	struct net *net = sock_net(in_skb->sk);
	struct in6_addr src = {}, grp = {};
	struct nlattr *tb[RTA_MAX + 1];
	struct mfc6_cache *cache;
	struct mr_table *mrt;
	struct sk_buff *skb;
	u32 tableid;
	int err;

	err = ip6mr_rtm_valid_getroute_req(in_skb, nlh, tb, extack);
	if (err < 0)
		return err;

	if (tb[RTA_SRC])
		src = nla_get_in6_addr(tb[RTA_SRC]);
	if (tb[RTA_DST])
		grp = nla_get_in6_addr(tb[RTA_DST]);
	tableid = tb[RTA_TABLE] ? nla_get_u32(tb[RTA_TABLE]) : 0;

	mrt = ip6mr_get_table(net, tableid ?: RT_TABLE_DEFAULT);
	if (!mrt) {
		NL_SET_ERR_MSG_MOD(extack, "MR table does not exist");
		return -ENOENT;
	}

	/* entries are added/deleted only under RTNL */
	rcu_read_lock();
	cache = ip6mr_cache_find(mrt, &src, &grp);
	rcu_read_unlock();
	if (!cache) {
		NL_SET_ERR_MSG_MOD(extack, "MR cache entry not found");
		return -ENOENT;
	}

	skb = nlmsg_new(mr6_msgsize(false, mrt->maxvif), GFP_KERNEL);
	if (!skb)
		return -ENOBUFS;

	err = ip6mr_fill_mroute(mrt, skb, NETLINK_CB(in_skb).portid,
				nlh->nlmsg_seq, cache, RTM_NEWROUTE, 0);
	if (err < 0) {
		kfree_skb(skb);
		return err;
	}

	return rtnl_unicast(skb, net, NETLINK_CB(in_skb).portid);
}

static int ip6mr_rtm_dumproute(struct sk_buff *skb, struct netlink_callback *cb)
{
	const struct nlmsghdr *nlh = cb->nlh;