Commit bbb63db3 authored by David S. Miller's avatar David S. Miller

Merge branch 'skb_segment-testing'

Willem de Bruijn says:

====================
add skb_segment kunit coverage

As discussed at netconf last week. Some kernel code is exercised in
many different ways. skb_segment is a prime example. This ~350 line
function has 49 different patches in git blame with 28 different
authors.

When making a change, e.g., to fix a bug in one specific use case,
it is hard to establish through analysis alone that the change does
not break the many other paths through the code. It is impractical to
exercise all code paths through regression testing from userspace.

Add the minimal infrastructure needed to add KUnit tests to networking,
and add code coverage for this function.

Patch 1 adds the infra and the first simple test case: a linear skb
Patch 2 adds variants with frags[]
Patch 3 adds variants with frag_list skbs
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 5247dbf1 4688ecb1
......@@ -508,4 +508,13 @@ config NETDEV_ADDR_LIST_TEST
default KUNIT_ALL_TESTS
depends on KUNIT
config NET_TEST
tristate "KUnit tests for networking" if !KUNIT_ALL_TESTS
depends on KUNIT
default KUNIT_ALL_TESTS
help
KUnit tests covering core networking infra, such as sk_buff.
If unsure, say N.
endif # if NET
......@@ -40,3 +40,4 @@ obj-$(CONFIG_NET_SOCK_MSG) += skmsg.o
obj-$(CONFIG_BPF_SYSCALL) += sock_map.o
obj-$(CONFIG_BPF_SYSCALL) += bpf_sk_storage.o
obj-$(CONFIG_OF) += of_net.o
obj-$(CONFIG_NET_TEST) += gso_test.o
// SPDX-License-Identifier: GPL-2.0-or-later
#include <kunit/test.h>
#include <linux/skbuff.h>
static const char hdr[] = "abcdefgh";
static const int gso_size = 1000;
static void __init_skb(struct sk_buff *skb)
{
skb_reset_mac_header(skb);
memcpy(skb_mac_header(skb), hdr, sizeof(hdr));
/* skb_segment expects skb->data at start of payload */
skb_pull(skb, sizeof(hdr));
skb_reset_network_header(skb);
skb_reset_transport_header(skb);
/* proto is arbitrary, as long as not ETH_P_TEB or vlan */
skb->protocol = htons(ETH_P_ATALK);
skb_shinfo(skb)->gso_size = gso_size;
}
enum gso_test_nr {
GSO_TEST_LINEAR,
GSO_TEST_NO_GSO,
GSO_TEST_FRAGS,
GSO_TEST_FRAGS_PURE,
GSO_TEST_GSO_PARTIAL,
GSO_TEST_FRAG_LIST,
GSO_TEST_FRAG_LIST_PURE,
GSO_TEST_FRAG_LIST_NON_UNIFORM,
GSO_TEST_GSO_BY_FRAGS,
};
struct gso_test_case {
enum gso_test_nr id;
const char *name;
/* input */
unsigned int linear_len;
unsigned int nr_frags;
const unsigned int *frags;
unsigned int nr_frag_skbs;
const unsigned int *frag_skbs;
/* output as expected */
unsigned int nr_segs;
const unsigned int *segs;
};
static struct gso_test_case cases[] = {
{
.id = GSO_TEST_NO_GSO,
.name = "no_gso",
.linear_len = gso_size,
.nr_segs = 1,
.segs = (const unsigned int[]) { gso_size },
},
{
.id = GSO_TEST_LINEAR,
.name = "linear",
.linear_len = gso_size + gso_size + 1,
.nr_segs = 3,
.segs = (const unsigned int[]) { gso_size, gso_size, 1 },
},
{
.id = GSO_TEST_FRAGS,
.name = "frags",
.linear_len = gso_size,
.nr_frags = 2,
.frags = (const unsigned int[]) { gso_size, 1 },
.nr_segs = 3,
.segs = (const unsigned int[]) { gso_size, gso_size, 1 },
},
{
.id = GSO_TEST_FRAGS_PURE,
.name = "frags_pure",
.nr_frags = 3,
.frags = (const unsigned int[]) { gso_size, gso_size, 2 },
.nr_segs = 3,
.segs = (const unsigned int[]) { gso_size, gso_size, 2 },
},
{
.id = GSO_TEST_GSO_PARTIAL,
.name = "gso_partial",
.linear_len = gso_size,
.nr_frags = 2,
.frags = (const unsigned int[]) { gso_size, 3 },
.nr_segs = 2,
.segs = (const unsigned int[]) { 2 * gso_size, 3 },
},
{
/* commit 89319d3801d1: frag_list on mss boundaries */
.id = GSO_TEST_FRAG_LIST,
.name = "frag_list",
.linear_len = gso_size,
.nr_frag_skbs = 2,
.frag_skbs = (const unsigned int[]) { gso_size, gso_size },
.nr_segs = 3,
.segs = (const unsigned int[]) { gso_size, gso_size, gso_size },
},
{
.id = GSO_TEST_FRAG_LIST_PURE,
.name = "frag_list_pure",
.nr_frag_skbs = 2,
.frag_skbs = (const unsigned int[]) { gso_size, gso_size },
.nr_segs = 2,
.segs = (const unsigned int[]) { gso_size, gso_size },
},
{
/* commit 43170c4e0ba7: GRO of frag_list trains */
.id = GSO_TEST_FRAG_LIST_NON_UNIFORM,
.name = "frag_list_non_uniform",
.linear_len = gso_size,
.nr_frag_skbs = 4,
.frag_skbs = (const unsigned int[]) { gso_size, 1, gso_size, 2 },
.nr_segs = 4,
.segs = (const unsigned int[]) { gso_size, gso_size, gso_size, 3 },
},
{
/* commit 3953c46c3ac7 ("sk_buff: allow segmenting based on frag sizes") and
* commit 90017accff61 ("sctp: Add GSO support")
*
* "there will be a cover skb with protocol headers and
* children ones containing the actual segments"
*/
.id = GSO_TEST_GSO_BY_FRAGS,
.name = "gso_by_frags",
.nr_frag_skbs = 4,
.frag_skbs = (const unsigned int[]) { 100, 200, 300, 400 },
.nr_segs = 4,
.segs = (const unsigned int[]) { 100, 200, 300, 400 },
},
};
static void gso_test_case_to_desc(struct gso_test_case *t, char *desc)
{
sprintf(desc, "%s", t->name);
}
KUNIT_ARRAY_PARAM(gso_test, cases, gso_test_case_to_desc);
static void gso_test_func(struct kunit *test)
{
const int shinfo_size = SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
const struct gso_test_case *tcase;
struct sk_buff *skb, *segs, *cur;
netdev_features_t features;
struct page *page;
int i;
tcase = test->param_value;
page = alloc_page(GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, page);
skb = build_skb(page_address(page), sizeof(hdr) + tcase->linear_len + shinfo_size);
KUNIT_ASSERT_NOT_NULL(test, skb);
__skb_put(skb, sizeof(hdr) + tcase->linear_len);
__init_skb(skb);
if (tcase->nr_frags) {
unsigned int pg_off = 0;
page = alloc_page(GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, page);
page_ref_add(page, tcase->nr_frags - 1);
for (i = 0; i < tcase->nr_frags; i++) {
skb_fill_page_desc(skb, i, page, pg_off, tcase->frags[i]);
pg_off += tcase->frags[i];
}
KUNIT_ASSERT_LE(test, pg_off, PAGE_SIZE);
skb->data_len = pg_off;
skb->len += skb->data_len;
skb->truesize += skb->data_len;
}
if (tcase->frag_skbs) {
unsigned int total_size = 0, total_true_size = 0, alloc_size = 0;
struct sk_buff *frag_skb, *prev = NULL;
page = alloc_page(GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, page);
page_ref_add(page, tcase->nr_frag_skbs - 1);
for (i = 0; i < tcase->nr_frag_skbs; i++) {
unsigned int frag_size;
frag_size = tcase->frag_skbs[i];
frag_skb = build_skb(page_address(page) + alloc_size,
frag_size + shinfo_size);
KUNIT_ASSERT_NOT_NULL(test, frag_skb);
__skb_put(frag_skb, frag_size);
if (prev)
prev->next = frag_skb;
else
skb_shinfo(skb)->frag_list = frag_skb;
prev = frag_skb;
total_size += frag_size;
total_true_size += frag_skb->truesize;
alloc_size += frag_size + shinfo_size;
}
KUNIT_ASSERT_LE(test, alloc_size, PAGE_SIZE);
skb->len += total_size;
skb->data_len += total_size;
skb->truesize += total_true_size;
if (tcase->id == GSO_TEST_GSO_BY_FRAGS)
skb_shinfo(skb)->gso_size = GSO_BY_FRAGS;
}
features = NETIF_F_SG | NETIF_F_HW_CSUM;
if (tcase->id == GSO_TEST_GSO_PARTIAL)
features |= NETIF_F_GSO_PARTIAL;
/* TODO: this should also work with SG,
* rather than hit BUG_ON(i >= nfrags)
*/
if (tcase->id == GSO_TEST_FRAG_LIST_NON_UNIFORM)
features &= ~NETIF_F_SG;
segs = skb_segment(skb, features);
if (IS_ERR(segs)) {
KUNIT_FAIL(test, "segs error %lld", PTR_ERR(segs));
goto free_gso_skb;
} else if (!segs) {
KUNIT_FAIL(test, "no segments");
goto free_gso_skb;
}
for (cur = segs, i = 0; cur; cur = cur->next, i++) {
KUNIT_ASSERT_EQ(test, cur->len, sizeof(hdr) + tcase->segs[i]);
/* segs have skb->data pointing to the mac header */
KUNIT_ASSERT_PTR_EQ(test, skb_mac_header(cur), cur->data);
KUNIT_ASSERT_PTR_EQ(test, skb_network_header(cur), cur->data + sizeof(hdr));
/* header was copied to all segs */
KUNIT_ASSERT_EQ(test, memcmp(skb_mac_header(cur), hdr, sizeof(hdr)), 0);
/* last seg can be found through segs->prev pointer */
if (!cur->next)
KUNIT_ASSERT_PTR_EQ(test, cur, segs->prev);
}
KUNIT_ASSERT_EQ(test, i, tcase->nr_segs);
consume_skb(segs);
free_gso_skb:
consume_skb(skb);
}
static struct kunit_case gso_test_cases[] = {
KUNIT_CASE_PARAM(gso_test_func, gso_test_gen_params),
{}
};
static struct kunit_suite gso_test_suite = {
.name = "net_core_gso",
.test_cases = gso_test_cases,
};
kunit_test_suite(gso_test_suite);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("KUnit tests for segmentation offload");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment