Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
menuconfig MCTP
depends on NET
bool "MCTP core protocol support"
help
Management Component Transport Protocol (MCTP) is an in-system
protocol for communicating between management controllers and
their managed devices (peripherals, host processors, etc.). The
protocol is defined by DMTF specification DSP0236.
This option enables core MCTP support. For communicating with other
devices, you'll want to enable a driver for a specific hardware
channel.
config MCTP_TEST
bool "MCTP core tests" if !KUNIT_ALL_TESTS
select MCTP_FLOWS
depends on MCTP=y && KUNIT=y
default KUNIT_ALL_TESTS
config MCTP_FLOWS
bool
depends on MCTP
select SKB_EXTENSIONS
+6
View File
@@ -0,0 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_MCTP) += mctp.o
mctp-objs := af_mctp.o device.o route.o neigh.o
# tests
obj-$(CONFIG_MCTP_TEST) += test/utils.o
+911
View File
@@ -0,0 +1,911 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Management Component Transport Protocol (MCTP)
*
* Copyright (c) 2021 Code Construct
* Copyright (c) 2021 Google
*/
#include <linux/compat.h>
#include <linux/if_arp.h>
#include <linux/net.h>
#include <linux/mctp.h>
#include <linux/module.h>
#include <linux/socket.h>
#include <linux/uio.h>
#include <net/mctp.h>
#include <net/mctpdevice.h>
#include <net/sock.h>
#define CREATE_TRACE_POINTS
#include <trace/events/mctp.h>
/* socket implementation */
static void mctp_sk_expire_keys(struct timer_list *timer);
static int mctp_release(struct socket *sock)
{
struct sock *sk = sock->sk;
if (sk) {
sock->sk = NULL;
sk->sk_prot->close(sk, 0);
}
return 0;
}
/* Generic sockaddr checks, padding checks only so far */
static bool mctp_sockaddr_is_ok(const struct sockaddr_mctp *addr)
{
return !addr->__smctp_pad0 && !addr->__smctp_pad1;
}
static bool mctp_sockaddr_ext_is_ok(const struct sockaddr_mctp_ext *addr)
{
return !addr->__smctp_pad0[0] &&
!addr->__smctp_pad0[1] &&
!addr->__smctp_pad0[2];
}
static int mctp_bind(struct socket *sock, struct sockaddr_unsized *addr, int addrlen)
{
struct sock *sk = sock->sk;
struct mctp_sock *msk = container_of(sk, struct mctp_sock, sk);
struct net *net = sock_net(&msk->sk);
struct sockaddr_mctp *smctp;
int rc;
if (addrlen < sizeof(*smctp))
return -EINVAL;
if (addr->sa_family != AF_MCTP)
return -EAFNOSUPPORT;
if (!capable(CAP_NET_BIND_SERVICE))
return -EACCES;
/* it's a valid sockaddr for MCTP, cast and do protocol checks */
smctp = (struct sockaddr_mctp *)addr;
if (!mctp_sockaddr_is_ok(smctp))
return -EINVAL;
lock_sock(sk);
if (sk_hashed(sk)) {
rc = -EADDRINUSE;
goto out_release;
}
msk->bind_local_addr = smctp->smctp_addr.s_addr;
/* MCTP_NET_ANY with a specific EID is resolved to the default net
* at bind() time.
* For bind_addr=MCTP_ADDR_ANY it is handled specially at route
* lookup time.
*/
if (smctp->smctp_network == MCTP_NET_ANY &&
msk->bind_local_addr != MCTP_ADDR_ANY) {
msk->bind_net = mctp_default_net(net);
} else {
msk->bind_net = smctp->smctp_network;
}
/* ignore the IC bit */
smctp->smctp_type &= 0x7f;
if (msk->bind_peer_set) {
if (msk->bind_type != smctp->smctp_type) {
/* Prior connect() had a different type */
rc = -EINVAL;
goto out_release;
}
if (msk->bind_net == MCTP_NET_ANY) {
/* Restrict to the network passed to connect() */
msk->bind_net = msk->bind_peer_net;
}
if (msk->bind_net != msk->bind_peer_net) {
/* connect() had a different net to bind() */
rc = -EINVAL;
goto out_release;
}
} else {
msk->bind_type = smctp->smctp_type;
}
rc = sk->sk_prot->hash(sk);
out_release:
release_sock(sk);
return rc;
}
/* Used to set a specific peer prior to bind. Not used for outbound
* connections (Tag Owner set) since MCTP is a datagram protocol.
*/
static int mctp_connect(struct socket *sock, struct sockaddr_unsized *addr,
int addrlen, int flags)
{
struct sock *sk = sock->sk;
struct mctp_sock *msk = container_of(sk, struct mctp_sock, sk);
struct net *net = sock_net(&msk->sk);
struct sockaddr_mctp *smctp;
int rc;
if (addrlen != sizeof(*smctp))
return -EINVAL;
if (addr->sa_family != AF_MCTP)
return -EAFNOSUPPORT;
/* It's a valid sockaddr for MCTP, cast and do protocol checks */
smctp = (struct sockaddr_mctp *)addr;
if (!mctp_sockaddr_is_ok(smctp))
return -EINVAL;
/* Can't bind by tag */
if (smctp->smctp_tag)
return -EINVAL;
/* IC bit must be unset */
if (smctp->smctp_type & 0x80)
return -EINVAL;
lock_sock(sk);
if (sk_hashed(sk)) {
/* bind() already */
rc = -EADDRINUSE;
goto out_release;
}
if (msk->bind_peer_set) {
/* connect() already */
rc = -EADDRINUSE;
goto out_release;
}
msk->bind_peer_set = true;
msk->bind_peer_addr = smctp->smctp_addr.s_addr;
msk->bind_type = smctp->smctp_type;
if (smctp->smctp_network == MCTP_NET_ANY)
msk->bind_peer_net = mctp_default_net(net);
else
msk->bind_peer_net = smctp->smctp_network;
rc = 0;
out_release:
release_sock(sk);
return rc;
}
static int mctp_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
{
DECLARE_SOCKADDR(struct sockaddr_mctp *, addr, msg->msg_name);
int rc, addrlen = msg->msg_namelen;
struct sock *sk = sock->sk;
struct mctp_sock *msk = container_of(sk, struct mctp_sock, sk);
struct mctp_skb_cb *cb;
struct sk_buff *skb = NULL;
struct mctp_dst dst;
int hlen;
if (addr) {
const u8 tagbits = MCTP_TAG_MASK | MCTP_TAG_OWNER |
MCTP_TAG_PREALLOC;
if (addrlen < sizeof(struct sockaddr_mctp))
return -EINVAL;
if (addr->smctp_family != AF_MCTP)
return -EINVAL;
if (!mctp_sockaddr_is_ok(addr))
return -EINVAL;
if (addr->smctp_tag & ~tagbits)
return -EINVAL;
/* can't preallocate a non-owned tag */
if (addr->smctp_tag & MCTP_TAG_PREALLOC &&
!(addr->smctp_tag & MCTP_TAG_OWNER))
return -EINVAL;
} else {
/* TODO: connect()ed sockets */
return -EDESTADDRREQ;
}
if (!capable(CAP_NET_RAW))
return -EACCES;
if (addr->smctp_network == MCTP_NET_ANY)
addr->smctp_network = mctp_default_net(sock_net(sk));
/* direct addressing */
if (msk->addr_ext && addrlen >= sizeof(struct sockaddr_mctp_ext)) {
DECLARE_SOCKADDR(struct sockaddr_mctp_ext *,
extaddr, msg->msg_name);
if (!mctp_sockaddr_ext_is_ok(extaddr))
return -EINVAL;
rc = mctp_dst_from_extaddr(&dst, sock_net(sk),
extaddr->smctp_ifindex,
extaddr->smctp_halen,
extaddr->smctp_haddr);
if (rc)
return rc;
} else {
rc = mctp_route_lookup(sock_net(sk), addr->smctp_network,
addr->smctp_addr.s_addr, &dst);
if (rc)
return rc;
}
hlen = LL_RESERVED_SPACE(dst.dev->dev) + sizeof(struct mctp_hdr);
skb = sock_alloc_send_skb(sk, hlen + 1 + len,
msg->msg_flags & MSG_DONTWAIT, &rc);
if (!skb)
goto err_release_dst;
skb_reserve(skb, hlen);
/* set type as first byte in payload */
*(u8 *)skb_put(skb, 1) = addr->smctp_type;
rc = memcpy_from_msg((void *)skb_put(skb, len), msg, len);
if (rc < 0)
goto err_free;
/* set up cb */
cb = __mctp_cb(skb);
cb->net = addr->smctp_network;
rc = mctp_local_output(sk, &dst, skb, addr->smctp_addr.s_addr,
addr->smctp_tag);
mctp_dst_release(&dst);
return rc ? : len;
err_free:
kfree_skb(skb);
err_release_dst:
mctp_dst_release(&dst);
return rc;
}
static int mctp_recvmsg(struct socket *sock, struct msghdr *msg, size_t len,
int flags)
{
DECLARE_SOCKADDR(struct sockaddr_mctp *, addr, msg->msg_name);
struct sock *sk = sock->sk;
struct mctp_sock *msk = container_of(sk, struct mctp_sock, sk);
struct sk_buff *skb;
size_t msglen;
u8 type;
int rc;
if (flags & ~(MSG_DONTWAIT | MSG_TRUNC | MSG_PEEK))
return -EOPNOTSUPP;
skb = skb_recv_datagram(sk, flags, &rc);
if (!skb)
return rc;
if (!skb->len) {
rc = 0;
goto out_free;
}
/* extract message type, remove from data */
type = *((u8 *)skb->data);
msglen = skb->len - 1;
if (len < msglen)
msg->msg_flags |= MSG_TRUNC;
else
len = msglen;
rc = skb_copy_datagram_msg(skb, 1, msg, len);
if (rc < 0)
goto out_free;
sock_recv_cmsgs(msg, sk, skb);
if (addr) {
struct mctp_skb_cb *cb = mctp_cb(skb);
/* TODO: expand mctp_skb_cb for header fields? */
struct mctp_hdr *hdr = mctp_hdr(skb);
addr = msg->msg_name;
addr->smctp_family = AF_MCTP;
addr->__smctp_pad0 = 0;
addr->smctp_network = cb->net;
addr->smctp_addr.s_addr = hdr->src;
addr->smctp_type = type;
addr->smctp_tag = hdr->flags_seq_tag &
(MCTP_HDR_TAG_MASK | MCTP_HDR_FLAG_TO);
addr->__smctp_pad1 = 0;
msg->msg_namelen = sizeof(*addr);
if (msk->addr_ext) {
DECLARE_SOCKADDR(struct sockaddr_mctp_ext *, ae,
msg->msg_name);
msg->msg_namelen = sizeof(*ae);
ae->smctp_ifindex = cb->ifindex;
ae->smctp_halen = cb->halen;
memset(ae->__smctp_pad0, 0x0, sizeof(ae->__smctp_pad0));
memset(ae->smctp_haddr, 0x0, sizeof(ae->smctp_haddr));
memcpy(ae->smctp_haddr, cb->haddr, cb->halen);
}
}
rc = len;
if (flags & MSG_TRUNC)
rc = msglen;
out_free:
skb_free_datagram(sk, skb);
return rc;
}
/* We're done with the key; invalidate, stop reassembly, and remove from lists.
*/
static void __mctp_key_remove(struct mctp_sk_key *key, struct net *net,
unsigned long flags, unsigned long reason)
__releases(&key->lock)
__must_hold(&net->mctp.keys_lock)
{
struct sk_buff *skb;
trace_mctp_key_release(key, reason);
skb = key->reasm_head;
key->reasm_head = NULL;
key->reasm_dead = true;
key->valid = false;
mctp_dev_release_key(key->dev, key);
spin_unlock_irqrestore(&key->lock, flags);
if (!hlist_unhashed(&key->hlist)) {
hlist_del_init(&key->hlist);
hlist_del_init(&key->sklist);
/* unref for the lists */
mctp_key_unref(key);
}
kfree_skb(skb);
}
static int mctp_setsockopt(struct socket *sock, int level, int optname,
sockptr_t optval, unsigned int optlen)
{
struct mctp_sock *msk = container_of(sock->sk, struct mctp_sock, sk);
int val;
if (level != SOL_MCTP)
return -EINVAL;
if (optname == MCTP_OPT_ADDR_EXT) {
if (optlen != sizeof(int))
return -EINVAL;
if (copy_from_sockptr(&val, optval, sizeof(int)))
return -EFAULT;
msk->addr_ext = val;
return 0;
}
return -ENOPROTOOPT;
}
static int mctp_getsockopt(struct socket *sock, int level, int optname,
sockopt_t *opt)
{
struct mctp_sock *msk = container_of(sock->sk, struct mctp_sock, sk);
int len, val;
if (level != SOL_MCTP)
return -EINVAL;
len = opt->optlen;
if (optname == MCTP_OPT_ADDR_EXT) {
if (len != sizeof(int))
return -EINVAL;
val = !!msk->addr_ext;
if (copy_to_iter(&val, len, &opt->iter_out) != len)
return -EFAULT;
return 0;
}
return -ENOPROTOOPT;
}
/* helpers for reading/writing the tag ioc, handling compatibility across the
* two versions, and some basic API error checking
*/
static int mctp_ioctl_tag_copy_from_user(unsigned long arg,
struct mctp_ioc_tag_ctl2 *ctl,
bool tagv2)
{
struct mctp_ioc_tag_ctl ctl_compat;
unsigned long size;
void *ptr;
int rc;
if (tagv2) {
size = sizeof(*ctl);
ptr = ctl;
} else {
size = sizeof(ctl_compat);
ptr = &ctl_compat;
}
rc = copy_from_user(ptr, (void __user *)arg, size);
if (rc)
return -EFAULT;
if (!tagv2) {
/* compat, using defaults for new fields */
ctl->net = MCTP_INITIAL_DEFAULT_NET;
ctl->peer_addr = ctl_compat.peer_addr;
ctl->local_addr = MCTP_ADDR_ANY;
ctl->flags = ctl_compat.flags;
ctl->tag = ctl_compat.tag;
}
if (ctl->flags)
return -EINVAL;
if (ctl->local_addr != MCTP_ADDR_ANY &&
ctl->local_addr != MCTP_ADDR_NULL)
return -EINVAL;
return 0;
}
static int mctp_ioctl_tag_copy_to_user(unsigned long arg,
struct mctp_ioc_tag_ctl2 *ctl,
bool tagv2)
{
struct mctp_ioc_tag_ctl ctl_compat;
unsigned long size;
void *ptr;
int rc;
if (tagv2) {
ptr = ctl;
size = sizeof(*ctl);
} else {
ctl_compat.peer_addr = ctl->peer_addr;
ctl_compat.tag = ctl->tag;
ctl_compat.flags = ctl->flags;
ptr = &ctl_compat;
size = sizeof(ctl_compat);
}
rc = copy_to_user((void __user *)arg, ptr, size);
if (rc)
return -EFAULT;
return 0;
}
static int mctp_ioctl_alloctag(struct mctp_sock *msk, bool tagv2,
unsigned long arg)
{
struct net *net = sock_net(&msk->sk);
struct mctp_sk_key *key = NULL;
struct mctp_ioc_tag_ctl2 ctl;
unsigned long flags;
u8 tag;
int rc;
rc = mctp_ioctl_tag_copy_from_user(arg, &ctl, tagv2);
if (rc)
return rc;
if (ctl.tag)
return -EINVAL;
key = mctp_alloc_local_tag(msk, ctl.net, MCTP_ADDR_ANY,
ctl.peer_addr, true, &tag);
if (IS_ERR(key))
return PTR_ERR(key);
ctl.tag = tag | MCTP_TAG_OWNER | MCTP_TAG_PREALLOC;
rc = mctp_ioctl_tag_copy_to_user(arg, &ctl, tagv2);
if (rc) {
unsigned long fl2;
/* Unwind our key allocation: the keys list lock needs to be
* taken before the individual key locks, and we need a valid
* flags value (fl2) to pass to __mctp_key_remove, hence the
* second spin_lock_irqsave() rather than a plain spin_lock().
*/
spin_lock_irqsave(&net->mctp.keys_lock, flags);
spin_lock_irqsave(&key->lock, fl2);
__mctp_key_remove(key, net, fl2, MCTP_TRACE_KEY_DROPPED);
mctp_key_unref(key);
spin_unlock_irqrestore(&net->mctp.keys_lock, flags);
return rc;
}
mctp_key_unref(key);
return 0;
}
static int mctp_ioctl_droptag(struct mctp_sock *msk, bool tagv2,
unsigned long arg)
{
struct net *net = sock_net(&msk->sk);
struct mctp_ioc_tag_ctl2 ctl;
unsigned long flags, fl2;
struct mctp_sk_key *key;
struct hlist_node *tmp;
int rc;
u8 tag;
rc = mctp_ioctl_tag_copy_from_user(arg, &ctl, tagv2);
if (rc)
return rc;
/* Must be a local tag, TO set, preallocated */
if ((ctl.tag & ~MCTP_TAG_MASK) != (MCTP_TAG_OWNER | MCTP_TAG_PREALLOC))
return -EINVAL;
tag = ctl.tag & MCTP_TAG_MASK;
rc = -EINVAL;
if (ctl.peer_addr == MCTP_ADDR_NULL)
ctl.peer_addr = MCTP_ADDR_ANY;
spin_lock_irqsave(&net->mctp.keys_lock, flags);
hlist_for_each_entry_safe(key, tmp, &msk->keys, sklist) {
/* we do an irqsave here, even though we know the irq state,
* so we have the flags to pass to __mctp_key_remove
*/
spin_lock_irqsave(&key->lock, fl2);
if (key->manual_alloc &&
ctl.net == key->net &&
ctl.peer_addr == key->peer_addr &&
tag == key->tag) {
__mctp_key_remove(key, net, fl2,
MCTP_TRACE_KEY_DROPPED);
rc = 0;
} else {
spin_unlock_irqrestore(&key->lock, fl2);
}
}
spin_unlock_irqrestore(&net->mctp.keys_lock, flags);
return rc;
}
static int mctp_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
{
struct mctp_sock *msk = container_of(sock->sk, struct mctp_sock, sk);
bool tagv2 = false;
switch (cmd) {
case SIOCMCTPALLOCTAG2:
case SIOCMCTPALLOCTAG:
tagv2 = cmd == SIOCMCTPALLOCTAG2;
return mctp_ioctl_alloctag(msk, tagv2, arg);
case SIOCMCTPDROPTAG:
case SIOCMCTPDROPTAG2:
tagv2 = cmd == SIOCMCTPDROPTAG2;
return mctp_ioctl_droptag(msk, tagv2, arg);
}
return -EINVAL;
}
#ifdef CONFIG_COMPAT
static int mctp_compat_ioctl(struct socket *sock, unsigned int cmd,
unsigned long arg)
{
void __user *argp = compat_ptr(arg);
switch (cmd) {
/* These have compatible ptr layouts */
case SIOCMCTPALLOCTAG:
case SIOCMCTPDROPTAG:
return mctp_ioctl(sock, cmd, (unsigned long)argp);
}
return -ENOIOCTLCMD;
}
#endif
static const struct proto_ops mctp_dgram_ops = {
.family = PF_MCTP,
.release = mctp_release,
.bind = mctp_bind,
.connect = mctp_connect,
.socketpair = sock_no_socketpair,
.accept = sock_no_accept,
.getname = sock_no_getname,
.poll = datagram_poll,
.ioctl = mctp_ioctl,
.gettstamp = sock_gettstamp,
.listen = sock_no_listen,
.shutdown = sock_no_shutdown,
.setsockopt = mctp_setsockopt,
.getsockopt_iter = mctp_getsockopt,
.sendmsg = mctp_sendmsg,
.recvmsg = mctp_recvmsg,
.mmap = sock_no_mmap,
#ifdef CONFIG_COMPAT
.compat_ioctl = mctp_compat_ioctl,
#endif
};
static void mctp_sk_expire_keys(struct timer_list *timer)
{
struct mctp_sock *msk = container_of(timer, struct mctp_sock,
key_expiry);
struct net *net = sock_net(&msk->sk);
unsigned long next_expiry, flags, fl2;
struct mctp_sk_key *key;
struct hlist_node *tmp;
bool next_expiry_valid = false;
spin_lock_irqsave(&net->mctp.keys_lock, flags);
hlist_for_each_entry_safe(key, tmp, &msk->keys, sklist) {
/* don't expire. manual_alloc is immutable, no locking
* required.
*/
if (key->manual_alloc)
continue;
spin_lock_irqsave(&key->lock, fl2);
if (!time_after_eq(key->expiry, jiffies)) {
__mctp_key_remove(key, net, fl2,
MCTP_TRACE_KEY_TIMEOUT);
continue;
}
if (next_expiry_valid) {
if (time_before(key->expiry, next_expiry))
next_expiry = key->expiry;
} else {
next_expiry = key->expiry;
next_expiry_valid = true;
}
spin_unlock_irqrestore(&key->lock, fl2);
}
spin_unlock_irqrestore(&net->mctp.keys_lock, flags);
if (next_expiry_valid)
mod_timer(timer, next_expiry);
}
static int mctp_sk_init(struct sock *sk)
{
struct mctp_sock *msk = container_of(sk, struct mctp_sock, sk);
INIT_HLIST_HEAD(&msk->keys);
timer_setup(&msk->key_expiry, mctp_sk_expire_keys, 0);
msk->bind_peer_set = false;
return 0;
}
static void mctp_sk_close(struct sock *sk, long timeout)
{
sk_common_release(sk);
}
static int mctp_sk_hash(struct sock *sk)
{
struct net *net = sock_net(sk);
struct sock *existing;
struct mctp_sock *msk;
mctp_eid_t remote;
u32 hash;
int rc;
msk = container_of(sk, struct mctp_sock, sk);
if (msk->bind_peer_set)
remote = msk->bind_peer_addr;
else
remote = MCTP_ADDR_ANY;
hash = mctp_bind_hash(msk->bind_type, msk->bind_local_addr, remote);
mutex_lock(&net->mctp.bind_lock);
/* Prevent duplicate binds. */
sk_for_each(existing, &net->mctp.binds[hash]) {
struct mctp_sock *mex =
container_of(existing, struct mctp_sock, sk);
bool same_peer = (mex->bind_peer_set && msk->bind_peer_set &&
mex->bind_peer_addr == msk->bind_peer_addr) ||
(!mex->bind_peer_set && !msk->bind_peer_set);
if (mex->bind_type == msk->bind_type &&
mex->bind_local_addr == msk->bind_local_addr && same_peer &&
mex->bind_net == msk->bind_net) {
rc = -EADDRINUSE;
goto out;
}
}
/* Bind lookup runs under RCU, remain live during that. */
sock_set_flag(sk, SOCK_RCU_FREE);
sk_add_node_rcu(sk, &net->mctp.binds[hash]);
rc = 0;
out:
mutex_unlock(&net->mctp.bind_lock);
return rc;
}
static void mctp_sk_unhash(struct sock *sk)
{
struct mctp_sock *msk = container_of(sk, struct mctp_sock, sk);
struct net *net = sock_net(sk);
unsigned long flags, fl2;
struct mctp_sk_key *key;
struct hlist_node *tmp;
/* remove from any type-based binds */
mutex_lock(&net->mctp.bind_lock);
sk_del_node_init_rcu(sk);
mutex_unlock(&net->mctp.bind_lock);
/* remove tag allocations */
spin_lock_irqsave(&net->mctp.keys_lock, flags);
hlist_for_each_entry_safe(key, tmp, &msk->keys, sklist) {
spin_lock_irqsave(&key->lock, fl2);
__mctp_key_remove(key, net, fl2, MCTP_TRACE_KEY_CLOSED);
}
sock_set_flag(sk, SOCK_DEAD);
spin_unlock_irqrestore(&net->mctp.keys_lock, flags);
/* Since there are no more tag allocations (we have removed all of the
* keys), stop any pending expiry events. the timer cannot be re-queued
* as the sk is no longer observable
*/
timer_delete_sync(&msk->key_expiry);
}
static void mctp_sk_destruct(struct sock *sk)
{
skb_queue_purge(&sk->sk_receive_queue);
}
static struct proto mctp_proto = {
.name = "MCTP",
.owner = THIS_MODULE,
.obj_size = sizeof(struct mctp_sock),
.init = mctp_sk_init,
.close = mctp_sk_close,
.hash = mctp_sk_hash,
.unhash = mctp_sk_unhash,
};
static int mctp_pf_create(struct net *net, struct socket *sock,
int protocol, int kern)
{
const struct proto_ops *ops;
struct proto *proto;
struct sock *sk;
int rc;
if (protocol)
return -EPROTONOSUPPORT;
/* only datagram sockets are supported */
if (sock->type != SOCK_DGRAM)
return -ESOCKTNOSUPPORT;
proto = &mctp_proto;
ops = &mctp_dgram_ops;
sock->state = SS_UNCONNECTED;
sock->ops = ops;
sk = sk_alloc(net, PF_MCTP, GFP_KERNEL, proto, kern);
if (!sk)
return -ENOMEM;
sock_init_data(sock, sk);
sk->sk_destruct = mctp_sk_destruct;
rc = 0;
if (sk->sk_prot->init)
rc = sk->sk_prot->init(sk);
if (rc)
goto err_sk_put;
return 0;
err_sk_put:
sock_orphan(sk);
sock_put(sk);
return rc;
}
static struct net_proto_family mctp_pf = {
.family = PF_MCTP,
.create = mctp_pf_create,
.owner = THIS_MODULE,
};
static __init int mctp_init(void)
{
int rc;
/* ensure our uapi tag definitions match the header format */
BUILD_BUG_ON(MCTP_TAG_OWNER != MCTP_HDR_FLAG_TO);
BUILD_BUG_ON(MCTP_TAG_MASK != MCTP_HDR_TAG_MASK);
pr_info("mctp: management component transport protocol core\n");
rc = sock_register(&mctp_pf);
if (rc)
return rc;
rc = proto_register(&mctp_proto, 0);
if (rc)
goto err_unreg_sock;
rc = mctp_routes_init();
if (rc)
goto err_unreg_proto;
rc = mctp_neigh_init();
if (rc)
goto err_unreg_routes;
rc = mctp_device_init();
if (rc)
goto err_unreg_neigh;
return 0;
err_unreg_neigh:
mctp_neigh_exit();
err_unreg_routes:
mctp_routes_exit();
err_unreg_proto:
proto_unregister(&mctp_proto);
err_unreg_sock:
sock_unregister(PF_MCTP);
return rc;
}
static __exit void mctp_exit(void)
{
mctp_device_exit();
mctp_neigh_exit();
mctp_routes_exit();
proto_unregister(&mctp_proto);
sock_unregister(PF_MCTP);
}
subsys_initcall(mctp_init);
module_exit(mctp_exit);
MODULE_DESCRIPTION("MCTP core");
MODULE_AUTHOR("Jeremy Kerr <jk@codeconstruct.com.au>");
MODULE_ALIAS_NETPROTO(PF_MCTP);
#if IS_ENABLED(CONFIG_MCTP_TEST)
#include "test/sock-test.c"
#endif
+564
View File
@@ -0,0 +1,564 @@
// 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();
rc = mctp_register_netdevice(dev, ops, binding);
rtnl_unlock();
return rc;
}
EXPORT_SYMBOL_GPL(mctp_register_netdev);
void mctp_unregister_netdev(struct net_device *dev)
{
unregister_netdev(dev);
}
EXPORT_SYMBOL_GPL(mctp_unregister_netdev);
static struct rtnl_af_ops mctp_af_ops = {
.family = AF_MCTP,
.fill_link_af = mctp_fill_link_af,
.get_link_af_size = mctp_get_link_af_size,
.set_link_af = mctp_set_link_af,
};
static struct notifier_block mctp_dev_nb = {
.notifier_call = mctp_dev_notify,
.priority = ADDRCONF_NOTIFY_PRIORITY,
};
static const struct rtnl_msg_handler mctp_device_rtnl_msg_handlers[] = {
{.owner = THIS_MODULE, .protocol = PF_MCTP, .msgtype = RTM_NEWADDR,
.doit = mctp_rtm_newaddr},
{.owner = THIS_MODULE, .protocol = PF_MCTP, .msgtype = RTM_DELADDR,
.doit = mctp_rtm_deladdr},
{.owner = THIS_MODULE, .protocol = PF_MCTP, .msgtype = RTM_GETADDR,
.dumpit = mctp_dump_addrinfo},
};
int __init mctp_device_init(void)
{
int err;
err = register_netdevice_notifier(&mctp_dev_nb);
if (err)
return err;
err = rtnl_af_register(&mctp_af_ops);
if (err)
goto err_notifier;
err = rtnl_register_many(mctp_device_rtnl_msg_handlers);
if (err)
goto err_af;
return 0;
err_af:
rtnl_af_unregister(&mctp_af_ops);
err_notifier:
unregister_netdevice_notifier(&mctp_dev_nb);
return err;
}
void __exit mctp_device_exit(void)
{
rtnl_unregister_many(mctp_device_rtnl_msg_handlers);
rtnl_af_unregister(&mctp_af_ops);
unregister_netdevice_notifier(&mctp_dev_nb);
}
+354
View File
@@ -0,0 +1,354 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Management Component Transport Protocol (MCTP) - routing
* implementation.
*
* This is currently based on a simple routing table, with no dst cache. The
* number of routes should stay fairly small, so the lookup cost is small.
*
* Copyright (c) 2021 Code Construct
* Copyright (c) 2021 Google
*/
#include <linux/idr.h>
#include <linux/mctp.h>
#include <linux/netdevice.h>
#include <linux/rtnetlink.h>
#include <linux/skbuff.h>
#include <net/mctp.h>
#include <net/mctpdevice.h>
#include <net/netlink.h>
#include <net/sock.h>
static int mctp_neigh_add(struct mctp_dev *mdev, mctp_eid_t eid,
enum mctp_neigh_source source,
size_t lladdr_len, const void *lladdr)
{
struct net *net = dev_net(mdev->dev);
struct mctp_neigh *neigh;
int rc;
mutex_lock(&net->mctp.neigh_lock);
if (mctp_neigh_lookup(mdev, eid, NULL) == 0) {
rc = -EEXIST;
goto out;
}
if (lladdr_len > sizeof(neigh->ha)) {
rc = -EINVAL;
goto out;
}
neigh = kzalloc_obj(*neigh);
if (!neigh) {
rc = -ENOMEM;
goto out;
}
INIT_LIST_HEAD(&neigh->list);
neigh->dev = mdev;
mctp_dev_hold(neigh->dev);
neigh->eid = eid;
neigh->source = source;
memcpy(neigh->ha, lladdr, lladdr_len);
list_add_rcu(&neigh->list, &net->mctp.neighbours);
rc = 0;
out:
mutex_unlock(&net->mctp.neigh_lock);
return rc;
}
static void __mctp_neigh_free(struct rcu_head *rcu)
{
struct mctp_neigh *neigh = container_of(rcu, struct mctp_neigh, rcu);
mctp_dev_put(neigh->dev);
kfree(neigh);
}
/* Removes all neighbour entries referring to a device */
void mctp_neigh_remove_dev(struct mctp_dev *mdev)
{
struct net *net = dev_net(mdev->dev);
struct mctp_neigh *neigh, *tmp;
mutex_lock(&net->mctp.neigh_lock);
list_for_each_entry_safe(neigh, tmp, &net->mctp.neighbours, list) {
if (neigh->dev == mdev) {
list_del_rcu(&neigh->list);
/* TODO: immediate RTM_DELNEIGH */
call_rcu(&neigh->rcu, __mctp_neigh_free);
}
}
mutex_unlock(&net->mctp.neigh_lock);
}
static int mctp_neigh_remove(struct mctp_dev *mdev, mctp_eid_t eid,
enum mctp_neigh_source source)
{
struct net *net = dev_net(mdev->dev);
struct mctp_neigh *neigh, *tmp;
bool dropped = false;
mutex_lock(&net->mctp.neigh_lock);
list_for_each_entry_safe(neigh, tmp, &net->mctp.neighbours, list) {
if (neigh->dev == mdev && neigh->eid == eid &&
neigh->source == source) {
list_del_rcu(&neigh->list);
/* TODO: immediate RTM_DELNEIGH */
call_rcu(&neigh->rcu, __mctp_neigh_free);
dropped = true;
}
}
mutex_unlock(&net->mctp.neigh_lock);
return dropped ? 0 : -ENOENT;
}
static const struct nla_policy nd_mctp_policy[NDA_MAX + 1] = {
[NDA_DST] = { .type = NLA_U8 },
[NDA_LLADDR] = { .type = NLA_BINARY, .len = MAX_ADDR_LEN },
};
static int mctp_rtm_newneigh(struct sk_buff *skb, struct nlmsghdr *nlh,
struct netlink_ext_ack *extack)
{
struct net *net = sock_net(skb->sk);
struct net_device *dev;
struct mctp_dev *mdev;
struct ndmsg *ndm;
struct nlattr *tb[NDA_MAX + 1];
int rc;
mctp_eid_t eid;
void *lladdr;
int lladdr_len;
rc = nlmsg_parse(nlh, sizeof(*ndm), tb, NDA_MAX, nd_mctp_policy,
extack);
if (rc < 0) {
NL_SET_ERR_MSG(extack, "lladdr too large?");
return rc;
}
if (!tb[NDA_DST]) {
NL_SET_ERR_MSG(extack, "Neighbour EID must be specified");
return -EINVAL;
}
if (!tb[NDA_LLADDR]) {
NL_SET_ERR_MSG(extack, "Neighbour lladdr must be specified");
return -EINVAL;
}
eid = nla_get_u8(tb[NDA_DST]);
if (!mctp_address_unicast(eid)) {
NL_SET_ERR_MSG(extack, "Invalid neighbour EID");
return -EINVAL;
}
lladdr = nla_data(tb[NDA_LLADDR]);
lladdr_len = nla_len(tb[NDA_LLADDR]);
ndm = nlmsg_data(nlh);
dev = __dev_get_by_index(net, ndm->ndm_ifindex);
if (!dev)
return -ENODEV;
mdev = mctp_dev_get_rtnl(dev);
if (!mdev)
return -ENODEV;
if (lladdr_len != dev->addr_len) {
NL_SET_ERR_MSG(extack, "Wrong lladdr length");
return -EINVAL;
}
return mctp_neigh_add(mdev, eid, MCTP_NEIGH_STATIC,
lladdr_len, lladdr);
}
static int mctp_rtm_delneigh(struct sk_buff *skb, struct nlmsghdr *nlh,
struct netlink_ext_ack *extack)
{
struct net *net = sock_net(skb->sk);
struct nlattr *tb[NDA_MAX + 1];
struct net_device *dev;
struct mctp_dev *mdev;
struct ndmsg *ndm;
int rc;
mctp_eid_t eid;
rc = nlmsg_parse(nlh, sizeof(*ndm), tb, NDA_MAX, nd_mctp_policy,
extack);
if (rc < 0) {
NL_SET_ERR_MSG(extack, "incorrect format");
return rc;
}
if (!tb[NDA_DST]) {
NL_SET_ERR_MSG(extack, "Neighbour EID must be specified");
return -EINVAL;
}
eid = nla_get_u8(tb[NDA_DST]);
ndm = nlmsg_data(nlh);
dev = __dev_get_by_index(net, ndm->ndm_ifindex);
if (!dev)
return -ENODEV;
mdev = mctp_dev_get_rtnl(dev);
if (!mdev)
return -ENODEV;
return mctp_neigh_remove(mdev, eid, MCTP_NEIGH_STATIC);
}
static int mctp_fill_neigh(struct sk_buff *skb, u32 portid, u32 seq, int event,
unsigned int flags, struct mctp_neigh *neigh)
{
struct net_device *dev = neigh->dev->dev;
struct nlmsghdr *nlh;
struct ndmsg *hdr;
nlh = nlmsg_put(skb, portid, seq, event, sizeof(*hdr), flags);
if (!nlh)
return -EMSGSIZE;
hdr = nlmsg_data(nlh);
memset(hdr, 0, sizeof(*hdr));
hdr->ndm_family = AF_MCTP;
hdr->ndm_ifindex = dev->ifindex;
hdr->ndm_state = 0; // TODO other state bits?
if (neigh->source == MCTP_NEIGH_STATIC)
hdr->ndm_state |= NUD_PERMANENT;
hdr->ndm_flags = 0;
hdr->ndm_type = RTN_UNICAST; // TODO: is loopback RTN_LOCAL?
if (nla_put_u8(skb, NDA_DST, neigh->eid))
goto cancel;
if (nla_put(skb, NDA_LLADDR, dev->addr_len, neigh->ha))
goto cancel;
nlmsg_end(skb, nlh);
return 0;
cancel:
nlmsg_cancel(skb, nlh);
return -EMSGSIZE;
}
static int mctp_rtm_getneigh(struct sk_buff *skb, struct netlink_callback *cb)
{
struct net *net = sock_net(skb->sk);
int rc, idx, req_ifindex;
struct mctp_neigh *neigh;
struct ndmsg *ndmsg;
struct {
int idx;
} *cbctx = (void *)cb->ctx;
ndmsg = nlmsg_payload(cb->nlh, sizeof(*ndmsg));
if (!ndmsg)
return -EINVAL;
req_ifindex = ndmsg->ndm_ifindex;
idx = 0;
rcu_read_lock();
list_for_each_entry_rcu(neigh, &net->mctp.neighbours, list) {
if (idx < cbctx->idx)
goto cont;
rc = 0;
if (req_ifindex == 0 || req_ifindex == neigh->dev->dev->ifindex)
rc = mctp_fill_neigh(skb, NETLINK_CB(cb->skb).portid,
cb->nlh->nlmsg_seq,
RTM_NEWNEIGH, NLM_F_MULTI, neigh);
if (rc)
break;
cont:
idx++;
}
rcu_read_unlock();
cbctx->idx = idx;
return skb->len;
}
int mctp_neigh_lookup(struct mctp_dev *mdev, mctp_eid_t eid, void *ret_hwaddr)
{
struct net *net = dev_net(mdev->dev);
struct mctp_neigh *neigh;
int rc = -EHOSTUNREACH; // TODO: or ENOENT?
rcu_read_lock();
list_for_each_entry_rcu(neigh, &net->mctp.neighbours, list) {
if (mdev == neigh->dev && eid == neigh->eid) {
if (ret_hwaddr)
memcpy(ret_hwaddr, neigh->ha,
sizeof(neigh->ha));
rc = 0;
break;
}
}
rcu_read_unlock();
return rc;
}
/* namespace registration */
static int __net_init mctp_neigh_net_init(struct net *net)
{
struct netns_mctp *ns = &net->mctp;
INIT_LIST_HEAD(&ns->neighbours);
mutex_init(&ns->neigh_lock);
return 0;
}
static void __net_exit mctp_neigh_net_exit(struct net *net)
{
struct netns_mctp *ns = &net->mctp;
struct mctp_neigh *neigh;
list_for_each_entry(neigh, &ns->neighbours, list)
call_rcu(&neigh->rcu, __mctp_neigh_free);
}
/* net namespace implementation */
static struct pernet_operations mctp_net_ops = {
.init = mctp_neigh_net_init,
.exit = mctp_neigh_net_exit,
};
static const struct rtnl_msg_handler mctp_neigh_rtnl_msg_handlers[] = {
{THIS_MODULE, PF_MCTP, RTM_NEWNEIGH, mctp_rtm_newneigh, NULL, 0},
{THIS_MODULE, PF_MCTP, RTM_DELNEIGH, mctp_rtm_delneigh, NULL, 0},
{THIS_MODULE, PF_MCTP, RTM_GETNEIGH, NULL, mctp_rtm_getneigh, 0},
};
int __init mctp_neigh_init(void)
{
int err;
err = register_pernet_subsys(&mctp_net_ops);
if (err)
return err;
err = rtnl_register_many(mctp_neigh_rtnl_msg_handlers);
if (err)
unregister_pernet_subsys(&mctp_net_ops);
return err;
}
void mctp_neigh_exit(void)
{
rtnl_unregister_many(mctp_neigh_rtnl_msg_handlers);
unregister_pernet_subsys(&mctp_net_ops);
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+396
View File
@@ -0,0 +1,396 @@
// SPDX-License-Identifier: GPL-2.0
#include <kunit/static_stub.h>
#include <kunit/test.h>
#include <linux/socket.h>
#include <linux/spinlock.h>
#include "utils.h"
static const u8 dev_default_lladdr[] = { 0x01, 0x02 };
/* helper for simple sock setup: single device, with dev_default_lladdr as its
* hardware address, assigned with a local EID 8, and a route to EID 9
*/
static void __mctp_sock_test_init(struct kunit *test,
struct mctp_test_dev **devp,
struct mctp_test_route **rtp,
struct socket **sockp)
{
struct mctp_test_route *rt;
struct mctp_test_dev *dev;
struct socket *sock;
unsigned long flags;
u8 *addrs;
int rc;
dev = mctp_test_create_dev_lladdr(sizeof(dev_default_lladdr),
dev_default_lladdr);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
addrs = kmalloc(1, GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, addrs);
addrs[0] = 8;
spin_lock_irqsave(&dev->mdev->addrs_lock, flags);
dev->mdev->num_addrs = 1;
swap(addrs, dev->mdev->addrs);
spin_unlock_irqrestore(&dev->mdev->addrs_lock, flags);
kfree(addrs);
rt = mctp_test_create_route_direct(dev_net(dev->ndev), dev->mdev, 9, 0);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt);
rc = sock_create_kern(&init_net, AF_MCTP, SOCK_DGRAM, 0, &sock);
KUNIT_ASSERT_EQ(test, rc, 0);
*devp = dev;
*rtp = rt;
*sockp = sock;
}
static void __mctp_sock_test_fini(struct kunit *test,
struct mctp_test_dev *dev,
struct mctp_test_route *rt,
struct socket *sock)
{
sock_release(sock);
mctp_test_route_destroy(test, rt);
mctp_test_destroy_dev(dev);
}
struct mctp_test_sock_local_output_config {
struct mctp_test_dev *dev;
size_t halen;
u8 haddr[MAX_ADDR_LEN];
bool invoked;
int rc;
};
static int mctp_test_sock_local_output(struct sock *sk,
struct mctp_dst *dst,
struct sk_buff *skb,
mctp_eid_t daddr, u8 req_tag)
{
struct kunit *test = kunit_get_current_test();
struct mctp_test_sock_local_output_config *cfg = test->priv;
KUNIT_EXPECT_PTR_EQ(test, dst->dev, cfg->dev->mdev);
KUNIT_EXPECT_EQ(test, dst->halen, cfg->halen);
KUNIT_EXPECT_MEMEQ(test, dst->haddr, cfg->haddr, dst->halen);
cfg->invoked = true;
kfree_skb(skb);
return cfg->rc;
}
static void mctp_test_sock_sendmsg_extaddr(struct kunit *test)
{
struct sockaddr_mctp_ext addr = {
.smctp_base = {
.smctp_family = AF_MCTP,
.smctp_tag = MCTP_TAG_OWNER,
.smctp_network = MCTP_NET_ANY,
},
};
struct mctp_test_sock_local_output_config cfg = { 0 };
u8 haddr[] = { 0xaa, 0x01 };
u8 buf[4] = { 0, 1, 2, 3 };
struct mctp_test_route *rt;
struct msghdr msg = { 0 };
struct mctp_test_dev *dev;
struct mctp_sock *msk;
struct socket *sock;
ssize_t send_len;
struct kvec vec = {
.iov_base = buf,
.iov_len = sizeof(buf),
};
__mctp_sock_test_init(test, &dev, &rt, &sock);
/* Expect to see the dst configured up with the addressing data we
* provide in the struct sockaddr_mctp_ext
*/
cfg.dev = dev;
cfg.halen = sizeof(haddr);
memcpy(cfg.haddr, haddr, sizeof(haddr));
test->priv = &cfg;
kunit_activate_static_stub(test, mctp_local_output,
mctp_test_sock_local_output);
/* enable and configure direct addressing */
msk = container_of(sock->sk, struct mctp_sock, sk);
msk->addr_ext = true;
addr.smctp_ifindex = dev->ndev->ifindex;
addr.smctp_halen = sizeof(haddr);
memcpy(addr.smctp_haddr, haddr, sizeof(haddr));
msg.msg_name = &addr;
msg.msg_namelen = sizeof(addr);
iov_iter_kvec(&msg.msg_iter, ITER_SOURCE, &vec, 1, sizeof(buf));
send_len = mctp_sendmsg(sock, &msg, sizeof(buf));
KUNIT_EXPECT_EQ(test, send_len, sizeof(buf));
KUNIT_EXPECT_TRUE(test, cfg.invoked);
__mctp_sock_test_fini(test, dev, rt, sock);
}
static void mctp_test_sock_recvmsg_extaddr(struct kunit *test)
{
struct sockaddr_mctp_ext recv_addr = { 0 };
u8 rcv_buf[1], rcv_data[] = { 0, 1 };
u8 haddr[] = { 0xaa, 0x02 };
struct mctp_test_route *rt;
struct mctp_test_dev *dev;
struct mctp_skb_cb *cb;
struct mctp_sock *msk;
struct sk_buff *skb;
struct mctp_hdr hdr;
struct socket *sock;
struct msghdr msg;
ssize_t recv_len;
int rc;
struct kvec vec = {
.iov_base = rcv_buf,
.iov_len = sizeof(rcv_buf),
};
__mctp_sock_test_init(test, &dev, &rt, &sock);
/* enable extended addressing on recv */
msk = container_of(sock->sk, struct mctp_sock, sk);
msk->addr_ext = true;
/* base incoming header, using a nul-EID dest */
hdr.ver = 1;
hdr.dest = 0;
hdr.src = 9;
hdr.flags_seq_tag = MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM |
MCTP_HDR_FLAG_TO;
skb = mctp_test_create_skb_data(&hdr, &rcv_data);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, skb);
mctp_test_skb_set_dev(skb, dev);
/* set incoming extended address data */
cb = mctp_cb(skb);
cb->halen = sizeof(haddr);
cb->ifindex = dev->ndev->ifindex;
memcpy(cb->haddr, haddr, sizeof(haddr));
/* Deliver to socket. The route input path pulls the network header,
* leaving skb data at type byte onwards. recvmsg will consume the
* type for addr.smctp_type
*/
skb_pull(skb, sizeof(hdr));
rc = sock_queue_rcv_skb(sock->sk, skb);
KUNIT_ASSERT_EQ(test, rc, 0);
msg.msg_name = &recv_addr;
msg.msg_namelen = sizeof(recv_addr);
iov_iter_kvec(&msg.msg_iter, ITER_DEST, &vec, 1, sizeof(rcv_buf));
recv_len = mctp_recvmsg(sock, &msg, sizeof(rcv_buf),
MSG_DONTWAIT | MSG_TRUNC);
KUNIT_EXPECT_EQ(test, recv_len, sizeof(rcv_buf));
/* expect our extended address to be populated from hdr and cb */
KUNIT_EXPECT_EQ(test, msg.msg_namelen, sizeof(recv_addr));
KUNIT_EXPECT_EQ(test, recv_addr.smctp_base.smctp_family, AF_MCTP);
KUNIT_EXPECT_EQ(test, recv_addr.smctp_ifindex, dev->ndev->ifindex);
KUNIT_EXPECT_EQ(test, recv_addr.smctp_halen, sizeof(haddr));
KUNIT_EXPECT_MEMEQ(test, recv_addr.smctp_haddr, haddr, sizeof(haddr));
__mctp_sock_test_fini(test, dev, rt, sock);
}
static const struct mctp_test_bind_setup bind_addrany_netdefault_type1 = {
.bind_addr = MCTP_ADDR_ANY, .bind_net = MCTP_NET_ANY, .bind_type = 1,
};
static const struct mctp_test_bind_setup bind_addrany_net2_type1 = {
.bind_addr = MCTP_ADDR_ANY, .bind_net = 2, .bind_type = 1,
};
/* 1 is default net */
static const struct mctp_test_bind_setup bind_addr8_net1_type1 = {
.bind_addr = 8, .bind_net = 1, .bind_type = 1,
};
static const struct mctp_test_bind_setup bind_addrany_net1_type1 = {
.bind_addr = MCTP_ADDR_ANY, .bind_net = 1, .bind_type = 1,
};
/* 2 is an arbitrary net */
static const struct mctp_test_bind_setup bind_addr8_net2_type1 = {
.bind_addr = 8, .bind_net = 2, .bind_type = 1,
};
static const struct mctp_test_bind_setup bind_addr8_netdefault_type1 = {
.bind_addr = 8, .bind_net = MCTP_NET_ANY, .bind_type = 1,
};
static const struct mctp_test_bind_setup bind_addrany_net2_type2 = {
.bind_addr = MCTP_ADDR_ANY, .bind_net = 2, .bind_type = 2,
};
static const struct mctp_test_bind_setup bind_addrany_net2_type1_peer9 = {
.bind_addr = MCTP_ADDR_ANY, .bind_net = 2, .bind_type = 1,
.have_peer = true, .peer_addr = 9, .peer_net = 2,
};
struct mctp_bind_pair_test {
const struct mctp_test_bind_setup *bind1;
const struct mctp_test_bind_setup *bind2;
int error;
};
/* Pairs of binds and whether they will conflict */
static const struct mctp_bind_pair_test mctp_bind_pair_tests[] = {
/* Both ADDR_ANY, conflict */
{ &bind_addrany_netdefault_type1, &bind_addrany_netdefault_type1,
EADDRINUSE },
/* Same specific EID, conflict */
{ &bind_addr8_netdefault_type1, &bind_addr8_netdefault_type1,
EADDRINUSE },
/* ADDR_ANY vs specific EID, OK */
{ &bind_addrany_netdefault_type1, &bind_addr8_netdefault_type1, 0 },
/* ADDR_ANY different types, OK */
{ &bind_addrany_net2_type2, &bind_addrany_net2_type1, 0 },
/* ADDR_ANY different nets, OK */
{ &bind_addrany_net2_type1, &bind_addrany_netdefault_type1, 0 },
/* specific EID, NET_ANY (resolves to default)
* vs specific EID, explicit default net 1, conflict
*/
{ &bind_addr8_netdefault_type1, &bind_addr8_net1_type1, EADDRINUSE },
/* specific EID, net 1 vs specific EID, net 2, ok */
{ &bind_addr8_net1_type1, &bind_addr8_net2_type1, 0 },
/* ANY_ADDR, NET_ANY (doesn't resolve to default)
* vs ADDR_ANY, explicit default net 1, OK
*/
{ &bind_addrany_netdefault_type1, &bind_addrany_net1_type1, 0 },
/* specific remote peer doesn't conflict with any-peer bind */
{ &bind_addrany_net2_type1_peer9, &bind_addrany_net2_type1, 0 },
/* bind() NET_ANY is allowed with a connect() net */
{ &bind_addrany_net2_type1_peer9, &bind_addrany_netdefault_type1, 0 },
};
static void mctp_bind_pair_desc(const struct mctp_bind_pair_test *t, char *desc)
{
char peer1[25] = {0}, peer2[25] = {0};
if (t->bind1->have_peer)
snprintf(peer1, sizeof(peer1), ", peer %d net %d",
t->bind1->peer_addr, t->bind1->peer_net);
if (t->bind2->have_peer)
snprintf(peer2, sizeof(peer2), ", peer %d net %d",
t->bind2->peer_addr, t->bind2->peer_net);
snprintf(desc, KUNIT_PARAM_DESC_SIZE,
"{bind(addr %d, type %d, net %d%s)} {bind(addr %d, type %d, net %d%s)} -> error %d",
t->bind1->bind_addr, t->bind1->bind_type,
t->bind1->bind_net, peer1,
t->bind2->bind_addr, t->bind2->bind_type,
t->bind2->bind_net, peer2, t->error);
}
KUNIT_ARRAY_PARAM(mctp_bind_pair, mctp_bind_pair_tests, mctp_bind_pair_desc);
static void mctp_test_bind_invalid(struct kunit *test)
{
struct socket *sock;
int rc;
/* bind() fails if the bind() vs connect() networks mismatch. */
const struct mctp_test_bind_setup bind_connect_net_mismatch = {
.bind_addr = MCTP_ADDR_ANY, .bind_net = 1, .bind_type = 1,
.have_peer = true, .peer_addr = 9, .peer_net = 2,
};
mctp_test_bind_run(test, &bind_connect_net_mismatch, &rc, &sock);
KUNIT_EXPECT_EQ(test, -rc, EINVAL);
sock_release(sock);
}
static int
mctp_test_bind_conflicts_inner(struct kunit *test,
const struct mctp_test_bind_setup *bind1,
const struct mctp_test_bind_setup *bind2)
{
struct socket *sock1 = NULL, *sock2 = NULL, *sock3 = NULL;
int bind_errno;
/* Bind to first address, always succeeds */
mctp_test_bind_run(test, bind1, &bind_errno, &sock1);
KUNIT_EXPECT_EQ(test, bind_errno, 0);
/* A second identical bind always fails */
mctp_test_bind_run(test, bind1, &bind_errno, &sock2);
KUNIT_EXPECT_EQ(test, -bind_errno, EADDRINUSE);
/* A different bind, result is returned */
mctp_test_bind_run(test, bind2, &bind_errno, &sock3);
if (sock1)
sock_release(sock1);
if (sock2)
sock_release(sock2);
if (sock3)
sock_release(sock3);
return bind_errno;
}
static void mctp_test_bind_conflicts(struct kunit *test)
{
const struct mctp_bind_pair_test *pair;
int bind_errno;
pair = test->param_value;
bind_errno =
mctp_test_bind_conflicts_inner(test, pair->bind1, pair->bind2);
KUNIT_EXPECT_EQ(test, -bind_errno, pair->error);
/* swapping the calls, the second bind should still fail */
bind_errno =
mctp_test_bind_conflicts_inner(test, pair->bind2, pair->bind1);
KUNIT_EXPECT_EQ(test, -bind_errno, pair->error);
}
static void mctp_test_assumptions(struct kunit *test)
{
/* check assumption of default net from bind_addr8_net1_type1 */
KUNIT_ASSERT_EQ(test, mctp_default_net(&init_net), 1);
}
static struct kunit_case mctp_test_cases[] = {
KUNIT_CASE(mctp_test_assumptions),
KUNIT_CASE(mctp_test_sock_sendmsg_extaddr),
KUNIT_CASE(mctp_test_sock_recvmsg_extaddr),
KUNIT_CASE_PARAM(mctp_test_bind_conflicts, mctp_bind_pair_gen_params),
KUNIT_CASE(mctp_test_bind_invalid),
{}
};
static struct kunit_suite mctp_test_suite = {
.name = "mctp-sock",
.test_cases = mctp_test_cases,
};
kunit_test_suite(mctp_test_suite);
+311
View File
@@ -0,0 +1,311 @@
// 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, GFP_KERNEL);
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));
}
+75
View File
@@ -0,0 +1,75 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __NET_MCTP_TEST_UTILS_H
#define __NET_MCTP_TEST_UTILS_H
#include <uapi/linux/netdevice.h>
#include <net/mctp.h>
#include <net/mctpdevice.h>
#include <kunit/test.h>
#define MCTP_DEV_TEST_MTU 68
struct mctp_test_dev {
struct net_device *ndev;
struct mctp_dev *mdev;
unsigned short lladdr_len;
unsigned char lladdr[MAX_ADDR_LEN];
struct sk_buff_head pkts;
};
struct mctp_test_dev;
struct mctp_test_route {
struct mctp_route rt;
};
struct mctp_test_bind_setup {
mctp_eid_t bind_addr;
int bind_net;
u8 bind_type;
bool have_peer;
mctp_eid_t peer_addr;
int peer_net;
/* optional name. Used for comparison in "lookup" tests */
const char *name;
};
struct mctp_test_dev *mctp_test_create_dev(void);
struct mctp_test_dev *mctp_test_create_dev_with_addr(mctp_eid_t eid);
struct mctp_test_dev *mctp_test_create_dev_lladdr(unsigned short lladdr_len,
const unsigned char *lladdr);
void mctp_test_destroy_dev(struct mctp_test_dev *dev);
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 *mctp_test_create_route_gw(struct net *net,
unsigned int netid,
mctp_eid_t eid,
mctp_eid_t gw,
unsigned int mtu);
void mctp_test_dst_setup(struct kunit *test, struct mctp_dst *dst,
struct mctp_test_dev *dev, unsigned int mtu);
void mctp_test_route_destroy(struct kunit *test, struct mctp_test_route *rt);
void mctp_test_skb_set_dev(struct sk_buff *skb, struct mctp_test_dev *dev);
struct sk_buff *mctp_test_create_skb(const struct mctp_hdr *hdr,
unsigned int data_len);
struct sk_buff *__mctp_test_create_skb_data(const struct mctp_hdr *hdr,
const void *data, size_t data_len);
#define mctp_test_create_skb_data(h, d) \
__mctp_test_create_skb_data(h, d, sizeof(*d))
void mctp_test_bind_run(struct kunit *test,
const struct mctp_test_bind_setup *setup,
int *ret_bind_errno, struct socket **sock);
#endif /* __NET_MCTP_TEST_UTILS_H */