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