// 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. Load more
// 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 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; 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);