// SPDX-License-Identifier: GPL-2.0
/*
* tcpdevmem netcat. Works similarly to netcat but does device memory TCP
* instead of regular TCP. Uses udmabuf to mock a dmabuf provider.
*
* Usage:
*
* On server:
* ncdevmem -s <server IP> [-c <client IP>] -f eth1 -l -p 5201
*
* On client:
* echo -n "hello\nworld" | \
* ncdevmem -s <server IP> [-c <client IP>] -p 5201 -f eth1
*
* Note this is compatible with regular netcat. i.e. the sender or receiver can
* be replaced with regular netcat to test the RX or TX path in isolation.
*
* Test data validation (devmem TCP on RX only):
*
* On server:
* ncdevmem -s <server IP> [-c <client IP>] -f eth1 -l -p 5201 -v 7
*
* On client:
* yes $(echo -e \\x01\\x02\\x03\\x04\\x05\\x06) | \
* head -c 1G | \
* nc <server IP> 5201 -p 5201
*
* Test data validation (devmem TCP on RX and TX, validation happens on RX):
*
* On server:
* ncdevmem -s <server IP> [-c <client IP>] -l -p 5201 -v 8 -f eth1
*
* On client:
* yes $(echo -e \\x01\\x02\\x03\\x04\\x05\\x06\\x07) | \
* head -c 1M | \
* ncdevmem -s <server IP> [-c <client IP>] -p 5201 -f eth1
*/
#define _GNU_SOURCE
#define __EXPORTED_HEADERS__
#include <linux/uio.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#define __iovec_defined
#include <fcntl.h>
#include <limits.h>
#include <malloc.h>
#include <error.h>
#include <poll.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <linux/memfd.h>
#include <linux/dma-buf.h>
#include <linux/errqueue.h>
#include <linux/udmabuf.h>
#include <linux/types.h>
#include <linux/netlink.h>
#include <linux/genetlink.h>
#include <linux/netdev.h>
#include <linux/ethtool_netlink.h>
#include <time.h>
#include <net/if.h>
#include "netdev-user.h"
#include "ethtool-user.h"
#include <ynl.h>
#define PAGE_SHIFT 12
#define TEST_PREFIX "ncdevmem"
#define NUM_PAGES 16000
#ifndef MSG_SOCK_DEVMEM
#define MSG_SOCK_DEVMEM 0x2000000
#endif
#define MAX_IOV 1024
static size_t max_chunk;
static char *server_ip;
static char *client_ip;
static char *port;
static size_t do_validation;
static int start_queue = -1;
static int num_queues = -1;
static char *ifname;
static unsigned int ifindex;
static unsigned int dmabuf_id;
static uint32_t tx_dmabuf_id;
static int waittime_ms = 500;
static bool fail_on_linear;
/* System state loaded by current_config_load() */
#define MAX_FLOWS 8
static int ntuple_ids[MAX_FLOWS] = { -1, -1, -1, -1, -1, -1, -1, -1, };
struct memory_buffer {
int fd;
size_t size;
int devfd;
int memfd;
char *buf_mem;
};
struct memory_provider {
struct memory_buffer *(*alloc)(size_t size);
void (*free)(struct memory_buffer *ctx);
void (*memcpy_to_device)(struct memory_buffer *dst, size_t off,
void *src, int n);
void (*memcpy_from_device)(void *dst, struct memory_buffer *src,
size_t off, int n);
};
static void pr_err(const char *fmt, ...)
{
va_list args;
fprintf(stderr, "%s: ", TEST_PREFIX);
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
if (errno != 0)
fprintf(stderr, ": %s", strerror(errno));
fprintf(stderr, "\n");
}
static struct memory_buffer *udmabuf_alloc(size_t size)
{
struct udmabuf_create create;
struct memory_buffer *ctx;
int ret;
ctx = malloc(sizeof(*ctx));
if (!ctx)
return NULL;
ctx->size = size;
ctx->devfd = open("/dev/udmabuf", O_RDWR);
if (ctx->devfd < 0) {
pr_err("[skip,no-udmabuf: Unable to access DMA buffer device file]");
goto err_free_ctx;
}
ctx->memfd = memfd_create("udmabuf-test", MFD_ALLOW_SEALING);
if (ctx->memfd < 0) {
pr_err("[skip,no-memfd]");
goto err_close_dev;
}
ret = fcntl(ctx->memfd, F_ADD_SEALS, F_SEAL_SHRINK);
if (ret < 0) {
pr_err("[skip,fcntl-add-seals]");
goto err_close_memfd;
}
ret = ftruncate(ctx->memfd, size);
if (ret == -1) {
pr_err("[FAIL,memfd-truncate]");
goto err_close_memfd;
}
memset(&create, 0, sizeof(create));
create.memfd = ctx->memfd;
create.offset = 0;
create.size = size;
ctx->fd = ioctl(ctx->devfd, UDMABUF_CREATE, &create);
if (ctx->fd < 0) {
pr_err("[FAIL, create udmabuf]");
goto err_close_fd;
}
ctx->buf_mem = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED,
ctx->fd, 0);
if (ctx->buf_mem == MAP_FAILED) {
pr_err("[FAIL, map udmabuf]");
goto err_close_fd;
}
return ctx;
err_close_fd:
close(ctx->fd);
err_close_memfd:
close(ctx->memfd);
err_close_dev:
close(ctx->devfd);
err_free_ctx:
free(ctx);
return NULL;
}
static void udmabuf_free(struct memory_buffer *ctx)
{
munmap(ctx->buf_mem, ctx->size);
close(ctx->fd);
close(ctx->memfd);
close(ctx->devfd);
free(ctx);
}
static void udmabuf_memcpy_to_device(struct memory_buffer *dst, size_t off,
void *src, int n)
{
struct dma_buf_sync sync = {};
sync.flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE;
ioctl(dst->fd, DMA_BUF_IOCTL_SYNC, &sync);
memcpy(dst->buf_mem + off, src, n);
sync.flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_WRITE;
ioctl(dst->fd, DMA_BUF_IOCTL_SYNC, &sync);
}
static void udmabuf_memcpy_from_device(void *dst, struct memory_buffer *src,
size_t off, int n)
{
struct dma_buf_sync sync = {};
sync.flags = DMA_BUF_SYNC_START;
ioctl(src->fd, DMA_BUF_IOCTL_SYNC, &sync);
memcpy(dst, src->buf_mem + off, n);
sync.flags = DMA_BUF_SYNC_END;
ioctl(src->fd, DMA_BUF_IOCTL_SYNC, &sync);
}
static struct memory_provider udmabuf_memory_provider = {
.alloc = udmabuf_alloc,
.free = udmabuf_free,
.memcpy_to_device = udmabuf_memcpy_to_device,
.memcpy_from_device = udmabuf_memcpy_from_device,
};
static struct memory_provider *provider = &udmabuf_memory_provider;
static void print_nonzero_bytes(void *ptr, size_t size)
{
unsigned char *p = ptr;
unsigned int i;
for (i = 0; i < size; i++)
putchar(p[i]);
}
int validate_buffer(void *line, size_t size)
{
static unsigned char seed = 1;
unsigned char *ptr = line;
unsigned char expected;
static int errors;
size_t i;
for (i = 0; i < size; i++) {
expected = seed ? seed : '\n';
if (ptr[i] != expected) {
fprintf(stderr,
"Failed validation: expected=%u, actual=%u, index=%lu\n",
expected, ptr[i], i);
errors++;
if (errors > 20) {
pr_err("validation failed");
return -1;
}
}
seed++;
if (seed == do_validation)
seed = 0;
}
fprintf(stdout, "Validated buffer\n");
return 0;
}
static int
__run_command(char *out
|