-
Notifications
You must be signed in to change notification settings - Fork 55.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Send udp data between a source and sink, optionally with udp gso. The two processes are expected to be run on separate hosts. A script is included that runs them together over loopback in a single namespace for functionality testing. Signed-off-by: Willem de Bruijn <[email protected]> Signed-off-by: David S. Miller <[email protected]>
- Loading branch information
Showing
5 changed files
with
763 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,3 +9,5 @@ reuseport_dualstack | |
reuseaddr_conflict | ||
tcp_mmap | ||
udpgso | ||
udpgso_bench_rx | ||
udpgso_bench_tx |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
#!/bin/sh | ||
# SPDX-License-Identifier: GPL-2.0 | ||
# | ||
# Run a series of udpgso benchmarks | ||
|
||
wake_children() { | ||
local -r jobs="$(jobs -p)" | ||
|
||
if [[ "${jobs}" != "" ]]; then | ||
kill -1 ${jobs} 2>/dev/null | ||
fi | ||
} | ||
trap wake_children EXIT | ||
|
||
run_one() { | ||
local -r args=$@ | ||
|
||
./udpgso_bench_rx & | ||
./udpgso_bench_rx -t & | ||
|
||
./udpgso_bench_tx ${args} | ||
} | ||
|
||
run_in_netns() { | ||
local -r args=$@ | ||
|
||
./in_netns.sh $0 __subprocess ${args} | ||
} | ||
|
||
run_udp() { | ||
local -r args=$@ | ||
|
||
echo "udp" | ||
run_in_netns ${args} | ||
|
||
echo "udp gso" | ||
run_in_netns ${args} -S | ||
|
||
echo "udp gso zerocopy" | ||
run_in_netns ${args} -S -z | ||
} | ||
|
||
run_tcp() { | ||
local -r args=$@ | ||
|
||
echo "tcp" | ||
run_in_netns ${args} -t | ||
|
||
echo "tcp zerocopy" | ||
run_in_netns ${args} -t -z | ||
} | ||
|
||
run_all() { | ||
local -r core_args="-l 4" | ||
local -r ipv4_args="${core_args} -4 -D 127.0.0.1" | ||
local -r ipv6_args="${core_args} -6 -D ::1" | ||
|
||
echo "ipv4" | ||
run_tcp "${ipv4_args}" | ||
run_udp "${ipv4_args}" | ||
|
||
echo "ipv6" | ||
run_tcp "${ipv4_args}" | ||
run_udp "${ipv6_args}" | ||
} | ||
|
||
if [[ $# -eq 0 ]]; then | ||
run_all | ||
elif [[ $1 == "__subprocess" ]]; then | ||
shift | ||
run_one $@ | ||
else | ||
run_in_netns $@ | ||
fi |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,265 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
|
||
#define _GNU_SOURCE | ||
|
||
#include <arpa/inet.h> | ||
#include <error.h> | ||
#include <errno.h> | ||
#include <limits.h> | ||
#include <linux/errqueue.h> | ||
#include <linux/if_packet.h> | ||
#include <linux/socket.h> | ||
#include <linux/sockios.h> | ||
#include <net/ethernet.h> | ||
#include <net/if.h> | ||
#include <netinet/ip.h> | ||
#include <netinet/ip6.h> | ||
#include <netinet/tcp.h> | ||
#include <netinet/udp.h> | ||
#include <poll.h> | ||
#include <sched.h> | ||
#include <stdbool.h> | ||
#include <stdio.h> | ||
#include <stdint.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
#include <sys/ioctl.h> | ||
#include <sys/socket.h> | ||
#include <sys/stat.h> | ||
#include <sys/time.h> | ||
#include <sys/types.h> | ||
#include <sys/wait.h> | ||
#include <unistd.h> | ||
|
||
static int cfg_port = 8000; | ||
static bool cfg_tcp; | ||
static bool cfg_verify; | ||
|
||
static bool interrupted; | ||
static unsigned long packets, bytes; | ||
|
||
static void sigint_handler(int signum) | ||
{ | ||
if (signum == SIGINT) | ||
interrupted = true; | ||
} | ||
|
||
static unsigned long gettimeofday_ms(void) | ||
{ | ||
struct timeval tv; | ||
|
||
gettimeofday(&tv, NULL); | ||
return (tv.tv_sec * 1000) + (tv.tv_usec / 1000); | ||
} | ||
|
||
static void do_poll(int fd) | ||
{ | ||
struct pollfd pfd; | ||
int ret; | ||
|
||
pfd.events = POLLIN; | ||
pfd.revents = 0; | ||
pfd.fd = fd; | ||
|
||
do { | ||
ret = poll(&pfd, 1, 10); | ||
if (ret == -1) | ||
error(1, errno, "poll"); | ||
if (ret == 0) | ||
continue; | ||
if (pfd.revents != POLLIN) | ||
error(1, errno, "poll: 0x%x expected 0x%x\n", | ||
pfd.revents, POLLIN); | ||
} while (!ret && !interrupted); | ||
} | ||
|
||
static int do_socket(bool do_tcp) | ||
{ | ||
struct sockaddr_in6 addr = {0}; | ||
int fd, val; | ||
|
||
fd = socket(PF_INET6, cfg_tcp ? SOCK_STREAM : SOCK_DGRAM, 0); | ||
if (fd == -1) | ||
error(1, errno, "socket"); | ||
|
||
val = 1 << 21; | ||
if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val))) | ||
error(1, errno, "setsockopt rcvbuf"); | ||
val = 1; | ||
if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val))) | ||
error(1, errno, "setsockopt reuseport"); | ||
|
||
addr.sin6_family = PF_INET6; | ||
addr.sin6_port = htons(cfg_port); | ||
addr.sin6_addr = in6addr_any; | ||
if (bind(fd, (void *) &addr, sizeof(addr))) | ||
error(1, errno, "bind"); | ||
|
||
if (do_tcp) { | ||
int accept_fd = fd; | ||
|
||
if (listen(accept_fd, 1)) | ||
error(1, errno, "listen"); | ||
|
||
do_poll(accept_fd); | ||
|
||
fd = accept(accept_fd, NULL, NULL); | ||
if (fd == -1) | ||
error(1, errno, "accept"); | ||
if (close(accept_fd)) | ||
error(1, errno, "close accept fd"); | ||
} | ||
|
||
return fd; | ||
} | ||
|
||
/* Flush all outstanding bytes for the tcp receive queue */ | ||
static void do_flush_tcp(int fd) | ||
{ | ||
int ret; | ||
|
||
while (true) { | ||
/* MSG_TRUNC flushes up to len bytes */ | ||
ret = recv(fd, NULL, 1 << 21, MSG_TRUNC | MSG_DONTWAIT); | ||
if (ret == -1 && errno == EAGAIN) | ||
return; | ||
if (ret == -1) | ||
error(1, errno, "flush"); | ||
if (ret == 0) { | ||
/* client detached */ | ||
exit(0); | ||
} | ||
|
||
packets++; | ||
bytes += ret; | ||
} | ||
|
||
} | ||
|
||
static char sanitized_char(char val) | ||
{ | ||
return (val >= 'a' && val <= 'z') ? val : '.'; | ||
} | ||
|
||
static void do_verify_udp(const char *data, int len) | ||
{ | ||
char cur = data[0]; | ||
int i; | ||
|
||
/* verify contents */ | ||
if (cur < 'a' || cur > 'z') | ||
error(1, 0, "data initial byte out of range"); | ||
|
||
for (i = 1; i < len; i++) { | ||
if (cur == 'z') | ||
cur = 'a'; | ||
else | ||
cur++; | ||
|
||
if (data[i] != cur) | ||
error(1, 0, "data[%d]: len %d, %c(%hhu) != %c(%hhu)\n", | ||
i, len, | ||
sanitized_char(data[i]), data[i], | ||
sanitized_char(cur), cur); | ||
} | ||
} | ||
|
||
/* Flush all outstanding datagrams. Verify first few bytes of each. */ | ||
static void do_flush_udp(int fd) | ||
{ | ||
static char rbuf[ETH_DATA_LEN]; | ||
int ret, len, budget = 256; | ||
|
||
len = cfg_verify ? sizeof(rbuf) : 0; | ||
while (budget--) { | ||
/* MSG_TRUNC will make return value full datagram length */ | ||
ret = recv(fd, rbuf, len, MSG_TRUNC | MSG_DONTWAIT); | ||
if (ret == -1 && errno == EAGAIN) | ||
return; | ||
if (ret == -1) | ||
error(1, errno, "recv"); | ||
if (len) { | ||
if (ret == 0) | ||
error(1, errno, "recv: 0 byte datagram\n"); | ||
|
||
do_verify_udp(rbuf, ret); | ||
} | ||
|
||
packets++; | ||
bytes += ret; | ||
} | ||
} | ||
|
||
static void usage(const char *filepath) | ||
{ | ||
error(1, 0, "Usage: %s [-tv] [-p port]", filepath); | ||
} | ||
|
||
static void parse_opts(int argc, char **argv) | ||
{ | ||
int c; | ||
|
||
while ((c = getopt(argc, argv, "ptv")) != -1) { | ||
switch (c) { | ||
case 'p': | ||
cfg_port = htons(strtoul(optarg, NULL, 0)); | ||
break; | ||
case 't': | ||
cfg_tcp = true; | ||
break; | ||
case 'v': | ||
cfg_verify = true; | ||
break; | ||
} | ||
} | ||
|
||
if (optind != argc) | ||
usage(argv[0]); | ||
|
||
if (cfg_tcp && cfg_verify) | ||
error(1, 0, "TODO: implement verify mode for tcp"); | ||
} | ||
|
||
static void do_recv(void) | ||
{ | ||
unsigned long tnow, treport; | ||
int fd; | ||
|
||
fd = do_socket(cfg_tcp); | ||
|
||
treport = gettimeofday_ms() + 1000; | ||
do { | ||
do_poll(fd); | ||
|
||
if (cfg_tcp) | ||
do_flush_tcp(fd); | ||
else | ||
do_flush_udp(fd); | ||
|
||
tnow = gettimeofday_ms(); | ||
if (tnow > treport) { | ||
if (packets) | ||
fprintf(stderr, | ||
"%s rx: %6lu MB/s %8lu calls/s\n", | ||
cfg_tcp ? "tcp" : "udp", | ||
bytes >> 20, packets); | ||
bytes = packets = 0; | ||
treport = tnow + 1000; | ||
} | ||
|
||
} while (!interrupted); | ||
|
||
if (close(fd)) | ||
error(1, errno, "close"); | ||
} | ||
|
||
int main(int argc, char **argv) | ||
{ | ||
parse_opts(argc, argv); | ||
|
||
signal(SIGINT, sigint_handler); | ||
|
||
do_recv(); | ||
|
||
return 0; | ||
} |
Oops, something went wrong.