/* main.c - Application main entry point */ /* * Copyright (c) 2018 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #define NET_LOG_LEVEL CONFIG_NET_L2_ETHERNET_LOG_LEVEL #include LOG_MODULE_REGISTER(net_test, NET_LOG_LEVEL); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ipv6.h" #include "udp_internal.h" #define NET_LOG_ENABLED 1 #include "net_private.h" #if NET_LOG_LEVEL >= LOG_LEVEL_DBG #define DBG(fmt, ...) printk(fmt, ##__VA_ARGS__) #else #define DBG(fmt, ...) #endif #define TEST_PORT 9999 static char *test_data = "Test data to be sent"; static uint8_t test_data_large[2000]; static uint8_t verify_buf[2000]; /* Interface 1 addresses */ static struct in6_addr my_addr1 = { { { 0x20, 0x01, 0x0d, 0xb8, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1 } } }; /* Interface 2 addresses */ static struct in6_addr my_addr2 = { { { 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1 } } }; /* Destination address for test packets (interface 1) */ static struct in6_addr dst_addr1 = { { { 0x20, 0x01, 0x0d, 0xb8, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2 } } }; /* Destination address for test packets (interface 2) */ static struct in6_addr dst_addr2 = { { { 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2 } } }; /* Extra address is assigned to ll_addr */ static struct in6_addr ll_addr = { { { 0xfe, 0x80, 0x43, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0xf2, 0xaa, 0x29, 0x02, 0x04 } } }; static struct in_addr in4addr_my = { { { 192, 0, 2, 1 } } }; static struct in_addr in4addr_dst = { { { 192, 0, 2, 2 } } }; static struct in_addr in4addr_my2 = { { { 192, 0, 42, 1 } } }; static struct in_addr in4addr_dst2 = { { { 192, 0, 42, 2 } } }; /* Keep track of all ethernet interfaces. For native_sim board, we need * to increase the count as it has one extra network interface defined in * eth_native_posix driver. */ static struct net_if *eth_interfaces[2 + IS_ENABLED(CONFIG_ETH_NATIVE_POSIX)]; static bool test_failed; static bool test_started; static int test_proto; static bool verify_fragment; static bool start_receiving; static bool change_chksum; static int fragment_count; static int fragment_offset; static K_SEM_DEFINE(wait_data_off, 0, UINT_MAX); static K_SEM_DEFINE(wait_data_nonoff, 0, UINT_MAX); #define WAIT_TIME K_MSEC(100) struct eth_context { struct net_if *iface; uint8_t mac_addr[6]; uint16_t expecting_tag; }; static struct eth_context eth_context_offloading_disabled; static struct eth_context eth_context_offloading_enabled; static void verify_test_data_large(uint8_t *buf, size_t offset, size_t len) { zassert(offset + len <= sizeof(test_data_large), "Out of bound data"); for (size_t i = 0; i < len; i++) { zassert_equal(buf[i], test_data_large[offset + i], "Invalid data"); } } static void eth_iface_init(struct net_if *iface) { const struct device *dev = net_if_get_device(iface); struct eth_context *context = dev->data; net_if_set_link_addr(iface, context->mac_addr, sizeof(context->mac_addr), NET_LINK_ETHERNET); DBG("Iface %p addr %s\n", iface, net_sprint_ll_addr(context->mac_addr, sizeof(context->mac_addr))); ethernet_init(iface); } static uint16_t get_udp_chksum(struct net_pkt *pkt) { NET_PKT_DATA_ACCESS_DEFINE(udp_access, struct net_udp_hdr); struct net_udp_hdr *udp_hdr; struct net_pkt_cursor backup; net_pkt_set_overwrite(pkt, true); net_pkt_cursor_backup(pkt, &backup); net_pkt_cursor_init(pkt); /* Let's move the cursor to UDP header */ if (net_pkt_skip(pkt, sizeof(struct net_eth_hdr) + net_pkt_ip_hdr_len(pkt) + net_pkt_ipv6_ext_len(pkt))) { return 0; } udp_hdr = (struct net_udp_hdr *)net_pkt_get_data(pkt, &udp_access); if (!udp_hdr) { return 0; } net_pkt_cursor_restore(pkt, &backup); return udp_hdr->chksum; } static uint16_t get_icmp_chksum(struct net_pkt *pkt) { NET_PKT_DATA_ACCESS_DEFINE(icmp_access, struct net_icmp_hdr); struct net_icmp_hdr *icmp_hdr; struct net_pkt_cursor backup; net_pkt_set_overwrite(pkt, true); net_pkt_cursor_backup(pkt, &backup); net_pkt_cursor_init(pkt); /* Move the cursor to the ICMP header */ if (net_pkt_skip(pkt, sizeof(struct net_eth_hdr) + net_pkt_ip_hdr_len(pkt) + net_pkt_ipv6_ext_len(pkt))) { return 0; } icmp_hdr = (struct net_icmp_hdr *)net_pkt_get_data(pkt, &icmp_access); if (!icmp_hdr) { return 0; } net_pkt_cursor_restore(pkt, &backup); return icmp_hdr->chksum; } static void test_receiving(struct net_pkt *pkt) { uint16_t port; uint8_t lladdr[6]; DBG("Packet %p received\n", pkt); memcpy(lladdr, ((struct net_eth_hdr *)net_pkt_data(pkt))->src.addr, sizeof(lladdr)); memcpy(((struct net_eth_hdr *)net_pkt_data(pkt))->src.addr, ((struct net_eth_hdr *)net_pkt_data(pkt))->dst.addr, sizeof(lladdr)); memcpy(((struct net_eth_hdr *)net_pkt_data(pkt))->dst.addr, lladdr, sizeof(lladdr)); net_pkt_skip(pkt, sizeof(struct net_eth_hdr)); /* Swap IP src and destination address so that we can receive * the packet and the stack will not reject it. */ if (net_pkt_family(pkt) == AF_INET6) { NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(ipv6_access, struct net_ipv6_hdr); struct net_ipv6_hdr *ipv6_hdr; struct in6_addr addr; ipv6_hdr = (struct net_ipv6_hdr *) net_pkt_get_data(pkt, &ipv6_access); zassert_not_null(ipv6_hdr, "Can't access IPv6 header"); net_ipv6_addr_copy_raw((uint8_t *)&addr, ipv6_hdr->src); net_ipv6_addr_copy_raw(ipv6_hdr->src, ipv6_hdr->dst); net_ipv6_addr_copy_raw(ipv6_hdr->dst, (uint8_t *)&addr); } else { NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(ipv4_access, struct net_ipv4_hdr); struct net_ipv4_hdr *ipv4_hdr; struct in_addr addr; ipv4_hdr = (struct net_ipv4_hdr *) net_pkt_get_data(pkt, &ipv4_access); zassert_not_null(ipv4_hdr, "Can't access IPv4 header"); net_ipv4_addr_copy_raw((uint8_t *)&addr, ipv4_hdr->src); net_ipv4_addr_copy_raw(ipv4_hdr->src, ipv4_hdr->dst); net_ipv4_addr_copy_raw(ipv4_hdr->dst, (uint8_t *)&addr); } if (!verify_fragment || fragment_count == 1) { net_pkt_skip(pkt, net_pkt_ip_hdr_len(pkt) + net_pkt_ip_opts_len(pkt)); if (test_proto == IPPROTO_UDP) { NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE( udp_access, struct net_udp_hdr); struct net_udp_hdr *udp_hdr; udp_hdr = (struct net_udp_hdr *) net_pkt_get_data(pkt, &udp_access); zassert_not_null(udp_hdr, "Can't access UDP header"); port = udp_hdr->src_port; udp_hdr->src_port = udp_hdr->dst_port; udp_hdr->dst_port = port; if (change_chksum) { udp_hdr->chksum++; } } else if (test_proto == IPPROTO_ICMP || test_proto == IPPROTO_ICMPV6) { NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE( icmp_access, struct net_icmp_hdr); struct net_icmp_hdr *icmp_hdr; icmp_hdr = (struct net_icmp_hdr *) net_pkt_get_data(pkt, &icmp_access); zassert_not_null(icmp_hdr, "Can't access ICMP header"); if (change_chksum) { icmp_hdr->chksum++; } } } net_pkt_cursor_init(pkt); if (net_recv_data(net_pkt_iface(pkt), net_pkt_rx_clone(pkt, K_NO_WAIT)) < 0) { test_failed = true; zassert_true(false, "Packet %p receive failed\n", pkt); } } static void test_fragment(struct net_pkt *pkt, bool offloaded) { uint16_t chksum = 0; size_t data_len; size_t hdr_offset = sizeof(struct net_eth_hdr) + net_pkt_ip_hdr_len(pkt) + net_pkt_ip_opts_len(pkt); fragment_count++; net_pkt_set_overwrite(pkt, true); net_pkt_cursor_init(pkt); if (start_receiving) { test_receiving(pkt); return; } if (fragment_count == 1) { if (test_proto == IPPROTO_UDP) { chksum = get_udp_chksum(pkt); hdr_offset += sizeof(struct net_udp_hdr); } else if (test_proto == IPPROTO_ICMP || test_proto == IPPROTO_ICMPV6) { chksum = get_icmp_chksum(pkt); hdr_offset += sizeof(struct net_icmp_hdr) + sizeof(struct net_icmpv6_echo_req); } /* Fragmented packet should have checksum set regardless of * checksum offloading */ zassert_not_equal(chksum, 0, "Checksum missing"); } zassert_true(net_pkt_is_chksum_done(pkt), "Checksum should me marked as ready on net_pkt"); /* Verify that payload has not been altered. */ data_len = net_pkt_get_len(pkt) - hdr_offset; net_pkt_skip(pkt, hdr_offset); net_pkt_read(pkt, verify_buf, data_len); verify_test_data_large(verify_buf, fragment_offset, data_len); fragment_offset += data_len; if (fragment_count > 1) { if (offloaded) { k_sem_give(&wait_data_off); } else { k_sem_give(&wait_data_nonoff); } } } static int eth_tx_offloading_disabled(const struct device *dev, struct net_pkt *pkt) { struct eth_context *context = dev->data; zassert_equal_ptr(ð_context_offloading_disabled, context, "Context pointers do not match (%p vs %p)", eth_context_offloading_disabled, context); if (!pkt->buffer) { DBG("No data to send!\n"); return -ENODATA; } if (verify_fragment) { test_fragment(pkt, false); return 0; } if (start_receiving) { test_receiving(pkt); return 0; } if (test_started) { uint16_t chksum = 0; if (test_proto == IPPROTO_UDP) { chksum = get_udp_chksum(pkt); } else if (test_proto == IPPROTO_ICMP || test_proto == IPPROTO_ICMPV6) { chksum = get_icmp_chksum(pkt); } DBG("Chksum 0x%x offloading disabled\n", chksum); zassert_not_equal(chksum, 0, "Checksum not calculated"); k_sem_give(&wait_data_nonoff); } return 0; } static int eth_tx_offloading_enabled(const struct device *dev, struct net_pkt *pkt) { struct eth_context *context = dev->data; zassert_equal_ptr(ð_context_offloading_enabled, context, "Context pointers do not match (%p vs %p)", eth_context_offloading_enabled, context); if (!pkt->buffer) { DBG("No data to send!\n"); return -ENODATA; } if (verify_fragment) { test_fragment(pkt, true); return 0; } if (start_receiving) { test_receiving(pkt); } if (test_started) { uint16_t chksum = 0; if (test_proto == IPPROTO_UDP) { chksum = get_udp_chksum(pkt); } else if (test_proto == IPPROTO_ICMP || test_proto == IPPROTO_ICMPV6) { chksum = get_icmp_chksum(pkt); } DBG("Chksum 0x%x offloading enabled\n", chksum); zassert_equal(chksum, 0, "Checksum calculated"); k_sem_give(&wait_data_off); } return 0; } static enum ethernet_hw_caps eth_offloading_enabled(const struct device *dev) { return ETHERNET_HW_TX_CHKSUM_OFFLOAD | ETHERNET_HW_RX_CHKSUM_OFFLOAD; } static enum ethernet_hw_caps eth_offloading_disabled(const struct device *dev) { return 0; } static struct ethernet_api api_funcs_offloading_disabled = { .iface_api.init = eth_iface_init, .get_capabilities = eth_offloading_disabled, .send = eth_tx_offloading_disabled, }; static struct ethernet_api api_funcs_offloading_enabled = { .iface_api.init = eth_iface_init, .get_capabilities = eth_offloading_enabled, .send = eth_tx_offloading_enabled, }; static void generate_mac(uint8_t *mac_addr) { /* 00-00-5E-00-53-xx Documentation RFC 7042 */ mac_addr[0] = 0x00; mac_addr[1] = 0x00; mac_addr[2] = 0x5E; mac_addr[3] = 0x00; mac_addr[4] = 0x53; mac_addr[5] = sys_rand8_get(); } static int eth_init(const struct device *dev) { struct eth_context *context = dev->data; generate_mac(context->mac_addr); return 0; } ETH_NET_DEVICE_INIT(eth1_offloading_disabled_test, "eth1_offloading_disabled_test", eth_init, NULL, ð_context_offloading_disabled, NULL, CONFIG_ETH_INIT_PRIORITY, &api_funcs_offloading_disabled, NET_ETH_MTU); ETH_NET_DEVICE_INIT(eth0_offloading_enabled_test, "eth0_offloading_enabled_test", eth_init, NULL, ð_context_offloading_enabled, NULL, CONFIG_ETH_INIT_PRIORITY, &api_funcs_offloading_enabled, NET_ETH_MTU); struct user_data { int eth_if_count; int total_if_count; }; #if NET_LOG_LEVEL >= LOG_LEVEL_DBG static const char *iface2str(struct net_if *iface) { #ifdef CONFIG_NET_L2_ETHERNET if (net_if_l2(iface) == &NET_L2_GET_NAME(ETHERNET)) { return "Ethernet"; } #endif #ifdef CONFIG_NET_L2_DUMMY if (net_if_l2(iface) == &NET_L2_GET_NAME(DUMMY)) { return "Dummy"; } #endif return ""; } #endif static void iface_cb(struct net_if *iface, void *user_data) { struct user_data *ud = user_data; DBG("Interface %p (%s) [%d]\n", iface, iface2str(iface), net_if_get_by_iface(iface)); if (net_if_l2(iface) == &NET_L2_GET_NAME(ETHERNET)) { struct eth_context *eth_ctx = net_if_get_device(iface)->data; if (eth_ctx == ð_context_offloading_disabled) { DBG("Iface %p without offloading\n", iface); eth_interfaces[0] = iface; } if (eth_ctx == ð_context_offloading_enabled) { DBG("Iface %p with offloading\n", iface); eth_interfaces[1] = iface; } ud->eth_if_count++; } /* By default all interfaces are down initially */ net_if_down(iface); ud->total_if_count++; } static void test_eth_setup(void) { struct user_data ud = { 0 }; /* Make sure we have enough virtual interfaces */ net_if_foreach(iface_cb, &ud); zassert_equal(ud.eth_if_count, sizeof(eth_interfaces) / sizeof(void *), "Invalid number of interfaces (%d vs %d)\n", ud.eth_if_count, sizeof(eth_interfaces) / sizeof(void *)); } static void test_address_setup(void) { struct in_addr netmask = { { { 255, 255, 255, 0 } } }; struct net_if_addr *ifaddr; struct net_if *iface1, *iface2; iface1 = eth_interfaces[0]; iface2 = eth_interfaces[1]; zassert_not_null(iface1, "Interface 1"); zassert_not_null(iface2, "Interface 2"); ifaddr = net_if_ipv6_addr_add(iface1, &my_addr1, NET_ADDR_MANUAL, 0); if (!ifaddr) { DBG("Cannot add IPv6 address %s\n", net_sprint_ipv6_addr(&my_addr1)); zassert_not_null(ifaddr, "addr1"); } /* For testing purposes we need to set the addresses preferred */ ifaddr->addr_state = NET_ADDR_PREFERRED; ifaddr = net_if_ipv6_addr_add(iface1, &ll_addr, NET_ADDR_MANUAL, 0); if (!ifaddr) { DBG("Cannot add IPv6 address %s\n", net_sprint_ipv6_addr(&ll_addr)); zassert_not_null(ifaddr, "ll_addr"); } ifaddr->addr_state = NET_ADDR_PREFERRED; ifaddr = net_if_ipv4_addr_add(iface1, &in4addr_my, NET_ADDR_MANUAL, 0); zassert_not_null(ifaddr, "Cannot add IPv4 address"); net_if_ipv4_set_netmask_by_addr(iface1, &in4addr_my, &netmask); ifaddr = net_if_ipv6_addr_add(iface2, &my_addr2, NET_ADDR_MANUAL, 0); if (!ifaddr) { DBG("Cannot add IPv6 address %s\n", net_sprint_ipv6_addr(&my_addr2)); zassert_not_null(ifaddr, "addr2"); } ifaddr->addr_state = NET_ADDR_PREFERRED; ifaddr = net_if_ipv4_addr_add(iface2, &in4addr_my2, NET_ADDR_MANUAL, 0); zassert_not_null(ifaddr, "Cannot add IPv4 address"); net_if_ipv4_set_netmask_by_addr(iface2, &in4addr_my2, &netmask); net_if_up(iface1); net_if_up(iface2); /* The interface might receive data which might fail the checks * in the iface sending function, so we need to reset the failure * flag. */ test_failed = false; } static void add_neighbor(struct net_if *iface, struct in6_addr *addr) { struct net_linkaddr_storage llstorage; struct net_linkaddr lladdr; struct net_nbr *nbr; llstorage.addr[0] = 0x01; llstorage.addr[1] = 0x02; llstorage.addr[2] = 0x33; llstorage.addr[3] = 0x44; llstorage.addr[4] = 0x05; llstorage.addr[5] = 0x06; lladdr.len = 6U; lladdr.addr = llstorage.addr; lladdr.type = NET_LINK_ETHERNET; nbr = net_ipv6_nbr_add(iface, addr, &lladdr, false, NET_IPV6_NBR_STATE_REACHABLE); if (!nbr) { DBG("Cannot add dst %s to neighbor cache\n", net_sprint_ipv6_addr(addr)); } } static struct net_context *test_udp_context_prepare(sa_family_t family, bool offloaded, struct sockaddr *dst_addr) { struct net_context *net_ctx; struct eth_context *ctx; /* This is interface context */ struct sockaddr src_addr; socklen_t addrlen; struct net_if *iface; int ret; if (family == AF_INET6) { struct sockaddr_in6 *dst_addr6 = (struct sockaddr_in6 *)dst_addr; struct sockaddr_in6 *src_addr6 = (struct sockaddr_in6 *)&src_addr; dst_addr6->sin6_family = AF_INET6; dst_addr6->sin6_port = htons(TEST_PORT); src_addr6->sin6_family = AF_INET6; src_addr6->sin6_port = 0; if (offloaded) { memcpy(&src_addr6->sin6_addr, &my_addr2, sizeof(struct in6_addr)); memcpy(&dst_addr6->sin6_addr, &dst_addr2, sizeof(struct in6_addr)); } else { memcpy(&src_addr6->sin6_addr, &my_addr1, sizeof(struct in6_addr)); memcpy(&dst_addr6->sin6_addr, &dst_addr1, sizeof(struct in6_addr)); } addrlen = sizeof(struct sockaddr_in6); } else { struct sockaddr_in *dst_addr4 = (struct sockaddr_in *)dst_addr; struct sockaddr_in *src_addr4 = (struct sockaddr_in *)&src_addr; dst_addr4->sin_family = AF_INET; dst_addr4->sin_port = htons(TEST_PORT); src_addr4->sin_family = AF_INET; src_addr4->sin_port = 0; if (offloaded) { memcpy(&src_addr4->sin_addr, &in4addr_my2, sizeof(struct in_addr)); memcpy(&dst_addr4->sin_addr, &in4addr_dst2, sizeof(struct in_addr)); } else { memcpy(&src_addr4->sin_addr, &in4addr_my, sizeof(struct in_addr)); memcpy(&dst_addr4->sin_addr, &in4addr_dst, sizeof(struct in_addr)); } addrlen = sizeof(struct sockaddr_in6); } ret = net_context_get(family, SOCK_DGRAM, IPPROTO_UDP, &net_ctx); zassert_equal(ret, 0, "Create %s UDP context failed", family == AF_INET6 ? "IPv6" : "IPv4"); ret = net_context_bind(net_ctx, &src_addr, addrlen); zassert_equal(ret, 0, "Context bind failure test failed"); /* Verify iface data */ if (offloaded) { iface = eth_interfaces[1]; ctx = net_if_get_device(iface)->data; zassert_equal_ptr(ð_context_offloading_enabled, ctx, "eth context mismatch"); } else { iface = eth_interfaces[0]; ctx = net_if_get_device(iface)->data; zassert_equal_ptr(ð_context_offloading_disabled, ctx, "eth context mismatch"); } return net_ctx; } static void test_tx_chksum(sa_family_t family, bool offloaded) { struct k_sem *wait_data = offloaded ? &wait_data_off : &wait_data_nonoff; socklen_t addrlen = (family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in); struct net_context *net_ctx; struct sockaddr dst_addr; int ret, len; net_ctx = test_udp_context_prepare(family, offloaded, &dst_addr); zassert_not_null(net_ctx, "Failed to obtain net_ctx"); test_started = true; test_proto = IPPROTO_UDP; len = strlen(test_data); ret = net_context_sendto(net_ctx, test_data, len, &dst_addr, addrlen, NULL, K_FOREVER, NULL); zassert_equal(ret, len, "Send UDP pkt failed (%d)\n", ret); if (k_sem_take(wait_data, WAIT_TIME)) { DBG("Timeout while waiting interface data\n"); zassert_false(true, "Timeout"); } net_context_unref(net_ctx); } ZTEST(net_chksum_offload, test_tx_chksum_offload_disabled_test_v6) { test_tx_chksum(AF_INET6, false); } ZTEST(net_chksum_offload, test_tx_chksum_offload_disabled_test_v4) { test_tx_chksum(AF_INET, false); } ZTEST(net_chksum_offload, test_tx_chksum_offload_enabled_test_v6) { test_tx_chksum(AF_INET6, true); } ZTEST(net_chksum_offload, test_tx_chksum_offload_enabled_test_v4) { test_tx_chksum(AF_INET, true); } static void test_tx_chksum_udp_frag(sa_family_t family, bool offloaded) { struct k_sem *wait_data = offloaded ? &wait_data_off : &wait_data_nonoff; socklen_t addrlen = (family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in); struct net_context *net_ctx; struct sockaddr dst_addr; int ret, len; net_ctx = test_udp_context_prepare(family, offloaded, &dst_addr); zassert_not_null(net_ctx, "Failed to obtain net_ctx"); test_started = true; test_proto = IPPROTO_UDP; verify_fragment = true; len = sizeof(test_data_large); ret = net_context_sendto(net_ctx, test_data_large, len, &dst_addr, addrlen, NULL, K_FOREVER, NULL); zassert_equal(ret, len, "Send UDP pkt failed (%d)\n", ret); if (k_sem_take(wait_data, WAIT_TIME)) { DBG("Timeout while waiting interface data\n"); zassert_false(true, "Timeout"); } net_context_unref(net_ctx); } ZTEST(net_chksum_offload, test_tx_chksum_offload_disabled_test_v6_udp_frag) { test_tx_chksum_udp_frag(AF_INET6, false); } ZTEST(net_chksum_offload, test_tx_chksum_offload_disabled_test_v4_udp_frag) { test_tx_chksum_udp_frag(AF_INET, false); } ZTEST(net_chksum_offload, test_tx_chksum_offload_enabled_test_v6_udp_frag) { test_tx_chksum_udp_frag(AF_INET6, true); } ZTEST(net_chksum_offload, test_tx_chksum_offload_enabled_test_v4_udp_frag) { test_tx_chksum_udp_frag(AF_INET, true); } static int dummy_icmp_handler(struct net_icmp_ctx *ctx, struct net_pkt *pkt, struct net_icmp_ip_hdr *hdr, struct net_icmp_hdr *icmp_hdr, void *user_data) { ARG_UNUSED(ctx); ARG_UNUSED(pkt); ARG_UNUSED(hdr); ARG_UNUSED(icmp_hdr); ARG_UNUSED(user_data); return 0; } static void test_icmp_init(sa_family_t family, bool offloaded, struct sockaddr *dst_addr, struct net_if **iface) { if (family == AF_INET6) { struct sockaddr_in6 *dst_addr6 = (struct sockaddr_in6 *)dst_addr; dst_addr6->sin6_family = AF_INET6; if (offloaded) { memcpy(&dst_addr6->sin6_addr, &dst_addr2, sizeof(struct in6_addr)); } else { memcpy(&dst_addr6->sin6_addr, &dst_addr1, sizeof(struct in6_addr)); } } else { struct sockaddr_in *dst_addr4 = (struct sockaddr_in *)dst_addr; dst_addr4->sin_family = AF_INET; if (offloaded) { memcpy(&dst_addr4->sin_addr, &in4addr_dst2, sizeof(struct in_addr)); } else { memcpy(&dst_addr4->sin_addr, &in4addr_dst, sizeof(struct in_addr)); } } if (offloaded) { *iface = eth_interfaces[1]; } else { *iface = eth_interfaces[0]; } } static void test_tx_chksum_icmp_frag(sa_family_t family, bool offloaded) { struct k_sem *wait_data = offloaded ? &wait_data_off : &wait_data_nonoff; struct net_icmp_ping_params params = { 0 }; struct net_icmp_ctx ctx; struct sockaddr dst_addr; struct net_if *iface; int ret; test_icmp_init(family, offloaded, &dst_addr, &iface); ret = net_icmp_init_ctx(&ctx, 0, 0, dummy_icmp_handler); zassert_equal(ret, 0, "Cannot init ICMP (%d)", ret); test_started = true; test_proto = (family == AF_INET6) ? IPPROTO_ICMPV6 : IPPROTO_ICMP; verify_fragment = true; params.data = test_data_large; params.data_size = sizeof(test_data_large); ret = net_icmp_send_echo_request(&ctx, iface, &dst_addr, ¶ms, NULL); zassert_equal(ret, 0, "Cannot send ICMP Echo-Request (%d)", ret); if (k_sem_take(wait_data, WAIT_TIME)) { DBG("Timeout while waiting interface data\n"); zassert_false(true, "Timeout"); } ret = net_icmp_cleanup_ctx(&ctx); zassert_equal(ret, 0, "Cannot cleanup ICMP (%d)", ret); } ZTEST(net_chksum_offload, test_tx_chksum_offload_disabled_test_v6_icmp_frag) { test_tx_chksum_icmp_frag(AF_INET6, false); } ZTEST(net_chksum_offload, test_tx_chksum_offload_disabled_test_v4_icmp_frag) { test_tx_chksum_icmp_frag(AF_INET, false); } ZTEST(net_chksum_offload, test_tx_chksum_offload_enabled_test_v6_icmp_frag) { test_tx_chksum_icmp_frag(AF_INET6, true); } ZTEST(net_chksum_offload, test_tx_chksum_offload_enabled_test_v4_icmp_frag) { test_tx_chksum_icmp_frag(AF_INET, true); } static void test_fragment_rx_udp(struct net_pkt *pkt, union net_proto_header *proto_hdr) { size_t hdr_offset = net_pkt_ip_hdr_len(pkt) + net_pkt_ip_opts_len(pkt) + sizeof(struct net_udp_hdr); size_t data_len = net_pkt_get_len(pkt) - hdr_offset; /* In case of fragmented packets, checksum shall be present/verified * regardless. */ zassert_not_equal(proto_hdr->udp->chksum, 0, "Checksum is not set"); zassert_equal(net_calc_verify_chksum_udp(pkt), 0, "Incorrect checksum"); /* Verify that packet content has not been altered */ net_pkt_read(pkt, verify_buf, data_len); verify_test_data_large(verify_buf, 0, data_len); } static void recv_cb_offload_disabled(struct net_context *context, struct net_pkt *pkt, union net_ip_header *ip_hdr, union net_proto_header *proto_hdr, int status, void *user_data) { zassert_not_null(proto_hdr->udp, "UDP header missing"); if (verify_fragment) { test_fragment_rx_udp(pkt, proto_hdr); } else { zassert_not_equal(proto_hdr->udp->chksum, 0, "Checksum is not set"); zassert_equal(net_calc_verify_chksum_udp(pkt), 0, "Incorrect checksum"); } if (net_pkt_family(pkt) == AF_INET) { struct net_ipv4_hdr *ipv4 = NET_IPV4_HDR(pkt); zassert_not_equal(ipv4->chksum, 0, "IPv4 checksum is not set"); } k_sem_give(&wait_data_nonoff); net_pkt_unref(pkt); } static void recv_cb_offload_enabled(struct net_context *context, struct net_pkt *pkt, union net_ip_header *ip_hdr, union net_proto_header *proto_hdr, int status, void *user_data) { zassert_not_null(proto_hdr->udp, "UDP header missing"); if (verify_fragment) { test_fragment_rx_udp(pkt, proto_hdr); } else { zassert_equal(proto_hdr->udp->chksum, 0, "Checksum is set"); if (net_pkt_family(pkt) == AF_INET) { struct net_ipv4_hdr *ipv4 = NET_IPV4_HDR(pkt); zassert_equal(ipv4->chksum, 0, "IPv4 checksum is set"); } } k_sem_give(&wait_data_off); net_pkt_unref(pkt); } static void test_rx_chksum(sa_family_t family, bool offloaded) { struct k_sem *wait_data = offloaded ? &wait_data_off : &wait_data_nonoff; net_context_recv_cb_t cb = offloaded ? recv_cb_offload_enabled : recv_cb_offload_disabled; socklen_t addrlen = (family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in); struct net_context *net_ctx; struct sockaddr dst_addr; int ret, len; net_ctx = test_udp_context_prepare(family, offloaded, &dst_addr); zassert_not_null(net_ctx, "Failed to obtain net_ctx"); test_started = true; test_proto = IPPROTO_UDP; start_receiving = true; ret = net_context_recv(net_ctx, cb, K_NO_WAIT, NULL); zassert_equal(ret, 0, "Recv UDP failed (%d)\n", ret); len = strlen(test_data); ret = net_context_sendto(net_ctx, test_data, len, &dst_addr, addrlen, NULL, K_FOREVER, NULL); zassert_equal(ret, len, "Send UDP pkt failed (%d)\n", ret); if (k_sem_take(wait_data, WAIT_TIME)) { DBG("Timeout while waiting interface data\n"); zassert_false(true, "Timeout"); } /* Let the receiver to receive the packets */ k_sleep(K_MSEC(10)); net_context_unref(net_ctx); } ZTEST(net_chksum_offload, test_rx_chksum_offload_disabled_test_v6) { test_rx_chksum(AF_INET6, false); } ZTEST(net_chksum_offload, test_rx_chksum_offload_disabled_test_v4) { test_rx_chksum(AF_INET, false); } ZTEST(net_chksum_offload, test_rx_chksum_offload_enabled_test_v6) { test_rx_chksum(AF_INET6, true); } ZTEST(net_chksum_offload, test_rx_chksum_offload_enabled_test_v4) { test_rx_chksum(AF_INET, true); } static void test_rx_chksum_udp_frag(sa_family_t family, bool offloaded) { struct k_sem *wait_data = offloaded ? &wait_data_off : &wait_data_nonoff; net_context_recv_cb_t cb = offloaded ? recv_cb_offload_enabled : recv_cb_offload_disabled; socklen_t addrlen = (family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in); struct net_context *net_ctx; struct sockaddr dst_addr; int ret, len; net_ctx = test_udp_context_prepare(family, offloaded, &dst_addr); zassert_not_null(net_ctx, "Failed to obtain net_ctx"); test_started = true; test_proto = IPPROTO_UDP; start_receiving = true; verify_fragment = true; ret = net_context_recv(net_ctx, cb, K_NO_WAIT, NULL); zassert_equal(ret, 0, "Recv UDP failed (%d)\n", ret); len = sizeof(test_data_large); ret = net_context_sendto(net_ctx, test_data_large, len, &dst_addr, addrlen, NULL, K_FOREVER, NULL); zassert_equal(ret, len, "Send UDP pkt failed (%d)\n", ret); if (k_sem_take(wait_data, WAIT_TIME)) { DBG("Timeout while waiting interface data\n"); zassert_false(true, "Timeout"); } /* Let the receiver to receive the packets */ k_sleep(K_MSEC(10)); net_context_unref(net_ctx); } ZTEST(net_chksum_offload, test_rx_chksum_offload_disabled_test_v6_udp_frag) { test_rx_chksum_udp_frag(AF_INET6, false); } ZTEST(net_chksum_offload, test_rx_chksum_offload_disabled_test_v4_udp_frag) { test_rx_chksum_udp_frag(AF_INET, false); } ZTEST(net_chksum_offload, test_rx_chksum_offload_enabled_test_v6_udp_frag) { test_rx_chksum_udp_frag(AF_INET6, true); } ZTEST(net_chksum_offload, test_rx_chksum_offload_enabled_test_v4_udp_frag) { test_rx_chksum_udp_frag(AF_INET, true); } static void test_rx_chksum_udp_frag_bad(sa_family_t family, bool offloaded) { struct k_sem *wait_data = offloaded ? &wait_data_off : &wait_data_nonoff; net_context_recv_cb_t cb = offloaded ? recv_cb_offload_enabled : recv_cb_offload_disabled; socklen_t addrlen = (family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in); struct net_context *net_ctx; struct sockaddr dst_addr; int ret, len; net_ctx = test_udp_context_prepare(family, offloaded, &dst_addr); zassert_not_null(net_ctx, "Failed to obtain net_ctx"); test_started = true; test_proto = IPPROTO_UDP; start_receiving = true; verify_fragment = true; change_chksum = true; ret = net_context_recv(net_ctx, cb, K_NO_WAIT, NULL); zassert_equal(ret, 0, "Recv UDP failed (%d)\n", ret); len = sizeof(test_data_large); ret = net_context_sendto(net_ctx, test_data_large, len, &dst_addr, addrlen, NULL, K_FOREVER, NULL); zassert_equal(ret, len, "Send UDP pkt failed (%d)\n", ret); if (k_sem_take(wait_data, WAIT_TIME) == 0) { DBG("Packet with bad chksum should be dropped\n"); zassert_false(true, "Packet received"); } /* Let the receiver to receive the packets */ k_sleep(K_MSEC(10)); net_context_unref(net_ctx); } ZTEST(net_chksum_offload, test_tx_chksum_offload_disabled_test_v6_udp_frag_bad) { test_rx_chksum_udp_frag_bad(AF_INET6, false); } ZTEST(net_chksum_offload, test_tx_chksum_offload_disabled_test_v4_udp_frag_bad) { test_rx_chksum_udp_frag_bad(AF_INET, false); } ZTEST(net_chksum_offload, test_tx_chksum_offload_enabled_test_v6_udp_frag_bad) { test_rx_chksum_udp_frag_bad(AF_INET6, true); } ZTEST(net_chksum_offload, test_tx_chksum_offload_enabled_test_v4_udp_frag_bad) { test_rx_chksum_udp_frag_bad(AF_INET, true); } static int icmp_handler(struct net_icmp_ctx *ctx, struct net_pkt *pkt, struct net_icmp_ip_hdr *hdr, struct net_icmp_hdr *icmp_hdr, void *user_data) { struct k_sem *wait_data = user_data; size_t hdr_offset = net_pkt_ip_hdr_len(pkt) + net_pkt_ip_opts_len(pkt) + sizeof(struct net_icmp_hdr) + sizeof(struct net_icmpv6_echo_req); size_t data_len = net_pkt_get_len(pkt) - hdr_offset; /* In case of fragmented packets, checksum shall be present/verified * regardless. */ zassert_not_equal(icmp_hdr->chksum, 0, "Checksum is not set"); if (test_proto == IPPROTO_ICMPV6) { zassert_equal(net_calc_chksum_icmpv6(pkt), 0, "Incorrect checksum"); } else { zassert_equal(net_calc_chksum_icmpv4(pkt), 0, "Incorrect checksum"); } /* Verify that packet content has not been altered */ net_pkt_set_overwrite(pkt, true); net_pkt_cursor_init(pkt); net_pkt_skip(pkt, hdr_offset); net_pkt_read(pkt, verify_buf, data_len); verify_test_data_large(verify_buf, 0, data_len); k_sem_give(wait_data); return 0; } static void test_rx_chksum_icmp_frag(sa_family_t family, bool offloaded) { struct k_sem *wait_data = offloaded ? &wait_data_off : &wait_data_nonoff; struct net_icmp_ping_params params = { 0 }; struct net_icmp_ctx ctx; struct sockaddr dst_addr; struct net_if *iface; int ret; test_icmp_init(family, offloaded, &dst_addr, &iface); ret = net_icmp_init_ctx(&ctx, family == AF_INET6 ? NET_ICMPV6_ECHO_REPLY : NET_ICMPV4_ECHO_REPLY, 0, icmp_handler); zassert_equal(ret, 0, "Cannot init ICMP (%d)", ret); test_started = true; test_proto = (family == AF_INET6) ? IPPROTO_ICMPV6 : IPPROTO_ICMP; start_receiving = true; verify_fragment = true; params.data = test_data_large; params.data_size = sizeof(test_data_large); ret = net_icmp_send_echo_request(&ctx, iface, &dst_addr, ¶ms, wait_data); zassert_equal(ret, 0, "Cannot send ICMP Echo-Request (%d)", ret); if (k_sem_take(wait_data, WAIT_TIME)) { DBG("Timeout while waiting interface data\n"); zassert_false(true, "Timeout"); } ret = net_icmp_cleanup_ctx(&ctx); zassert_equal(ret, 0, "Cannot cleanup ICMP (%d)", ret); } ZTEST(net_chksum_offload, test_rx_chksum_offload_disabled_test_v6_icmp_frag) { test_rx_chksum_icmp_frag(AF_INET6, false); } ZTEST(net_chksum_offload, test_rx_chksum_offload_disabled_test_v4_icmp_frag) { test_rx_chksum_icmp_frag(AF_INET, false); } ZTEST(net_chksum_offload, test_rx_chksum_offload_enabled_test_v6_icmp_frag) { test_rx_chksum_icmp_frag(AF_INET6, true); } ZTEST(net_chksum_offload, test_rx_chksum_offload_enabled_test_v4_icmp_frag) { test_rx_chksum_icmp_frag(AF_INET, true); } static void test_rx_chksum_icmp_frag_bad(sa_family_t family, bool offloaded) { struct k_sem *wait_data = offloaded ? &wait_data_off : &wait_data_nonoff; struct net_icmp_ping_params params = { 0 }; struct net_icmp_ctx ctx; struct sockaddr dst_addr; struct net_if *iface; int ret; test_icmp_init(family, offloaded, &dst_addr, &iface); ret = net_icmp_init_ctx(&ctx, family == AF_INET6 ? NET_ICMPV6_ECHO_REPLY : NET_ICMPV4_ECHO_REPLY, 0, icmp_handler); zassert_equal(ret, 0, "Cannot init ICMP (%d)", ret); test_started = true; test_proto = (family == AF_INET6) ? IPPROTO_ICMPV6 : IPPROTO_ICMP; start_receiving = true; verify_fragment = true; change_chksum = true; params.data = test_data_large; params.data_size = sizeof(test_data_large); ret = net_icmp_send_echo_request(&ctx, iface, &dst_addr, ¶ms, wait_data); zassert_equal(ret, 0, "Cannot send ICMP Echo-Request (%d)", ret); if (k_sem_take(wait_data, WAIT_TIME) == 0) { DBG("Packet with bad chksum should be dropped\n"); zassert_false(true, "Packet received"); } ret = net_icmp_cleanup_ctx(&ctx); zassert_equal(ret, 0, "Cannot cleanup ICMP (%d)", ret); } ZTEST(net_chksum_offload, test_rx_chksum_offload_disabled_test_v6_icmp_frag_bad) { test_rx_chksum_icmp_frag_bad(AF_INET6, false); } ZTEST(net_chksum_offload, test_rx_chksum_offload_disabled_test_v4_icmp_frag_bad) { test_rx_chksum_icmp_frag_bad(AF_INET, false); } ZTEST(net_chksum_offload, test_rx_chksum_offload_enabled_test_v6_icmp_frag_bad) { test_rx_chksum_icmp_frag_bad(AF_INET6, true); } ZTEST(net_chksum_offload, test_rx_chksum_offload_enabled_test_v4_icmp_frag_bad) { test_rx_chksum_icmp_frag_bad(AF_INET, true); } static void *net_chksum_offload_tests_setup(void) { test_eth_setup(); test_address_setup(); add_neighbor(eth_interfaces[0], &dst_addr1); add_neighbor(eth_interfaces[1], &dst_addr2); for (size_t i = 0; i < sizeof(test_data_large); i++) { test_data_large[i] = (uint8_t)i; } return NULL; } static void net_chksum_offload_tests_before(void *fixture) { ARG_UNUSED(fixture); k_sem_reset(&wait_data_off); k_sem_reset(&wait_data_nonoff); test_failed = false; test_started = false; start_receiving = false; verify_fragment = false; change_chksum = false; fragment_count = 0; fragment_offset = 0; test_proto = 0; memset(verify_buf, 0, sizeof(verify_buf)); } ZTEST_SUITE(net_chksum_offload, NULL, net_chksum_offload_tests_setup, net_chksum_offload_tests_before, NULL, NULL);