blob: d8f3ed1573e04ab4b7bdf1160f8f238eb06c2929 [file]
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Zperf sample.
*/
#include <string.h>
#include <zephyr/kernel.h>
#include <zephyr/usb/usbd.h>
#include <zephyr/net/net_config.h>
LOG_MODULE_REGISTER(zperf, CONFIG_NET_ZPERF_LOG_LEVEL);
#ifdef CONFIG_NET_LOOPBACK_SIMULATE_PACKET_DROP
#include <zephyr/net/loopback.h>
#endif
#if defined(CONFIG_USB_DEVICE_STACK_NEXT)
#include <sample_usbd.h>
#endif
#if defined(CONFIG_ZPERF_LOOPBACK_SELFTEST)
#include <zephyr/net/net_ip.h>
#include <zephyr/net/zperf.h>
#define SELFTEST_PORT CONFIG_ZPERF_LOOPBACK_SELFTEST_PORT
#define SELFTEST_DURATION_MS CONFIG_ZPERF_LOOPBACK_SELFTEST_DURATION_MS
#define SELFTEST_PACKET_SIZE CONFIG_ZPERF_LOOPBACK_SELFTEST_PACKET_SIZE
#define SELFTEST_RATE_KBPS CONFIG_ZPERF_LOOPBACK_SELFTEST_RATE_KBPS
#define SELFTEST_FRAG_PACKET_SIZE CONFIG_ZPERF_LOOPBACK_SELFTEST_FRAG_PACKET_SIZE
/* Non-default socket priority applied to every upload so that, when more than
* one traffic class is configured, traffic is routed through the per-traffic-
* class threads (net_tc.c) instead of the caller's context. Set to -1 (i.e. do
* not set SO_PRIORITY) when priority support is not compiled in.
*/
#if defined(CONFIG_NET_CONTEXT_PRIORITY)
#define SELFTEST_PRIORITY NET_PRIORITY_VI
#else
#define SELFTEST_PRIORITY (-1)
#endif
/* Loopback addresses used for both the server bind and the client connect.
* The loopback driver assigns 127.0.0.1 and ::1 to the loopback interface, so
* both are valid local endpoints.
*/
#define SELFTEST_IPV4_ADDR "127.0.0.1"
#define SELFTEST_IPV6_ADDR "::1"
/* When the configured rate is 0 (unlimited), the runner picks a rate high
* enough that the per-packet pacing delay in the UDP uploader rounds down to
* zero, so the client sends as fast as the (virtual) CPU allows. The computed
* rate (in kbps) is stored in a uint32_t field, so it must fit; the math below
* is done in 64-bit to avoid an intermediate overflow. This bounds the usable
* packet size at roughly 500 kB, far above any realistic MTU.
*/
BUILD_ASSERT(((uint64_t)SELFTEST_PACKET_SIZE * 8U * 1000000U) / 1024U + 1U <=
UINT32_MAX,
"Unlimited-rate loopback selftest packet size overflows rate_kbps");
BUILD_ASSERT(((uint64_t)SELFTEST_FRAG_PACKET_SIZE * 8U * 1000000U) / 1024U + 1U <=
UINT32_MAX,
"Unlimited-rate loopback selftest frag packet size overflows rate_kbps");
static void selftest_session_cb(enum zperf_status status,
struct zperf_results *result,
void *user_data)
{
ARG_UNUSED(status);
ARG_UNUSED(result);
ARG_UNUSED(user_data);
}
/* Fill a socket address with the loopback endpoint for the given family. */
static void selftest_fill_addr(struct net_sockaddr *addr, int family)
{
if (family == NET_AF_INET6) {
struct net_sockaddr_in6 *a6 = net_sin6(addr);
memset(a6, 0, sizeof(*a6));
a6->sin6_family = NET_AF_INET6;
a6->sin6_port = net_htons(SELFTEST_PORT);
(void)net_addr_pton(NET_AF_INET6, SELFTEST_IPV6_ADDR,
&a6->sin6_addr);
} else {
struct net_sockaddr_in *a4 = net_sin(addr);
memset(a4, 0, sizeof(*a4));
a4->sin_family = NET_AF_INET;
a4->sin_port = net_htons(SELFTEST_PORT);
(void)net_addr_pton(NET_AF_INET, SELFTEST_IPV4_ADDR,
&a4->sin_addr);
}
}
static void selftest_fill_upload(struct zperf_upload_params *param, int family,
uint32_t packet_size, bool tcp_nodelay)
{
uint32_t rate_kbps = SELFTEST_RATE_KBPS;
if (rate_kbps == 0U) {
/* Force packet_duration_us == 0, i.e. no pacing. Compute in
* 64-bit to avoid overflowing the intermediate product for
* near-MTU packet sizes, then store the (small enough) result.
*/
rate_kbps = (uint32_t)(((uint64_t)packet_size * 8U *
1000000U) / 1024U + 1U);
}
memset(param, 0, sizeof(*param));
selftest_fill_addr(net_sad(&param->peer_addr_storage), family);
param->duration_ms = SELFTEST_DURATION_MS;
param->packet_size = packet_size;
param->rate_kbps = rate_kbps;
param->unix_offset_us = (uint64_t)k_uptime_get() * USEC_PER_MSEC;
param->options.priority = SELFTEST_PRIORITY;
param->options.tcp_nodelay = tcp_nodelay ? 1 : 0;
}
/* Machine-parseable throughput line for the twister console harness. */
static void selftest_report(const char *proto, struct zperf_results *res)
{
uint64_t kbps = 0U;
if (res->client_time_in_us != 0U) {
kbps = ((uint64_t)res->nb_packets_sent *
(uint64_t)res->packet_size * 8U * USEC_PER_SEC) /
((uint64_t)res->client_time_in_us * 1000U);
}
printk("ZPERF-RESULT %s_mbps=%u.%03u\n", proto,
(uint32_t)(kbps / 1000U), (uint32_t)(kbps % 1000U));
}
static int selftest_run_udp(int family, const char *label, uint32_t packet_size)
{
struct zperf_download_params dl = { .port = SELFTEST_PORT };
struct zperf_upload_params ul;
struct zperf_results res = { 0 };
int ret;
/* Bind the receiver to the loopback address so the client can reach it
* (in particular ::1, which is not the interface's default address).
*/
selftest_fill_addr(&dl.addr, family);
ret = zperf_udp_download(&dl, selftest_session_cb, NULL);
if (ret < 0) {
return ret;
}
selftest_fill_upload(&ul, family, packet_size, false);
ret = zperf_udp_upload(&ul, &res);
(void)zperf_udp_download_stop();
if (ret < 0) {
return ret;
}
selftest_report(label, &res);
return 0;
}
static int selftest_run_tcp(int family, const char *label, bool tcp_nodelay)
{
struct zperf_download_params dl = { .port = SELFTEST_PORT };
struct zperf_upload_params ul;
struct zperf_results res = { 0 };
int ret;
selftest_fill_addr(&dl.addr, family);
ret = zperf_tcp_download(&dl, selftest_session_cb, NULL);
if (ret < 0) {
return ret;
}
selftest_fill_upload(&ul, family, SELFTEST_PACKET_SIZE, tcp_nodelay);
ret = zperf_tcp_upload(&ul, &res);
(void)zperf_tcp_download_stop();
if (ret < 0) {
return ret;
}
selftest_report(label, &res);
return 0;
}
/* Whether the extra, intentionally-fragmenting UDP run should be performed for
* the given family (needs the matching IP fragmentation support compiled in).
*/
static bool selftest_frag_enabled(int family)
{
if (family == NET_AF_INET6) {
return IS_ENABLED(CONFIG_NET_IPV6_FRAGMENT);
}
return IS_ENABLED(CONFIG_NET_IPV4_FRAGMENT);
}
static void selftest_run_family(int family, const char *udp_label,
const char *udp_frag_label,
const char *tcp_label,
const char *tcp_nodelay_label)
{
int ret;
ret = selftest_run_udp(family, udp_label, SELFTEST_PACKET_SIZE);
if (ret < 0) {
printk("ZPERF-RESULT %s_mbps=error(%d)\n", udp_label, ret);
}
/* Oversized datagram: forces IP fragmentation on TX and reassembly on
* RX. Only meaningful when fragmentation is compiled in.
*/
if (selftest_frag_enabled(family)) {
ret = selftest_run_udp(family, udp_frag_label,
SELFTEST_FRAG_PACKET_SIZE);
if (ret < 0) {
printk("ZPERF-RESULT %s_mbps=error(%d)\n",
udp_frag_label, ret);
}
}
ret = selftest_run_tcp(family, tcp_label, false);
if (ret < 0) {
printk("ZPERF-RESULT %s_mbps=error(%d)\n", tcp_label, ret);
}
/* Same transfer with Nagle disabled to cover the TCP_NODELAY send path. */
ret = selftest_run_tcp(family, tcp_nodelay_label, true);
if (ret < 0) {
printk("ZPERF-RESULT %s_mbps=error(%d)\n", tcp_nodelay_label,
ret);
}
}
static void run_loopback_selftest(void)
{
if (IS_ENABLED(CONFIG_NET_IPV4)) {
selftest_run_family(NET_AF_INET, "udp4", "udp4_frag", "tcp4",
"tcp4_nodelay");
}
if (IS_ENABLED(CONFIG_NET_IPV6)) {
selftest_run_family(NET_AF_INET6, "udp6", "udp6_frag", "tcp6",
"tcp6_nodelay");
}
printk("ZPERF-DONE\n");
}
#endif /* CONFIG_ZPERF_LOOPBACK_SELFTEST */
int main(void)
{
#if defined(CONFIG_USB_DEVICE_STACK_NEXT)
struct usbd_context *sample_usbd;
int err;
sample_usbd = sample_usbd_init_device(NULL);
if (sample_usbd == NULL) {
return -ENODEV;
}
err = usbd_enable(sample_usbd);
if (err) {
return err;
}
(void)net_config_init_app(NULL, "Initializing network");
#endif /* CONFIG_USB_DEVICE_STACK_NEXT */
#ifdef CONFIG_NET_LOOPBACK_SIMULATE_PACKET_DROP
loopback_set_packet_drop_ratio(1);
#endif
#if defined(CONFIG_NET_DHCPV4) && !defined(CONFIG_NET_CONFIG_SETTINGS)
net_dhcpv4_start(net_if_get_default());
#endif
#if defined(CONFIG_ZPERF_LOOPBACK_SELFTEST)
run_loopback_selftest();
#endif /* CONFIG_ZPERF_LOOPBACK_SELFTEST */
return 0;
}