xref: /openbmc/linux/net/mctp/neigh.c (revision 7b7fd0ac7dc1ffcaf24d9bca0f051b0168e43cd4)
14d8b9319SMatt Johnston // SPDX-License-Identifier: GPL-2.0
24d8b9319SMatt Johnston /*
34d8b9319SMatt Johnston  * Management Component Transport Protocol (MCTP) - routing
44d8b9319SMatt Johnston  * implementation.
54d8b9319SMatt Johnston  *
64d8b9319SMatt Johnston  * This is currently based on a simple routing table, with no dst cache. The
74d8b9319SMatt Johnston  * number of routes should stay fairly small, so the lookup cost is small.
84d8b9319SMatt Johnston  *
94d8b9319SMatt Johnston  * Copyright (c) 2021 Code Construct
104d8b9319SMatt Johnston  * Copyright (c) 2021 Google
114d8b9319SMatt Johnston  */
124d8b9319SMatt Johnston 
134d8b9319SMatt Johnston #include <linux/idr.h>
144d8b9319SMatt Johnston #include <linux/mctp.h>
154d8b9319SMatt Johnston #include <linux/netdevice.h>
164d8b9319SMatt Johnston #include <linux/rtnetlink.h>
174d8b9319SMatt Johnston #include <linux/skbuff.h>
184d8b9319SMatt Johnston 
194d8b9319SMatt Johnston #include <net/mctp.h>
204d8b9319SMatt Johnston #include <net/mctpdevice.h>
214d8b9319SMatt Johnston #include <net/netlink.h>
224d8b9319SMatt Johnston #include <net/sock.h>
234d8b9319SMatt Johnston 
mctp_neigh_add(struct mctp_dev * mdev,mctp_eid_t eid,enum mctp_neigh_source source,size_t lladdr_len,const void * lladdr)24831119f8SMatt Johnston static int mctp_neigh_add(struct mctp_dev *mdev, mctp_eid_t eid,
254d8b9319SMatt Johnston 			  enum mctp_neigh_source source,
264d8b9319SMatt Johnston 			  size_t lladdr_len, const void *lladdr)
274d8b9319SMatt Johnston {
284d8b9319SMatt Johnston 	struct net *net = dev_net(mdev->dev);
294d8b9319SMatt Johnston 	struct mctp_neigh *neigh;
304d8b9319SMatt Johnston 	int rc;
314d8b9319SMatt Johnston 
324d8b9319SMatt Johnston 	mutex_lock(&net->mctp.neigh_lock);
334d8b9319SMatt Johnston 	if (mctp_neigh_lookup(mdev, eid, NULL) == 0) {
344d8b9319SMatt Johnston 		rc = -EEXIST;
354d8b9319SMatt Johnston 		goto out;
364d8b9319SMatt Johnston 	}
374d8b9319SMatt Johnston 
384d8b9319SMatt Johnston 	if (lladdr_len > sizeof(neigh->ha)) {
394d8b9319SMatt Johnston 		rc = -EINVAL;
404d8b9319SMatt Johnston 		goto out;
414d8b9319SMatt Johnston 	}
424d8b9319SMatt Johnston 
434d8b9319SMatt Johnston 	neigh = kzalloc(sizeof(*neigh), GFP_KERNEL);
444d8b9319SMatt Johnston 	if (!neigh) {
454d8b9319SMatt Johnston 		rc = -ENOMEM;
464d8b9319SMatt Johnston 		goto out;
474d8b9319SMatt Johnston 	}
484d8b9319SMatt Johnston 	INIT_LIST_HEAD(&neigh->list);
494d8b9319SMatt Johnston 	neigh->dev = mdev;
5043f55f23SJeremy Kerr 	mctp_dev_hold(neigh->dev);
514d8b9319SMatt Johnston 	neigh->eid = eid;
524d8b9319SMatt Johnston 	neigh->source = source;
534d8b9319SMatt Johnston 	memcpy(neigh->ha, lladdr, lladdr_len);
544d8b9319SMatt Johnston 
554d8b9319SMatt Johnston 	list_add_rcu(&neigh->list, &net->mctp.neighbours);
564d8b9319SMatt Johnston 	rc = 0;
574d8b9319SMatt Johnston out:
584d8b9319SMatt Johnston 	mutex_unlock(&net->mctp.neigh_lock);
594d8b9319SMatt Johnston 	return rc;
604d8b9319SMatt Johnston }
614d8b9319SMatt Johnston 
__mctp_neigh_free(struct rcu_head * rcu)624d8b9319SMatt Johnston static void __mctp_neigh_free(struct rcu_head *rcu)
634d8b9319SMatt Johnston {
644d8b9319SMatt Johnston 	struct mctp_neigh *neigh = container_of(rcu, struct mctp_neigh, rcu);
654d8b9319SMatt Johnston 
6643f55f23SJeremy Kerr 	mctp_dev_put(neigh->dev);
674d8b9319SMatt Johnston 	kfree(neigh);
684d8b9319SMatt Johnston }
694d8b9319SMatt Johnston 
704d8b9319SMatt Johnston /* Removes all neighbour entries referring to a device */
mctp_neigh_remove_dev(struct mctp_dev * mdev)714d8b9319SMatt Johnston void mctp_neigh_remove_dev(struct mctp_dev *mdev)
724d8b9319SMatt Johnston {
734d8b9319SMatt Johnston 	struct net *net = dev_net(mdev->dev);
744d8b9319SMatt Johnston 	struct mctp_neigh *neigh, *tmp;
754d8b9319SMatt Johnston 
764d8b9319SMatt Johnston 	mutex_lock(&net->mctp.neigh_lock);
774d8b9319SMatt Johnston 	list_for_each_entry_safe(neigh, tmp, &net->mctp.neighbours, list) {
784d8b9319SMatt Johnston 		if (neigh->dev == mdev) {
794d8b9319SMatt Johnston 			list_del_rcu(&neigh->list);
804d8b9319SMatt Johnston 			/* TODO: immediate RTM_DELNEIGH */
814d8b9319SMatt Johnston 			call_rcu(&neigh->rcu, __mctp_neigh_free);
824d8b9319SMatt Johnston 		}
834d8b9319SMatt Johnston 	}
844d8b9319SMatt Johnston 
854d8b9319SMatt Johnston 	mutex_unlock(&net->mctp.neigh_lock);
864d8b9319SMatt Johnston }
874d8b9319SMatt Johnston 
mctp_neigh_remove(struct mctp_dev * mdev,mctp_eid_t eid,enum mctp_neigh_source source)88ae81de73SGagan Kumar static int mctp_neigh_remove(struct mctp_dev *mdev, mctp_eid_t eid,
89ae81de73SGagan Kumar 			     enum mctp_neigh_source source)
90831119f8SMatt Johnston {
91831119f8SMatt Johnston 	struct net *net = dev_net(mdev->dev);
92831119f8SMatt Johnston 	struct mctp_neigh *neigh, *tmp;
93831119f8SMatt Johnston 	bool dropped = false;
94831119f8SMatt Johnston 
95831119f8SMatt Johnston 	mutex_lock(&net->mctp.neigh_lock);
96831119f8SMatt Johnston 	list_for_each_entry_safe(neigh, tmp, &net->mctp.neighbours, list) {
97ae81de73SGagan Kumar 		if (neigh->dev == mdev && neigh->eid == eid &&
98ae81de73SGagan Kumar 		    neigh->source == source) {
99831119f8SMatt Johnston 			list_del_rcu(&neigh->list);
100831119f8SMatt Johnston 			/* TODO: immediate RTM_DELNEIGH */
101831119f8SMatt Johnston 			call_rcu(&neigh->rcu, __mctp_neigh_free);
102831119f8SMatt Johnston 			dropped = true;
103831119f8SMatt Johnston 		}
104831119f8SMatt Johnston 	}
105831119f8SMatt Johnston 
106831119f8SMatt Johnston 	mutex_unlock(&net->mctp.neigh_lock);
107831119f8SMatt Johnston 	return dropped ? 0 : -ENOENT;
108831119f8SMatt Johnston }
109831119f8SMatt Johnston 
110831119f8SMatt Johnston static const struct nla_policy nd_mctp_policy[NDA_MAX + 1] = {
111831119f8SMatt Johnston 	[NDA_DST]		= { .type = NLA_U8 },
112831119f8SMatt Johnston 	[NDA_LLADDR]		= { .type = NLA_BINARY, .len = MAX_ADDR_LEN },
113831119f8SMatt Johnston };
114831119f8SMatt Johnston 
mctp_rtm_newneigh(struct sk_buff * skb,struct nlmsghdr * nlh,struct netlink_ext_ack * extack)115831119f8SMatt Johnston static int mctp_rtm_newneigh(struct sk_buff *skb, struct nlmsghdr *nlh,
116831119f8SMatt Johnston 			     struct netlink_ext_ack *extack)
117831119f8SMatt Johnston {
118831119f8SMatt Johnston 	struct net *net = sock_net(skb->sk);
119831119f8SMatt Johnston 	struct net_device *dev;
120831119f8SMatt Johnston 	struct mctp_dev *mdev;
121831119f8SMatt Johnston 	struct ndmsg *ndm;
122831119f8SMatt Johnston 	struct nlattr *tb[NDA_MAX + 1];
123831119f8SMatt Johnston 	int rc;
124831119f8SMatt Johnston 	mctp_eid_t eid;
125831119f8SMatt Johnston 	void *lladdr;
126831119f8SMatt Johnston 	int lladdr_len;
127831119f8SMatt Johnston 
128831119f8SMatt Johnston 	rc = nlmsg_parse(nlh, sizeof(*ndm), tb, NDA_MAX, nd_mctp_policy,
129831119f8SMatt Johnston 			 extack);
130831119f8SMatt Johnston 	if (rc < 0) {
131831119f8SMatt Johnston 		NL_SET_ERR_MSG(extack, "lladdr too large?");
132831119f8SMatt Johnston 		return rc;
133831119f8SMatt Johnston 	}
134831119f8SMatt Johnston 
135831119f8SMatt Johnston 	if (!tb[NDA_DST]) {
136831119f8SMatt Johnston 		NL_SET_ERR_MSG(extack, "Neighbour EID must be specified");
137831119f8SMatt Johnston 		return -EINVAL;
138831119f8SMatt Johnston 	}
139831119f8SMatt Johnston 
140831119f8SMatt Johnston 	if (!tb[NDA_LLADDR]) {
141831119f8SMatt Johnston 		NL_SET_ERR_MSG(extack, "Neighbour lladdr must be specified");
142831119f8SMatt Johnston 		return -EINVAL;
143831119f8SMatt Johnston 	}
144831119f8SMatt Johnston 
145831119f8SMatt Johnston 	eid = nla_get_u8(tb[NDA_DST]);
146cb196b72SJeremy Kerr 	if (!mctp_address_unicast(eid)) {
147831119f8SMatt Johnston 		NL_SET_ERR_MSG(extack, "Invalid neighbour EID");
148831119f8SMatt Johnston 		return -EINVAL;
149831119f8SMatt Johnston 	}
150831119f8SMatt Johnston 
151831119f8SMatt Johnston 	lladdr = nla_data(tb[NDA_LLADDR]);
152831119f8SMatt Johnston 	lladdr_len = nla_len(tb[NDA_LLADDR]);
153831119f8SMatt Johnston 
154831119f8SMatt Johnston 	ndm = nlmsg_data(nlh);
155831119f8SMatt Johnston 
156831119f8SMatt Johnston 	dev = __dev_get_by_index(net, ndm->ndm_ifindex);
157831119f8SMatt Johnston 	if (!dev)
158831119f8SMatt Johnston 		return -ENODEV;
159831119f8SMatt Johnston 
160831119f8SMatt Johnston 	mdev = mctp_dev_get_rtnl(dev);
161831119f8SMatt Johnston 	if (!mdev)
162831119f8SMatt Johnston 		return -ENODEV;
163831119f8SMatt Johnston 
164831119f8SMatt Johnston 	if (lladdr_len != dev->addr_len) {
165831119f8SMatt Johnston 		NL_SET_ERR_MSG(extack, "Wrong lladdr length");
166831119f8SMatt Johnston 		return -EINVAL;
167831119f8SMatt Johnston 	}
168831119f8SMatt Johnston 
169831119f8SMatt Johnston 	return mctp_neigh_add(mdev, eid, MCTP_NEIGH_STATIC,
170831119f8SMatt Johnston 			lladdr_len, lladdr);
171831119f8SMatt Johnston }
172831119f8SMatt Johnston 
mctp_rtm_delneigh(struct sk_buff * skb,struct nlmsghdr * nlh,struct netlink_ext_ack * extack)173831119f8SMatt Johnston static int mctp_rtm_delneigh(struct sk_buff *skb, struct nlmsghdr *nlh,
174831119f8SMatt Johnston 			     struct netlink_ext_ack *extack)
175831119f8SMatt Johnston {
176831119f8SMatt Johnston 	struct net *net = sock_net(skb->sk);
177831119f8SMatt Johnston 	struct nlattr *tb[NDA_MAX + 1];
178831119f8SMatt Johnston 	struct net_device *dev;
179831119f8SMatt Johnston 	struct mctp_dev *mdev;
180831119f8SMatt Johnston 	struct ndmsg *ndm;
181831119f8SMatt Johnston 	int rc;
182831119f8SMatt Johnston 	mctp_eid_t eid;
183831119f8SMatt Johnston 
184831119f8SMatt Johnston 	rc = nlmsg_parse(nlh, sizeof(*ndm), tb, NDA_MAX, nd_mctp_policy,
185831119f8SMatt Johnston 			 extack);
186831119f8SMatt Johnston 	if (rc < 0) {
187831119f8SMatt Johnston 		NL_SET_ERR_MSG(extack, "incorrect format");
188831119f8SMatt Johnston 		return rc;
189831119f8SMatt Johnston 	}
190831119f8SMatt Johnston 
191831119f8SMatt Johnston 	if (!tb[NDA_DST]) {
192831119f8SMatt Johnston 		NL_SET_ERR_MSG(extack, "Neighbour EID must be specified");
193831119f8SMatt Johnston 		return -EINVAL;
194831119f8SMatt Johnston 	}
195831119f8SMatt Johnston 	eid = nla_get_u8(tb[NDA_DST]);
196831119f8SMatt Johnston 
197831119f8SMatt Johnston 	ndm = nlmsg_data(nlh);
198831119f8SMatt Johnston 	dev = __dev_get_by_index(net, ndm->ndm_ifindex);
199831119f8SMatt Johnston 	if (!dev)
200831119f8SMatt Johnston 		return -ENODEV;
201831119f8SMatt Johnston 
202831119f8SMatt Johnston 	mdev = mctp_dev_get_rtnl(dev);
203831119f8SMatt Johnston 	if (!mdev)
204831119f8SMatt Johnston 		return -ENODEV;
205831119f8SMatt Johnston 
206ae81de73SGagan Kumar 	return mctp_neigh_remove(mdev, eid, MCTP_NEIGH_STATIC);
207831119f8SMatt Johnston }
208831119f8SMatt Johnston 
mctp_fill_neigh(struct sk_buff * skb,u32 portid,u32 seq,int event,unsigned int flags,struct mctp_neigh * neigh)209831119f8SMatt Johnston static int mctp_fill_neigh(struct sk_buff *skb, u32 portid, u32 seq, int event,
210831119f8SMatt Johnston 			   unsigned int flags, struct mctp_neigh *neigh)
211831119f8SMatt Johnston {
212831119f8SMatt Johnston 	struct net_device *dev = neigh->dev->dev;
213831119f8SMatt Johnston 	struct nlmsghdr *nlh;
214831119f8SMatt Johnston 	struct ndmsg *hdr;
215831119f8SMatt Johnston 
216831119f8SMatt Johnston 	nlh = nlmsg_put(skb, portid, seq, event, sizeof(*hdr), flags);
217831119f8SMatt Johnston 	if (!nlh)
218831119f8SMatt Johnston 		return -EMSGSIZE;
219831119f8SMatt Johnston 
220831119f8SMatt Johnston 	hdr = nlmsg_data(nlh);
221831119f8SMatt Johnston 	hdr->ndm_family = AF_MCTP;
222831119f8SMatt Johnston 	hdr->ndm_ifindex = dev->ifindex;
223831119f8SMatt Johnston 	hdr->ndm_state = 0; // TODO other state bits?
224831119f8SMatt Johnston 	if (neigh->source == MCTP_NEIGH_STATIC)
225831119f8SMatt Johnston 		hdr->ndm_state |= NUD_PERMANENT;
226831119f8SMatt Johnston 	hdr->ndm_flags = 0;
227831119f8SMatt Johnston 	hdr->ndm_type = RTN_UNICAST; // TODO: is loopback RTN_LOCAL?
228831119f8SMatt Johnston 
229831119f8SMatt Johnston 	if (nla_put_u8(skb, NDA_DST, neigh->eid))
230831119f8SMatt Johnston 		goto cancel;
231831119f8SMatt Johnston 
232831119f8SMatt Johnston 	if (nla_put(skb, NDA_LLADDR, dev->addr_len, neigh->ha))
233831119f8SMatt Johnston 		goto cancel;
234831119f8SMatt Johnston 
235831119f8SMatt Johnston 	nlmsg_end(skb, nlh);
236831119f8SMatt Johnston 
237831119f8SMatt Johnston 	return 0;
238831119f8SMatt Johnston cancel:
239831119f8SMatt Johnston 	nlmsg_cancel(skb, nlh);
240831119f8SMatt Johnston 	return -EMSGSIZE;
241831119f8SMatt Johnston }
242831119f8SMatt Johnston 
mctp_rtm_getneigh(struct sk_buff * skb,struct netlink_callback * cb)243831119f8SMatt Johnston static int mctp_rtm_getneigh(struct sk_buff *skb, struct netlink_callback *cb)
244831119f8SMatt Johnston {
245831119f8SMatt Johnston 	struct net *net = sock_net(skb->sk);
246831119f8SMatt Johnston 	int rc, idx, req_ifindex;
247831119f8SMatt Johnston 	struct mctp_neigh *neigh;
248831119f8SMatt Johnston 	struct ndmsg *ndmsg;
249831119f8SMatt Johnston 	struct {
250831119f8SMatt Johnston 		int idx;
251831119f8SMatt Johnston 	} *cbctx = (void *)cb->ctx;
252831119f8SMatt Johnston 
253831119f8SMatt Johnston 	ndmsg = nlmsg_data(cb->nlh);
254831119f8SMatt Johnston 	req_ifindex = ndmsg->ndm_ifindex;
255831119f8SMatt Johnston 
256831119f8SMatt Johnston 	idx = 0;
257831119f8SMatt Johnston 	rcu_read_lock();
258831119f8SMatt Johnston 	list_for_each_entry_rcu(neigh, &net->mctp.neighbours, list) {
259831119f8SMatt Johnston 		if (idx < cbctx->idx)
260831119f8SMatt Johnston 			goto cont;
261831119f8SMatt Johnston 
262831119f8SMatt Johnston 		rc = 0;
263831119f8SMatt Johnston 		if (req_ifindex == 0 || req_ifindex == neigh->dev->dev->ifindex)
264831119f8SMatt Johnston 			rc = mctp_fill_neigh(skb, NETLINK_CB(cb->skb).portid,
265831119f8SMatt Johnston 					     cb->nlh->nlmsg_seq,
266831119f8SMatt Johnston 					     RTM_NEWNEIGH, NLM_F_MULTI, neigh);
267831119f8SMatt Johnston 
268831119f8SMatt Johnston 		if (rc)
269831119f8SMatt Johnston 			break;
270831119f8SMatt Johnston cont:
271831119f8SMatt Johnston 		idx++;
272831119f8SMatt Johnston 	}
273831119f8SMatt Johnston 	rcu_read_unlock();
274831119f8SMatt Johnston 
275831119f8SMatt Johnston 	cbctx->idx = idx;
276831119f8SMatt Johnston 	return skb->len;
277831119f8SMatt Johnston }
278831119f8SMatt Johnston 
mctp_neigh_lookup(struct mctp_dev * mdev,mctp_eid_t eid,void * ret_hwaddr)2794d8b9319SMatt Johnston int mctp_neigh_lookup(struct mctp_dev *mdev, mctp_eid_t eid, void *ret_hwaddr)
2804d8b9319SMatt Johnston {
2814d8b9319SMatt Johnston 	struct net *net = dev_net(mdev->dev);
2824d8b9319SMatt Johnston 	struct mctp_neigh *neigh;
2834d8b9319SMatt Johnston 	int rc = -EHOSTUNREACH; // TODO: or ENOENT?
2844d8b9319SMatt Johnston 
2854d8b9319SMatt Johnston 	rcu_read_lock();
2864d8b9319SMatt Johnston 	list_for_each_entry_rcu(neigh, &net->mctp.neighbours, list) {
2874d8b9319SMatt Johnston 		if (mdev == neigh->dev && eid == neigh->eid) {
2884d8b9319SMatt Johnston 			if (ret_hwaddr)
2894d8b9319SMatt Johnston 				memcpy(ret_hwaddr, neigh->ha,
2904d8b9319SMatt Johnston 				       sizeof(neigh->ha));
2914d8b9319SMatt Johnston 			rc = 0;
2924d8b9319SMatt Johnston 			break;
2934d8b9319SMatt Johnston 		}
2944d8b9319SMatt Johnston 	}
2954d8b9319SMatt Johnston 	rcu_read_unlock();
2964d8b9319SMatt Johnston 	return rc;
2974d8b9319SMatt Johnston }
2984d8b9319SMatt Johnston 
2994d8b9319SMatt Johnston /* namespace registration */
mctp_neigh_net_init(struct net * net)3004d8b9319SMatt Johnston static int __net_init mctp_neigh_net_init(struct net *net)
3014d8b9319SMatt Johnston {
3024d8b9319SMatt Johnston 	struct netns_mctp *ns = &net->mctp;
3034d8b9319SMatt Johnston 
3044d8b9319SMatt Johnston 	INIT_LIST_HEAD(&ns->neighbours);
305831119f8SMatt Johnston 	mutex_init(&ns->neigh_lock);
3064d8b9319SMatt Johnston 	return 0;
3074d8b9319SMatt Johnston }
3084d8b9319SMatt Johnston 
mctp_neigh_net_exit(struct net * net)3094d8b9319SMatt Johnston static void __net_exit mctp_neigh_net_exit(struct net *net)
3104d8b9319SMatt Johnston {
3114d8b9319SMatt Johnston 	struct netns_mctp *ns = &net->mctp;
3124d8b9319SMatt Johnston 	struct mctp_neigh *neigh;
3134d8b9319SMatt Johnston 
3144d8b9319SMatt Johnston 	list_for_each_entry(neigh, &ns->neighbours, list)
3154d8b9319SMatt Johnston 		call_rcu(&neigh->rcu, __mctp_neigh_free);
3164d8b9319SMatt Johnston }
3174d8b9319SMatt Johnston 
3184d8b9319SMatt Johnston /* net namespace implementation */
3194d8b9319SMatt Johnston 
3204d8b9319SMatt Johnston static struct pernet_operations mctp_net_ops = {
3214d8b9319SMatt Johnston 	.init = mctp_neigh_net_init,
3224d8b9319SMatt Johnston 	.exit = mctp_neigh_net_exit,
3234d8b9319SMatt Johnston };
3244d8b9319SMatt Johnston 
325*f4df31a0SKuniyuki Iwashima static const struct rtnl_msg_handler mctp_neigh_rtnl_msg_handlers[] = {
326*f4df31a0SKuniyuki Iwashima 	{THIS_MODULE, PF_MCTP, RTM_NEWNEIGH, mctp_rtm_newneigh, NULL, 0},
327*f4df31a0SKuniyuki Iwashima 	{THIS_MODULE, PF_MCTP, RTM_DELNEIGH, mctp_rtm_delneigh, NULL, 0},
328*f4df31a0SKuniyuki Iwashima 	{THIS_MODULE, PF_MCTP, RTM_GETNEIGH, NULL, mctp_rtm_getneigh, 0},
329*f4df31a0SKuniyuki Iwashima };
330*f4df31a0SKuniyuki Iwashima 
mctp_neigh_init(void)3314d8b9319SMatt Johnston int __init mctp_neigh_init(void)
3324d8b9319SMatt Johnston {
333*f4df31a0SKuniyuki Iwashima 	int err;
334831119f8SMatt Johnston 
335*f4df31a0SKuniyuki Iwashima 	err = register_pernet_subsys(&mctp_net_ops);
336*f4df31a0SKuniyuki Iwashima 	if (err)
337*f4df31a0SKuniyuki Iwashima 		return err;
338*f4df31a0SKuniyuki Iwashima 
339*f4df31a0SKuniyuki Iwashima 	err = rtnl_register_many(mctp_neigh_rtnl_msg_handlers);
340*f4df31a0SKuniyuki Iwashima 	if (err)
341*f4df31a0SKuniyuki Iwashima 		unregister_pernet_subsys(&mctp_net_ops);
342*f4df31a0SKuniyuki Iwashima 
343*f4df31a0SKuniyuki Iwashima 	return err;
3444d8b9319SMatt Johnston }
3454d8b9319SMatt Johnston 
mctp_neigh_exit(void)346*f4df31a0SKuniyuki Iwashima void mctp_neigh_exit(void)
3474d8b9319SMatt Johnston {
348*f4df31a0SKuniyuki Iwashima 	rtnl_unregister_many(mctp_neigh_rtnl_msg_handlers);
3494d8b9319SMatt Johnston 	unregister_pernet_subsys(&mctp_net_ops);
3504d8b9319SMatt Johnston }
351