Merge branch 'net-mctp-add-support-for-gateway-routing'

Jeremy Kerr says:

====================
net: mctp: Add support for gateway routing

This series adds a gateway route type for the MCTP core, allowing
non-local EIDs as the match for a route.

Example setup using the mctp tools:

    mctp route add 9 via mctpi2c0
    mctp neigh add 9 dev mctpi2c0 lladdr 0x1d
    mctp route add 10 gw 9

- will route packets to eid 10 through mctpi2c0, using a dest lladdr
of 0x1d (ie, that of the directly-attached eid 9).

The core change to support this is the introduction of a struct
mctp_dst, which represents the result of a route lookup. Since this
involves a bit of surgery through the routing code, we add a few tests
along the way.

We're introducing an ABI change in the new RTM_{NEW,GET,DEL}ROUTE
netlink formats, with the support for a RTA_GATEWAY attribute. Because
we need a network ID specified to fully-qualify a gateway EID, the
RTA_GATEWAY attribute carries the (net, eid) tuple in full:

    struct mctp_fq_addr {
        unsigned int net;
        mctp_eid_t eid;
    }

Of course, any questions, comments etc are most welcome.

Signed-off-by: Jeremy Kerr <jk@codeconstruct.com.au>
====================

Link: https://patch.msgid.link/20250702-dev-forwarding-v5-0-1468191da8a4@codeconstruct.com.au
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Paolo Abeni 2025-07-08 12:41:45 +02:00
commit d23647fd54
8 changed files with 1331 additions and 464 deletions

View File

@ -183,8 +183,8 @@ struct mctp_sk_key {
struct mctp_skb_cb {
unsigned int magic;
unsigned int net;
int ifindex; /* extended/direct addressing if set */
mctp_eid_t src;
/* fields below provide extended addressing for ingress to recvmsg() */
int ifindex;
unsigned char halen;
unsigned char haddr[MAX_ADDR_LEN];
};
@ -222,6 +222,8 @@ struct mctp_flow {
struct mctp_sk_key *key;
};
struct mctp_dst;
/* Route definition.
*
* These are held in the pernet->mctp.routes list, with RCU protection for
@ -229,16 +231,25 @@ struct mctp_flow {
* dropped on NETDEV_UNREGISTER events.
*
* Updates to the route table are performed under rtnl; all reads under RCU,
* so routes cannot be referenced over a RCU grace period. Specifically: A
* caller cannot block between mctp_route_lookup and mctp_route_release()
* so routes cannot be referenced over a RCU grace period.
*/
struct mctp_route {
mctp_eid_t min, max;
unsigned char type;
unsigned int mtu;
struct mctp_dev *dev;
int (*output)(struct mctp_route *route,
enum {
MCTP_ROUTE_DIRECT,
MCTP_ROUTE_GATEWAY,
} dst_type;
union {
struct mctp_dev *dev;
struct mctp_fq_addr gateway;
};
int (*output)(struct mctp_dst *dst,
struct sk_buff *skb);
struct list_head list;
@ -246,12 +257,35 @@ struct mctp_route {
struct rcu_head rcu;
};
/* Route lookup result: dst. Represents the results of a routing decision,
* but is only held over the individual routing operation.
*
* Will typically be stored on the caller stack, and must be released after
* usage.
*/
struct mctp_dst {
struct mctp_dev *dev;
unsigned int mtu;
mctp_eid_t nexthop;
/* set for direct addressing */
unsigned char halen;
unsigned char haddr[MAX_ADDR_LEN];
int (*output)(struct mctp_dst *dst, struct sk_buff *skb);
};
int mctp_dst_from_extaddr(struct mctp_dst *dst, struct net *net, int ifindex,
unsigned char halen, const unsigned char *haddr);
/* route interfaces */
struct mctp_route *mctp_route_lookup(struct net *net, unsigned int dnet,
mctp_eid_t daddr);
int mctp_route_lookup(struct net *net, unsigned int dnet,
mctp_eid_t daddr, struct mctp_dst *dst);
void mctp_dst_release(struct mctp_dst *dst);
/* always takes ownership of skb */
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);
void mctp_key_unref(struct mctp_sk_key *key);

View File

@ -37,6 +37,14 @@ struct sockaddr_mctp_ext {
__u8 smctp_haddr[MAX_ADDR_LEN];
};
/* A "fully qualified" MCTP address, which includes the system-local network ID,
* required to uniquely resolve a routable EID.
*/
struct mctp_fq_addr {
unsigned int net;
mctp_eid_t eid;
};
#define MCTP_NET_ANY 0x0
#define MCTP_ADDR_NULL 0x00

View File

@ -97,8 +97,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 +133,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 +171,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;
}
@ -793,3 +775,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

View File

@ -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,7 +34,7 @@ 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;
@ -368,7 +370,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 +394,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 +561,31 @@ static int mctp_route_input(struct mctp_route *route, struct sk_buff *skb)
return rc;
}
static unsigned int mctp_route_mtu(struct mctp_route *rt)
static int mctp_dst_output(struct mctp_dst *dst, struct sk_buff *skb)
{
return rt->mtu ?: READ_ONCE(rt->dev->dev->mtu);
}
static int mctp_route_output(struct mctp_route *route, 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 +596,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 +609,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 +626,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 +799,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 +817,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)
{
struct mctp_route *tmp, *rt = NULL;
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;
}
}
}
rcu_read_unlock();
return rt;
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;
}
static struct mctp_route *mctp_route_lookup_null(struct net *net,
struct net_device *dev)
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) {
if (tmp->dev->dev == dev && tmp->type == RTN_LOCAL &&
refcount_inc_not_zero(&tmp->refs)) {
rt = tmp;
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;
}
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;
}
/* 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)
{
const unsigned int max_depth = 32;
unsigned int depth, mtu = 0;
int rc = -EHOSTUNREACH;
rcu_read_lock();
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_do_fragment_route(struct mctp_route *rt, struct sk_buff *skb,
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_dst *dst, struct sk_buff *skb,
unsigned int mtu, u8 tag)
{
const unsigned int hlen = sizeof(struct mctp_hdr);
@ -933,7 +1033,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 +1045,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;
KUNIT_STATIC_STUB_REDIRECT(mctp_local_output, sk, dst, skb, daddr,
req_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;
/* 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;
}
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 +1098,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 +1112,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;
switch (type) {
if (rt->dst_type == MCTP_ROUTE_GATEWAY && !rt->gateway.eid)
return -EINVAL;
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 +1179,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 +1196,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 +1211,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 +1247,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 +1264,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 +1308,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 +1340,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)),
};
/* Common part for RTM_NEWROUTE and RTM_DELROUTE parsing.
* tb must hold RTA_MAX+1 elements.
static const struct nla_policy rta_metrics_policy[RTAX_MAX + 1] = {
[RTAX_MTU] = { .type = NLA_U32 },
};
/* 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 +1377,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 +1422,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 nlattr *tbx[RTAX_MAX + 1];
mctp_eid_t daddr_start;
struct mctp_fq_addr gw;
struct mctp_dev *mdev;
struct rtmsg *rtm;
unsigned int mtu;
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,
&mdev, &gw, daddr_start);
if (rc)
return rc;
if (rtm->rtm_type != RTN_UNICAST) {
NL_SET_ERR_MSG(extack, "rtm_type must be RTN_UNICAST");
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 *dev;
struct rtmsg *rtm;
u32 mtu = 0;
int rc;
rc = mctp_route_nlparse_common(net, nlh, extack, tb, &rtm,
&dev, &gw, &daddr_start);
if (rc)
return rc;
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 +1598,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 +1614,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;

File diff suppressed because it is too large Load Diff

229
net/mctp/test/sock-test.c Normal file
View File

@ -0,0 +1,229 @@
// 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 struct kunit_case mctp_test_cases[] = {
KUNIT_CASE(mctp_test_sock_sendmsg_extaddr),
KUNIT_CASE(mctp_test_sock_recvmsg_extaddr),
{}
};
static struct kunit_suite mctp_test_suite = {
.name = "mctp-sock",
.test_cases = mctp_test_cases,
};
kunit_test_suite(mctp_test_suite);

View File

@ -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,195 @@ 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;
}

View File

@ -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,50 @@
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_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))
#endif /* __NET_MCTP_TEST_UTILS_H */