564 lines · 12.2 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * Management Component Transport Protocol (MCTP) - device implementation. * * Copyright (c) 2021 Code Construct * Copyright (c) 2021 Google */ #include <linux/if_arp.h>#include <linux/if_link.h>#include <linux/mctp.h>#include <linux/netdevice.h>#include <linux/rcupdate.h>#include <linux/rtnetlink.h> #include <net/addrconf.h>#include <net/netlink.h>#include <net/mctp.h>#include <net/mctpdevice.h>#include <net/sock.h> struct mctp_dump_cb {	unsigned long ifindex;	size_t a_idx;}; /* unlocked: caller must hold rcu_read_lock. * Returned mctp_dev has its refcount incremented, or NULL if unset. */struct mctp_dev *__mctp_dev_get(const struct net_device *dev){	struct mctp_dev *mdev = rcu_dereference(dev->mctp_ptr); 	/* RCU guarantees that any mdev is still live.	 * Zero refcount implies a pending free, return NULL.	 */	if (mdev)		if (!refcount_inc_not_zero(&mdev->refs))			return NULL;	return mdev;} /* Returned mctp_dev does not have refcount incremented. The returned pointer * remains live while rtnl_lock is held, as that prevents mctp_unregister() */struct mctp_dev *mctp_dev_get_rtnl(const struct net_device *dev){	return rtnl_dereference(dev->mctp_ptr);} static int mctp_addrinfo_size(void){	return NLMSG_ALIGN(sizeof(struct ifaddrmsg))		+ nla_total_size(1) // IFA_LOCAL		+ nla_total_size(1) // IFA_ADDRESS		;} /* flag should be NLM_F_MULTI for dump calls */static int mctp_fill_addrinfo(struct sk_buff *skb,			      struct mctp_dev *mdev, mctp_eid_t eid,			      int msg_type, u32 portid, u32 seq, int flag){	struct ifaddrmsg *hdr;	struct nlmsghdr *nlh; 	nlh = nlmsg_put(skb, portid, seq,			msg_type, sizeof(*hdr), flag);	if (!nlh)		return -EMSGSIZE; 	hdr = nlmsg_data(nlh);	memset(hdr, 0, sizeof(*hdr));	hdr->ifa_family = AF_MCTP;	hdr->ifa_prefixlen = 0;	hdr->ifa_flags = 0;	hdr->ifa_scope = 0;	hdr->ifa_index = mdev->dev->ifindex; 	if (nla_put_u8(skb, IFA_LOCAL, eid))		goto cancel; 	if (nla_put_u8(skb, IFA_ADDRESS, eid))		goto cancel; 	nlmsg_end(skb, nlh); 	return 0; cancel:	nlmsg_cancel(skb, nlh);	return -EMSGSIZE;} static int mctp_dump_dev_addrinfo(struct mctp_dev *mdev, struct sk_buff *skb,				  struct netlink_callback *cb){	struct mctp_dump_cb *mcb = (void *)cb->ctx;	u32 portid, seq;	int rc = 0; 	portid = NETLINK_CB(cb->skb).portid;	seq = cb->nlh->nlmsg_seq;	for (; mcb->a_idx < mdev->num_addrs; mcb->a_idx++) {		rc = mctp_fill_addrinfo(skb, mdev, mdev->addrs[mcb->a_idx],					RTM_NEWADDR, portid, seq, NLM_F_MULTI);		if (rc < 0)			break;	} 	return rc;} static int mctp_dump_addrinfo(struct sk_buff *skb, struct netlink_callback *cb){	struct mctp_dump_cb *mcb = (void *)cb->ctx;	struct net *net = sock_net(skb->sk);	struct net_device *dev;	struct ifaddrmsg *hdr;	struct mctp_dev *mdev;	int ifindex = 0, rc; 	/* Filter by ifindex if a header is provided */	hdr = nlmsg_payload(cb->nlh, sizeof(*hdr));	if (hdr) {		ifindex = hdr->ifa_index;	} else {		if (cb->strict_check) {			NL_SET_ERR_MSG(cb->extack, "mctp: Invalid header for addr dump request");			return -EINVAL;		}	} 	rcu_read_lock();	for_each_netdev_dump(net, dev, mcb->ifindex) {		if (ifindex && ifindex != dev->ifindex)			continue;		mdev = __mctp_dev_get(dev);		if (!mdev)			continue;		rc = mctp_dump_dev_addrinfo(mdev, skb, cb);		mctp_dev_put(mdev);		if (rc < 0)			break;		mcb->a_idx = 0;	}	rcu_read_unlock(); 	return skb->len;} static void mctp_addr_notify(struct mctp_dev *mdev, mctp_eid_t eid, int msg_type,			     struct sk_buff *req_skb, struct nlmsghdr *req_nlh){	u32 portid = NETLINK_CB(req_skb).portid;	struct net *net = dev_net(mdev->dev);	struct sk_buff *skb;	int rc = -ENOBUFS; 	skb = nlmsg_new(mctp_addrinfo_size(), GFP_KERNEL);	if (!skb)		goto out; 	rc = mctp_fill_addrinfo(skb, mdev, eid, msg_type,				portid, req_nlh->nlmsg_seq, 0);	if (rc < 0) {		WARN_ON_ONCE(rc == -EMSGSIZE);		goto out;	} 	rtnl_notify(skb, net, portid, RTNLGRP_MCTP_IFADDR, req_nlh, GFP_KERNEL);	return;out:	kfree_skb(skb);	rtnl_set_sk_err(net, RTNLGRP_MCTP_IFADDR, rc);} static const struct nla_policy ifa_mctp_policy[IFA_MAX + 1] = {	[IFA_ADDRESS]		= { .type = NLA_U8 },	[IFA_LOCAL]		= { .type = NLA_U8 },}; static int mctp_rtm_newaddr(struct sk_buff *skb, struct nlmsghdr *nlh,			    struct netlink_ext_ack *extack){	struct net *net = sock_net(skb->sk);	struct nlattr *tb[IFA_MAX + 1];	struct net_device *dev;	struct mctp_addr *addr;	struct mctp_dev *mdev;	struct ifaddrmsg *ifm;	unsigned long flags;	u8 *tmp_addrs;	int rc; 	rc = nlmsg_parse(nlh, sizeof(*ifm), tb, IFA_MAX, ifa_mctp_policy,			 extack);	if (rc < 0)		return rc; 	ifm = nlmsg_data(nlh); 	if (tb[IFA_LOCAL])		addr = nla_data(tb[IFA_LOCAL]);	else if (tb[IFA_ADDRESS])		addr = nla_data(tb[IFA_ADDRESS]);	else		return -EINVAL; 	/* find device */	dev = __dev_get_by_index(net, ifm->ifa_index);	if (!dev)		return -ENODEV; 	mdev = mctp_dev_get_rtnl(dev);	if (!mdev)		return -ENODEV; 	if (!mctp_address_unicast(addr->s_addr))		return -EINVAL; 	/* Prevent duplicates. Under RTNL so don't need to lock for reading */	if (memchr(mdev->addrs, addr->s_addr, mdev->num_addrs))		return -EEXIST; 	tmp_addrs = kmalloc(mdev->num_addrs + 1, GFP_KERNEL);	if (!tmp_addrs)		return -ENOMEM;	memcpy(tmp_addrs, mdev->addrs, mdev->num_addrs);	tmp_addrs[mdev->num_addrs] = addr->s_addr; 	/* Lock to write */	spin_lock_irqsave(&mdev->addrs_lock, flags);	mdev->num_addrs++;	swap(mdev->addrs, tmp_addrs);	spin_unlock_irqrestore(&mdev->addrs_lock, flags); 	kfree(tmp_addrs); 	mctp_addr_notify(mdev, addr->s_addr, RTM_NEWADDR, skb, nlh);	mctp_route_add_local(mdev, addr->s_addr); 	return 0;} static int mctp_rtm_deladdr(struct sk_buff *skb, struct nlmsghdr *nlh,			    struct netlink_ext_ack *extack){	struct net *net = sock_net(skb->sk);	struct nlattr *tb[IFA_MAX + 1];	struct net_device *dev;	struct mctp_addr *addr;	struct mctp_dev *mdev;	struct ifaddrmsg *ifm;	unsigned long flags;	u8 *pos;	int rc; 	rc = nlmsg_parse(nlh, sizeof(*ifm), tb, IFA_MAX, ifa_mctp_policy,			 extack);	if (rc < 0)		return rc; 	ifm = nlmsg_data(nlh); 	if (tb[IFA_LOCAL])		addr = nla_data(tb[IFA_LOCAL]);	else if (tb[IFA_ADDRESS])		addr = nla_data(tb[IFA_ADDRESS]);	else		return -EINVAL; 	/* find device */	dev = __dev_get_by_index(net, ifm->ifa_index);	if (!dev)		return -ENODEV; 	mdev = mctp_dev_get_rtnl(dev);	if (!mdev)		return -ENODEV; 	pos = memchr(mdev->addrs, addr->s_addr, mdev->num_addrs);	if (!pos)		return -ENOENT; 	rc = mctp_route_remove_local(mdev, addr->s_addr);	// we can ignore -ENOENT in the case a route was already removed	if (rc < 0 && rc != -ENOENT)		return rc; 	spin_lock_irqsave(&mdev->addrs_lock, flags);	memmove(pos, pos + 1, mdev->num_addrs - 1 - (pos - mdev->addrs));	mdev->num_addrs--;	spin_unlock_irqrestore(&mdev->addrs_lock, flags); 	mctp_addr_notify(mdev, addr->s_addr, RTM_DELADDR, skb, nlh); 	return 0;} void mctp_dev_hold(struct mctp_dev *mdev){	refcount_inc(&mdev->refs);} void mctp_dev_put(struct mctp_dev *mdev){	if (mdev && refcount_dec_and_test(&mdev->refs)) {		kfree(mdev->addrs);		dev_put(mdev->dev);		kfree_rcu(mdev, rcu);	}} void mctp_dev_release_key(struct mctp_dev *dev, struct mctp_sk_key *key)	__must_hold(&key->lock){	if (!dev)		return;	if (dev->ops && dev->ops->release_flow)		dev->ops->release_flow(dev, key);	key->dev = NULL;	mctp_dev_put(dev);} void mctp_dev_set_key(struct mctp_dev *dev, struct mctp_sk_key *key)	__must_hold(&key->lock){	mctp_dev_hold(dev);	key->dev = dev;} static struct mctp_dev *mctp_add_dev(struct net_device *dev){	struct mctp_dev *mdev; 	ASSERT_RTNL(); 	mdev = kzalloc_obj(*mdev);	if (!mdev)		return ERR_PTR(-ENOMEM); 	spin_lock_init(&mdev->addrs_lock); 	mdev->net = mctp_default_net(dev_net(dev)); 	/* associate to net_device */	refcount_set(&mdev->refs, 1);	rcu_assign_pointer(dev->mctp_ptr, mdev); 	dev_hold(dev);	mdev->dev = dev; 	return mdev;} static int mctp_fill_link_af(struct sk_buff *skb,			     const struct net_device *dev, u32 ext_filter_mask){	struct mctp_dev *mdev; 	mdev = mctp_dev_get_rtnl(dev);	if (!mdev)		return -ENODATA;	if (nla_put_u32(skb, IFLA_MCTP_NET, mdev->net))		return -EMSGSIZE;	if (nla_put_u8(skb, IFLA_MCTP_PHYS_BINDING, mdev->binding))		return -EMSGSIZE;	return 0;} static size_t mctp_get_link_af_size(const struct net_device *dev,				    u32 ext_filter_mask){	struct mctp_dev *mdev;	unsigned int ret; 	/* caller holds RCU */	mdev = __mctp_dev_get(dev);	if (!mdev)		return 0;	ret = nla_total_size(4); /* IFLA_MCTP_NET */	ret += nla_total_size(1); /* IFLA_MCTP_PHYS_BINDING */	mctp_dev_put(mdev);	return ret;} static const struct nla_policy ifla_af_mctp_policy[IFLA_MCTP_MAX + 1] = {	[IFLA_MCTP_NET]		= { .type = NLA_U32 },}; static int mctp_set_link_af(struct net_device *dev, const struct nlattr *attr,			    struct netlink_ext_ack *extack){	struct nlattr *tb[IFLA_MCTP_MAX + 1];	struct mctp_dev *mdev;	int rc; 	rc = nla_parse_nested(tb, IFLA_MCTP_MAX, attr, ifla_af_mctp_policy,			      NULL);	if (rc)		return rc; 	mdev = mctp_dev_get_rtnl(dev);	if (!mdev)		return 0; 	if (tb[IFLA_MCTP_NET])		WRITE_ONCE(mdev->net, nla_get_u32(tb[IFLA_MCTP_NET])); 	return 0;} /* Matches netdev types that should have MCTP handling */static bool mctp_known(struct net_device *dev){	/* only register specific types (inc. NONE for TUN devices) */	return dev->type == ARPHRD_MCTP ||		   dev->type == ARPHRD_LOOPBACK ||		   dev->type == ARPHRD_NONE;} static void mctp_unregister(struct net_device *dev){	struct mctp_dev *mdev; 	mdev = mctp_dev_get_rtnl(dev);	if (!mdev)		return; 	RCU_INIT_POINTER(mdev->dev->mctp_ptr, NULL); 	mctp_route_remove_dev(mdev);	mctp_neigh_remove_dev(mdev); 	mctp_dev_put(mdev);} static int mctp_register(struct net_device *dev){	struct mctp_dev *mdev; 	/* Already registered? */	if (rtnl_dereference(dev->mctp_ptr))		return 0; 	/* only register specific types */	if (!mctp_known(dev))		return 0; 	mdev = mctp_add_dev(dev);	if (IS_ERR(mdev))		return PTR_ERR(mdev); 	return 0;} static int mctp_dev_notify(struct notifier_block *this, unsigned long event,			   void *ptr){	struct net_device *dev = netdev_notifier_info_to_dev(ptr);	int rc; 	switch (event) {	case NETDEV_REGISTER:		rc = mctp_register(dev);		if (rc)			return notifier_from_errno(rc);		break;	case NETDEV_UNREGISTER:		mctp_unregister(dev);		break;	} 	return NOTIFY_OK;} static int mctp_register_netdevice(struct net_device *dev,				   const struct mctp_netdev_ops *ops,				   enum mctp_phys_binding binding){	struct mctp_dev *mdev; 	mdev = mctp_add_dev(dev);	if (IS_ERR(mdev))		return PTR_ERR(mdev); 	mdev->ops = ops;	mdev->binding = binding; 	return register_netdevice(dev);} int mctp_register_netdev(struct net_device *dev,			 const struct mctp_netdev_ops *ops,			 enum mctp_phys_binding binding){	int rc; 	rtnl_lock();

Lines 1–500 of 564.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t