samples: zperf: Use native IP stack for TCP support

Enable zperf code to support TCP using the native IP stack
when testing the network throughput.

Change-Id: I3e58754cfff65525ad15e63adf57f1ea22e4559d
Signed-off-by: Jukka Rissanen <jukka.rissanen@linux.intel.com>
This commit is contained in:
Jukka Rissanen 2017-01-17 15:20:24 +02:00
commit 48b9b96d3f
6 changed files with 338 additions and 143 deletions

View file

@ -45,6 +45,12 @@ For example, the following command line must be used for UDP testing:
$ iperf -s -l 1K -u -V -B 2001:db8::2
For TCP testing, the command line would look like this:
.. code-block:: console
$ iperf -s -l 1K -V -B 2001:db8::2
In the Zephyr console, zperf can be executed as follows:
@ -53,6 +59,13 @@ In the Zephyr console, zperf can be executed as follows:
zperf> udp.upload 2001:db8::2 5001 10 1K 1M
For TCP the zperf command would look like this:
.. code-block:: console
zperf> tcp.upload 2001:db8::2 5001 10 1K 1M
If the IP addresses of Zephyr and the host machine are specified in the
config file, zperf can be started as follows:
@ -61,20 +74,41 @@ config file, zperf can be started as follows:
zperf> udp.upload2 v6 10 1K 1M
If Zephyr is acting as a server, set the download mode as follows:
or like this if you want to test TCP:
.. code-block:: console
zperf> tcp.upload2 v6 10 1K 1M
If Zephyr is acting as a server, set the download mode as follows for UDP:
.. code-block:: console
zperf> udp.download 5001
or like this for TCP:
.. code-block:: console
zperf> tcp.download 5001
and in the host side, iPerf must be executed with the following
command line:
command line if you are testing UDP:
.. code-block:: console
$ iperf -l 1K -u -V -c 2001:db8::1 -p 5001
and this if you are testing TCP:
.. code-block:: console
$ iperf -l 1K -V -c 2001:db8::1 -p 5001
iPerf output can be limited by using the -b option if Zephyr is not
able to receive all the packets in orderly manner.

View file

@ -16,6 +16,7 @@
#define CMD_STR_UDP_UPLOAD2 "udp.upload2"
#define CMD_STR_UDP_DOWNLOAD "udp.download"
#define CMD_STR_TCP_UPLOAD "tcp.upload"
#define CMD_STR_TCP_UPLOAD2 "tcp.upload2"
#define CMD_STR_TCP_DOWNLOAD "tcp.download"
struct zperf_results {

View file

@ -99,6 +99,7 @@ extern void zperf_receiver_init(int port);
#if defined(CONFIG_NET_TCP)
extern void zperf_tcp_receiver_init(int port);
extern void zperf_tcp_uploader_init(struct k_fifo *tx_queue);
extern void zperf_tcp_upload(struct net_context *net_context,
unsigned int duration_in_ms,
unsigned int packet_size,

View file

@ -59,6 +59,7 @@ static const char *CONFIG =
#define MY_SRC_PORT 50000
#define DEF_PORT 5001
#define WAIT_CONNECT (2 * 1000) /* in ms */
#if defined(CONFIG_NET_IPV6)
static struct in6_addr ipv6;
@ -328,7 +329,7 @@ static void shell_udp_upload2_usage(void)
printk("\t<baud rate>:\tBaudrate in kilobyte or megabyte\n");
printk("\nExample %s v6 1 1K 1M\n",
CMD_STR_UDP_UPLOAD2);
#if defined(CONFIG_NET_IPV6) && defined(MY_IPV6ADDR)
#if defined(CONFIG_NET_IPV6) && defined(MY_IP6ADDR)
printk("\nDefault IPv6 address is %s, destination [%s]:%d\n",
MY_IP6ADDR, DST_IP6ADDR, DEF_PORT);
#endif
@ -354,6 +355,29 @@ static void shell_tcp_upload_usage(void)
printk("\nExample %s 10.237.164.178 1111 1 1K 1M\n",
CMD_STR_TCP_UPLOAD);
}
static void shell_tcp_upload2_usage(void)
{
/* Print usage */
printk("\n%s:\n", CMD_STR_TCP_UPLOAD2);
printk("Usage:\t%s v6|v4 <duration> <packet "
"size>[K] <baud rate>[K|M]\n", CMD_STR_TCP_UPLOAD2);
printk("\t<v6|v4>:\tUse either IPv6 or IPv4\n");
printk("\t<duration>:\tDuration of the test in seconds\n");
printk("\t<packet size>:\tSize of the packet in byte or kilobyte "
"(with suffix K)\n");
printk("\t<baud rate>:\tBaudrate in kilobyte or megabyte\n");
printk("\nExample %s v6 1 1K 1M\n",
CMD_STR_TCP_UPLOAD2);
#if defined(CONFIG_NET_IPV6) && defined(MY_IP6ADDR)
printk("\nDefault IPv6 address is %s, destination [%s]:%d\n",
MY_IP6ADDR, DST_IP6ADDR, DEF_PORT);
#endif
#if defined(CONFIG_NET_IPV4) && defined(MY_IP4ADDR)
printk("\nDefault IPv4 address is %s, destination %s:%d\n",
MY_IP4ADDR, DST_IP4ADDR, DEF_PORT);
#endif
}
#endif
#if defined(CONFIG_NET_UDP)
@ -435,11 +459,11 @@ static void shell_tcp_upload_print_stats(struct zperf_results *results)
printk("[%s] duration:\t", CMD_STR_TCP_UPLOAD);
print_number(results->client_time_in_us, TIME_US, TIME_US_UNIT);
printk("\n");
printk("[%s] nb packets:\t%u\n", CMD_STR_UDP_UPLOAD,
printk("[%s] nb packets:\t%u\n", CMD_STR_TCP_UPLOAD,
results->nb_packets_sent);
printk("[%s] nb sending errors (retry or fail):\t%u\n",
CMD_STR_UDP_UPLOAD, results->nb_packets_errors);
printk("[%s] rate:\t", CMD_STR_UDP_UPLOAD);
CMD_STR_TCP_UPLOAD, results->nb_packets_errors);
printk("[%s] rate:\t", CMD_STR_TCP_UPLOAD);
print_number(client_rate_in_kbps, KBPS, KBPS_UNIT);
printk("\n");
}
@ -596,38 +620,53 @@ static int execute_upload(struct net_context *context6,
#endif
} else {
#if defined(CONFIG_NET_TCP)
if (context6) {
if (family == AF_INET6 && context6) {
ret = net_context_connect(context6,
(struct sockaddr *)ipv6,
sizeof(*ipv6),
NULL,
K_NO_WAIT,
WAIT_CONNECT,
NULL);
if (ret < 0) {
printk("[%s] IPv6 connect failed\n", argv0);
printk("[%s] IPv6 connect failed (%d)\n",
argv0, ret);
goto out;
}
/* We either upload using IPv4 or IPv6, not both at
* the same time.
*/
net_context_put(context4);
zperf_tcp_upload(context6, duration_in_ms,
packet_size, &results);
shell_tcp_upload_print_stats(&results);
return 0;
}
if (context4) {
if (family == AF_INET && context4) {
ret = net_context_connect(context4,
(struct sockaddr *)ipv4,
sizeof(*ipv4),
NULL,
K_NO_WAIT,
WAIT_CONNECT,
NULL);
if (ret < 0) {
printk("[%s] IPv4 connect failed\n", argv0);
printk("[%s] IPv4 connect failed (%d)\n",
argv0, ret);
goto out;
}
net_context_put(context6);
zperf_tcp_upload(context4, duration_in_ms,
packet_size, &results);
shell_tcp_upload_print_stats(&results);
return 0;
}
#else
printk("[%s] TCP not supported\n", argv0);
@ -983,6 +1022,8 @@ struct shell_cmd commands[] = {
{ CMD_STR_UDP_DOWNLOAD, shell_cmd_udp_download },
#endif
#if defined(CONFIG_NET_TCP)
{ CMD_STR_TCP_UPLOAD, shell_cmd_upload },
{ CMD_STR_TCP_UPLOAD2, shell_cmd_upload2 },
{ CMD_STR_TCP_DOWNLOAD, shell_cmd_tcp_download },
#endif
#if defined(PROFILER)

View file

@ -4,8 +4,6 @@
* SPDX-License-Identifier: Apache-2.0
*/
#error "FIXME - TCP not supported yet"
#include <zephyr.h>
#include <sections.h>
@ -22,110 +20,219 @@
#include "shell_utils.h"
#include "zperf_session.h"
/* To get net_sprint_ipv{4|6}_addr() */
#define NET_LOG_ENABLED 1
#include "net_private.h"
#define TAG CMD_STR_TCP_DOWNLOAD" "
#define TCP_RX_FIBER_STACK_SIZE 1024
/* Static data */
static char __noinit __stack zperf_tcp_rx_fiber_stack[TCP_RX_FIBER_STACK_SIZE];
static char __noinit __stack zperf_tcp_rx_stack[TCP_RX_FIBER_STACK_SIZE];
static struct net_addr in_addr_any = {
#if defined(CONFIG_NETWORKING_WITH_IPV6)
.family = AF_INET6,
.in6_addr = IN6ADDR_ANY_INIT
#else
.family = AF_INET, .in_addr = { { { 0 } } }
#endif
};
static struct sockaddr_in6 *in6_addr_my;
static struct sockaddr_in *in4_addr_my;
static struct net_addr in_addr_my = {
#if defined(CONFIG_NETWORKING_WITH_IPV6)
.family = AF_INET6,
.in6_addr = IN6ADDR_ANY_INIT
#else
.family = AF_INET, .in_addr = { { { 0 } } }
#endif
};
/* TCP RX fiber entry point */
static void zperf_tcp_rx_fiber(int port)
static void tcp_received(struct net_context *context,
struct net_buf *buf,
int status,
void *user_data)
{
struct net_context *net_context = net_context_get(IPPROTO_TCP, &in_addr_any,
0, &in_addr_my, port);
struct session *session;
uint32_t time;
if (!net_context) {
printk(TAG "ERROR! Cannot get network context.\n");
if (!buf) {
return;
}
while (1) {
struct net_buf *buf = net_receive(net_context, K_FOREVER);
struct session *session = NULL;
uint32_t time = k_cycle_get_32();
time = k_cycle_get_32();
if (buf == NULL) {
printk(TAG "buf is null\n");
continue;
}
session = get_session(buf, SESSION_UDP);
if (session == NULL) {
printk(TAG "ERROR! cannot get a session!\n");
/* free buffer */
ip_buf_unref(buf);
continue;
}
switch (session->state) {
case STATE_NULL:
case STATE_COMPLETED:
printk(TAG "New session started\n");
zperf_reset_session_stats(session);
session->start_time = k_cycle_get_32();
session->state = STATE_ONGOING;
case STATE_ONGOING:
session->counter++;
session->length += ip_buf_appdatalen(buf);
if (uip_closed(buf)) {
session->state = STATE_COMPLETED;
uint32_t rate_in_kbps;
uint32_t duration = HW_CYCLES_TO_USEC(
time_delta(session->start_time, time));
/* Compute baud rate */
if (duration != 0) {
rate_in_kbps = (uint32_t) ((
(uint64_t) session->length
* (uint64_t) 8
* (uint64_t) USEC_PER_SEC)
/ ((uint64_t) duration * 1024));
} else {
rate_in_kbps = 0;
}
printk(TAG "TCP session ended\n");
printk(TAG " duration:\t\t");
print_number(duration, TIME_US, TIME_US_UNIT);
printk("\n");
printk(TAG " rate:\t\t\t");
print_number(rate_in_kbps, KBPS, KBPS_UNIT);
printk("\n");
}
break;
case STATE_LAST_PACKET_RECEIVED:
break;
default:
printk(TAG "Error! Unsupported case\n");
}
/* free buffer */
ip_buf_unref(buf);
session = get_session(buf, SESSION_TCP);
if (!session) {
printk(TAG "ERROR! cannot get a session!\n");
return;
}
switch (session->state) {
case STATE_NULL:
case STATE_COMPLETED:
printk(TAG "New session started\n");
zperf_reset_session_stats(session);
session->start_time = sys_cycle_get_32();
session->state = STATE_ONGOING;
/* fall through */
case STATE_ONGOING:
session->counter++;
if (buf) {
session->length += net_nbuf_appdatalen(buf);
}
if (!buf && status == 0) { /* EOF */
uint32_t rate_in_kbps;
uint32_t duration = HW_CYCLES_TO_USEC(
time_delta(session->start_time, time));
session->state = STATE_COMPLETED;
/* Compute baud rate */
if (duration != 0) {
rate_in_kbps = (uint32_t)
(((uint64_t)session->length *
(uint64_t)8 *
(uint64_t)USEC_PER_SEC) /
((uint64_t)duration * 1024));
} else {
rate_in_kbps = 0;
}
printk(TAG "TCP session ended\n");
printk(TAG " duration:\t\t");
print_number(duration, TIME_US, TIME_US_UNIT);
printk("\n");
printk(TAG " rate:\t\t\t");
print_number(rate_in_kbps, KBPS, KBPS_UNIT);
printk("\n");
}
break;
case STATE_LAST_PACKET_RECEIVED:
break;
default:
printk(TAG "Error! Unsupported case\n");
}
net_nbuf_unref(buf);
}
static void tcp_accepted(struct net_context *context,
struct sockaddr *addr,
socklen_t addrlen,
int error,
void *user_data)
{
int ret;
ret = net_context_recv(context, tcp_received, K_NO_WAIT, user_data);
if (ret < 0) {
printk(TAG "Cannot receive TCP packet (family %d)",
net_context_get_family(context));
}
}
static void zperf_tcp_rx_thread(int port)
{
struct net_context *context4 = NULL, *context6 = NULL;
int ret, fail = 0;
#if defined(CONFIG_NET_IPV4) && defined(MY_IP4ADDR)
ret = net_context_get(AF_INET, SOCK_STREAM, IPPROTO_TCP, &context4);
if (ret < 0) {
printk(TAG "ERROR! Cannot get IPv4 TCP network context.\n");
return;
}
ret = zperf_get_ipv4_addr(MY_IP4ADDR, &in4_addr_my->sin_addr, TAG);
if (ret < 0) {
printk(TAG "ERROR! Unable to set IPv4\n");
return;
}
printk(TAG "Binding to %s\n",
net_sprint_ipv4_addr(&in4_addr_my->sin_addr));
in4_addr_my->sin_port = htons(port);
#endif
#if defined(CONFIG_NET_IPV6) && defined(MY_IP6ADDR)
ret = net_context_get(AF_INET6, SOCK_STREAM, IPPROTO_TCP, &context6);
if (ret < 0) {
printk(TAG "ERROR! Cannot get IPv6 TCP network context.\n");
return;
}
ret = zperf_get_ipv6_addr(MY_IP6ADDR, MY_PREFIX_LEN_STR,
&in6_addr_my->sin6_addr, TAG);
if (ret < 0) {
printk(TAG "ERROR! Unable to set IPv6\n");
return;
}
printk(TAG "Binding to %s\n",
net_sprint_ipv6_addr(&in6_addr_my->sin6_addr));
in6_addr_my->sin6_port = htons(port);
#endif
#if defined(CONFIG_NET_IPV6)
if (context6) {
ret = net_context_bind(context6,
(struct sockaddr *)in6_addr_my,
sizeof(struct sockaddr_in6));
if (ret < 0) {
printk(TAG "Cannot bind IPv6 TCP port %d (%d)\n",
ntohs(in6_addr_my->sin6_port), ret);
fail++;
}
ret = net_context_listen(context6, 0);
if (ret < 0) {
printk(TAG "Cannot listen IPv6 TCP (%d)", ret);
return;
}
ret = net_context_accept(context6, tcp_accepted, 0, NULL);
if (ret < 0) {
printk(TAG "Cannot receive IPv6 TCP packets (%d)", ret);
return;
}
}
#endif
#if defined(CONFIG_NET_IPV4)
if (context4) {
ret = net_context_bind(context4,
(struct sockaddr *)in4_addr_my,
sizeof(struct sockaddr_in));
if (ret < 0) {
printk(TAG "Cannot bind IPv4 TCP port %d (%d)\n",
ntohs(in4_addr_my->sin_port), ret);
fail++;
}
ret = net_context_listen(context4, 0);
if (ret < 0) {
printk(TAG "Cannot listen IPv4 TCP (%d)", ret);
return;
}
ret = net_context_accept(context4, tcp_accepted, 0, NULL);
if (ret < 0) {
printk(TAG "Cannot receive IPv4 TCP packets (%d)", ret);
return;
}
}
#endif
if (fail > 1) {
return;
}
k_sleep(K_FOREVER);
}
void zperf_tcp_receiver_init(int port)
{
fiber_start(zperf_tcp_rx_fiber_stack, sizeof(zperf_tcp_rx_fiber_stack),
(nano_fiber_entry_t) zperf_tcp_rx_fiber, port, 0, 7, 0);
#if defined(CONFIG_NET_IPV6)
in6_addr_my = zperf_get_sin6();
#endif
#if defined(CONFIG_NET_IPV4)
in4_addr_my = zperf_get_sin();
#endif
k_thread_spawn(zperf_tcp_rx_stack, sizeof(zperf_tcp_rx_stack),
(k_thread_entry_t)zperf_tcp_rx_thread,
INT_TO_POINTER(port), 0, 0,
K_PRIO_COOP(7), 0, K_NO_WAIT);
}

View file

@ -4,23 +4,26 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <errno.h>
#include <misc/printk.h>
#include <net/ip_buf.h>
#include <net/nbuf.h>
#include <net/net_ip.h>
#include <net/net_core.h>
#include <net/net_socket.h>
#include <zephyr.h>
#include "zperf.h"
#include "zperf_internal.h"
#define TAG CMD_STR_TCP_UPLOAD" "
void zperf_tcp_upload(struct net_context *net_context,
unsigned int duration_in_ms,
unsigned int packet_size,
zperf_results *results)
static char sample_packet[PACKET_SIZE_MAX];
void zperf_tcp_upload(struct net_context *ctx,
unsigned int duration_in_ms,
unsigned int packet_size,
struct zperf_results *results)
{
uint32_t duration = MSEC_TO_HW_CYCLES(duration_in_ms);
uint32_t nb_packets = 0, nb_errors = 0;
@ -29,7 +32,7 @@ void zperf_tcp_upload(struct net_context *net_context,
if (packet_size > PACKET_SIZE_MAX) {
printk(TAG "WARNING! packet size too large! max size: %u\n",
PACKET_SIZE_MAX);
PACKET_SIZE_MAX);
packet_size = PACKET_SIZE_MAX;
}
@ -38,69 +41,77 @@ void zperf_tcp_upload(struct net_context *net_context,
last_print_time = start_time;
last_loop_time = start_time;
printk(TAG "New session started\n");
memset(sample_packet, 'z', sizeof(sample_packet));
do {
uint32_t loop_time;
uint8_t *ptr = NULL;
int ret = 0;
struct net_buf *buf, *frag;
uint32_t loop_time;
bool st;
/* Timestamps */
loop_time = k_cycle_get_32();
last_loop_time = loop_time;
/* Get a new TX buffer */
struct net_buf *buf = ip_buf_get_tx(net_context);
buf = net_nbuf_get_tx(ctx);
if (!buf) {
printk(TAG "ERROR! Failed to retrieve a buffer\n");
continue;
}
/* Fill in the TCP payload */
ptr = net_buf_add(buf, packet_size);
memset(ptr, 'z', packet_size);
frag = net_nbuf_get_data(ctx);
if (!frag) {
printk(TAG "ERROR! Failed to retrieve a fragment\n");
continue;
}
/* If test time is elapsed, send a last packet with specific flag
* to request uIP to close the TCP connection
*/
if (time_elapsed) {
uip_flags(buf) |= UIP_CLOSE;
net_buf_frag_add(buf, frag);
/* Fill in the TCP payload */
st = net_nbuf_append(buf, sizeof(sample_packet),
sample_packet);
if (!st) {
printk(TAG "ERROR! Failed to fill packet\n");
net_nbuf_unref(buf);
nb_errors++;
break;
}
/* Send the packet */
again:
ret = net_send(buf);
ret = net_context_send(buf, NULL, K_NO_WAIT, NULL, NULL);
if (ret < 0) {
if (ret == -EINPROGRESS || ret == -EAGAIN) {
nb_errors++;
fiber_sleep(100);
goto again;
} else {
printk("ERROR! Failed to send the buffer\n");
nb_errors++;
}
printk(TAG "ERROR! Failed to send the buffer (%d)\n",
ret);
net_nbuf_unref(buf);
nb_errors++;
break;
} else {
nb_packets++;
/* if test time is elapsed and are here, that means TCP connection
* has been closed by uIP as requested. So exit the loop.
*/
if (time_elapsed) {
finished = 1;
}
}
if (!time_elapsed && time_delta(start_time, last_loop_time) > duration)
if (!time_elapsed && time_delta(start_time,
last_loop_time) > duration) {
time_elapsed = 1;
}
ip_buf_unref(buf);
fiber_yield();
k_yield();
} while (!finished);
end_time = k_cycle_get_32();
/* Add result coming from the client */
results->nb_packets_sent = nb_packets;
results->client_time_in_us = HW_CYCLES_TO_USEC(
time_delta(start_time, end_time));
results->client_time_in_us =
HW_CYCLES_TO_USEC(time_delta(start_time, end_time));
results->packet_size = packet_size;
results->nb_packets_errors = nb_errors;
net_context_put(ctx);
}