| // SPDX-License-Identifier: GPL-2.0 | |
| #include <linux/netdevice.h> | |
| #include <linux/mctp.h> | |
| #include <linux/if_arp.h> | |
| #include <net/mctp.h> | |
| #include <net/mctpdevice.h> | |
| #include <net/pkt_sched.h> | |
| #include "utils.h" | |
| static netdev_tx_t mctp_test_dev_tx(struct sk_buff *skb, | |
| struct net_device *ndev) | |
| { | |
| struct mctp_test_dev *dev = netdev_priv(ndev); | |
| skb_queue_tail(&dev->pkts, skb); | |
| return NETDEV_TX_OK; | |
| } | |
| static const struct net_device_ops mctp_test_netdev_ops = { | |
| .ndo_start_xmit = mctp_test_dev_tx, | |
| }; | |
| static void mctp_test_dev_setup(struct net_device *ndev) | |
| { | |
| ndev->type = ARPHRD_MCTP; | |
| ndev->mtu = MCTP_DEV_TEST_MTU; | |
| ndev->hard_header_len = 0; | |
| ndev->tx_queue_len = 0; | |
| ndev->flags = IFF_NOARP; | |
| ndev->netdev_ops = &mctp_test_netdev_ops; | |
| ndev->needs_free_netdev = true; | |
| } | |
| static struct mctp_test_dev *__mctp_test_create_dev(unsigned short lladdr_len, | |
| const unsigned char *lladdr) | |
| { | |
| struct mctp_test_dev *dev; | |
| struct net_device *ndev; | |
| int rc; | |
| if (WARN_ON(lladdr_len > MAX_ADDR_LEN)) | |
| return NULL; | |
| ndev = alloc_netdev(sizeof(*dev), "mctptest%d", NET_NAME_ENUM, | |
| mctp_test_dev_setup); | |
| if (!ndev) | |
| return NULL; | |
| dev = netdev_priv(ndev); | |
| dev->ndev = ndev; | |
| ndev->addr_len = lladdr_len; | |
| dev_addr_set(ndev, lladdr); | |
| skb_queue_head_init(&dev->pkts); | |
| rc = register_netdev(ndev); | |
| if (rc) { | |
| free_netdev(ndev); | |
| return NULL; | |
| } | |
| rcu_read_lock(); | |
| dev->mdev = __mctp_dev_get(ndev); | |
| dev->mdev->net = mctp_default_net(dev_net(ndev)); | |
| rcu_read_unlock(); | |
| /* bring the device up; we want to be able to TX immediately */ | |
| rtnl_lock(); | |
| dev_open(ndev, NULL); | |
| rtnl_unlock(); | |
| return dev; | |
| } | |
| struct mctp_test_dev *mctp_test_create_dev(void) | |
| { | |
| return __mctp_test_create_dev(0, NULL); | |
| } | |
| struct mctp_test_dev *mctp_test_create_dev_with_addr(mctp_eid_t addr) | |
| { | |
| struct mctp_test_dev *dev; | |
| dev = __mctp_test_create_dev(0, NULL); | |
| if (!dev) | |
| return NULL; | |
| dev->mdev->addrs = kmalloc_objs(u8, 1); | |
| if (!dev->mdev->addrs) { | |
| mctp_test_destroy_dev(dev); | |
| return NULL; | |
| } | |
| dev->mdev->num_addrs = 1; | |
| dev->mdev->addrs[0] = addr; | |
| return dev; | |
| } | |
| struct mctp_test_dev *mctp_test_create_dev_lladdr(unsigned short lladdr_len, | |
| const unsigned char *lladdr) | |
| { | |
| return __mctp_test_create_dev(lladdr_len, lladdr); | |
| } | |
| void mctp_test_destroy_dev(struct mctp_test_dev *dev) | |
| { | |
| skb_queue_purge(&dev->pkts); | |
| mctp_dev_put(dev->mdev); | |
| unregister_netdev(dev->ndev); | |
| } | |
| static int mctp_test_dst_output(struct mctp_dst *dst, struct sk_buff *skb) | |
| { | |
| skb->dev = dst->dev->dev; | |
| dev_direct_xmit(skb, 0); | |
| return 0; | |
| } | |
| /* local version of mctp_route_alloc() */ | |
| static struct mctp_test_route *mctp_route_test_alloc(void) | |
| { | |
| struct mctp_test_route *rt; | |
| rt = kzalloc_obj(*rt); | |
| if (!rt) | |
| return NULL; | |
| INIT_LIST_HEAD(&rt->rt.list); | |
| refcount_set(&rt->rt.refs, 1); | |
| rt->rt.output = mctp_test_dst_output; | |
| return rt; | |
| } | |
| struct mctp_test_route *mctp_test_create_route_direct(struct net *net, | |
| struct mctp_dev *dev, | |
| mctp_eid_t eid, | |
| unsigned int mtu) | |
| { | |
| struct mctp_test_route *rt; | |
| rt = mctp_route_test_alloc(); | |
| if (!rt) | |
| return NULL; | |
| rt->rt.min = eid; | |
| rt->rt.max = eid; | |
| rt->rt.mtu = mtu; | |
| rt->rt.type = RTN_UNSPEC; | |
| rt->rt.dst_type = MCTP_ROUTE_DIRECT; | |
| if (dev) | |
| mctp_dev_hold(dev); | |
| rt->rt.dev = dev; | |
| list_add_rcu(&rt->rt.list, &net->mctp.routes); | |
| return rt; | |
| } | |
| struct mctp_test_route *mctp_test_create_route_gw(struct net *net, | |
| unsigned int netid, | |
| mctp_eid_t eid, | |
| mctp_eid_t gw, | |
| unsigned int mtu) | |
| { | |
| struct mctp_test_route *rt; | |
| rt = mctp_route_test_alloc(); | |
| if (!rt) | |
| return NULL; | |
| rt->rt.min = eid; | |
| rt->rt.max = eid; | |
| rt->rt.mtu = mtu; | |
| rt->rt.type = RTN_UNSPEC; | |
| rt->rt.dst_type = MCTP_ROUTE_GATEWAY; | |
| rt->rt.gateway.eid = gw; | |
| rt->rt.gateway.net = netid; | |
| list_add_rcu(&rt->rt.list, &net->mctp.routes); | |
| return rt; | |
| } | |
| /* Convenience function for our test dst; release with mctp_dst_release() */ | |
| void mctp_test_dst_setup(struct kunit *test, struct mctp_dst *dst, | |
| struct mctp_test_dev *dev, unsigned int mtu) | |
| { | |
| unsigned long flags; | |
| KUNIT_EXPECT_NOT_ERR_OR_NULL(test, dev); | |
| memset(dst, 0, sizeof(*dst)); | |
| dst->dev = dev->mdev; | |
| __mctp_dev_get(dst->dev->dev); | |
| dst->mtu = mtu; | |
| dst->output = mctp_test_dst_output; | |
| dst->saddr = MCTP_ADDR_NULL; | |
| spin_lock_irqsave(&dev->mdev->addrs_lock, flags); | |
| if (dev->mdev->num_addrs) | |
| dst->saddr = dev->mdev->addrs[0]; | |
| spin_unlock_irqrestore(&dev->mdev->addrs_lock, flags); | |
| } | |
| void mctp_test_route_destroy(struct kunit *test, struct mctp_test_route *rt) | |
| { | |
| unsigned int refs; | |
| rtnl_lock(); | |
| list_del_rcu(&rt->rt.list); | |
| rtnl_unlock(); | |
| if (rt->rt.dst_type == MCTP_ROUTE_DIRECT && rt->rt.dev) | |
| mctp_dev_put(rt->rt.dev); | |
| refs = refcount_read(&rt->rt.refs); | |
| KUNIT_ASSERT_EQ_MSG(test, refs, 1, "route ref imbalance"); | |
| kfree_rcu(&rt->rt, rcu); | |
| } | |
| void mctp_test_skb_set_dev(struct sk_buff *skb, struct mctp_test_dev *dev) | |
| { | |
| struct mctp_skb_cb *cb; | |
| cb = mctp_cb(skb); | |
| cb->net = READ_ONCE(dev->mdev->net); | |
| skb->dev = dev->ndev; | |
| } | |
| struct sk_buff *mctp_test_create_skb(const struct mctp_hdr *hdr, | |
| unsigned int data_len) | |
| { | |
| size_t hdr_len = sizeof(*hdr); | |
| struct sk_buff *skb; | |
| unsigned int i; | |
| u8 *buf; | |
| skb = alloc_skb(hdr_len + data_len, GFP_KERNEL); | |
| if (!skb) | |
| return NULL; | |
| __mctp_cb(skb); | |
| memcpy(skb_put(skb, hdr_len), hdr, hdr_len); | |
| buf = skb_put(skb, data_len); | |
| for (i = 0; i < data_len; i++) | |
| buf[i] = i & 0xff; | |
| return skb; | |
| } | |
| struct sk_buff *__mctp_test_create_skb_data(const struct mctp_hdr *hdr, | |
| const void *data, size_t data_len) | |
| { | |
| size_t hdr_len = sizeof(*hdr); | |
| struct sk_buff *skb; | |
| skb = alloc_skb(hdr_len + data_len, GFP_KERNEL); | |
| if (!skb) | |
| return NULL; | |
| __mctp_cb(skb); | |
| memcpy(skb_put(skb, hdr_len), hdr, hdr_len); | |
| memcpy(skb_put(skb, data_len), data, data_len); | |
| return skb; | |
| } | |
| void mctp_test_bind_run(struct kunit *test, | |
| const struct mctp_test_bind_setup *setup, | |
| int *ret_bind_errno, struct socket **sock) | |
| { | |
| struct sockaddr_mctp addr; | |
| int rc; | |
| *ret_bind_errno = -EIO; | |
| rc = sock_create_kern(&init_net, AF_MCTP, SOCK_DGRAM, 0, sock); | |
| KUNIT_ASSERT_EQ(test, rc, 0); | |
| /* connect() if requested */ | |
| if (setup->have_peer) { | |
| memset(&addr, 0x0, sizeof(addr)); | |
| addr.smctp_family = AF_MCTP; | |
| addr.smctp_network = setup->peer_net; | |
| addr.smctp_addr.s_addr = setup->peer_addr; | |
| /* connect() type must match bind() type */ | |
| addr.smctp_type = setup->bind_type; | |
| rc = kernel_connect(*sock, (struct sockaddr_unsized *)&addr, | |
| sizeof(addr), 0); | |
| KUNIT_EXPECT_EQ(test, rc, 0); | |
| } | |
| /* bind() */ | |
| memset(&addr, 0x0, sizeof(addr)); | |
| addr.smctp_family = AF_MCTP; | |
| addr.smctp_network = setup->bind_net; | |
| addr.smctp_addr.s_addr = setup->bind_addr; | |
| addr.smctp_type = setup->bind_type; | |
| *ret_bind_errno = | |
| kernel_bind(*sock, (struct sockaddr_unsized *)&addr, | |
| sizeof(addr)); | |
| } |