xref: /kernel/linux/linux-6.6/net/mctp/neigh.c (revision 62306a36)
162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Management Component Transport Protocol (MCTP) - routing
462306a36Sopenharmony_ci * implementation.
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * This is currently based on a simple routing table, with no dst cache. The
762306a36Sopenharmony_ci * number of routes should stay fairly small, so the lookup cost is small.
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * Copyright (c) 2021 Code Construct
1062306a36Sopenharmony_ci * Copyright (c) 2021 Google
1162306a36Sopenharmony_ci */
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include <linux/idr.h>
1462306a36Sopenharmony_ci#include <linux/mctp.h>
1562306a36Sopenharmony_ci#include <linux/netdevice.h>
1662306a36Sopenharmony_ci#include <linux/rtnetlink.h>
1762306a36Sopenharmony_ci#include <linux/skbuff.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include <net/mctp.h>
2062306a36Sopenharmony_ci#include <net/mctpdevice.h>
2162306a36Sopenharmony_ci#include <net/netlink.h>
2262306a36Sopenharmony_ci#include <net/sock.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_cistatic int mctp_neigh_add(struct mctp_dev *mdev, mctp_eid_t eid,
2562306a36Sopenharmony_ci			  enum mctp_neigh_source source,
2662306a36Sopenharmony_ci			  size_t lladdr_len, const void *lladdr)
2762306a36Sopenharmony_ci{
2862306a36Sopenharmony_ci	struct net *net = dev_net(mdev->dev);
2962306a36Sopenharmony_ci	struct mctp_neigh *neigh;
3062306a36Sopenharmony_ci	int rc;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	mutex_lock(&net->mctp.neigh_lock);
3362306a36Sopenharmony_ci	if (mctp_neigh_lookup(mdev, eid, NULL) == 0) {
3462306a36Sopenharmony_ci		rc = -EEXIST;
3562306a36Sopenharmony_ci		goto out;
3662306a36Sopenharmony_ci	}
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	if (lladdr_len > sizeof(neigh->ha)) {
3962306a36Sopenharmony_ci		rc = -EINVAL;
4062306a36Sopenharmony_ci		goto out;
4162306a36Sopenharmony_ci	}
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	neigh = kzalloc(sizeof(*neigh), GFP_KERNEL);
4462306a36Sopenharmony_ci	if (!neigh) {
4562306a36Sopenharmony_ci		rc = -ENOMEM;
4662306a36Sopenharmony_ci		goto out;
4762306a36Sopenharmony_ci	}
4862306a36Sopenharmony_ci	INIT_LIST_HEAD(&neigh->list);
4962306a36Sopenharmony_ci	neigh->dev = mdev;
5062306a36Sopenharmony_ci	mctp_dev_hold(neigh->dev);
5162306a36Sopenharmony_ci	neigh->eid = eid;
5262306a36Sopenharmony_ci	neigh->source = source;
5362306a36Sopenharmony_ci	memcpy(neigh->ha, lladdr, lladdr_len);
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	list_add_rcu(&neigh->list, &net->mctp.neighbours);
5662306a36Sopenharmony_ci	rc = 0;
5762306a36Sopenharmony_ciout:
5862306a36Sopenharmony_ci	mutex_unlock(&net->mctp.neigh_lock);
5962306a36Sopenharmony_ci	return rc;
6062306a36Sopenharmony_ci}
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_cistatic void __mctp_neigh_free(struct rcu_head *rcu)
6362306a36Sopenharmony_ci{
6462306a36Sopenharmony_ci	struct mctp_neigh *neigh = container_of(rcu, struct mctp_neigh, rcu);
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	mctp_dev_put(neigh->dev);
6762306a36Sopenharmony_ci	kfree(neigh);
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci/* Removes all neighbour entries referring to a device */
7162306a36Sopenharmony_civoid mctp_neigh_remove_dev(struct mctp_dev *mdev)
7262306a36Sopenharmony_ci{
7362306a36Sopenharmony_ci	struct net *net = dev_net(mdev->dev);
7462306a36Sopenharmony_ci	struct mctp_neigh *neigh, *tmp;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	mutex_lock(&net->mctp.neigh_lock);
7762306a36Sopenharmony_ci	list_for_each_entry_safe(neigh, tmp, &net->mctp.neighbours, list) {
7862306a36Sopenharmony_ci		if (neigh->dev == mdev) {
7962306a36Sopenharmony_ci			list_del_rcu(&neigh->list);
8062306a36Sopenharmony_ci			/* TODO: immediate RTM_DELNEIGH */
8162306a36Sopenharmony_ci			call_rcu(&neigh->rcu, __mctp_neigh_free);
8262306a36Sopenharmony_ci		}
8362306a36Sopenharmony_ci	}
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	mutex_unlock(&net->mctp.neigh_lock);
8662306a36Sopenharmony_ci}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_cistatic int mctp_neigh_remove(struct mctp_dev *mdev, mctp_eid_t eid,
8962306a36Sopenharmony_ci			     enum mctp_neigh_source source)
9062306a36Sopenharmony_ci{
9162306a36Sopenharmony_ci	struct net *net = dev_net(mdev->dev);
9262306a36Sopenharmony_ci	struct mctp_neigh *neigh, *tmp;
9362306a36Sopenharmony_ci	bool dropped = false;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	mutex_lock(&net->mctp.neigh_lock);
9662306a36Sopenharmony_ci	list_for_each_entry_safe(neigh, tmp, &net->mctp.neighbours, list) {
9762306a36Sopenharmony_ci		if (neigh->dev == mdev && neigh->eid == eid &&
9862306a36Sopenharmony_ci		    neigh->source == source) {
9962306a36Sopenharmony_ci			list_del_rcu(&neigh->list);
10062306a36Sopenharmony_ci			/* TODO: immediate RTM_DELNEIGH */
10162306a36Sopenharmony_ci			call_rcu(&neigh->rcu, __mctp_neigh_free);
10262306a36Sopenharmony_ci			dropped = true;
10362306a36Sopenharmony_ci		}
10462306a36Sopenharmony_ci	}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	mutex_unlock(&net->mctp.neigh_lock);
10762306a36Sopenharmony_ci	return dropped ? 0 : -ENOENT;
10862306a36Sopenharmony_ci}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_cistatic const struct nla_policy nd_mctp_policy[NDA_MAX + 1] = {
11162306a36Sopenharmony_ci	[NDA_DST]		= { .type = NLA_U8 },
11262306a36Sopenharmony_ci	[NDA_LLADDR]		= { .type = NLA_BINARY, .len = MAX_ADDR_LEN },
11362306a36Sopenharmony_ci};
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_cistatic int mctp_rtm_newneigh(struct sk_buff *skb, struct nlmsghdr *nlh,
11662306a36Sopenharmony_ci			     struct netlink_ext_ack *extack)
11762306a36Sopenharmony_ci{
11862306a36Sopenharmony_ci	struct net *net = sock_net(skb->sk);
11962306a36Sopenharmony_ci	struct net_device *dev;
12062306a36Sopenharmony_ci	struct mctp_dev *mdev;
12162306a36Sopenharmony_ci	struct ndmsg *ndm;
12262306a36Sopenharmony_ci	struct nlattr *tb[NDA_MAX + 1];
12362306a36Sopenharmony_ci	int rc;
12462306a36Sopenharmony_ci	mctp_eid_t eid;
12562306a36Sopenharmony_ci	void *lladdr;
12662306a36Sopenharmony_ci	int lladdr_len;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	rc = nlmsg_parse(nlh, sizeof(*ndm), tb, NDA_MAX, nd_mctp_policy,
12962306a36Sopenharmony_ci			 extack);
13062306a36Sopenharmony_ci	if (rc < 0) {
13162306a36Sopenharmony_ci		NL_SET_ERR_MSG(extack, "lladdr too large?");
13262306a36Sopenharmony_ci		return rc;
13362306a36Sopenharmony_ci	}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	if (!tb[NDA_DST]) {
13662306a36Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Neighbour EID must be specified");
13762306a36Sopenharmony_ci		return -EINVAL;
13862306a36Sopenharmony_ci	}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	if (!tb[NDA_LLADDR]) {
14162306a36Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Neighbour lladdr must be specified");
14262306a36Sopenharmony_ci		return -EINVAL;
14362306a36Sopenharmony_ci	}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	eid = nla_get_u8(tb[NDA_DST]);
14662306a36Sopenharmony_ci	if (!mctp_address_unicast(eid)) {
14762306a36Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Invalid neighbour EID");
14862306a36Sopenharmony_ci		return -EINVAL;
14962306a36Sopenharmony_ci	}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	lladdr = nla_data(tb[NDA_LLADDR]);
15262306a36Sopenharmony_ci	lladdr_len = nla_len(tb[NDA_LLADDR]);
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	ndm = nlmsg_data(nlh);
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	dev = __dev_get_by_index(net, ndm->ndm_ifindex);
15762306a36Sopenharmony_ci	if (!dev)
15862306a36Sopenharmony_ci		return -ENODEV;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	mdev = mctp_dev_get_rtnl(dev);
16162306a36Sopenharmony_ci	if (!mdev)
16262306a36Sopenharmony_ci		return -ENODEV;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	if (lladdr_len != dev->addr_len) {
16562306a36Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Wrong lladdr length");
16662306a36Sopenharmony_ci		return -EINVAL;
16762306a36Sopenharmony_ci	}
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	return mctp_neigh_add(mdev, eid, MCTP_NEIGH_STATIC,
17062306a36Sopenharmony_ci			lladdr_len, lladdr);
17162306a36Sopenharmony_ci}
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_cistatic int mctp_rtm_delneigh(struct sk_buff *skb, struct nlmsghdr *nlh,
17462306a36Sopenharmony_ci			     struct netlink_ext_ack *extack)
17562306a36Sopenharmony_ci{
17662306a36Sopenharmony_ci	struct net *net = sock_net(skb->sk);
17762306a36Sopenharmony_ci	struct nlattr *tb[NDA_MAX + 1];
17862306a36Sopenharmony_ci	struct net_device *dev;
17962306a36Sopenharmony_ci	struct mctp_dev *mdev;
18062306a36Sopenharmony_ci	struct ndmsg *ndm;
18162306a36Sopenharmony_ci	int rc;
18262306a36Sopenharmony_ci	mctp_eid_t eid;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	rc = nlmsg_parse(nlh, sizeof(*ndm), tb, NDA_MAX, nd_mctp_policy,
18562306a36Sopenharmony_ci			 extack);
18662306a36Sopenharmony_ci	if (rc < 0) {
18762306a36Sopenharmony_ci		NL_SET_ERR_MSG(extack, "incorrect format");
18862306a36Sopenharmony_ci		return rc;
18962306a36Sopenharmony_ci	}
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	if (!tb[NDA_DST]) {
19262306a36Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Neighbour EID must be specified");
19362306a36Sopenharmony_ci		return -EINVAL;
19462306a36Sopenharmony_ci	}
19562306a36Sopenharmony_ci	eid = nla_get_u8(tb[NDA_DST]);
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	ndm = nlmsg_data(nlh);
19862306a36Sopenharmony_ci	dev = __dev_get_by_index(net, ndm->ndm_ifindex);
19962306a36Sopenharmony_ci	if (!dev)
20062306a36Sopenharmony_ci		return -ENODEV;
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	mdev = mctp_dev_get_rtnl(dev);
20362306a36Sopenharmony_ci	if (!mdev)
20462306a36Sopenharmony_ci		return -ENODEV;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	return mctp_neigh_remove(mdev, eid, MCTP_NEIGH_STATIC);
20762306a36Sopenharmony_ci}
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_cistatic int mctp_fill_neigh(struct sk_buff *skb, u32 portid, u32 seq, int event,
21062306a36Sopenharmony_ci			   unsigned int flags, struct mctp_neigh *neigh)
21162306a36Sopenharmony_ci{
21262306a36Sopenharmony_ci	struct net_device *dev = neigh->dev->dev;
21362306a36Sopenharmony_ci	struct nlmsghdr *nlh;
21462306a36Sopenharmony_ci	struct ndmsg *hdr;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	nlh = nlmsg_put(skb, portid, seq, event, sizeof(*hdr), flags);
21762306a36Sopenharmony_ci	if (!nlh)
21862306a36Sopenharmony_ci		return -EMSGSIZE;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	hdr = nlmsg_data(nlh);
22162306a36Sopenharmony_ci	hdr->ndm_family = AF_MCTP;
22262306a36Sopenharmony_ci	hdr->ndm_ifindex = dev->ifindex;
22362306a36Sopenharmony_ci	hdr->ndm_state = 0; // TODO other state bits?
22462306a36Sopenharmony_ci	if (neigh->source == MCTP_NEIGH_STATIC)
22562306a36Sopenharmony_ci		hdr->ndm_state |= NUD_PERMANENT;
22662306a36Sopenharmony_ci	hdr->ndm_flags = 0;
22762306a36Sopenharmony_ci	hdr->ndm_type = RTN_UNICAST; // TODO: is loopback RTN_LOCAL?
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	if (nla_put_u8(skb, NDA_DST, neigh->eid))
23062306a36Sopenharmony_ci		goto cancel;
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	if (nla_put(skb, NDA_LLADDR, dev->addr_len, neigh->ha))
23362306a36Sopenharmony_ci		goto cancel;
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	nlmsg_end(skb, nlh);
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	return 0;
23862306a36Sopenharmony_cicancel:
23962306a36Sopenharmony_ci	nlmsg_cancel(skb, nlh);
24062306a36Sopenharmony_ci	return -EMSGSIZE;
24162306a36Sopenharmony_ci}
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_cistatic int mctp_rtm_getneigh(struct sk_buff *skb, struct netlink_callback *cb)
24462306a36Sopenharmony_ci{
24562306a36Sopenharmony_ci	struct net *net = sock_net(skb->sk);
24662306a36Sopenharmony_ci	int rc, idx, req_ifindex;
24762306a36Sopenharmony_ci	struct mctp_neigh *neigh;
24862306a36Sopenharmony_ci	struct ndmsg *ndmsg;
24962306a36Sopenharmony_ci	struct {
25062306a36Sopenharmony_ci		int idx;
25162306a36Sopenharmony_ci	} *cbctx = (void *)cb->ctx;
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	ndmsg = nlmsg_data(cb->nlh);
25462306a36Sopenharmony_ci	req_ifindex = ndmsg->ndm_ifindex;
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	idx = 0;
25762306a36Sopenharmony_ci	rcu_read_lock();
25862306a36Sopenharmony_ci	list_for_each_entry_rcu(neigh, &net->mctp.neighbours, list) {
25962306a36Sopenharmony_ci		if (idx < cbctx->idx)
26062306a36Sopenharmony_ci			goto cont;
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci		rc = 0;
26362306a36Sopenharmony_ci		if (req_ifindex == 0 || req_ifindex == neigh->dev->dev->ifindex)
26462306a36Sopenharmony_ci			rc = mctp_fill_neigh(skb, NETLINK_CB(cb->skb).portid,
26562306a36Sopenharmony_ci					     cb->nlh->nlmsg_seq,
26662306a36Sopenharmony_ci					     RTM_NEWNEIGH, NLM_F_MULTI, neigh);
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci		if (rc)
26962306a36Sopenharmony_ci			break;
27062306a36Sopenharmony_cicont:
27162306a36Sopenharmony_ci		idx++;
27262306a36Sopenharmony_ci	}
27362306a36Sopenharmony_ci	rcu_read_unlock();
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	cbctx->idx = idx;
27662306a36Sopenharmony_ci	return skb->len;
27762306a36Sopenharmony_ci}
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ciint mctp_neigh_lookup(struct mctp_dev *mdev, mctp_eid_t eid, void *ret_hwaddr)
28062306a36Sopenharmony_ci{
28162306a36Sopenharmony_ci	struct net *net = dev_net(mdev->dev);
28262306a36Sopenharmony_ci	struct mctp_neigh *neigh;
28362306a36Sopenharmony_ci	int rc = -EHOSTUNREACH; // TODO: or ENOENT?
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	rcu_read_lock();
28662306a36Sopenharmony_ci	list_for_each_entry_rcu(neigh, &net->mctp.neighbours, list) {
28762306a36Sopenharmony_ci		if (mdev == neigh->dev && eid == neigh->eid) {
28862306a36Sopenharmony_ci			if (ret_hwaddr)
28962306a36Sopenharmony_ci				memcpy(ret_hwaddr, neigh->ha,
29062306a36Sopenharmony_ci				       sizeof(neigh->ha));
29162306a36Sopenharmony_ci			rc = 0;
29262306a36Sopenharmony_ci			break;
29362306a36Sopenharmony_ci		}
29462306a36Sopenharmony_ci	}
29562306a36Sopenharmony_ci	rcu_read_unlock();
29662306a36Sopenharmony_ci	return rc;
29762306a36Sopenharmony_ci}
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci/* namespace registration */
30062306a36Sopenharmony_cistatic int __net_init mctp_neigh_net_init(struct net *net)
30162306a36Sopenharmony_ci{
30262306a36Sopenharmony_ci	struct netns_mctp *ns = &net->mctp;
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci	INIT_LIST_HEAD(&ns->neighbours);
30562306a36Sopenharmony_ci	mutex_init(&ns->neigh_lock);
30662306a36Sopenharmony_ci	return 0;
30762306a36Sopenharmony_ci}
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_cistatic void __net_exit mctp_neigh_net_exit(struct net *net)
31062306a36Sopenharmony_ci{
31162306a36Sopenharmony_ci	struct netns_mctp *ns = &net->mctp;
31262306a36Sopenharmony_ci	struct mctp_neigh *neigh;
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	list_for_each_entry(neigh, &ns->neighbours, list)
31562306a36Sopenharmony_ci		call_rcu(&neigh->rcu, __mctp_neigh_free);
31662306a36Sopenharmony_ci}
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci/* net namespace implementation */
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_cistatic struct pernet_operations mctp_net_ops = {
32162306a36Sopenharmony_ci	.init = mctp_neigh_net_init,
32262306a36Sopenharmony_ci	.exit = mctp_neigh_net_exit,
32362306a36Sopenharmony_ci};
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ciint __init mctp_neigh_init(void)
32662306a36Sopenharmony_ci{
32762306a36Sopenharmony_ci	rtnl_register_module(THIS_MODULE, PF_MCTP, RTM_NEWNEIGH,
32862306a36Sopenharmony_ci			     mctp_rtm_newneigh, NULL, 0);
32962306a36Sopenharmony_ci	rtnl_register_module(THIS_MODULE, PF_MCTP, RTM_DELNEIGH,
33062306a36Sopenharmony_ci			     mctp_rtm_delneigh, NULL, 0);
33162306a36Sopenharmony_ci	rtnl_register_module(THIS_MODULE, PF_MCTP, RTM_GETNEIGH,
33262306a36Sopenharmony_ci			     NULL, mctp_rtm_getneigh, 0);
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_ci	return register_pernet_subsys(&mctp_net_ops);
33562306a36Sopenharmony_ci}
33662306a36Sopenharmony_ci
33762306a36Sopenharmony_civoid __exit mctp_neigh_exit(void)
33862306a36Sopenharmony_ci{
33962306a36Sopenharmony_ci	unregister_pernet_subsys(&mctp_net_ops);
34062306a36Sopenharmony_ci	rtnl_unregister(PF_MCTP, RTM_GETNEIGH);
34162306a36Sopenharmony_ci	rtnl_unregister(PF_MCTP, RTM_DELNEIGH);
34262306a36Sopenharmony_ci	rtnl_unregister(PF_MCTP, RTM_NEWNEIGH);
34362306a36Sopenharmony_ci}
344