diff options
Diffstat (limited to 'net/mctp')
-rw-r--r-- | net/mctp/af_mctp.c | 214 | ||||
-rw-r--r-- | net/mctp/route.c | 653 | ||||
-rw-r--r-- | net/mctp/test/route-test.c | 798 | ||||
-rw-r--r-- | net/mctp/test/sock-test.c | 396 | ||||
-rw-r--r-- | net/mctp/test/utils.c | 232 | ||||
-rw-r--r-- | net/mctp/test/utils.h | 61 |
6 files changed, 1883 insertions, 471 deletions
diff --git a/net/mctp/af_mctp.c b/net/mctp/af_mctp.c index 9b12ca97f412..df4e8cf33899 100644 --- a/net/mctp/af_mctp.c +++ b/net/mctp/af_mctp.c @@ -53,6 +53,7 @@ static int mctp_bind(struct socket *sock, struct sockaddr *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; @@ -73,14 +74,48 @@ static int mctp_bind(struct socket *sock, struct sockaddr *addr, int addrlen) lock_sock(sk); - /* TODO: allow rebind */ if (sk_hashed(sk)) { rc = -EADDRINUSE; goto out_release; } - msk->bind_net = smctp->smctp_network; - msk->bind_addr = smctp->smctp_addr.s_addr; - msk->bind_type = smctp->smctp_type & 0x7f; /* ignore the IC bit */ + + 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); @@ -90,6 +125,67 @@ out_release: 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 *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); @@ -97,8 +193,8 @@ static int mctp_sendmsg(struct socket *sock, struct msghdr *msg, size_t len) struct sock *sk = sock->sk; struct mctp_sock *msk = container_of(sk, struct mctp_sock, sk); struct mctp_skb_cb *cb; - struct mctp_route *rt; struct sk_buff *skb = NULL; + struct mctp_dst dst; int hlen; if (addr) { @@ -133,34 +229,30 @@ static int mctp_sendmsg(struct socket *sock, struct msghdr *msg, size_t len) if (msk->addr_ext && addrlen >= sizeof(struct sockaddr_mctp_ext)) { DECLARE_SOCKADDR(struct sockaddr_mctp_ext *, extaddr, msg->msg_name); - struct net_device *dev; - - rc = -EINVAL; - rcu_read_lock(); - dev = dev_get_by_index_rcu(sock_net(sk), extaddr->smctp_ifindex); - /* check for correct halen */ - if (dev && extaddr->smctp_halen == dev->addr_len) { - hlen = LL_RESERVED_SPACE(dev) + sizeof(struct mctp_hdr); - rc = 0; - } - rcu_read_unlock(); + + 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) - goto err_free; - rt = NULL; + return rc; + } else { - rt = mctp_route_lookup(sock_net(sk), addr->smctp_network, - addr->smctp_addr.s_addr); - if (!rt) { - rc = -EHOSTUNREACH; - goto err_free; - } - hlen = LL_RESERVED_SPACE(rt->dev->dev) + sizeof(struct mctp_hdr); + 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) - return rc; + goto err_release_dst; skb_reserve(skb, hlen); @@ -175,30 +267,16 @@ static int mctp_sendmsg(struct socket *sock, struct msghdr *msg, size_t len) cb = __mctp_cb(skb); cb->net = addr->smctp_network; - if (!rt) { - /* fill extended address in cb */ - DECLARE_SOCKADDR(struct sockaddr_mctp_ext *, - extaddr, msg->msg_name); - - if (!mctp_sockaddr_ext_is_ok(extaddr) || - extaddr->smctp_halen > sizeof(cb->haddr)) { - rc = -EINVAL; - goto err_free; - } - - cb->ifindex = extaddr->smctp_ifindex; - /* smctp_halen is checked above */ - cb->halen = extaddr->smctp_halen; - memcpy(cb->haddr, extaddr->smctp_haddr, cb->halen); - } - - rc = mctp_local_output(sk, rt, skb, addr->smctp_addr.s_addr, + 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; } @@ -551,7 +629,7 @@ static const struct proto_ops mctp_dgram_ops = { .family = PF_MCTP, .release = mctp_release, .bind = mctp_bind, - .connect = sock_no_connect, + .connect = mctp_connect, .socketpair = sock_no_socketpair, .accept = sock_no_accept, .getname = sock_no_getname, @@ -618,6 +696,7 @@ static int mctp_sk_init(struct sock *sk) INIT_HLIST_HEAD(&msk->keys); timer_setup(&msk->key_expiry, mctp_sk_expire_keys, 0); + msk->bind_peer_set = false; return 0; } @@ -629,15 +708,48 @@ static void mctp_sk_close(struct sock *sk, long timeout) 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); - mutex_lock(&net->mctp.bind_lock); - sk_add_node_rcu(sk, &net->mctp.binds); - mutex_unlock(&net->mctp.bind_lock); + sk_add_node_rcu(sk, &net->mctp.binds[hash]); + rc = 0; - return 0; +out: + mutex_unlock(&net->mctp.bind_lock); + return rc; } static void mctp_sk_unhash(struct sock *sk) @@ -793,3 +905,7 @@ 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 diff --git a/net/mctp/route.c b/net/mctp/route.c index d9c8e5a5f9ce..2b2b958ef6a3 100644 --- a/net/mctp/route.c +++ b/net/mctp/route.c @@ -17,6 +17,8 @@ #include <linux/rtnetlink.h> #include <linux/skbuff.h> +#include <kunit/static_stub.h> + #include <uapi/linux/if_arp.h> #include <net/mctp.h> @@ -32,39 +34,42 @@ static const unsigned long mctp_key_lifetime = 6 * CONFIG_HZ; static void mctp_flow_prepare_output(struct sk_buff *skb, struct mctp_dev *dev); /* route output callbacks */ -static int mctp_route_discard(struct mctp_route *route, struct sk_buff *skb) +static int mctp_dst_discard(struct mctp_dst *dst, struct sk_buff *skb) { kfree_skb(skb); return 0; } -static struct mctp_sock *mctp_lookup_bind(struct net *net, struct sk_buff *skb) +static struct mctp_sock *mctp_lookup_bind_details(struct net *net, + struct sk_buff *skb, + u8 type, u8 dest, + u8 src, bool allow_net_any) { struct mctp_skb_cb *cb = mctp_cb(skb); - struct mctp_hdr *mh; struct sock *sk; - u8 type; + u8 hash; - WARN_ON(!rcu_read_lock_held()); + WARN_ON_ONCE(!rcu_read_lock_held()); - /* TODO: look up in skb->cb? */ - mh = mctp_hdr(skb); - - if (!skb_headlen(skb)) - return NULL; + hash = mctp_bind_hash(type, dest, src); - type = (*(u8 *)skb->data) & 0x7f; - - sk_for_each_rcu(sk, &net->mctp.binds) { + sk_for_each_rcu(sk, &net->mctp.binds[hash]) { struct mctp_sock *msk = container_of(sk, struct mctp_sock, sk); + if (!allow_net_any && msk->bind_net == MCTP_NET_ANY) + continue; + if (msk->bind_net != MCTP_NET_ANY && msk->bind_net != cb->net) continue; if (msk->bind_type != type) continue; - if (!mctp_address_matches(msk->bind_addr, mh->dest)) + if (msk->bind_peer_set && + !mctp_address_matches(msk->bind_peer_addr, src)) + continue; + + if (!mctp_address_matches(msk->bind_local_addr, dest)) continue; return msk; @@ -73,6 +78,54 @@ static struct mctp_sock *mctp_lookup_bind(struct net *net, struct sk_buff *skb) return NULL; } +static struct mctp_sock *mctp_lookup_bind(struct net *net, struct sk_buff *skb) +{ + struct mctp_sock *msk; + struct mctp_hdr *mh; + u8 type; + + /* TODO: look up in skb->cb? */ + mh = mctp_hdr(skb); + + if (!skb_headlen(skb)) + return NULL; + + type = (*(u8 *)skb->data) & 0x7f; + + /* Look for binds in order of widening scope. A given destination or + * source address also implies matching on a particular network. + * + * - Matching destination and source + * - Matching destination + * - Matching source + * - Matching network, any address + * - Any network or address + */ + + msk = mctp_lookup_bind_details(net, skb, type, mh->dest, mh->src, + false); + if (msk) + return msk; + msk = mctp_lookup_bind_details(net, skb, type, MCTP_ADDR_ANY, mh->src, + false); + if (msk) + return msk; + msk = mctp_lookup_bind_details(net, skb, type, mh->dest, MCTP_ADDR_ANY, + false); + if (msk) + return msk; + msk = mctp_lookup_bind_details(net, skb, type, MCTP_ADDR_ANY, + MCTP_ADDR_ANY, false); + if (msk) + return msk; + msk = mctp_lookup_bind_details(net, skb, type, MCTP_ADDR_ANY, + MCTP_ADDR_ANY, true); + if (msk) + return msk; + + return NULL; +} + /* A note on the key allocations. * * struct net->mctp.keys contains our set of currently-allocated keys for @@ -368,7 +421,7 @@ static int mctp_frag_queue(struct mctp_sk_key *key, struct sk_buff *skb) return 0; } -static int mctp_route_input(struct mctp_route *route, struct sk_buff *skb) +static int mctp_dst_input(struct mctp_dst *dst, struct sk_buff *skb) { struct mctp_sk_key *key, *any_key = NULL; struct net *net = dev_net(skb->dev); @@ -392,6 +445,9 @@ static int mctp_route_input(struct mctp_route *route, struct sk_buff *skb) */ skb_orphan(skb); + if (skb->pkt_type == PACKET_OUTGOING) + skb->pkt_type = PACKET_LOOPBACK; + /* ensure we have enough data for a header and a type */ if (skb->len < sizeof(struct mctp_hdr) + 1) goto out; @@ -556,39 +612,31 @@ out: return rc; } -static unsigned int mctp_route_mtu(struct mctp_route *rt) -{ - return rt->mtu ?: READ_ONCE(rt->dev->dev->mtu); -} - -static int mctp_route_output(struct mctp_route *route, struct sk_buff *skb) +static int mctp_dst_output(struct mctp_dst *dst, struct sk_buff *skb) { - struct mctp_skb_cb *cb = mctp_cb(skb); - struct mctp_hdr *hdr = mctp_hdr(skb); char daddr_buf[MAX_ADDR_LEN]; char *daddr = NULL; - unsigned int mtu; int rc; skb->protocol = htons(ETH_P_MCTP); + skb->pkt_type = PACKET_OUTGOING; - mtu = READ_ONCE(skb->dev->mtu); - if (skb->len > mtu) { + if (skb->len > dst->mtu) { kfree_skb(skb); return -EMSGSIZE; } - if (cb->ifindex) { - /* direct route; use the hwaddr we stashed in sendmsg */ - if (cb->halen != skb->dev->addr_len) { + /* direct route; use the hwaddr we stashed in sendmsg */ + if (dst->halen) { + if (dst->halen != skb->dev->addr_len) { /* sanity check, sendmsg should have already caught this */ kfree_skb(skb); return -EMSGSIZE; } - daddr = cb->haddr; + daddr = dst->haddr; } else { /* If lookup fails let the device handle daddr==NULL */ - if (mctp_neigh_lookup(route->dev, hdr->dest, daddr_buf) == 0) + if (mctp_neigh_lookup(dst->dev, dst->nexthop, daddr_buf) == 0) daddr = daddr_buf; } @@ -599,7 +647,7 @@ static int mctp_route_output(struct mctp_route *route, struct sk_buff *skb) return -EHOSTUNREACH; } - mctp_flow_prepare_output(skb, route->dev); + mctp_flow_prepare_output(skb, dst->dev); rc = dev_queue_xmit(skb); if (rc) @@ -612,7 +660,8 @@ static int mctp_route_output(struct mctp_route *route, struct sk_buff *skb) static void mctp_route_release(struct mctp_route *rt) { if (refcount_dec_and_test(&rt->refs)) { - mctp_dev_put(rt->dev); + if (rt->dst_type == MCTP_ROUTE_DIRECT) + mctp_dev_put(rt->dev); kfree_rcu(rt, rcu); } } @@ -628,7 +677,7 @@ static struct mctp_route *mctp_route_alloc(void) INIT_LIST_HEAD(&rt->list); refcount_set(&rt->refs, 1); - rt->output = mctp_route_discard; + rt->output = mctp_dst_discard; return rt; } @@ -801,10 +850,16 @@ static struct mctp_sk_key *mctp_lookup_prealloc_tag(struct mctp_sock *msk, } /* routing lookups */ +static unsigned int mctp_route_netid(struct mctp_route *rt) +{ + return rt->dst_type == MCTP_ROUTE_DIRECT ? + READ_ONCE(rt->dev->net) : rt->gateway.net; +} + static bool mctp_rt_match_eid(struct mctp_route *rt, unsigned int net, mctp_eid_t eid) { - return READ_ONCE(rt->dev->net) == net && + return mctp_route_netid(rt) == net && rt->min <= eid && rt->max >= eid; } @@ -813,54 +868,150 @@ static bool mctp_rt_compare_exact(struct mctp_route *rt1, struct mctp_route *rt2) { ASSERT_RTNL(); - return rt1->dev->net == rt2->dev->net && + return mctp_route_netid(rt1) == mctp_route_netid(rt2) && rt1->min == rt2->min && rt1->max == rt2->max; } -struct mctp_route *mctp_route_lookup(struct net *net, unsigned int dnet, - mctp_eid_t daddr) +/* must only be called on a direct route, as the final output hop */ +static void mctp_dst_from_route(struct mctp_dst *dst, mctp_eid_t eid, + unsigned int mtu, struct mctp_route *route) +{ + mctp_dev_hold(route->dev); + dst->nexthop = eid; + dst->dev = route->dev; + dst->mtu = READ_ONCE(dst->dev->dev->mtu); + if (mtu) + dst->mtu = min(dst->mtu, mtu); + dst->halen = 0; + dst->output = route->output; +} + +int mctp_dst_from_extaddr(struct mctp_dst *dst, struct net *net, int ifindex, + unsigned char halen, const unsigned char *haddr) { - struct mctp_route *tmp, *rt = NULL; + struct net_device *netdev; + struct mctp_dev *dev; + int rc = -ENOENT; + + if (halen > sizeof(dst->haddr)) + return -EINVAL; rcu_read_lock(); - list_for_each_entry_rcu(tmp, &net->mctp.routes, list) { - /* TODO: add metrics */ - if (mctp_rt_match_eid(tmp, dnet, daddr)) { - if (refcount_inc_not_zero(&tmp->refs)) { - rt = tmp; - break; - } - } + netdev = dev_get_by_index_rcu(net, ifindex); + if (!netdev) + goto out_unlock; + + if (netdev->addr_len != halen) { + rc = -EINVAL; + goto out_unlock; } + dev = __mctp_dev_get(netdev); + if (!dev) + goto out_unlock; + + dst->dev = dev; + dst->mtu = READ_ONCE(netdev->mtu); + dst->halen = halen; + dst->output = mctp_dst_output; + dst->nexthop = 0; + memcpy(dst->haddr, haddr, halen); + + rc = 0; + +out_unlock: rcu_read_unlock(); + return rc; +} - return rt; +void mctp_dst_release(struct mctp_dst *dst) +{ + mctp_dev_put(dst->dev); +} + +static struct mctp_route *mctp_route_lookup_single(struct net *net, + unsigned int dnet, + mctp_eid_t daddr) +{ + struct mctp_route *rt; + + list_for_each_entry_rcu(rt, &net->mctp.routes, list) { + if (mctp_rt_match_eid(rt, dnet, daddr)) + return rt; + } + + return NULL; } -static struct mctp_route *mctp_route_lookup_null(struct net *net, - struct net_device *dev) +/* populates *dst on successful lookup, if set */ +int mctp_route_lookup(struct net *net, unsigned int dnet, + mctp_eid_t daddr, struct mctp_dst *dst) { - struct mctp_route *tmp, *rt = NULL; + const unsigned int max_depth = 32; + unsigned int depth, mtu = 0; + int rc = -EHOSTUNREACH; rcu_read_lock(); - list_for_each_entry_rcu(tmp, &net->mctp.routes, list) { - if (tmp->dev->dev == dev && tmp->type == RTN_LOCAL && - refcount_inc_not_zero(&tmp->refs)) { - rt = tmp; + for (depth = 0; depth < max_depth; depth++) { + struct mctp_route *rt; + + rt = mctp_route_lookup_single(net, dnet, daddr); + if (!rt) + break; + + /* clamp mtu to the smallest in the path, allowing 0 + * to specify no restrictions + */ + if (mtu && rt->mtu) + mtu = min(mtu, rt->mtu); + else + mtu = mtu ?: rt->mtu; + + if (rt->dst_type == MCTP_ROUTE_DIRECT) { + if (dst) + mctp_dst_from_route(dst, daddr, mtu, rt); + rc = 0; break; + + } else if (rt->dst_type == MCTP_ROUTE_GATEWAY) { + daddr = rt->gateway.eid; } } rcu_read_unlock(); - return rt; + return rc; +} + +static int mctp_route_lookup_null(struct net *net, struct net_device *dev, + struct mctp_dst *dst) +{ + int rc = -EHOSTUNREACH; + struct mctp_route *rt; + + rcu_read_lock(); + + list_for_each_entry_rcu(rt, &net->mctp.routes, list) { + if (rt->dst_type != MCTP_ROUTE_DIRECT || rt->type != RTN_LOCAL) + continue; + + if (rt->dev->dev != dev) + continue; + + mctp_dst_from_route(dst, 0, 0, rt); + rc = 0; + break; + } + + rcu_read_unlock(); + + return rc; } -static int mctp_do_fragment_route(struct mctp_route *rt, struct sk_buff *skb, +static int mctp_do_fragment_route(struct mctp_dst *dst, struct sk_buff *skb, unsigned int mtu, u8 tag) { const unsigned int hlen = sizeof(struct mctp_hdr); @@ -933,7 +1084,7 @@ static int mctp_do_fragment_route(struct mctp_route *rt, struct sk_buff *skb, skb_ext_copy(skb2, skb); /* do route */ - rc = rt->output(rt, skb2); + rc = dst->output(dst, skb2); if (rc) break; @@ -945,68 +1096,34 @@ static int mctp_do_fragment_route(struct mctp_route *rt, struct sk_buff *skb, return rc; } -int mctp_local_output(struct sock *sk, struct mctp_route *rt, +int mctp_local_output(struct sock *sk, struct mctp_dst *dst, struct sk_buff *skb, mctp_eid_t daddr, u8 req_tag) { struct mctp_sock *msk = container_of(sk, struct mctp_sock, sk); - struct mctp_skb_cb *cb = mctp_cb(skb); - struct mctp_route tmp_rt = {0}; struct mctp_sk_key *key; struct mctp_hdr *hdr; unsigned long flags; unsigned int netid; unsigned int mtu; mctp_eid_t saddr; - bool ext_rt; int rc; u8 tag; - rc = -ENODEV; - - if (rt) { - ext_rt = false; - if (WARN_ON(!rt->dev)) - goto out_release; - - } else if (cb->ifindex) { - struct net_device *dev; - - ext_rt = true; - rt = &tmp_rt; - - rcu_read_lock(); - dev = dev_get_by_index_rcu(sock_net(sk), cb->ifindex); - if (!dev) { - rcu_read_unlock(); - goto out_free; - } - rt->dev = __mctp_dev_get(dev); - rcu_read_unlock(); - - if (!rt->dev) - goto out_release; + KUNIT_STATIC_STUB_REDIRECT(mctp_local_output, sk, dst, skb, daddr, + req_tag); - /* establish temporary route - we set up enough to keep - * mctp_route_output happy - */ - rt->output = mctp_route_output; - rt->mtu = 0; - - } else { - rc = -EINVAL; - goto out_free; - } + rc = -ENODEV; - spin_lock_irqsave(&rt->dev->addrs_lock, flags); - if (rt->dev->num_addrs == 0) { + spin_lock_irqsave(&dst->dev->addrs_lock, flags); + if (dst->dev->num_addrs == 0) { rc = -EHOSTUNREACH; } else { /* use the outbound interface's first address as our source */ - saddr = rt->dev->addrs[0]; + saddr = dst->dev->addrs[0]; rc = 0; } - spin_unlock_irqrestore(&rt->dev->addrs_lock, flags); - netid = READ_ONCE(rt->dev->net); + spin_unlock_irqrestore(&dst->dev->addrs_lock, flags); + netid = READ_ONCE(dst->dev->net); if (rc) goto out_release; @@ -1032,15 +1149,13 @@ int mctp_local_output(struct sock *sk, struct mctp_route *rt, tag = req_tag & MCTP_TAG_MASK; } + skb->pkt_type = PACKET_OUTGOING; skb->protocol = htons(ETH_P_MCTP); skb->priority = 0; skb_reset_transport_header(skb); skb_push(skb, sizeof(struct mctp_hdr)); skb_reset_network_header(skb); - skb->dev = rt->dev->dev; - - /* cb->net will have been set on initial ingress */ - cb->src = saddr; + skb->dev = dst->dev->dev; /* set up common header fields */ hdr = mctp_hdr(skb); @@ -1048,73 +1163,64 @@ int mctp_local_output(struct sock *sk, struct mctp_route *rt, hdr->dest = daddr; hdr->src = saddr; - mtu = mctp_route_mtu(rt); + mtu = dst->mtu; if (skb->len + sizeof(struct mctp_hdr) <= mtu) { hdr->flags_seq_tag = MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM | tag; - rc = rt->output(rt, skb); + rc = dst->output(dst, skb); } else { - rc = mctp_do_fragment_route(rt, skb, mtu, tag); + rc = mctp_do_fragment_route(dst, skb, mtu, tag); } /* route output functions consume the skb, even on error */ skb = NULL; out_release: - if (!ext_rt) - mctp_route_release(rt); - - mctp_dev_put(tmp_rt.dev); - -out_free: kfree_skb(skb); return rc; } /* route management */ -static int mctp_route_add(struct mctp_dev *mdev, mctp_eid_t daddr_start, - unsigned int daddr_extent, unsigned int mtu, - unsigned char type) + +/* mctp_route_add(): Add the provided route, previously allocated via + * mctp_route_alloc(). On success, takes ownership of @rt, which includes a + * hold on rt->dev for usage in the route table. On failure a caller will want + * to mctp_route_release(). + * + * We expect that the caller has set rt->type, rt->dst_type, rt->min, rt->max, + * rt->mtu and either rt->dev (with a reference held appropriately) or + * rt->gateway. Other fields will be populated. + */ +static int mctp_route_add(struct net *net, struct mctp_route *rt) { - int (*rtfn)(struct mctp_route *rt, struct sk_buff *skb); - struct net *net = dev_net(mdev->dev); - struct mctp_route *rt, *ert; + struct mctp_route *ert; - if (!mctp_address_unicast(daddr_start)) + if (!mctp_address_unicast(rt->min) || !mctp_address_unicast(rt->max)) return -EINVAL; - if (daddr_extent > 0xff || daddr_start + daddr_extent >= 255) + if (rt->dst_type == MCTP_ROUTE_DIRECT && !rt->dev) + return -EINVAL; + + if (rt->dst_type == MCTP_ROUTE_GATEWAY && !rt->gateway.eid) return -EINVAL; - switch (type) { + switch (rt->type) { case RTN_LOCAL: - rtfn = mctp_route_input; + rt->output = mctp_dst_input; break; case RTN_UNICAST: - rtfn = mctp_route_output; + rt->output = mctp_dst_output; break; default: return -EINVAL; } - rt = mctp_route_alloc(); - if (!rt) - return -ENOMEM; - - rt->min = daddr_start; - rt->max = daddr_start + daddr_extent; - rt->mtu = mtu; - rt->dev = mdev; - mctp_dev_hold(rt->dev); - rt->type = type; - rt->output = rtfn; - ASSERT_RTNL(); + /* Prevent duplicate identical routes. */ list_for_each_entry(ert, &net->mctp.routes, list) { if (mctp_rt_compare_exact(rt, ert)) { - mctp_route_release(rt); return -EEXIST; } } @@ -1124,10 +1230,10 @@ static int mctp_route_add(struct mctp_dev *mdev, mctp_eid_t daddr_start, return 0; } -static int mctp_route_remove(struct mctp_dev *mdev, mctp_eid_t daddr_start, - unsigned int daddr_extent, unsigned char type) +static int mctp_route_remove(struct net *net, unsigned int netid, + mctp_eid_t daddr_start, unsigned int daddr_extent, + unsigned char type) { - struct net *net = dev_net(mdev->dev); struct mctp_route *rt, *tmp; mctp_eid_t daddr_end; bool dropped; @@ -1141,7 +1247,7 @@ static int mctp_route_remove(struct mctp_dev *mdev, mctp_eid_t daddr_start, ASSERT_RTNL(); list_for_each_entry_safe(rt, tmp, &net->mctp.routes, list) { - if (rt->dev == mdev && + if (mctp_route_netid(rt) == netid && rt->min == daddr_start && rt->max == daddr_end && rt->type == type) { list_del_rcu(&rt->list); @@ -1156,12 +1262,32 @@ static int mctp_route_remove(struct mctp_dev *mdev, mctp_eid_t daddr_start, int mctp_route_add_local(struct mctp_dev *mdev, mctp_eid_t addr) { - return mctp_route_add(mdev, addr, 0, 0, RTN_LOCAL); + struct mctp_route *rt; + int rc; + + rt = mctp_route_alloc(); + if (!rt) + return -ENOMEM; + + rt->min = addr; + rt->max = addr; + rt->dst_type = MCTP_ROUTE_DIRECT; + rt->dev = mdev; + rt->type = RTN_LOCAL; + + mctp_dev_hold(rt->dev); + + rc = mctp_route_add(dev_net(mdev->dev), rt); + if (rc) + mctp_route_release(rt); + + return rc; } int mctp_route_remove_local(struct mctp_dev *mdev, mctp_eid_t addr) { - return mctp_route_remove(mdev, addr, 0, RTN_LOCAL); + return mctp_route_remove(dev_net(mdev->dev), mdev->net, + addr, 0, RTN_LOCAL); } /* removes all entries for a given device */ @@ -1172,7 +1298,7 @@ void mctp_route_remove_dev(struct mctp_dev *mdev) ASSERT_RTNL(); list_for_each_entry_safe(rt, tmp, &net->mctp.routes, list) { - if (rt->dev == mdev) { + if (rt->dst_type == MCTP_ROUTE_DIRECT && rt->dev == mdev) { list_del_rcu(&rt->list); /* TODO: immediate RTM_DELROUTE */ mctp_route_release(rt); @@ -1189,8 +1315,9 @@ static int mctp_pkttype_receive(struct sk_buff *skb, struct net_device *dev, struct net *net = dev_net(dev); struct mctp_dev *mdev; struct mctp_skb_cb *cb; - struct mctp_route *rt; + struct mctp_dst dst; struct mctp_hdr *mh; + int rc; rcu_read_lock(); mdev = __mctp_dev_get(dev); @@ -1232,17 +1359,17 @@ static int mctp_pkttype_receive(struct sk_buff *skb, struct net_device *dev, cb->net = READ_ONCE(mdev->net); cb->ifindex = dev->ifindex; - rt = mctp_route_lookup(net, cb->net, mh->dest); + rc = mctp_route_lookup(net, cb->net, mh->dest, &dst); /* NULL EID, but addressed to our physical address */ - if (!rt && mh->dest == MCTP_ADDR_NULL && skb->pkt_type == PACKET_HOST) - rt = mctp_route_lookup_null(net, dev); + if (rc && mh->dest == MCTP_ADDR_NULL && skb->pkt_type == PACKET_HOST) + rc = mctp_route_lookup_null(net, dev, &dst); - if (!rt) + if (rc) goto err_drop; - rt->output(rt, skb); - mctp_route_release(rt); + dst.output(&dst, skb); + mctp_dst_release(&dst); mctp_dev_put(mdev); return NET_RX_SUCCESS; @@ -1264,19 +1391,28 @@ static const struct nla_policy rta_mctp_policy[RTA_MAX + 1] = { [RTA_DST] = { .type = NLA_U8 }, [RTA_METRICS] = { .type = NLA_NESTED }, [RTA_OIF] = { .type = NLA_U32 }, + [RTA_GATEWAY] = NLA_POLICY_EXACT_LEN(sizeof(struct mctp_fq_addr)), +}; + +static const struct nla_policy rta_metrics_policy[RTAX_MAX + 1] = { + [RTAX_MTU] = { .type = NLA_U32 }, }; -/* Common part for RTM_NEWROUTE and RTM_DELROUTE parsing. - * tb must hold RTA_MAX+1 elements. +/* base parsing; common to both _lookup and _populate variants. + * + * For gateway routes (which have a RTA_GATEWAY, and no RTA_OIF), we populate + * *gatweayp. for direct routes (RTA_OIF, no RTA_GATEWAY), we populate *mdev. */ -static int mctp_route_nlparse(struct sk_buff *skb, struct nlmsghdr *nlh, - struct netlink_ext_ack *extack, - struct nlattr **tb, struct rtmsg **rtm, - struct mctp_dev **mdev, mctp_eid_t *daddr_start) +static int mctp_route_nlparse_common(struct net *net, struct nlmsghdr *nlh, + struct netlink_ext_ack *extack, + struct nlattr **tb, struct rtmsg **rtm, + struct mctp_dev **mdev, + struct mctp_fq_addr *gatewayp, + mctp_eid_t *daddr_start) { - struct net *net = sock_net(skb->sk); + struct mctp_fq_addr *gateway = NULL; + unsigned int ifindex = 0; struct net_device *dev; - unsigned int ifindex; int rc; rc = nlmsg_parse(nlh, sizeof(struct rtmsg), tb, RTA_MAX, @@ -1292,11 +1428,44 @@ static int mctp_route_nlparse(struct sk_buff *skb, struct nlmsghdr *nlh, } *daddr_start = nla_get_u8(tb[RTA_DST]); - if (!tb[RTA_OIF]) { - NL_SET_ERR_MSG(extack, "ifindex missing"); + if (tb[RTA_OIF]) + ifindex = nla_get_u32(tb[RTA_OIF]); + + if (tb[RTA_GATEWAY]) + gateway = nla_data(tb[RTA_GATEWAY]); + + if (ifindex && gateway) { + NL_SET_ERR_MSG(extack, + "cannot specify both ifindex and gateway"); + return -EINVAL; + + } else if (ifindex) { + dev = __dev_get_by_index(net, ifindex); + if (!dev) { + NL_SET_ERR_MSG(extack, "bad ifindex"); + return -ENODEV; + } + *mdev = mctp_dev_get_rtnl(dev); + if (!*mdev) + return -ENODEV; + gatewayp->eid = 0; + + } else if (gateway) { + if (!mctp_address_unicast(gateway->eid)) { + NL_SET_ERR_MSG(extack, "bad gateway"); + return -EINVAL; + } + + gatewayp->eid = gateway->eid; + gatewayp->net = gateway->net != MCTP_NET_ANY ? + gateway->net : + READ_ONCE(net->mctp.default_net); + *mdev = NULL; + + } else { + NL_SET_ERR_MSG(extack, "no route output provided"); return -EINVAL; } - ifindex = nla_get_u32(tb[RTA_OIF]); *rtm = nlmsg_data(nlh); if ((*rtm)->rtm_family != AF_MCTP) { @@ -1304,82 +1473,157 @@ static int mctp_route_nlparse(struct sk_buff *skb, struct nlmsghdr *nlh, return -EINVAL; } - dev = __dev_get_by_index(net, ifindex); - if (!dev) { - NL_SET_ERR_MSG(extack, "bad ifindex"); - return -ENODEV; - } - *mdev = mctp_dev_get_rtnl(dev); - if (!*mdev) - return -ENODEV; - - if (dev->flags & IFF_LOOPBACK) { - NL_SET_ERR_MSG(extack, "no routes to loopback"); + if ((*rtm)->rtm_type != RTN_UNICAST) { + NL_SET_ERR_MSG(extack, "rtm_type must be RTN_UNICAST"); return -EINVAL; } return 0; } -static const struct nla_policy rta_metrics_policy[RTAX_MAX + 1] = { - [RTAX_MTU] = { .type = NLA_U32 }, -}; - -static int mctp_newroute(struct sk_buff *skb, struct nlmsghdr *nlh, - struct netlink_ext_ack *extack) +/* Route parsing for lookup operations; we only need the "route target" + * components (ie., network and dest-EID range). + */ +static int mctp_route_nlparse_lookup(struct net *net, struct nlmsghdr *nlh, + struct netlink_ext_ack *extack, + unsigned char *type, unsigned int *netid, + mctp_eid_t *daddr_start, + unsigned int *daddr_extent) { struct nlattr *tb[RTA_MAX + 1]; + struct mctp_fq_addr gw; + struct mctp_dev *mdev; + struct rtmsg *rtm; + int rc; + + rc = mctp_route_nlparse_common(net, nlh, extack, tb, &rtm, + &mdev, &gw, daddr_start); + if (rc) + return rc; + + if (mdev) { + *netid = mdev->net; + } else if (gw.eid) { + *netid = gw.net; + } else { + /* bug: _nlparse_common should not allow this */ + return -1; + } + + *type = rtm->rtm_type; + *daddr_extent = rtm->rtm_dst_len; + + return 0; +} + +/* Full route parse for RTM_NEWROUTE: populate @rt. On success, + * MCTP_ROUTE_DIRECT routes (ie, those with a direct dev) will hold a reference + * to that dev. + */ +static int mctp_route_nlparse_populate(struct net *net, struct nlmsghdr *nlh, + struct netlink_ext_ack *extack, + struct mctp_route *rt) +{ struct nlattr *tbx[RTAX_MAX + 1]; + struct nlattr *tb[RTA_MAX + 1]; + unsigned int daddr_extent; + struct mctp_fq_addr gw; mctp_eid_t daddr_start; - struct mctp_dev *mdev; + struct mctp_dev *dev; struct rtmsg *rtm; - unsigned int mtu; + u32 mtu = 0; int rc; - rc = mctp_route_nlparse(skb, nlh, extack, tb, - &rtm, &mdev, &daddr_start); - if (rc < 0) + rc = mctp_route_nlparse_common(net, nlh, extack, tb, &rtm, + &dev, &gw, &daddr_start); + if (rc) return rc; - if (rtm->rtm_type != RTN_UNICAST) { - NL_SET_ERR_MSG(extack, "rtm_type must be RTN_UNICAST"); + daddr_extent = rtm->rtm_dst_len; + + if (daddr_extent > 0xff || daddr_extent + daddr_start >= 255) { + NL_SET_ERR_MSG(extack, "invalid eid range"); return -EINVAL; } - mtu = 0; if (tb[RTA_METRICS]) { rc = nla_parse_nested(tbx, RTAX_MAX, tb[RTA_METRICS], rta_metrics_policy, NULL); - if (rc < 0) + if (rc < 0) { + NL_SET_ERR_MSG(extack, "incorrect RTA_METRICS format"); return rc; + } if (tbx[RTAX_MTU]) mtu = nla_get_u32(tbx[RTAX_MTU]); } - rc = mctp_route_add(mdev, daddr_start, rtm->rtm_dst_len, mtu, - rtm->rtm_type); + rt->type = rtm->rtm_type; + rt->min = daddr_start; + rt->max = daddr_start + daddr_extent; + rt->mtu = mtu; + if (gw.eid) { + rt->dst_type = MCTP_ROUTE_GATEWAY; + rt->gateway.eid = gw.eid; + rt->gateway.net = gw.net; + } else { + rt->dst_type = MCTP_ROUTE_DIRECT; + rt->dev = dev; + mctp_dev_hold(rt->dev); + } + + return 0; +} + +static int mctp_newroute(struct sk_buff *skb, struct nlmsghdr *nlh, + struct netlink_ext_ack *extack) +{ + struct net *net = sock_net(skb->sk); + struct mctp_route *rt; + int rc; + + rt = mctp_route_alloc(); + if (!rt) + return -ENOMEM; + + rc = mctp_route_nlparse_populate(net, nlh, extack, rt); + if (rc < 0) + goto err_free; + + if (rt->dst_type == MCTP_ROUTE_DIRECT && + rt->dev->dev->flags & IFF_LOOPBACK) { + NL_SET_ERR_MSG(extack, "no routes to loopback"); + rc = -EINVAL; + goto err_free; + } + + rc = mctp_route_add(net, rt); + if (!rc) + return 0; + +err_free: + mctp_route_release(rt); return rc; } static int mctp_delroute(struct sk_buff *skb, struct nlmsghdr *nlh, struct netlink_ext_ack *extack) { - struct nlattr *tb[RTA_MAX + 1]; + struct net *net = sock_net(skb->sk); + unsigned int netid, daddr_extent; + unsigned char type = RTN_UNSPEC; mctp_eid_t daddr_start; - struct mctp_dev *mdev; - struct rtmsg *rtm; int rc; - rc = mctp_route_nlparse(skb, nlh, extack, tb, - &rtm, &mdev, &daddr_start); + rc = mctp_route_nlparse_lookup(net, nlh, extack, &type, &netid, + &daddr_start, &daddr_extent); if (rc < 0) return rc; /* we only have unicast routes */ - if (rtm->rtm_type != RTN_UNICAST) + if (type != RTN_UNICAST) return -EINVAL; - rc = mctp_route_remove(mdev, daddr_start, rtm->rtm_dst_len, RTN_UNICAST); + rc = mctp_route_remove(net, netid, daddr_start, daddr_extent, type); return rc; } @@ -1405,7 +1649,6 @@ static int mctp_fill_rtinfo(struct sk_buff *skb, struct mctp_route *rt, hdr->rtm_tos = 0; hdr->rtm_table = RT_TABLE_DEFAULT; hdr->rtm_protocol = RTPROT_STATIC; /* everything is user-defined */ - hdr->rtm_scope = RT_SCOPE_LINK; /* TODO: scope in mctp_route? */ hdr->rtm_type = rt->type; if (nla_put_u8(skb, RTA_DST, rt->min)) @@ -1422,13 +1665,17 @@ static int mctp_fill_rtinfo(struct sk_buff *skb, struct mctp_route *rt, nla_nest_end(skb, metrics); - if (rt->dev) { + if (rt->dst_type == MCTP_ROUTE_DIRECT) { + hdr->rtm_scope = RT_SCOPE_LINK; if (nla_put_u32(skb, RTA_OIF, rt->dev->dev->ifindex)) goto cancel; + } else if (rt->dst_type == MCTP_ROUTE_GATEWAY) { + hdr->rtm_scope = RT_SCOPE_UNIVERSE; + if (nla_put(skb, RTA_GATEWAY, + sizeof(rt->gateway), &rt->gateway)) + goto cancel; } - /* TODO: conditional neighbour physaddr? */ - nlmsg_end(skb, nlh); return 0; @@ -1475,7 +1722,7 @@ static int __net_init mctp_routes_net_init(struct net *net) struct netns_mctp *ns = &net->mctp; INIT_LIST_HEAD(&ns->routes); - INIT_HLIST_HEAD(&ns->binds); + hash_init(ns->binds); mutex_init(&ns->bind_lock); INIT_HLIST_HEAD(&ns->keys); spin_lock_init(&ns->keys_lock); diff --git a/net/mctp/test/route-test.c b/net/mctp/test/route-test.c index 06c1897b685a..fb6b46a952cb 100644 --- a/net/mctp/test/route-test.c +++ b/net/mctp/test/route-test.c @@ -2,132 +2,11 @@ #include <kunit/test.h> -#include "utils.h" - -struct mctp_test_route { - struct mctp_route rt; - struct sk_buff_head pkts; -}; - -static int mctp_test_route_output(struct mctp_route *rt, struct sk_buff *skb) -{ - struct mctp_test_route *test_rt = container_of(rt, struct mctp_test_route, rt); - - skb_queue_tail(&test_rt->pkts, skb); - - return 0; -} - -/* local version of mctp_route_alloc() */ -static struct mctp_test_route *mctp_route_test_alloc(void) -{ - struct mctp_test_route *rt; +/* keep clangd happy when compiled outside of the route.c include */ +#include <net/mctp.h> +#include <net/mctpdevice.h> - rt = kzalloc(sizeof(*rt), GFP_KERNEL); - if (!rt) - return NULL; - - INIT_LIST_HEAD(&rt->rt.list); - refcount_set(&rt->rt.refs, 1); - rt->rt.output = mctp_test_route_output; - - skb_queue_head_init(&rt->pkts); - - return rt; -} - -static struct mctp_test_route *mctp_test_create_route(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; - if (dev) - mctp_dev_hold(dev); - rt->rt.dev = dev; - - list_add_rcu(&rt->rt.list, &net->mctp.routes); - - return rt; -} - -static 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(); - - skb_queue_purge(&rt->pkts); - if (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); -} - -static 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; -} - -static 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; -} - -static 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; -} +#include "utils.h" #define mctp_test_create_skb_data(h, d) \ __mctp_test_create_skb_data(h, d, sizeof(*d)) @@ -141,8 +20,10 @@ struct mctp_frag_test { static void mctp_test_fragment(struct kunit *test) { const struct mctp_frag_test *params; + struct mctp_test_pktqueue tpq; int rc, i, n, mtu, msgsize; - struct mctp_test_route *rt; + struct mctp_test_dev *dev; + struct mctp_dst dst; struct sk_buff *skb; struct mctp_hdr hdr; u8 seq; @@ -159,13 +40,15 @@ static void mctp_test_fragment(struct kunit *test) skb = mctp_test_create_skb(&hdr, msgsize); KUNIT_ASSERT_TRUE(test, skb); - rt = mctp_test_create_route(&init_net, NULL, 10, mtu); - KUNIT_ASSERT_TRUE(test, rt); + dev = mctp_test_create_dev(); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + mctp_test_dst_setup(test, &dst, dev, &tpq, mtu); - rc = mctp_do_fragment_route(&rt->rt, skb, mtu, MCTP_TAG_OWNER); + rc = mctp_do_fragment_route(&dst, skb, mtu, MCTP_TAG_OWNER); KUNIT_EXPECT_FALSE(test, rc); - n = rt->pkts.qlen; + n = tpq.pkts.qlen; KUNIT_EXPECT_EQ(test, n, params->n_frags); @@ -178,7 +61,7 @@ static void mctp_test_fragment(struct kunit *test) first = i == 0; last = i == (n - 1); - skb2 = skb_dequeue(&rt->pkts); + skb2 = skb_dequeue(&tpq.pkts); if (!skb2) break; @@ -216,7 +99,8 @@ static void mctp_test_fragment(struct kunit *test) kfree_skb(skb2); } - mctp_test_route_destroy(test, rt); + mctp_test_dst_release(&dst, &tpq); + mctp_test_destroy_dev(dev); } static const struct mctp_frag_test mctp_frag_tests[] = { @@ -246,25 +130,30 @@ struct mctp_rx_input_test { static void mctp_test_rx_input(struct kunit *test) { const struct mctp_rx_input_test *params; + struct mctp_test_pktqueue tpq; struct mctp_test_route *rt; struct mctp_test_dev *dev; struct sk_buff *skb; params = test->param_value; + test->priv = &tpq; dev = mctp_test_create_dev(); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); - rt = mctp_test_create_route(&init_net, dev->mdev, 8, 68); + rt = mctp_test_create_route_direct(&init_net, dev->mdev, 8, 68); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt); skb = mctp_test_create_skb(¶ms->hdr, 1); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, skb); + mctp_test_pktqueue_init(&tpq); + mctp_pkttype_receive(skb, dev->ndev, &mctp_packet_type, NULL); - KUNIT_EXPECT_EQ(test, !!rt->pkts.qlen, params->input); + KUNIT_EXPECT_EQ(test, !!tpq.pkts.qlen, params->input); + skb_queue_purge(&tpq.pkts); mctp_test_route_destroy(test, rt); mctp_test_destroy_dev(dev); } @@ -292,12 +181,12 @@ KUNIT_ARRAY_PARAM(mctp_rx_input, mctp_rx_input_tests, /* set up a local dev, route on EID 8, and a socket listening on type 0 */ static void __mctp_route_test_init(struct kunit *test, struct mctp_test_dev **devp, - struct mctp_test_route **rtp, + struct mctp_dst *dst, + struct mctp_test_pktqueue *tpq, struct socket **sockp, unsigned int netid) { struct sockaddr_mctp addr = {0}; - struct mctp_test_route *rt; struct mctp_test_dev *dev; struct socket *sock; int rc; @@ -307,8 +196,7 @@ static void __mctp_route_test_init(struct kunit *test, if (netid != MCTP_NET_ANY) WRITE_ONCE(dev->mdev->net, netid); - rt = mctp_test_create_route(&init_net, dev->mdev, 8, 68); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt); + mctp_test_dst_setup(test, dst, dev, tpq, 68); rc = sock_create_kern(&init_net, AF_MCTP, SOCK_DGRAM, 0, &sock); KUNIT_ASSERT_EQ(test, rc, 0); @@ -320,18 +208,18 @@ static void __mctp_route_test_init(struct kunit *test, rc = kernel_bind(sock, (struct sockaddr *)&addr, sizeof(addr)); KUNIT_ASSERT_EQ(test, rc, 0); - *rtp = rt; *devp = dev; *sockp = sock; } static void __mctp_route_test_fini(struct kunit *test, struct mctp_test_dev *dev, - struct mctp_test_route *rt, + struct mctp_dst *dst, + struct mctp_test_pktqueue *tpq, struct socket *sock) { sock_release(sock); - mctp_test_route_destroy(test, rt); + mctp_test_dst_release(dst, tpq); mctp_test_destroy_dev(dev); } @@ -344,22 +232,24 @@ struct mctp_route_input_sk_test { static void mctp_test_route_input_sk(struct kunit *test) { const struct mctp_route_input_sk_test *params; + struct mctp_test_pktqueue tpq; struct sk_buff *skb, *skb2; - struct mctp_test_route *rt; struct mctp_test_dev *dev; + struct mctp_dst dst; struct socket *sock; int rc; params = test->param_value; - __mctp_route_test_init(test, &dev, &rt, &sock, MCTP_NET_ANY); + __mctp_route_test_init(test, &dev, &dst, &tpq, &sock, MCTP_NET_ANY); skb = mctp_test_create_skb_data(¶ms->hdr, ¶ms->type); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, skb); mctp_test_skb_set_dev(skb, dev); + mctp_test_pktqueue_init(&tpq); - rc = mctp_route_input(&rt->rt, skb); + rc = mctp_dst_input(&dst, skb); if (params->deliver) { KUNIT_EXPECT_EQ(test, rc, 0); @@ -376,7 +266,7 @@ static void mctp_test_route_input_sk(struct kunit *test) KUNIT_EXPECT_NULL(test, skb2); } - __mctp_route_test_fini(test, dev, rt, sock); + __mctp_route_test_fini(test, dev, &dst, &tpq, sock); } #define FL_S (MCTP_HDR_FLAG_SOM) @@ -413,16 +303,17 @@ struct mctp_route_input_sk_reasm_test { static void mctp_test_route_input_sk_reasm(struct kunit *test) { const struct mctp_route_input_sk_reasm_test *params; + struct mctp_test_pktqueue tpq; struct sk_buff *skb, *skb2; - struct mctp_test_route *rt; struct mctp_test_dev *dev; + struct mctp_dst dst; struct socket *sock; int i, rc; u8 c; params = test->param_value; - __mctp_route_test_init(test, &dev, &rt, &sock, MCTP_NET_ANY); + __mctp_route_test_init(test, &dev, &dst, &tpq, &sock, MCTP_NET_ANY); for (i = 0; i < params->n_hdrs; i++) { c = i; @@ -431,7 +322,7 @@ static void mctp_test_route_input_sk_reasm(struct kunit *test) mctp_test_skb_set_dev(skb, dev); - rc = mctp_route_input(&rt->rt, skb); + rc = mctp_dst_input(&dst, skb); } skb2 = skb_recv_datagram(sock->sk, MSG_DONTWAIT, &rc); @@ -445,7 +336,7 @@ static void mctp_test_route_input_sk_reasm(struct kunit *test) KUNIT_EXPECT_NULL(test, skb2); } - __mctp_route_test_fini(test, dev, rt, sock); + __mctp_route_test_fini(test, dev, &dst, &tpq, sock); } #define RX_FRAG(f, s) RX_HDR(1, 10, 8, FL_TO | (f) | ((s) << MCTP_HDR_SEQ_SHIFT)) @@ -547,7 +438,7 @@ struct mctp_route_input_sk_keys_test { static void mctp_test_route_input_sk_keys(struct kunit *test) { const struct mctp_route_input_sk_keys_test *params; - struct mctp_test_route *rt; + struct mctp_test_pktqueue tpq; struct sk_buff *skb, *skb2; struct mctp_test_dev *dev; struct mctp_sk_key *key; @@ -555,6 +446,7 @@ static void mctp_test_route_input_sk_keys(struct kunit *test) struct mctp_sock *msk; struct socket *sock; unsigned long flags; + struct mctp_dst dst; unsigned int net; int rc; u8 c; @@ -565,8 +457,7 @@ static void mctp_test_route_input_sk_keys(struct kunit *test) KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); net = READ_ONCE(dev->mdev->net); - rt = mctp_test_create_route(&init_net, dev->mdev, 8, 68); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt); + mctp_test_dst_setup(test, &dst, dev, &tpq, 68); rc = sock_create_kern(&init_net, AF_MCTP, SOCK_DGRAM, 0, &sock); KUNIT_ASSERT_EQ(test, rc, 0); @@ -592,7 +483,7 @@ static void mctp_test_route_input_sk_keys(struct kunit *test) mctp_test_skb_set_dev(skb, dev); - rc = mctp_route_input(&rt->rt, skb); + rc = mctp_dst_input(&dst, skb); /* (potentially) receive message */ skb2 = skb_recv_datagram(sock->sk, MSG_DONTWAIT, &rc); @@ -606,7 +497,7 @@ static void mctp_test_route_input_sk_keys(struct kunit *test) skb_free_datagram(sock->sk, skb2); mctp_key_unref(key); - __mctp_route_test_fini(test, dev, rt, sock); + __mctp_route_test_fini(test, dev, &dst, &tpq, sock); } static const struct mctp_route_input_sk_keys_test mctp_route_input_sk_keys_tests[] = { @@ -681,7 +572,8 @@ KUNIT_ARRAY_PARAM(mctp_route_input_sk_keys, mctp_route_input_sk_keys_tests, struct test_net { unsigned int netid; struct mctp_test_dev *dev; - struct mctp_test_route *rt; + struct mctp_test_pktqueue tpq; + struct mctp_dst dst; struct socket *sock; struct sk_buff *skb; struct mctp_sk_key *key; @@ -699,18 +591,20 @@ mctp_test_route_input_multiple_nets_bind_init(struct kunit *test, t->msg.data = t->netid; - __mctp_route_test_init(test, &t->dev, &t->rt, &t->sock, t->netid); + __mctp_route_test_init(test, &t->dev, &t->dst, &t->tpq, &t->sock, + t->netid); t->skb = mctp_test_create_skb_data(&hdr, &t->msg); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, t->skb); mctp_test_skb_set_dev(t->skb, t->dev); + mctp_test_pktqueue_init(&t->tpq); } static void mctp_test_route_input_multiple_nets_bind_fini(struct kunit *test, struct test_net *t) { - __mctp_route_test_fini(test, t->dev, t->rt, t->sock); + __mctp_route_test_fini(test, t->dev, &t->dst, &t->tpq, t->sock); } /* Test that skbs from different nets (otherwise identical) get routed to their @@ -731,9 +625,9 @@ static void mctp_test_route_input_multiple_nets_bind(struct kunit *test) mctp_test_route_input_multiple_nets_bind_init(test, &t1); mctp_test_route_input_multiple_nets_bind_init(test, &t2); - rc = mctp_route_input(&t1.rt->rt, t1.skb); + rc = mctp_dst_input(&t1.dst, t1.skb); KUNIT_ASSERT_EQ(test, rc, 0); - rc = mctp_route_input(&t2.rt->rt, t2.skb); + rc = mctp_dst_input(&t2.dst, t2.skb); KUNIT_ASSERT_EQ(test, rc, 0); rx_skb1 = skb_recv_datagram(t1.sock->sk, MSG_DONTWAIT, &rc); @@ -767,7 +661,8 @@ mctp_test_route_input_multiple_nets_key_init(struct kunit *test, t->msg.data = t->netid; - __mctp_route_test_init(test, &t->dev, &t->rt, &t->sock, t->netid); + __mctp_route_test_init(test, &t->dev, &t->dst, &t->tpq, &t->sock, + t->netid); msk = container_of(t->sock->sk, struct mctp_sock, sk); @@ -790,7 +685,7 @@ mctp_test_route_input_multiple_nets_key_fini(struct kunit *test, struct test_net *t) { mctp_key_unref(t->key); - __mctp_route_test_fini(test, t->dev, t->rt, t->sock); + __mctp_route_test_fini(test, t->dev, &t->dst, &t->tpq, t->sock); } /* test that skbs from different nets (otherwise identical) get routed to their @@ -812,9 +707,9 @@ static void mctp_test_route_input_multiple_nets_key(struct kunit *test) mctp_test_route_input_multiple_nets_key_init(test, &t1); mctp_test_route_input_multiple_nets_key_init(test, &t2); - rc = mctp_route_input(&t1.rt->rt, t1.skb); + rc = mctp_dst_input(&t1.dst, t1.skb); KUNIT_ASSERT_EQ(test, rc, 0); - rc = mctp_route_input(&t2.rt->rt, t2.skb); + rc = mctp_dst_input(&t2.dst, t2.skb); KUNIT_ASSERT_EQ(test, rc, 0); rx_skb1 = skb_recv_datagram(t1.sock->sk, MSG_DONTWAIT, &rc); @@ -843,13 +738,14 @@ static void mctp_test_route_input_multiple_nets_key(struct kunit *test) static void mctp_test_route_input_sk_fail_single(struct kunit *test) { const struct mctp_hdr hdr = RX_HDR(1, 10, 8, FL_S | FL_E | FL_TO); - struct mctp_test_route *rt; + struct mctp_test_pktqueue tpq; struct mctp_test_dev *dev; + struct mctp_dst dst; struct socket *sock; struct sk_buff *skb; int rc; - __mctp_route_test_init(test, &dev, &rt, &sock, MCTP_NET_ANY); + __mctp_route_test_init(test, &dev, &dst, &tpq, &sock, MCTP_NET_ANY); /* No rcvbuf space, so delivery should fail. __sock_set_rcvbuf will * clamp the minimum to SOCK_MIN_RCVBUF, so we open-code this. @@ -865,14 +761,14 @@ static void mctp_test_route_input_sk_fail_single(struct kunit *test) mctp_test_skb_set_dev(skb, dev); /* do route input, which should fail */ - rc = mctp_route_input(&rt->rt, skb); + rc = mctp_dst_input(&dst, skb); KUNIT_EXPECT_NE(test, rc, 0); /* we should hold the only reference to skb */ KUNIT_EXPECT_EQ(test, refcount_read(&skb->users), 1); kfree_skb(skb); - __mctp_route_test_fini(test, dev, rt, sock); + __mctp_route_test_fini(test, dev, &dst, &tpq, sock); } /* Input route to socket, using a fragmented message, where sock delivery fails. @@ -880,14 +776,15 @@ static void mctp_test_route_input_sk_fail_single(struct kunit *test) static void mctp_test_route_input_sk_fail_frag(struct kunit *test) { const struct mctp_hdr hdrs[2] = { RX_FRAG(FL_S, 0), RX_FRAG(FL_E, 1) }; - struct mctp_test_route *rt; + struct mctp_test_pktqueue tpq; struct mctp_test_dev *dev; struct sk_buff *skbs[2]; + struct mctp_dst dst; struct socket *sock; unsigned int i; int rc; - __mctp_route_test_init(test, &dev, &rt, &sock, MCTP_NET_ANY); + __mctp_route_test_init(test, &dev, &dst, &tpq, &sock, MCTP_NET_ANY); lock_sock(sock->sk); WRITE_ONCE(sock->sk->sk_rcvbuf, 0); @@ -904,11 +801,11 @@ static void mctp_test_route_input_sk_fail_frag(struct kunit *test) /* first route input should succeed, we're only queueing to the * frag list */ - rc = mctp_route_input(&rt->rt, skbs[0]); + rc = mctp_dst_input(&dst, skbs[0]); KUNIT_EXPECT_EQ(test, rc, 0); /* final route input should fail to deliver to the socket */ - rc = mctp_route_input(&rt->rt, skbs[1]); + rc = mctp_dst_input(&dst, skbs[1]); KUNIT_EXPECT_NE(test, rc, 0); /* we should hold the only reference to both skbs */ @@ -918,7 +815,7 @@ static void mctp_test_route_input_sk_fail_frag(struct kunit *test) KUNIT_EXPECT_EQ(test, refcount_read(&skbs[1]->users), 1); kfree_skb(skbs[1]); - __mctp_route_test_fini(test, dev, rt, sock); + __mctp_route_test_fini(test, dev, &dst, &tpq, sock); } /* Input route to socket, using a fragmented message created from clones. @@ -933,23 +830,22 @@ static void mctp_test_route_input_cloned_frag(struct kunit *test) RX_FRAG(FL_S, 0), RX_FRAG(FL_E, 1), }; - struct mctp_test_route *rt; + const size_t data_len = 3; /* arbitrary */ + u8 compare[3 * ARRAY_SIZE(hdrs)]; + u8 flat[3 * ARRAY_SIZE(hdrs)]; + struct mctp_test_pktqueue tpq; struct mctp_test_dev *dev; struct sk_buff *skb[5]; struct sk_buff *rx_skb; + struct mctp_dst dst; struct socket *sock; - size_t data_len; - u8 compare[100]; - u8 flat[100]; size_t total; void *p; int rc; - /* Arbitrary length */ - data_len = 3; total = data_len + sizeof(struct mctp_hdr); - __mctp_route_test_init(test, &dev, &rt, &sock, MCTP_NET_ANY); + __mctp_route_test_init(test, &dev, &dst, &tpq, &sock, MCTP_NET_ANY); /* Create a single skb initially with concatenated packets */ skb[0] = mctp_test_create_skb(&hdrs[0], 5 * total); @@ -988,7 +884,7 @@ static void mctp_test_route_input_cloned_frag(struct kunit *test) /* Feed the fragments into MCTP core */ for (int i = 0; i < 5; i++) { - rc = mctp_route_input(&rt->rt, skb[i]); + rc = mctp_dst_input(&dst, skb[i]); KUNIT_EXPECT_EQ(test, rc, 0); } @@ -1026,29 +922,29 @@ static void mctp_test_route_input_cloned_frag(struct kunit *test) kfree_skb(skb[i]); } - __mctp_route_test_fini(test, dev, rt, sock); + __mctp_route_test_fini(test, dev, &dst, &tpq, sock); } #if IS_ENABLED(CONFIG_MCTP_FLOWS) static void mctp_test_flow_init(struct kunit *test, struct mctp_test_dev **devp, - struct mctp_test_route **rtp, + struct mctp_dst *dst, + struct mctp_test_pktqueue *tpq, struct socket **sock, struct sk_buff **skbp, unsigned int len) { - struct mctp_test_route *rt; struct mctp_test_dev *dev; struct sk_buff *skb; /* we have a slightly odd routing setup here; the test route * is for EID 8, which is our local EID. We don't do a routing * lookup, so that's fine - all we require is a path through - * mctp_local_output, which will call rt->output on whatever + * mctp_local_output, which will call dst->output on whatever * route we provide */ - __mctp_route_test_init(test, &dev, &rt, sock, MCTP_NET_ANY); + __mctp_route_test_init(test, &dev, dst, tpq, sock, MCTP_NET_ANY); /* Assign a single EID. ->addrs is freed on mctp netdev release */ dev->mdev->addrs = kmalloc(sizeof(u8), GFP_KERNEL); @@ -1061,42 +957,41 @@ static void mctp_test_flow_init(struct kunit *test, skb_reserve(skb, sizeof(struct mctp_hdr) + 1); memset(skb_put(skb, len), 0, len); - /* take a ref for the route, we'll decrement in local output */ - refcount_inc(&rt->rt.refs); *devp = dev; - *rtp = rt; *skbp = skb; } static void mctp_test_flow_fini(struct kunit *test, struct mctp_test_dev *dev, - struct mctp_test_route *rt, + struct mctp_dst *dst, + struct mctp_test_pktqueue *tpq, struct socket *sock) { - __mctp_route_test_fini(test, dev, rt, sock); + __mctp_route_test_fini(test, dev, dst, tpq, sock); } /* test that an outgoing skb has the correct MCTP extension data set */ static void mctp_test_packet_flow(struct kunit *test) { + struct mctp_test_pktqueue tpq; struct sk_buff *skb, *skb2; - struct mctp_test_route *rt; struct mctp_test_dev *dev; + struct mctp_dst dst; struct mctp_flow *flow; struct socket *sock; - u8 dst = 8; + u8 dst_eid = 8; int n, rc; - mctp_test_flow_init(test, &dev, &rt, &sock, &skb, 30); + mctp_test_flow_init(test, &dev, &dst, &tpq, &sock, &skb, 30); - rc = mctp_local_output(sock->sk, &rt->rt, skb, dst, MCTP_TAG_OWNER); + rc = mctp_local_output(sock->sk, &dst, skb, dst_eid, MCTP_TAG_OWNER); KUNIT_ASSERT_EQ(test, rc, 0); - n = rt->pkts.qlen; + n = tpq.pkts.qlen; KUNIT_ASSERT_EQ(test, n, 1); - skb2 = skb_dequeue(&rt->pkts); + skb2 = skb_dequeue(&tpq.pkts); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, skb2); flow = skb_ext_find(skb2, SKB_EXT_MCTP); @@ -1105,7 +1000,7 @@ static void mctp_test_packet_flow(struct kunit *test) KUNIT_ASSERT_PTR_EQ(test, flow->key->sk, sock->sk); kfree_skb(skb2); - mctp_test_flow_fini(test, dev, rt, sock); + mctp_test_flow_fini(test, dev, &dst, &tpq, sock); } /* test that outgoing skbs, after fragmentation, all have the correct MCTP @@ -1113,26 +1008,27 @@ static void mctp_test_packet_flow(struct kunit *test) */ static void mctp_test_fragment_flow(struct kunit *test) { + struct mctp_test_pktqueue tpq; struct mctp_flow *flows[2]; struct sk_buff *tx_skbs[2]; - struct mctp_test_route *rt; struct mctp_test_dev *dev; + struct mctp_dst dst; struct sk_buff *skb; struct socket *sock; - u8 dst = 8; + u8 dst_eid = 8; int n, rc; - mctp_test_flow_init(test, &dev, &rt, &sock, &skb, 100); + mctp_test_flow_init(test, &dev, &dst, &tpq, &sock, &skb, 100); - rc = mctp_local_output(sock->sk, &rt->rt, skb, dst, MCTP_TAG_OWNER); + rc = mctp_local_output(sock->sk, &dst, skb, dst_eid, MCTP_TAG_OWNER); KUNIT_ASSERT_EQ(test, rc, 0); - n = rt->pkts.qlen; + n = tpq.pkts.qlen; KUNIT_ASSERT_EQ(test, n, 2); /* both resulting packets should have the same flow data */ - tx_skbs[0] = skb_dequeue(&rt->pkts); - tx_skbs[1] = skb_dequeue(&rt->pkts); + tx_skbs[0] = skb_dequeue(&tpq.pkts); + tx_skbs[1] = skb_dequeue(&tpq.pkts); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, tx_skbs[0]); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, tx_skbs[1]); @@ -1148,7 +1044,7 @@ static void mctp_test_fragment_flow(struct kunit *test) kfree_skb(tx_skbs[0]); kfree_skb(tx_skbs[1]); - mctp_test_flow_fini(test, dev, rt, sock); + mctp_test_flow_fini(test, dev, &dst, &tpq, sock); } #else @@ -1166,15 +1062,16 @@ static void mctp_test_fragment_flow(struct kunit *test) /* Test that outgoing skbs cause a suitable tag to be created */ static void mctp_test_route_output_key_create(struct kunit *test) { + const u8 dst_eid = 26, src_eid = 15; + struct mctp_test_pktqueue tpq; const unsigned int netid = 50; - const u8 dst = 26, src = 15; - struct mctp_test_route *rt; struct mctp_test_dev *dev; struct mctp_sk_key *key; struct netns_mctp *mns; unsigned long flags; struct socket *sock; struct sk_buff *skb; + struct mctp_dst dst; bool empty, single; const int len = 2; int rc; @@ -1183,15 +1080,14 @@ static void mctp_test_route_output_key_create(struct kunit *test) KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); WRITE_ONCE(dev->mdev->net, netid); - rt = mctp_test_create_route(&init_net, dev->mdev, dst, 68); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt); + mctp_test_dst_setup(test, &dst, dev, &tpq, 68); rc = sock_create_kern(&init_net, AF_MCTP, SOCK_DGRAM, 0, &sock); KUNIT_ASSERT_EQ(test, rc, 0); dev->mdev->addrs = kmalloc(sizeof(u8), GFP_KERNEL); dev->mdev->num_addrs = 1; - dev->mdev->addrs[0] = src; + dev->mdev->addrs[0] = src_eid; skb = alloc_skb(sizeof(struct mctp_hdr) + 1 + len, GFP_KERNEL); KUNIT_ASSERT_TRUE(test, skb); @@ -1199,8 +1095,6 @@ static void mctp_test_route_output_key_create(struct kunit *test) skb_reserve(skb, sizeof(struct mctp_hdr) + 1 + len); memset(skb_put(skb, len), 0, len); - refcount_inc(&rt->rt.refs); - mns = &sock_net(sock->sk)->mctp; /* We assume we're starting from an empty keys list, which requires @@ -1211,7 +1105,7 @@ static void mctp_test_route_output_key_create(struct kunit *test) spin_unlock_irqrestore(&mns->keys_lock, flags); KUNIT_ASSERT_TRUE(test, empty); - rc = mctp_local_output(sock->sk, &rt->rt, skb, dst, MCTP_TAG_OWNER); + rc = mctp_local_output(sock->sk, &dst, skb, dst_eid, MCTP_TAG_OWNER); KUNIT_ASSERT_EQ(test, rc, 0); key = NULL; @@ -1227,16 +1121,480 @@ static void mctp_test_route_output_key_create(struct kunit *test) KUNIT_ASSERT_TRUE(test, single); KUNIT_EXPECT_EQ(test, key->net, netid); - KUNIT_EXPECT_EQ(test, key->local_addr, src); - KUNIT_EXPECT_EQ(test, key->peer_addr, dst); + KUNIT_EXPECT_EQ(test, key->local_addr, src_eid); + KUNIT_EXPECT_EQ(test, key->peer_addr, dst_eid); /* key has incoming tag, so inverse of what we sent */ KUNIT_EXPECT_FALSE(test, key->tag & MCTP_TAG_OWNER); sock_release(sock); - mctp_test_route_destroy(test, rt); + mctp_test_dst_release(&dst, &tpq); mctp_test_destroy_dev(dev); } +static void mctp_test_route_extaddr_input(struct kunit *test) +{ + static const unsigned char haddr[] = { 0xaa, 0x55 }; + struct mctp_test_pktqueue tpq; + struct mctp_skb_cb *cb, *cb2; + const unsigned int len = 40; + struct mctp_test_dev *dev; + struct sk_buff *skb, *skb2; + struct mctp_dst dst; + struct mctp_hdr hdr; + struct socket *sock; + int rc; + + hdr.ver = 1; + hdr.src = 10; + hdr.dest = 8; + hdr.flags_seq_tag = FL_S | FL_E | FL_TO; + + __mctp_route_test_init(test, &dev, &dst, &tpq, &sock, MCTP_NET_ANY); + + skb = mctp_test_create_skb(&hdr, len); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, skb); + + /* set our hardware addressing data */ + cb = mctp_cb(skb); + memcpy(cb->haddr, haddr, sizeof(haddr)); + cb->halen = sizeof(haddr); + + mctp_test_skb_set_dev(skb, dev); + + rc = mctp_dst_input(&dst, skb); + KUNIT_ASSERT_EQ(test, rc, 0); + + skb2 = skb_recv_datagram(sock->sk, MSG_DONTWAIT, &rc); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, skb2); + KUNIT_ASSERT_EQ(test, skb2->len, len); + + cb2 = mctp_cb(skb2); + + /* Received SKB should have the hardware addressing as set above. + * We're likely to have the same actual cb here (ie., cb == cb2), + * but it's the comparison that we care about + */ + KUNIT_EXPECT_EQ(test, cb2->halen, sizeof(haddr)); + KUNIT_EXPECT_MEMEQ(test, cb2->haddr, haddr, sizeof(haddr)); + + kfree_skb(skb2); + __mctp_route_test_fini(test, dev, &dst, &tpq, sock); +} + +static void mctp_test_route_gw_lookup(struct kunit *test) +{ + struct mctp_test_route *rt1, *rt2; + struct mctp_dst dst = { 0 }; + struct mctp_test_dev *dev; + int rc; + + dev = mctp_test_create_dev(); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + /* 8 (local) -> 10 (gateway) via 9 (direct) */ + rt1 = mctp_test_create_route_direct(&init_net, dev->mdev, 9, 0); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt1); + rt2 = mctp_test_create_route_gw(&init_net, dev->mdev->net, 10, 9, 0); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt2); + + rc = mctp_route_lookup(&init_net, dev->mdev->net, 10, &dst); + KUNIT_EXPECT_EQ(test, rc, 0); + KUNIT_EXPECT_PTR_EQ(test, dst.dev, dev->mdev); + KUNIT_EXPECT_EQ(test, dst.mtu, dev->ndev->mtu); + KUNIT_EXPECT_EQ(test, dst.nexthop, 9); + KUNIT_EXPECT_EQ(test, dst.halen, 0); + + mctp_dst_release(&dst); + + mctp_test_route_destroy(test, rt2); + mctp_test_route_destroy(test, rt1); + mctp_test_destroy_dev(dev); +} + +static void mctp_test_route_gw_loop(struct kunit *test) +{ + struct mctp_test_route *rt1, *rt2; + struct mctp_dst dst = { 0 }; + struct mctp_test_dev *dev; + int rc; + + dev = mctp_test_create_dev(); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + + /* two routes using each other as the gw */ + rt1 = mctp_test_create_route_gw(&init_net, dev->mdev->net, 9, 10, 0); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt1); + rt2 = mctp_test_create_route_gw(&init_net, dev->mdev->net, 10, 9, 0); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt2); + + /* this should fail, rather than infinite-loop */ + rc = mctp_route_lookup(&init_net, dev->mdev->net, 10, &dst); + KUNIT_EXPECT_NE(test, rc, 0); + + mctp_test_route_destroy(test, rt2); + mctp_test_route_destroy(test, rt1); + mctp_test_destroy_dev(dev); +} + +struct mctp_route_gw_mtu_test { + /* working away from the local stack */ + unsigned int dev, neigh, gw, dst; + unsigned int exp; +}; + +static void mctp_route_gw_mtu_to_desc(const struct mctp_route_gw_mtu_test *t, + char *desc) +{ + sprintf(desc, "dev %d, neigh %d, gw %d, dst %d -> %d", + t->dev, t->neigh, t->gw, t->dst, t->exp); +} + +static const struct mctp_route_gw_mtu_test mctp_route_gw_mtu_tests[] = { + /* no route-specific MTUs */ + { 68, 0, 0, 0, 68 }, + { 100, 0, 0, 0, 100 }, + /* one route MTU (smaller than dev mtu), others unrestricted */ + { 100, 68, 0, 0, 68 }, + { 100, 0, 68, 0, 68 }, + { 100, 0, 0, 68, 68 }, + /* smallest applied, regardless of order */ + { 100, 99, 98, 68, 68 }, + { 99, 100, 98, 68, 68 }, + { 98, 99, 100, 68, 68 }, + { 68, 98, 99, 100, 68 }, +}; + +KUNIT_ARRAY_PARAM(mctp_route_gw_mtu, mctp_route_gw_mtu_tests, + mctp_route_gw_mtu_to_desc); + +static void mctp_test_route_gw_mtu(struct kunit *test) +{ + const struct mctp_route_gw_mtu_test *mtus = test->param_value; + struct mctp_test_route *rt1, *rt2, *rt3; + struct mctp_dst dst = { 0 }; + struct mctp_test_dev *dev; + struct mctp_dev *mdev; + unsigned int netid; + int rc; + + dev = mctp_test_create_dev(); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + dev->ndev->mtu = mtus->dev; + mdev = dev->mdev; + netid = mdev->net; + + /* 8 (local) -> 11 (dst) via 10 (gw) via 9 (neigh) */ + rt1 = mctp_test_create_route_direct(&init_net, mdev, 9, mtus->neigh); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt1); + + rt2 = mctp_test_create_route_gw(&init_net, netid, 10, 9, mtus->gw); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt2); + + rt3 = mctp_test_create_route_gw(&init_net, netid, 11, 10, mtus->dst); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rt3); + + rc = mctp_route_lookup(&init_net, dev->mdev->net, 11, &dst); + KUNIT_EXPECT_EQ(test, rc, 0); + KUNIT_EXPECT_EQ(test, dst.mtu, mtus->exp); + + mctp_dst_release(&dst); + + mctp_test_route_destroy(test, rt3); + mctp_test_route_destroy(test, rt2); + mctp_test_route_destroy(test, rt1); + mctp_test_destroy_dev(dev); +} + +#define MCTP_TEST_LLADDR_LEN 2 +struct mctp_test_llhdr { + unsigned int magic; + unsigned char src[MCTP_TEST_LLADDR_LEN]; + unsigned char dst[MCTP_TEST_LLADDR_LEN]; +}; + +static const unsigned int mctp_test_llhdr_magic = 0x5c78339c; + +static int test_dev_header_create(struct sk_buff *skb, struct net_device *dev, + unsigned short type, const void *daddr, + const void *saddr, unsigned int len) +{ + struct kunit *test = current->kunit_test; + struct mctp_test_llhdr *hdr; + + hdr = skb_push(skb, sizeof(*hdr)); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, hdr); + skb_reset_mac_header(skb); + + hdr->magic = mctp_test_llhdr_magic; + memcpy(&hdr->src, saddr, sizeof(hdr->src)); + memcpy(&hdr->dst, daddr, sizeof(hdr->dst)); + + return 0; +} + +/* Test the dst_output path for a gateway-routed skb: we should have it + * lookup the nexthop EID in the neighbour table, and call into + * header_ops->create to resolve that to a lladdr. Our mock header_ops->create + * will just set a synthetic link-layer header, which we check after transmit. + */ +static void mctp_test_route_gw_output(struct kunit *test) +{ + const unsigned char haddr_self[MCTP_TEST_LLADDR_LEN] = { 0xaa, 0x03 }; + const unsigned char haddr_peer[MCTP_TEST_LLADDR_LEN] = { 0xaa, 0x02 }; + const struct header_ops ops = { + .create = test_dev_header_create, + }; + struct mctp_neigh neigh = { 0 }; + struct mctp_test_llhdr *ll_hdr; + struct mctp_dst dst = { 0 }; + struct mctp_hdr hdr = { 0 }; + struct mctp_test_dev *dev; + struct sk_buff *skb; + unsigned char *buf; + int i, rc; + + dev = mctp_test_create_dev_lladdr(sizeof(haddr_self), haddr_self); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + dev->ndev->header_ops = &ops; + + dst.dev = dev->mdev; + __mctp_dev_get(dst.dev->dev); + dst.mtu = 68; + dst.nexthop = 9; + + /* simple mctp_neigh_add for the gateway (not dest!) endpoint */ + INIT_LIST_HEAD(&neigh.list); + neigh.dev = dev->mdev; + mctp_dev_hold(dev->mdev); + neigh.eid = 9; + neigh.source = MCTP_NEIGH_STATIC; + memcpy(neigh.ha, haddr_peer, sizeof(haddr_peer)); + list_add_rcu(&neigh.list, &init_net.mctp.neighbours); + + hdr.ver = 1; + hdr.src = 8; + hdr.dest = 10; + hdr.flags_seq_tag = FL_S | FL_E | FL_TO; + + /* construct enough for a future link-layer header, the provided + * mctp header, and 4 bytes of data + */ + skb = alloc_skb(sizeof(*ll_hdr) + sizeof(hdr) + 4, GFP_KERNEL); + skb->dev = dev->ndev; + __mctp_cb(skb); + + skb_reserve(skb, sizeof(*ll_hdr)); + + memcpy(skb_put(skb, sizeof(hdr)), &hdr, sizeof(hdr)); + buf = skb_put(skb, 4); + for (i = 0; i < 4; i++) + buf[i] = i; + + /* extra ref over the dev_xmit */ + skb_get(skb); + + rc = mctp_dst_output(&dst, skb); + KUNIT_EXPECT_EQ(test, rc, 0); + + mctp_dst_release(&dst); + list_del_rcu(&neigh.list); + mctp_dev_put(dev->mdev); + + /* check that we have our header created with the correct neighbour */ + ll_hdr = (void *)skb_mac_header(skb); + KUNIT_EXPECT_EQ(test, ll_hdr->magic, mctp_test_llhdr_magic); + KUNIT_EXPECT_MEMEQ(test, ll_hdr->src, haddr_self, sizeof(haddr_self)); + KUNIT_EXPECT_MEMEQ(test, ll_hdr->dst, haddr_peer, sizeof(haddr_peer)); + kfree_skb(skb); +} + +struct mctp_bind_lookup_test { + /* header of incoming message */ + struct mctp_hdr hdr; + u8 ty; + /* mctp network of incoming interface (smctp_network) */ + unsigned int net; + + /* expected socket, matches .name in lookup_binds, NULL for dropped */ + const char *expect; +}; + +/* Single-packet TO-set message */ +#define LK(src, dst) RX_HDR(1, (src), (dst), FL_S | FL_E | FL_TO) + +/* Input message test cases for bind lookup tests. + * + * 10 and 11 are local EIDs. + * 20 and 21 are remote EIDs. + */ +static const struct mctp_bind_lookup_test mctp_bind_lookup_tests[] = { + /* both local-eid and remote-eid binds, remote eid is preferenced */ + { .hdr = LK(20, 10), .ty = 1, .net = 1, .expect = "remote20" }, + + { .hdr = LK(20, 255), .ty = 1, .net = 1, .expect = "remote20" }, + { .hdr = LK(20, 0), .ty = 1, .net = 1, .expect = "remote20" }, + { .hdr = LK(0, 255), .ty = 1, .net = 1, .expect = "any" }, + { .hdr = LK(0, 11), .ty = 1, .net = 1, .expect = "any" }, + { .hdr = LK(0, 0), .ty = 1, .net = 1, .expect = "any" }, + { .hdr = LK(0, 10), .ty = 1, .net = 1, .expect = "local10" }, + { .hdr = LK(21, 10), .ty = 1, .net = 1, .expect = "local10" }, + { .hdr = LK(21, 11), .ty = 1, .net = 1, .expect = "remote21local11" }, + + /* both src and dest set to eid=99. unusual, but accepted + * by MCTP stack currently. + */ + { .hdr = LK(99, 99), .ty = 1, .net = 1, .expect = "any" }, + + /* unbound smctp_type */ + { .hdr = LK(20, 10), .ty = 3, .net = 1, .expect = NULL }, + + /* smctp_network tests */ + + { .hdr = LK(0, 0), .ty = 1, .net = 7, .expect = "any" }, + { .hdr = LK(21, 10), .ty = 1, .net = 2, .expect = "any" }, + + /* remote EID 20 matches, but MCTP_NET_ANY in "remote20" resolved + * to net=1, so lookup doesn't match "remote20" + */ + { .hdr = LK(20, 10), .ty = 1, .net = 3, .expect = "any" }, + + { .hdr = LK(21, 10), .ty = 1, .net = 3, .expect = "remote21net3" }, + { .hdr = LK(21, 10), .ty = 1, .net = 4, .expect = "remote21net4" }, + { .hdr = LK(21, 10), .ty = 1, .net = 5, .expect = "remote21net5" }, + + { .hdr = LK(21, 10), .ty = 1, .net = 5, .expect = "remote21net5" }, + + { .hdr = LK(99, 10), .ty = 1, .net = 8, .expect = "local10net8" }, + + { .hdr = LK(99, 10), .ty = 1, .net = 9, .expect = "anynet9" }, + { .hdr = LK(0, 0), .ty = 1, .net = 9, .expect = "anynet9" }, + { .hdr = LK(99, 99), .ty = 1, .net = 9, .expect = "anynet9" }, + { .hdr = LK(20, 10), .ty = 1, .net = 9, .expect = "anynet9" }, +}; + +/* Binds to create during the lookup tests */ +static const struct mctp_test_bind_setup lookup_binds[] = { + /* any address and net, type 1 */ + { .name = "any", .bind_addr = MCTP_ADDR_ANY, + .bind_net = MCTP_NET_ANY, .bind_type = 1, }, + /* local eid 10, net 1 (resolved from MCTP_NET_ANY) */ + { .name = "local10", .bind_addr = 10, + .bind_net = MCTP_NET_ANY, .bind_type = 1, }, + /* local eid 10, net 8 */ + { .name = "local10net8", .bind_addr = 10, + .bind_net = 8, .bind_type = 1, }, + /* any EID, net 9 */ + { .name = "anynet9", .bind_addr = MCTP_ADDR_ANY, + .bind_net = 9, .bind_type = 1, }, + + /* remote eid 20, net 1, any local eid */ + { .name = "remote20", .bind_addr = MCTP_ADDR_ANY, + .bind_net = MCTP_NET_ANY, .bind_type = 1, + .have_peer = true, .peer_addr = 20, .peer_net = MCTP_NET_ANY, }, + + /* remote eid 20, net 1, local eid 11 */ + { .name = "remote21local11", .bind_addr = 11, + .bind_net = MCTP_NET_ANY, .bind_type = 1, + .have_peer = true, .peer_addr = 21, .peer_net = MCTP_NET_ANY, }, + + /* remote eid 21, specific net=3 for connect() */ + { .name = "remote21net3", .bind_addr = MCTP_ADDR_ANY, + .bind_net = MCTP_NET_ANY, .bind_type = 1, + .have_peer = true, .peer_addr = 21, .peer_net = 3, }, + + /* remote eid 21, net 4 for bind, specific net=4 for connect() */ + { .name = "remote21net4", .bind_addr = MCTP_ADDR_ANY, + .bind_net = 4, .bind_type = 1, + .have_peer = true, .peer_addr = 21, .peer_net = 4, }, + + /* remote eid 21, net 5 for bind, specific net=5 for connect() */ + { .name = "remote21net5", .bind_addr = MCTP_ADDR_ANY, + .bind_net = 5, .bind_type = 1, + .have_peer = true, .peer_addr = 21, .peer_net = 5, }, +}; + +static void mctp_bind_lookup_desc(const struct mctp_bind_lookup_test *t, + char *desc) +{ + snprintf(desc, KUNIT_PARAM_DESC_SIZE, + "{src %d dst %d ty %d net %d expect %s}", + t->hdr.src, t->hdr.dest, t->ty, t->net, t->expect); +} + +KUNIT_ARRAY_PARAM(mctp_bind_lookup, mctp_bind_lookup_tests, + mctp_bind_lookup_desc); + +static void mctp_test_bind_lookup(struct kunit *test) +{ + const struct mctp_bind_lookup_test *rx; + struct socket *socks[ARRAY_SIZE(lookup_binds)]; + struct sk_buff *skb_pkt = NULL, *skb_sock = NULL; + struct socket *sock_ty0, *sock_expect = NULL; + struct mctp_test_pktqueue tpq; + struct mctp_test_dev *dev; + struct mctp_dst dst; + int rc; + + rx = test->param_value; + + __mctp_route_test_init(test, &dev, &dst, &tpq, &sock_ty0, rx->net); + /* Create all binds */ + for (size_t i = 0; i < ARRAY_SIZE(lookup_binds); i++) { + mctp_test_bind_run(test, &lookup_binds[i], + &rc, &socks[i]); + KUNIT_ASSERT_EQ(test, rc, 0); + + /* Record the expected receive socket */ + if (rx->expect && + strcmp(rx->expect, lookup_binds[i].name) == 0) { + KUNIT_ASSERT_NULL(test, sock_expect); + sock_expect = socks[i]; + } + } + KUNIT_ASSERT_EQ(test, !!sock_expect, !!rx->expect); + + /* Create test message */ + skb_pkt = mctp_test_create_skb_data(&rx->hdr, &rx->ty); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, skb_pkt); + mctp_test_skb_set_dev(skb_pkt, dev); + mctp_test_pktqueue_init(&tpq); + + rc = mctp_dst_input(&dst, skb_pkt); + if (rx->expect) { + /* Test the message is received on the expected socket */ + KUNIT_EXPECT_EQ(test, rc, 0); + skb_sock = skb_recv_datagram(sock_expect->sk, + MSG_DONTWAIT, &rc); + if (!skb_sock) { + /* Find which socket received it instead */ + for (size_t i = 0; i < ARRAY_SIZE(lookup_binds); i++) { + skb_sock = skb_recv_datagram(socks[i]->sk, + MSG_DONTWAIT, &rc); + if (skb_sock) { + KUNIT_FAIL(test, + "received on incorrect socket '%s', expect '%s'", + lookup_binds[i].name, + rx->expect); + goto cleanup; + } + } + KUNIT_FAIL(test, "no message received"); + } + } else { + KUNIT_EXPECT_NE(test, rc, 0); + } + +cleanup: + kfree_skb(skb_sock); + kfree_skb(skb_pkt); + + /* Drop all binds */ + for (size_t i = 0; i < ARRAY_SIZE(lookup_binds); i++) + sock_release(socks[i]); + + __mctp_route_test_fini(test, dev, &dst, &tpq, sock_ty0); +} + static struct kunit_case mctp_test_cases[] = { KUNIT_CASE_PARAM(mctp_test_fragment, mctp_frag_gen_params), KUNIT_CASE_PARAM(mctp_test_rx_input, mctp_rx_input_gen_params), @@ -1253,11 +1611,17 @@ static struct kunit_case mctp_test_cases[] = { KUNIT_CASE(mctp_test_fragment_flow), KUNIT_CASE(mctp_test_route_output_key_create), KUNIT_CASE(mctp_test_route_input_cloned_frag), + KUNIT_CASE(mctp_test_route_extaddr_input), + KUNIT_CASE(mctp_test_route_gw_lookup), + KUNIT_CASE(mctp_test_route_gw_loop), + KUNIT_CASE_PARAM(mctp_test_route_gw_mtu, mctp_route_gw_mtu_gen_params), + KUNIT_CASE(mctp_test_route_gw_output), + KUNIT_CASE_PARAM(mctp_test_bind_lookup, mctp_bind_lookup_gen_params), {} }; static struct kunit_suite mctp_test_suite = { - .name = "mctp", + .name = "mctp-route", .test_cases = mctp_test_cases, }; diff --git a/net/mctp/test/sock-test.c b/net/mctp/test/sock-test.c new file mode 100644 index 000000000000..b0942deb5019 --- /dev/null +++ b/net/mctp/test/sock-test.c @@ -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); diff --git a/net/mctp/test/utils.c b/net/mctp/test/utils.c index 565763eb0211..953d41902771 100644 --- a/net/mctp/test/utils.c +++ b/net/mctp/test/utils.c @@ -26,19 +26,22 @@ 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->addr_len = 0; ndev->tx_queue_len = DEFAULT_TX_QUEUE_LEN; ndev->flags = IFF_NOARP; ndev->netdev_ops = &mctp_test_netdev_ops; ndev->needs_free_netdev = true; } -struct mctp_test_dev *mctp_test_create_dev(void) +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) @@ -46,6 +49,8 @@ struct mctp_test_dev *mctp_test_create_dev(void) dev = netdev_priv(ndev); dev->ndev = ndev; + ndev->addr_len = lladdr_len; + dev_addr_set(ndev, lladdr); rc = register_netdev(ndev); if (rc) { @@ -61,8 +66,231 @@ struct mctp_test_dev *mctp_test_create_dev(void) 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_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) { mctp_dev_put(dev->mdev); unregister_netdev(dev->ndev); } + +static const unsigned int test_pktqueue_magic = 0x5f713aef; + +void mctp_test_pktqueue_init(struct mctp_test_pktqueue *tpq) +{ + tpq->magic = test_pktqueue_magic; + skb_queue_head_init(&tpq->pkts); +} + +static int mctp_test_dst_output(struct mctp_dst *dst, struct sk_buff *skb) +{ + struct kunit *test = current->kunit_test; + struct mctp_test_pktqueue *tpq = test->priv; + + KUNIT_ASSERT_EQ(test, tpq->magic, test_pktqueue_magic); + + skb_queue_tail(&tpq->pkts, skb); + + 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(sizeof(*rt), GFP_KERNEL); + 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_test_dst_release() + */ +void mctp_test_dst_setup(struct kunit *test, struct mctp_dst *dst, + struct mctp_test_dev *dev, + struct mctp_test_pktqueue *tpq, unsigned int mtu) +{ + 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; + mctp_test_pktqueue_init(tpq); + test->priv = tpq; +} + +void mctp_test_dst_release(struct mctp_dst *dst, + struct mctp_test_pktqueue *tpq) +{ + mctp_dst_release(dst); + skb_queue_purge(&tpq->pkts); +} + +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 *)&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 *)&addr, sizeof(addr)); +} diff --git a/net/mctp/test/utils.h b/net/mctp/test/utils.h index df6aa1c03440..06bdb6cb5eff 100644 --- a/net/mctp/test/utils.h +++ b/net/mctp/test/utils.h @@ -3,6 +3,11 @@ #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 @@ -10,11 +15,67 @@ struct mctp_test_dev { struct net_device *ndev; struct mctp_dev *mdev; + + unsigned short lladdr_len; + unsigned char lladdr[MAX_ADDR_LEN]; }; struct mctp_test_dev; +struct mctp_test_route { + struct mctp_route rt; +}; + +struct mctp_test_pktqueue { + unsigned int magic; + struct sk_buff_head pkts; +}; + +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_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, + struct mctp_test_pktqueue *tpq, unsigned int mtu); +void mctp_test_dst_release(struct mctp_dst *dst, + struct mctp_test_pktqueue *tpq); +void mctp_test_pktqueue_init(struct mctp_test_pktqueue *tpq); +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 */ |