1574 lines · 34.8 KB · C
RawDownload
// 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 <stdint.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 <sys/param.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#define MB(x) ((x) << 20) #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 int skip_config;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;static uint32_t rx_page_size; /* System state loaded by current_config_load() */#define MAX_FLOWS	8static 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;	unsigned int memfd_flags;	int ret; 	ctx = malloc(sizeof(*ctx));	if (!ctx)		return NULL; 	ctx->size = size; 	ctx->devfd = open("/dev/udmabuf", O_RDONLY);	if (ctx->devfd < 0) {		pr_err("[skip,no-udmabuf: Unable to access DMA buffer device file]");		goto err_free_ctx;	} 	memfd_flags = MFD_ALLOW_SEALING;	if (rx_page_size > getpagesize())		memfd_flags |= MFD_HUGETLB | MFD_HUGE_2MB; 	ctx->memfd = memfd_create("udmabuf-test", memfd_flags);	if (ctx->memfd < 0) {		pr_err("[skip,no-memfd%s]",		       (memfd_flags & MFD_HUGETLB) ? " (need hugepages)" : "");		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;	} 	if (memfd_flags & MFD_HUGETLB) {		size = roundup(size, MB(2));		ctx->size = size;	} 	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, size_t outlen, const char *cmd, va_list args){	char command[256];	FILE *fp; 	vsnprintf(command, sizeof(command), cmd, args); 	fprintf(stderr, "Running: %s\n", command);	fp = popen(command, "r");	if (!fp)		return -1;	if (out) {		size_t len; 		if (!fgets(out, outlen, fp))			return -1; 		/* Remove trailing newline if present */		len = strlen(out);		if (len && out[len - 1] == '\n')			out[len - 1] = '\0';	}	return pclose(fp);} static int run_command(const char *cmd, ...){	va_list args;	int ret; 	va_start(args, cmd);	ret = __run_command(NULL, 0, cmd, args);	va_end(args); 	return ret;} static int ethtool_add_flow(const char *format, ...){	char local_output[256], cmd[256];	const char *id_start;	int flow_idx, ret;	char *endptr;	long flow_id;	va_list args; 	for (flow_idx = 0; flow_idx < MAX_FLOWS; flow_idx++)		if (ntuple_ids[flow_idx] == -1)			break;	if (flow_idx == MAX_FLOWS) {		fprintf(stderr, "Error: too many flows\n");		return -1;	} 	snprintf(cmd, sizeof(cmd), "ethtool -N %s %s", ifname, format); 	va_start(args, format);	ret = __run_command(local_output, sizeof(local_output), cmd, args);	va_end(args); 	if (ret != 0)		return ret; 	/* Extract the ID from the output */	id_start = strstr(local_output, "Added rule with ID ");	if (!id_start)		return -1;	id_start += strlen("Added rule with ID "); 	flow_id = strtol(id_start, &endptr, 10);	if (endptr == id_start || flow_id < 0 || flow_id > INT_MAX)		return -1; 	fprintf(stderr, "Added flow rule with ID %ld\n", flow_id);	ntuple_ids[flow_idx] = flow_id;	return flow_id;} static int rxq_num(int ifindex){	struct ethtool_channels_get_req *req;	struct ethtool_channels_get_rsp *rsp;	struct ynl_error yerr;	struct ynl_sock *ys;	int num = -1; 	ys = ynl_sock_create(&ynl_ethtool_family, &yerr);	if (!ys) {		fprintf(stderr, "YNL: %s\n", yerr.msg);		return -1;	} 	req = ethtool_channels_get_req_alloc();	ethtool_channels_get_req_set_header_dev_index(req, ifindex);	rsp = ethtool_channels_get(ys, req);	if (rsp)		num = rsp->rx_count + rsp->combined_count;	ethtool_channels_get_req_free(req);	ethtool_channels_get_rsp_free(rsp); 	ynl_sock_destroy(ys); 	return num;} static void reset_flow_steering(void){	int i; 	for (i = 0; i < MAX_FLOWS; i++) {		if (ntuple_ids[i] == -1)			continue;		run_command("ethtool -N %s delete %d",			    ifname, ntuple_ids[i]);		ntuple_ids[i] = -1;	}} static const char *tcp_data_split_str(int val){	switch (val) {	case 0:		return "off";	case 1:		return "auto";	case 2:		return "on";	default:		return "?";	}} static struct ethtool_rings_get_rsp *get_ring_config(void){	struct ethtool_rings_get_req *get_req;	struct ethtool_rings_get_rsp *get_rsp;	struct ynl_error yerr;	struct ynl_sock *ys; 	ys = ynl_sock_create(&ynl_ethtool_family, &yerr);	if (!ys) {		fprintf(stderr, "YNL: %s\n", yerr.msg);		return NULL;	} 	get_req = ethtool_rings_get_req_alloc();	ethtool_rings_get_req_set_header_dev_index(get_req, ifindex);	get_rsp = ethtool_rings_get(ys, get_req);	ethtool_rings_get_req_free(get_req); 	ynl_sock_destroy(ys); 	return get_rsp;} static void restore_ring_config(const struct ethtool_rings_get_rsp *config){	struct ethtool_rings_get_req *get_req;	struct ethtool_rings_get_rsp *get_rsp;	struct ethtool_rings_set_req *req;	struct ynl_error yerr;	struct ynl_sock *ys;	int ret; 	if (!config)		return; 	ys = ynl_sock_create(&ynl_ethtool_family, &yerr);	if (!ys) {		fprintf(stderr, "YNL: %s\n", yerr.msg);		return;	} 	req = ethtool_rings_set_req_alloc();	ethtool_rings_set_req_set_header_dev_index(req, ifindex);	ethtool_rings_set_req_set_tcp_data_split(req,						ETHTOOL_TCP_DATA_SPLIT_UNKNOWN);	if (config->_present.hds_thresh)		ethtool_rings_set_req_set_hds_thresh(req, config->hds_thresh); 	ret = ethtool_rings_set(ys, req);	if (ret < 0)		fprintf(stderr, "YNL restoring HDS cfg: %s\n", ys->err.msg); 	get_req = ethtool_rings_get_req_alloc();	ethtool_rings_get_req_set_header_dev_index(get_req, ifindex);	get_rsp = ethtool_rings_get(ys, get_req);	ethtool_rings_get_req_free(get_req); 	/* use explicit value if UKNOWN didn't give us the previous */	if (get_rsp->tcp_data_split != config->tcp_data_split) {		ethtool_rings_set_req_set_tcp_data_split(req,							config->tcp_data_split);

Lines 1–500 of 1574.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t