summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan Ribas <jonathan.ribas@fraudbuster.mobi>2018-10-26 11:12:28 +0200
committerJonathan Ribas <jonathan.ribas@fraudbuster.mobi>2018-10-26 11:12:28 +0200
commit12803d630d8b98a7255ecf34546c1930cc230ef9 (patch)
treec8752e6afad496fa7f843a2dc6161e39319366ed
parent4e378f93295b4dee91903cc4b5167cc67239f0db (diff)
downloaddpdk-burst-replay-12803d630d8b98a7255ecf34546c1930cc230ef9.zip
dpdk-burst-replay-12803d630d8b98a7255ecf34546c1930cc230ef9.tar.gz
dpdk-burst-replay-12803d630d8b98a7255ecf34546c1930cc230ef9.tar.xz
Publish project files.
-rw-r--r--AUTHORS1
-rw-r--r--LICENSE3
-rw-r--r--Makefile.am28
-rw-r--r--README.md47
-rw-r--r--configure.ac51
-rw-r--r--include/main.h143
-rw-r--r--src/Makefile.am37
-rw-r--r--src/cpus.c124
-rw-r--r--src/dpdk.c509
-rw-r--r--src/main.c285
-rw-r--r--src/pcap.c333
-rw-r--r--src/utils.c54
12 files changed, 1613 insertions, 2 deletions
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..e81e13a
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1 @@
+Jonathan Ribas <jonathan.ribas@fraudbuster.mobi>
diff --git a/LICENSE b/LICENSE
index aa5af37..2b8e67f 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,7 +1,6 @@
BSD 3-Clause License
-Copyright (c) 2018, FraudBuster
-All rights reserved.
+Copyright (c) 2018, Jonathan Ribas, FraudBuster. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..9f5d2cf
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,28 @@
+# Copyright 2018 Jonathan Ribas, FraudBuster. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation and/or
+# other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors
+# may be used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+SUBDIRS = src
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..491e3d7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,47 @@
+# DPDK burst replay tool
+
+## Introduction
+
+The tool is designed to provide high DPDK performances to burst any pcap dump on
+a single or multiple NIC port(s).
+
+To do so, the pcap files will be cached on hugepages before being sent through DPDK.
+
+## How to play with it
+
+### Install dependencies
+
+* dpdk-dev (obsly)
+* libnuma-dev
+* That's all.
+
+NB: libpcap is not required, as dpdk-replay process pcap files manually.
+
+### Compiling it
+
+> autoreconf -i && ./configure [--enable-debug] && make
+
+### Installing it
+
+> sudo make install
+
+### Launching it
+
+> dpdk-replay [--nbruns NB] [--numacore 0|1] FILE NIC_ADDR[,NIC_ADDR...]
+
+Example:
+> dpdk-replay --nbruns 1000 --numacore 0 foobar.pcap 04:00.0,04:00.1,04:00.2,04:00.3
+
+## TODO
+
+* Add a configuration file or cmdline options for all code defines.
+* Add an option to configure maximum bitrate.
+* Add an option to send the pcap with the good pcap timers.
+* Add an option to send the pcap with a multiplicative speed (like, ten times the normal speed).
+* Add an option to select multiple pcap files at once.
+* Be able to send dumps simultaneously on both numacores.
+* Split big pkts into multiple mbufs.
+
+## BSD LICENCE
+
+Copyright 2018 Jonathan Ribas, FraudBuster. All rights reserved.
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..24890d0
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,51 @@
+# Copyright 2018 Jonathan Ribas, FraudBuster. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation and/or
+# other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors
+# may be used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+AC_INIT([dpdk-replay], 1.0.0)
+AC_PREREQ([2.59])
+AC_DEFINE_UNQUOTED([DPDK_REPLAY_VERSION_GIT], ["`git rev-parse HEAD`"], [current version])
+
+AC_CONFIG_SRCDIR([src/main.c])
+AC_CONFIG_HEADER([config.h])
+
+AM_INIT_AUTOMAKE([foreign])
+AC_PROG_CC
+AM_PROG_CC_C_O
+
+# DEBUG
+AC_ARG_ENABLE(debug,
+ AS_HELP_STRING([--enable-debug], [turns on debug compiler, default:no]))
+if test x$enable_debug = xyes; then
+ AC_DEFINE([DEBUG], [1], [debug enabled])
+ CFLAGS="-ggdb -W -Wall -O0"
+else
+ CFLAGS="-W -Wall -DNDEBUG -O2"
+fi
+
+AC_CONFIG_FILES([Makefile
+ src/Makefile])
+AC_OUTPUT
diff --git a/include/main.h b/include/main.h
new file mode 100644
index 0000000..71a2cf0
--- /dev/null
+++ b/include/main.h
@@ -0,0 +1,143 @@
+/*
+ Copyright 2018 Jonathan Ribas, FraudBuster. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without modification,
+ are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation and/or
+ other materials provided with the distribution.
+
+ 3. Neither the name of the copyright holder nor the names of its contributors
+ may be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef __COMMON_H__
+#define __COMMON_H__
+
+#include <stdint.h>
+#include <semaphore.h>
+
+#define MBUF_CACHE_SZ 32
+#define TX_QUEUE_SIZE 4096
+#define NB_TX_QUEUES 64 /* ^2 needed to make fast modulos % */
+#define BURST_SZ 128
+#define NB_RETRY_TX (NB_TX_QUEUES * 2)
+
+#define TX_PTHRESH 36 // Default value of TX prefetch threshold register.
+#define TX_HTHRESH 0 // Default value of TX host threshold register.
+#define TX_WTHRESH 0 // Default value of TX write-back threshold register.
+
+#ifndef min
+#define min(x, y) (x < y ? x : y)
+#endif /* min */
+#ifndef max
+#define max(x, y) (x > y ? x : y)
+#endif /* max */
+
+/* struct to store the command line args */
+struct cmd_opts {
+ char** pcicards;
+ int nb_pcicards;
+ int numacore;
+ int nbruns;
+ unsigned int maxbitrate;
+ char* trace;
+};
+
+/* struct to store the cpus context */
+struct cpus_bindings {
+ int numacores; /* nb of numacores of the system */
+ int numacore; /* wanted numacore to run */
+ unsigned int nb_available_cpus;
+ unsigned int nb_needed_cpus;
+ unsigned int* cpus_to_use;
+ char* prefix;
+ char* suffix;
+ uint64_t coremask;
+};
+
+/* struct corresponding to a cache for one NIC port */
+struct pcap_cache {
+ struct rte_mbuf** mbufs;
+};
+
+/* struct to store dpdk context */
+struct dpdk_ctx {
+ unsigned int nb_mbuf; /* number of needed mbuf (see main.c) */
+ unsigned int mbuf_sz; /* wanted/needed size for the mbuf (see main.c) */
+ unsigned int pool_sz; /* mempool wanted/needed size (see main.c) */
+ struct rte_mempool* pktmbuf_pool;
+
+ /* pcap file caches */
+ long int pcap_sz; /* size of the capture */
+ struct pcap_cache* pcap_caches; /* tab of caches, one per NIC port */
+};
+
+/* struct to store threads context */
+struct thread_ctx {
+ sem_t* sem;
+ pthread_t thread;
+ int tx_port_id; /* assigned tx port id */
+ int nbruns;
+ unsigned int nb_pkt;
+ int nb_tx_queues;
+ /* results */
+ double duration;
+ unsigned int total_drop;
+ unsigned int total_drop_sz;
+ struct pcap_cache* pcap_cache;
+};
+
+struct pcap_ctx {
+ int fd;
+ unsigned int nb_pkts;
+ unsigned int max_pkt_sz;
+ size_t cap_sz;
+};
+
+/*
+ FUNC PROTOTYPES
+*/
+
+/* CPUS.C */
+int init_cpus(const struct cmd_opts* opts, struct cpus_bindings* cpus);
+
+/* DPDK.C */
+int init_dpdk_eal_mempool(const struct cmd_opts* opts,
+ const struct cpus_bindings* cpus,
+ struct dpdk_ctx* dpdk);
+int init_dpdk_ports(struct cpus_bindings* cpus);
+void* myrealloc(void* ptr, size_t new_size);
+int start_tx_threads(const struct cmd_opts* opts,
+ const struct cpus_bindings* cpus,
+ const struct dpdk_ctx* dpdk,
+ const struct pcap_ctx *pcap);
+void dpdk_cleanup(struct dpdk_ctx* dpdk, struct cpus_bindings* cpus);
+
+/* PCAP.C */
+int preload_pcap(const struct cmd_opts* opts, struct pcap_ctx* pcap);
+int load_pcap(const struct cmd_opts* opts, struct pcap_ctx* pcap,
+ const struct cpus_bindings* cpus, struct dpdk_ctx* dpdk);
+void clean_pcap_ctx(struct pcap_ctx* pcap);
+
+/* UTILS.C */
+char* nb_oct_to_human_str(float size);
+unsigned int get_next_power_of_2(const unsigned int nb);
+
+#endif /* __COMMON_H__ */
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..5ed0431
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,37 @@
+# Copyright 2018 Jonathan Ribas, FraudBuster. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation and/or
+# other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors
+# may be used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+bin_PROGRAMS = dpdk-replay
+dpdk_replay_SOURCES = main.c \
+ cpus.c \
+ dpdk.c \
+ pcap.c \
+ utils.c
+
+dpdk_replay_CFLAGS = $(CFLAGS) -I$(top_srcdir)/include -I/usr/include/dpdk -mssse3
+dpdk_replay_LDFLAGS = $(LDFLAGS) -pthread -lnuma -ldpdk -lm
diff --git a/src/cpus.c b/src/cpus.c
new file mode 100644
index 0000000..0a33cdb
--- /dev/null
+++ b/src/cpus.c
@@ -0,0 +1,124 @@
+/*
+ Copyright 2018 Jonathan Ribas, FraudBuster. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without modification,
+ are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation and/or
+ other materials provided with the distribution.
+
+ 3. Neither the name of the copyright holder nor the names of its contributors
+ may be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include <strings.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <numa.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "main.h"
+
+static int find_cpus_to_use(const struct cmd_opts* opts, struct cpus_bindings* cpus)
+{
+ unsigned int i;
+ unsigned int cpu_cpt;
+
+ if (!opts || !cpus)
+ return (EINVAL);
+
+ cpus->numacores = 1;
+ cpus->numacore = opts->numacore;
+ cpus->cpus_to_use = (void*)malloc(sizeof(*(cpus->cpus_to_use)) * (cpus->nb_needed_cpus + 1));
+ if (cpus->cpus_to_use == NULL) {
+ printf("%s: malloc failed.\n", __FUNCTION__);
+ return (ENOMEM);
+ }
+ printf("CPU cores to use:");
+ for (i = 0, cpu_cpt = 0; i < cpus->nb_available_cpus; i++) {
+ /* be sure that we get cores on the wanted numa */
+ if (cpus->numacore == numa_node_of_cpu(i)) {
+ cpus->cpus_to_use[cpu_cpt++] = i;
+ printf(" %i", i);
+ if (cpu_cpt == cpus->nb_needed_cpus + 1) /* +1 to keep the first as fake master */
+ break;
+ } else cpus->numacores = 2;
+ }
+ putchar('\n');
+ if (cpu_cpt < cpus->nb_needed_cpus + 1) {
+ printf("Wanted %i threads on numa %i, but found only %i CPUs.\n",
+ cpus->nb_needed_cpus + 1, cpus->numacore, cpu_cpt);
+ free(cpus->cpus_to_use);
+ return (ENODEV);
+ }
+ return (0);
+}
+
+static uint64_t generate_mask(const struct cpus_bindings* cpus, uint8_t number)
+{
+ int i;
+ uint64_t coremask;
+
+ if (!cpus)
+ return (EINVAL);
+
+ if ((0 == number) || ( 64 < number)) /* out of bounds */
+ return (0);
+
+ /* generate coremask */
+ for (coremask = 0, i = 0; i < number; i++)
+ coremask |= (uint64_t)(1 << cpus->cpus_to_use[i]);
+ printf("%s for %u cores -> 0x%lx\n", __FUNCTION__, number, coremask);
+ return (coremask);
+}
+
+int init_cpus(const struct cmd_opts* opts, struct cpus_bindings* cpus)
+{
+ int ret;
+ int i;
+
+ if (!opts || !cpus)
+ return (EINVAL);
+
+ /* get the number of available cpus */
+ cpus->nb_available_cpus = (int)sysconf(_SC_NPROCESSORS_ONLN);
+#ifdef DEBUG
+ printf("available cpus: %i\n", cpus->nb_available_cpus);
+#endif /* DEBUG */
+
+ /* calculate the number of needed cpu cores */
+ for (i = 0; opts->pcicards[i]; i++);
+ cpus->nb_needed_cpus = i;
+ printf("-> Needed cpus: %i\n", cpus->nb_needed_cpus);
+
+ /* lookup on cores ID to use */
+ ret = find_cpus_to_use(opts, cpus);
+ if (ret)
+ return (ret);
+
+ /* generate coremask of selected cpu cores for dpdk init */
+ /* NOTES: get an extra one to not use the 0/master one. TODO: do better :) */
+ cpus->coremask = generate_mask(cpus, cpus->nb_needed_cpus + 1);
+ if (!cpus->coremask)
+ return (EINVAL);
+ return (0);
+}
diff --git a/src/dpdk.c b/src/dpdk.c
new file mode 100644
index 0000000..12ef8f3
--- /dev/null
+++ b/src/dpdk.c
@@ -0,0 +1,509 @@
+/*
+ Copyright 2018 Jonathan Ribas, FraudBuster. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without modification,
+ are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation and/or
+ other materials provided with the distribution.
+
+ 3. Neither the name of the copyright holder nor the names of its contributors
+ may be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+
+/* DPDK includes */
+#include <rte_version.h>
+#include <rte_ethdev.h>
+
+#include "config.h"
+#include "main.h"
+
+static struct rte_eth_conf ethconf = {
+#ifdef RTE_VER_YEAR
+ /* version > to 2.2.0, last one with old major.minor.patch system */
+ .link_speeds = ETH_LINK_SPEED_AUTONEG,
+#else
+ /* compatibility with older version */
+ .link_speed = 0, // autonegociated speed link
+ .link_duplex = 0, // autonegociated link mode
+#endif
+ .rxmode = {
+ // Multi queue packet routing mode. We wont use DPDK RSS scaling for now,
+ // we will use our own ashkey
+ .mq_mode = ETH_MQ_RX_NONE,
+ // Default maximum frame length. Whenever this is > ETHER_MAX_LEN,
+ // jumbo_frame has to be set to 1
+ .max_rx_pkt_len = 9000,
+ .split_hdr_size = 0, // Disable header split
+ .header_split = 0, // Disable header split
+ .hw_ip_checksum = 0, // Disable ip checksum
+ .hw_vlan_filter = 0, // Disable vlan filtering
+ .jumbo_frame = 1, // Enable Jumbo frame
+ .hw_strip_crc = 0, // Disable hardware CRC stripping
+ },
+
+ .txmode = {
+ .mq_mode = ETH_MQ_TX_NONE, // Multi queue packet routing mode. We wont use
+ // DPDK RSS scaling for now, we will use our own ashkey
+ },
+
+ .fdir_conf = {
+ .mode = RTE_FDIR_MODE_NONE, // Disable flow director support
+ },
+
+ .intr_conf = {
+ .lsc = 0, // Disable lsc interrupts
+ },
+};
+
+static struct rte_eth_txconf const txconf = {
+ .tx_thresh = {
+ .pthresh = TX_PTHRESH,
+ .hthresh = TX_HTHRESH,
+ .wthresh = TX_WTHRESH,
+ },
+ .tx_free_thresh = 32,
+};
+
+void* myrealloc(void* ptr, size_t new_size)
+{
+ void* res = realloc(ptr, new_size);
+ if (!res && ptr)
+ free(ptr);
+ return (res);
+}
+
+char** fill_eal_args(const struct cmd_opts* opts, const struct cpus_bindings* cpus,
+ const struct dpdk_ctx* dpdk, int* eal_args_ac)
+{
+ char buf_coremask[30];
+ char socket_mem_arg[32];
+ char** eal_args;
+ int i, cpt, mempool;
+
+ if (!opts || !cpus || !dpdk)
+ return (NULL);
+
+ mempool = dpdk->pool_sz * 1024;
+ /* Set EAL init parameters */
+ snprintf(buf_coremask, 20, "0x%lx", cpus->coremask);
+ if (cpus->numacores == 1)
+ snprintf(socket_mem_arg, sizeof(socket_mem_arg), "--socket-mem=%i", mempool);
+ else if (cpus->numacore == 0)
+ snprintf(socket_mem_arg, sizeof(socket_mem_arg), "--socket-mem=%i,0", mempool);
+ else
+ snprintf(socket_mem_arg, sizeof(socket_mem_arg), "--socket-mem=0,%i", mempool);
+ char *pre_eal_args[] = {
+ "./dpdk-replay",
+ "-c", strdup(buf_coremask),
+ "-n", "1", /* NUM MEM CHANNELS */
+ "--proc-type", "auto",
+ "--file-prefix", "dpdkreplay_",
+ strdup(socket_mem_arg),
+ NULL
+ };
+ /* fill pci whitelist args */
+ eal_args = malloc(sizeof(*eal_args) * sizeof(pre_eal_args));
+ if (!eal_args)
+ return (NULL);
+ memcpy(eal_args, (char**)pre_eal_args, sizeof(pre_eal_args));
+ cpt = sizeof(pre_eal_args) / sizeof(*pre_eal_args);
+ for (i = 0; opts->pcicards[i]; i++) {
+ eal_args = myrealloc(eal_args, sizeof(char*) * (cpt + 2));
+ if (!eal_args)
+ return (NULL);
+ eal_args[cpt - 1] = "--pci-whitelist"; /* overwrite "NULL" */
+ eal_args[cpt] = opts->pcicards[i];
+ eal_args[cpt + 1] = NULL;
+ cpt += 2;
+ }
+ *eal_args_ac = cpt - 1;
+ return (eal_args);
+}
+
+int dpdk_init_port(const struct cpus_bindings* cpus, int port)
+{
+ int ret, i;
+#ifdef DEBUG
+ struct rte_eth_link eth_link;
+#endif /* DEBUG */
+
+ if (!cpus)
+ return (EINVAL);
+
+ /* Configure for each port (ethernet device), the number of rx queues & tx queues */
+ if (rte_eth_dev_configure(port,
+ 0, /* nb rx queue */
+ NB_TX_QUEUES, /* nb tx queue */
+ &ethconf) < 0) {
+ fprintf(stderr, "DPDK: RTE ETH Ethernet device configuration failed\n");
+ return (-1);
+ }
+
+ /* Then allocate and set up the transmit queues for this Ethernet device */
+ for (i = 0; i < NB_TX_QUEUES; i++) {
+ ret = rte_eth_tx_queue_setup(port,
+ i,
+ TX_QUEUE_SIZE,
+ cpus->numacore,
+ &txconf);
+ if (ret < 0) {
+ fprintf(stderr, "DPDK: RTE ETH Ethernet device tx queue %i setup failed: %s",
+ i, strerror(-ret));
+ return (ret);
+ }
+ }
+
+ /* Start the ethernet device */
+ if (rte_eth_dev_start(port) < 0) {
+ fprintf(stderr, "DPDK: RTE ETH Ethernet device start failed\n");
+ return (-1);
+ }
+
+#ifdef DEBUG
+ /* Get link status and display it. */
+ rte_eth_link_get(port, &eth_link);
+ if (eth_link.link_status) {
+ printf(" Link up - speed %u Mbps - %s\n",
+ eth_link.link_speed,
+ (eth_link.link_duplex == ETH_LINK_FULL_DUPLEX) ?
+ "full-duplex" : "half-duplex\n");
+ } else {
+ printf("Link down\n");
+ }
+#endif /* DEBUG */
+ return (0);
+}
+
+int init_dpdk_eal_mempool(const struct cmd_opts* opts,
+ const struct cpus_bindings* cpus,
+ struct dpdk_ctx* dpdk)
+{
+ char** eal_args;
+ int eal_args_ac = 0;
+ unsigned int nb_ports;
+ int ret;
+
+ if (!opts || !cpus || !dpdk)
+ return (EINVAL);
+
+ /* craft an eal arg list */
+ eal_args = fill_eal_args(opts, cpus, dpdk, &eal_args_ac);
+ if (!eal_args) {
+ printf("%s: fill_eal_args failed.\n", __FUNCTION__);
+ return (1);
+ }
+
+#ifdef DEBUG
+ puts("EAL ARGS:");
+ for (int i = 0; eal_args[i]; i++)
+ printf("eal_args[%i] = %s\n", i, eal_args[i]);
+#endif /* DEBUG */
+
+ /* DPDK RTE EAL INIT */
+ ret = rte_eal_init(eal_args_ac, eal_args);
+ free(eal_args);
+ if (ret < 0) {
+ printf("%s: rte_eal_init failed (%d)\n", __FUNCTION__, ret);
+ return (ret);
+ }
+
+ /* check that dpdk see enough usable cores */
+ if (rte_lcore_count() != cpus->nb_needed_cpus + 1) {
+ printf("%s error: not enough rte_lcore founds\n", __FUNCTION__);
+ return (1);
+ }
+
+ /* check that dpdk detects all wanted/needed NIC ports */
+ nb_ports = rte_eth_dev_count();
+ if (nb_ports != cpus->nb_needed_cpus) {
+ printf("%s error: wanted %u NIC ports, found %u\n", __FUNCTION__,
+ cpus->nb_needed_cpus, nb_ports);
+ return (1);
+ }
+
+ printf("-> Create mempool of %u mbufs of %u octs.\n",
+ dpdk->nb_mbuf, dpdk->mbuf_sz);
+ dpdk->pktmbuf_pool = rte_mempool_create("dpdk_replay_mempool",
+ dpdk->nb_mbuf,
+ dpdk->mbuf_sz,
+ MBUF_CACHE_SZ,
+ sizeof(struct rte_pktmbuf_pool_private),
+ rte_pktmbuf_pool_init, NULL,
+ rte_pktmbuf_init, NULL,
+ cpus->numacore,
+ 0);
+ if (dpdk->pktmbuf_pool == NULL) {
+ fprintf(stderr, "DPDK: RTE Mempool creation failed\n");
+ return (1);
+ }
+ return (0);
+}
+
+int init_dpdk_ports(struct cpus_bindings* cpus)
+{
+ int i;
+ int numa;
+
+ if (!cpus)
+ return (EINVAL);
+
+ for (i = 0; (unsigned)i < cpus->nb_needed_cpus; i++) {
+ /* if the port ID isn't on the good numacore, exit */
+ numa = rte_eth_dev_socket_id(i);
+ if (numa != cpus->numacore) {
+ fprintf(stderr, "port %i is not on the good numa id (%i).\n", i, numa);
+ return (1);
+ }
+ /* init ports */
+ if (dpdk_init_port(cpus, i))
+ return (1);
+ printf("-> NIC port %i ready.\n", i);
+ }
+ return (0);
+}
+
+double timespec_diff_to_double(const struct timespec start, const struct timespec end)
+{
+ struct timespec diff;
+ double duration;
+
+ diff.tv_sec = end.tv_sec - start.tv_sec;
+ if (end.tv_nsec > start.tv_nsec)
+ diff.tv_nsec = end.tv_nsec - start.tv_nsec;
+ else {
+ diff.tv_nsec = end.tv_nsec - start.tv_nsec + 1000000000;
+ diff.tv_sec--;
+ }
+ duration = diff.tv_sec + ((double)diff.tv_nsec / 1000000000);
+ return (duration);
+}
+
+int tx_thread(void* thread_ctx)
+{
+ struct thread_ctx* ctx;
+ struct rte_mbuf** mbuf;
+ struct timespec start, end;
+ unsigned int tx_queue;
+ int ret, thread_id, index, i, run_cpt, retry_tx;
+ int nb_sent, to_sent, total_to_sent, total_sent;
+ int nb_drop;
+
+ if (!thread_ctx)
+ return (EINVAL);
+
+ /* retrieve thread context */
+ ctx = (struct thread_ctx*)thread_ctx;
+ thread_id = ctx->tx_port_id;
+ mbuf = ctx->pcap_cache->mbufs;
+ printf("Starting thread %i.\n", thread_id);
+
+ /* init semaphore to wait to start the burst */
+ ret = sem_wait(ctx->sem);
+ if (ret) {
+ fprintf(stderr, "sem_wait failed on thread %i: %s\n",
+ thread_id, strerror(ret));
+ return (ret);
+ }
+
+ /* get the start time */
+ ret = clock_gettime(CLOCK_MONOTONIC, &start);
+ if (ret) {
+ fprintf(stderr, "clock_gettime failed on start for thread %i: %s\n",
+ thread_id, strerror(errno));
+ return (errno);
+ }
+
+ /* iterate on each wanted runs */
+ for (run_cpt = ctx->nbruns, tx_queue = ctx->total_drop = ctx->total_drop_sz = 0;
+ run_cpt;
+ ctx->total_drop += nb_drop, run_cpt--) {
+ /* iterate on pkts for every batch of BURST_SZ number of packets */
+ for (total_to_sent = ctx->nb_pkt, nb_drop = 0, to_sent = min(BURST_SZ, total_to_sent);
+ to_sent;
+ total_to_sent -= to_sent, to_sent = min(BURST_SZ, total_to_sent)) {
+ /* calculate the mbuf index for the current batch */
+ index = ctx->nb_pkt - total_to_sent;
+
+ /* send the burst batch, and retry NB_RETRY_TX times if we */
+ /* didn't success to sent all the wanted batch */
+ for (total_sent = 0, retry_tx = NB_RETRY_TX;
+ total_sent < to_sent && retry_tx;
+ total_sent += nb_sent, retry_tx--) {
+ nb_sent = rte_eth_tx_burst(ctx->tx_port_id,
+ (tx_queue++ % NB_TX_QUEUES),
+ &(mbuf[index + total_sent]),
+ to_sent - total_sent);
+ if (retry_tx != NB_RETRY_TX &&
+ tx_queue % NB_TX_QUEUES == 0)
+ usleep(100);
+ }
+ /* free unseccessfully sent */
+ if (unlikely(!retry_tx))
+ for (i = total_sent; i < to_sent; i++) {
+ nb_drop++;
+ ctx->total_drop_sz += mbuf[index + i]->pkt_len;
+ rte_pktmbuf_free(mbuf[index + i]);
+ }
+ }
+#ifdef DEBUG
+ if (unlikely(nb_drop))
+ printf("[thread %i]: on loop %i: sent %i pkts (%i were dropped).\n",
+ thread_id, ctx->nbruns - run_cpt, ctx->nb_pkt, nb_drop);
+#endif /* DEBUG */
+ }
+
+ /* get the ends time and calculate the duration */
+ ret = clock_gettime(CLOCK_MONOTONIC, &end);
+ if (ret) {
+ fprintf(stderr, "clock_gettime failed on finish for thread %i: %s\n",
+ thread_id, strerror(errno));
+ return (errno);
+ }
+ ctx->duration = timespec_diff_to_double(start, end);
+ printf("Exiting thread %i properly.\n", thread_id);
+ return (0);
+}
+
+int process_result_stats(const struct cpus_bindings* cpus,
+ const struct dpdk_ctx* dpdk,
+ const struct cmd_opts* opts,
+ const struct thread_ctx* ctx)
+{
+ double pps, bitrate;
+ double total_pps, total_bitrate;
+ unsigned long int total_pkt_sent, total_pkt_sent_sz;
+ unsigned int i, total_drop, total_pkt;
+
+ if (!cpus || !dpdk || !opts || !ctx)
+ return (EINVAL);
+
+ total_pps = total_bitrate = 0;
+ total_drop = 0;
+ puts("\nRESULTS :");
+ for (i = 0; i < cpus->nb_needed_cpus; i++) {
+ total_pkt_sent = (ctx[i].nb_pkt * opts->nbruns) - ctx[i].total_drop;
+ total_pkt_sent_sz = (dpdk->pcap_sz * opts->nbruns) - ctx[i].total_drop_sz;
+ pps = total_pkt_sent / ctx[i].duration;
+ bitrate = total_pkt_sent_sz / ctx[i].duration
+ * 8 /* Bytes to bits */
+ / 1024 /* bits to Kbits */
+ / 1024 /* Kbits to Mbits */
+ / 1024; /* Mbits to Gbits */
+ total_bitrate += bitrate;
+ total_pps += pps;
+ total_drop += ctx[i].total_drop;
+ printf("[thread %02u]: %f Gbit/s, %f pps on %f sec (%u pkts dropped)\n",
+ i, bitrate, pps, ctx[i].duration, ctx[i].total_drop);
+ }
+ puts("-----");
+ printf("TOTAL : %.3f Gbit/s. %.3f pps.\n", total_bitrate, total_pps);
+ total_pkt = ctx[0].nb_pkt * opts->nbruns * cpus->nb_needed_cpus;
+ printf("Total dropped: %u/%u packets (%f%%)\n", total_drop, total_pkt,
+ (double)(total_drop * 100) / (double)(total_pkt));
+ return (0);
+}
+
+int start_tx_threads(const struct cmd_opts* opts,
+ const struct cpus_bindings* cpus,
+ const struct dpdk_ctx* dpdk,
+ const struct pcap_ctx* pcap)
+{
+ struct thread_ctx* ctx = NULL;
+ sem_t sem;
+ unsigned int i;
+ int ret;
+
+ /* init semaphore for synchronous threads startup */
+ if (sem_init(&sem, 0, 0)) {
+ fprintf(stderr, "sem_init failed: %s\n", strerror(errno));
+ return (errno);
+ }
+
+ /* create threads contexts */
+ ctx = malloc(sizeof(*ctx) * cpus->nb_needed_cpus);
+ if (!ctx)
+ return (ENOMEM);
+ bzero(ctx, sizeof(*ctx) * cpus->nb_needed_cpus);
+ for (i = 0; i < cpus->nb_needed_cpus; i++) {
+ ctx[i].sem = &sem;
+ ctx[i].tx_port_id = i;
+ ctx[i].nbruns = opts->nbruns;
+ ctx[i].pcap_cache = &(dpdk->pcap_caches[i]);
+ ctx[i].nb_pkt = pcap->nb_pkts;
+ ctx[i].nb_tx_queues = NB_TX_QUEUES;
+ }
+
+ /* launch threads, which will wait on the semaphore to start */
+ for (i = 0; i < cpus->nb_needed_cpus; i++) {
+ ret = rte_eal_remote_launch(tx_thread, &(ctx[i]),
+ cpus->cpus_to_use[i + 1]); /* skip fake master core */
+ if (ret) {
+ fprintf(stderr, "rte_eal_remote_launch failed: %s\n", strerror(ret));
+ free(ctx);
+ return (ret);
+ }
+ }
+
+ /* wait for ENTER and starts threads */
+ puts("Threads are ready to be launched, please press ENTER to start sending packets.");
+ for (ret = getchar(); ret != '\n'; ret = getchar()) ;
+ for (i = 0; i < cpus->nb_needed_cpus; i++) {
+ ret = sem_post(&sem);
+ if (ret) {
+ fprintf(stderr, "sem_post failed: %s\n", strerror(errno));
+ free(ctx);
+ return (errno);
+ }
+ }
+
+ /* wait all threads */
+ rte_eal_mp_wait_lcore();
+
+ /* get results */
+ ret = process_result_stats(cpus, dpdk, opts, ctx);
+ free(ctx);
+ return (ret);
+}
+
+void dpdk_cleanup(struct dpdk_ctx* dpdk, struct cpus_bindings* cpus)
+{
+ unsigned int i;
+
+ /* free caches */
+ if (dpdk->pcap_caches) {
+ for (i = 0; i < cpus->nb_needed_cpus; i++)
+ free(dpdk->pcap_caches[i].mbufs);
+ free(dpdk->pcap_caches);
+ }
+
+ /* close ethernet devices */
+ for (i = 0; i < cpus->nb_needed_cpus; i++)
+ rte_eth_dev_close(i);
+
+ /* free mempool */
+ if (dpdk->pktmbuf_pool)
+ rte_mempool_free(dpdk->pktmbuf_pool);
+ return ;
+}
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..5083624
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,285 @@
+/*
+ Copyright 2018 Jonathan Ribas, FraudBuster. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without modification,
+ are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation and/or
+ other materials provided with the distribution.
+
+ 3. Neither the name of the copyright holder nor the names of its contributors
+ may be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include <strings.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <math.h>
+
+#include <rte_ethdev.h>
+
+#include "config.h"
+#include "main.h"
+
+void usage(void)
+{
+ puts("dpdk-replay [options] pcap_file port1[,portx...]\n"
+ "pcap_file: the file to send through the DPDK ports.\n"
+ "port1[,portx...] : specify the list of ports to be used (pci addresses).\n"
+ "Options:\n"
+ "--numacore numacore : use cores only if it fits the wanted numa.\n"
+ "--nbruns X : set the wanted number of replay (1 by default). Set to 0 to infinite mode.\n"
+ /* TODO: */
+ /* "[--maxbitrate bitrate]|[--normalspeed] : bitrate not to be exceeded (default: no limit) in ko/s.\n" */
+ /* " specify --normalspeed to replay the trace with the good timings." */
+ );
+ return ;
+}
+
+#ifdef DEBUG
+void print_opts(const struct cmd_opts* opts)
+{
+ int i;
+
+ if (!opts)
+ return ;
+ puts("--");
+ printf("numacore: %i\n", (int)(opts->numacore));
+ printf("nb runs: %u\n", opts->nbruns);
+ /* if (opts->maxbitrate) */
+ /* printf("MAX BITRATE: %u\n", opts->maxbitrate); */
+ /* else */
+ /* puts("MAX BITRATE: FULL SPEED"); */
+ printf("trace: %s\n", opts->trace);
+ printf("pci nic ports:");
+ for (i = 0; opts->pcicards[i]; i++)
+ printf(" %s", opts->pcicards[i]);
+ puts("\n--");
+ return ;
+}
+#endif /* DEBUG */
+
+char** str_to_pcicards_list(struct cmd_opts* opts, char* pcis)
+{
+ char** list = NULL;
+ int i;
+
+ if (!pcis || !opts)
+ return (NULL);
+
+ for (i = 1; ; i++) {
+ list = realloc(list, sizeof(*list) * (i + 1));
+ if (!list)
+ return (NULL);
+ list[i - 1] = pcis;
+ list[i] = NULL;
+ while (*pcis != '\0' && *pcis != ',')
+ pcis++;
+ if (*pcis == '\0')
+ break;
+ else { /* , */
+ *pcis = '\0';
+ pcis++;
+ }
+ }
+ opts->nb_pcicards = i;
+ return (list);
+}
+
+int parse_options(const int ac, char** av, struct cmd_opts* opts)
+{
+ int i;
+
+ if (!av || !opts)
+ return (EINVAL);
+
+ /* if no trace or no pcicard is specified */
+ if (ac < 3)
+ return (ENOENT);
+
+ for (i = 1; i < ac - 2; i++) {
+ /* --numacore numacore */
+ if (!strcmp(av[i], "--numacore")) {
+ int nc;
+
+ /* if no numa core is specified */
+ if (i + 1 >= ac - 2)
+ return (ENOENT);
+
+ nc = atoi(av[i + 1]);
+ if (nc < 0 || nc > 127)
+ return (ENOENT);
+ opts->numacore = (char)nc;
+ i++;
+ continue;
+ }
+
+ /* --nbruns nbruns */
+ if (!strcmp(av[i], "--nbruns")) {
+ /* if no nb runs is specified */
+ if (i + 1 >= ac - 2)
+ return (ENOENT);
+
+ opts->nbruns = atoi(av[i + 1]);
+ if (opts->nbruns < 0)
+ return (EPROTO);
+ i++;
+ continue;
+ }
+ break;
+ }
+ if (i + 2 > ac)
+ return (EPROTO);
+ opts->trace = av[i];
+ opts->pcicards = str_to_pcicards_list(opts, av[i + 1]);
+ return (0);
+}
+
+int check_needed_memory(const struct cmd_opts* opts, const struct pcap_ctx* pcap,
+ struct dpdk_ctx* dpdk)
+{
+ float needed_mem;
+ char* hsize;
+
+ if (!opts || !pcap || !dpdk)
+ return (EINVAL);
+
+ /* # CALCULATE THE NEEDED SIZE FOR MBUF STRUCTS */
+ dpdk->mbuf_sz = sizeof(struct rte_mbuf) + pcap->max_pkt_sz;
+ dpdk->mbuf_sz += (dpdk->mbuf_sz % (sizeof(int)));
+#ifdef DEBUG
+ puts("Needed paket allocation size = "
+ "(size of MBUF) + (size of biggest pcap packet), "
+ "rounded up to the next multiple of an integer.");
+ printf("(%lu + %u) + ((%lu + %u) %% %lu) = %u\n",
+ sizeof(struct rte_mbuf), pcap->max_pkt_sz,
+ sizeof(struct rte_mbuf), pcap->max_pkt_sz,
+ sizeof(int), dpdk->mbuf_sz);
+#endif /* DEBUG */
+ printf("-> Needed MBUF size: %u\n", dpdk->mbuf_sz);
+
+ /* # CALCULATE THE NEEDED NUMBER OF MBUFS */
+ /* For number of pkts to be allocated on the mempool, DPDK says: */
+ /* The optimum size (in terms of memory usage) for a mempool is when n is a
+ power of two minus one: n = (2^q - 1). */
+#ifdef DEBUG
+ puts("Needed number of MBUFS: next power of two minus one of "
+ "(nb pkts * nb ports)");
+#endif /* DEBUG */
+ dpdk->nb_mbuf = get_next_power_of_2(pcap->nb_pkts * opts->nb_pcicards) - 1;
+ printf("-> Needed number of MBUFS: %u\n", dpdk->nb_mbuf);
+
+ /* # CALCULATE THE TOTAL NEEDED MEMORY SIZE */
+ needed_mem = dpdk->mbuf_sz * dpdk->nb_mbuf;
+#ifdef DEBUG
+ puts("Needed memory = (needed mbuf size) * (number of needed mbuf).");
+ printf("%u * %u = %.0f bytes\n", dpdk->mbuf_sz, dpdk->nb_mbuf, needed_mem);
+#endif /* DEBUG */
+ hsize = nb_oct_to_human_str(needed_mem);
+ if (!hsize)
+ return (-1);
+ printf("-> Needed Memory = %s\n", hsize);
+ free(hsize);
+
+ /* # CALCULATE THE NEEDED NUMBER OF GIGABYTE HUGEPAGES */
+ if (fmod(needed_mem,((double)(1024*1024*1024))))
+ dpdk->pool_sz = needed_mem / (float)(1024*1024*1024) + 1;
+ else
+ dpdk->pool_sz = needed_mem / (1024*1024*1024);
+ printf("-> Needed Hugepages of 1 Go = %i\n", dpdk->pool_sz);
+ return (0);
+}
+
+int main(const int ac, char** av)
+{
+ struct cmd_opts opts;
+ struct cpus_bindings cpus;
+ struct dpdk_ctx dpdk;
+ struct pcap_ctx pcap;
+ int ret;
+
+ /* set default opts */
+ bzero(&cpus, sizeof(cpus));
+ bzero(&opts, sizeof(opts));
+ bzero(&dpdk, sizeof(dpdk));
+ bzero(&pcap, sizeof(pcap));
+ opts.nbruns = 1;
+
+ /* parse cmdline options */
+ ret = parse_options(ac, av, &opts);
+ if (ret) {
+ usage();
+ return (1);
+ }
+#ifdef DEBUG
+ print_opts(&opts);
+#endif /* DEBUG */
+
+ /*
+ pre parse the pcap file to get needed informations:
+ . number of packets
+ . biggest packet size
+ */
+ ret = preload_pcap(&opts, &pcap);
+ if (ret)
+ goto mainExit;
+
+ /* calculate needed memory to allocate for mempool */
+ ret = check_needed_memory(&opts, &pcap, &dpdk);
+ if (ret)
+ goto mainExit;
+
+ /*
+ check that we have enough cpus, find the ones to use and calculate
+ corresponding coremask
+ */
+ ret = init_cpus(&opts, &cpus);
+ if (ret)
+ goto mainExit;
+
+ /* init dpdk eal and mempool */
+ ret = init_dpdk_eal_mempool(&opts, &cpus, &dpdk);
+ if (ret)
+ goto mainExit;
+
+ /* cache pcap file into mempool */
+ ret = load_pcap(&opts, &pcap, &cpus, &dpdk);
+ if (ret)
+ goto mainExit;
+
+ /* init dpdk ports to send pkts */
+ ret = init_dpdk_ports(&cpus);
+ if (ret)
+ goto mainExit;
+
+ /* start tx threads and wait to start to send pkts */
+ ret = start_tx_threads(&opts, &cpus, &dpdk, &pcap);
+ if (ret)
+ goto mainExit;
+
+mainExit:
+ /* cleanup */
+ clean_pcap_ctx(&pcap);
+ dpdk_cleanup(&dpdk, &cpus);
+ if (cpus.cpus_to_use)
+ free(cpus.cpus_to_use);
+ return (ret);
+}
diff --git a/src/pcap.c b/src/pcap.c
new file mode 100644
index 0000000..d985d4e
--- /dev/null
+++ b/src/pcap.c
@@ -0,0 +1,333 @@
+/*
+ Copyright 2018 Jonathan Ribas, FraudBuster. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without modification,
+ are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation and/or
+ other materials provided with the distribution.
+
+ 3. Neither the name of the copyright holder nor the names of its contributors
+ may be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include <strings.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <rte_malloc.h>
+#include <rte_mbuf.h>
+
+#include "config.h"
+#include "main.h"
+
+#define MAX_PKT_SZ (1024*64) /* 64ko */
+
+/*
+ PCAP file header
+*/
+#define PCAP_MAGIC (0xa1b2c3d4)
+#define PCAP_MAJOR_VERSION (2)
+#define PCAP_MINOR_VERSION (4)
+#define PCAP_SNAPLEN (262144)
+#define PCAP_NETWORK (1) /* ethernet layer */
+typedef struct pcap_hdr_s {
+ uint32_t magic_number; /* magic number */
+ uint16_t version_major; /* major version number */
+ uint16_t version_minor; /* minor version number */
+ int32_t thiszone; /* GMT to local correction */
+ uint32_t sigfigs; /* accuracy of timestamps */
+ uint32_t snaplen; /* max length of captured packets, in octets */
+ uint32_t network; /* data link type */
+} __attribute__((__packed__)) pcap_hdr_t;
+
+typedef struct pcaprec_hdr_s {
+ uint32_t ts_sec; /* timestamp seconds */
+ uint32_t ts_usec; /* timestamp microseconds */
+ uint32_t incl_len; /* number of octets of packet saved in file */
+ uint32_t orig_len; /* actual length of packet */
+} __attribute__((__packed__)) pcaprec_hdr_t;
+
+int check_pcap_hdr(const int fd)
+{
+ pcap_hdr_t pcap_h;
+ size_t nb_read;
+
+ nb_read = read(fd, &pcap_h, sizeof(pcap_h));
+ if (nb_read != sizeof(pcap_h))
+ return (EIO);
+ if (pcap_h.magic_number != PCAP_MAGIC ||
+ pcap_h.version_major != PCAP_MAJOR_VERSION ||
+ pcap_h.version_minor != PCAP_MINOR_VERSION) {
+ printf("%s: check failed. magic (0x%.8x), major: %u, minor: %u\n",
+ __FUNCTION__, pcap_h.magic_number,
+ pcap_h.version_major, pcap_h.version_minor);
+ return (EPROTO);
+ }
+ return (0);
+}
+
+int add_pkt_to_cache(const struct dpdk_ctx* dpdk, const int cache_index,
+ const unsigned char* pkt_buf, const size_t pkt_sz,
+ const unsigned int cpt, const int nbruns)
+{
+ struct rte_mbuf* m;
+
+ if (!dpdk || !pkt_buf)
+ return (EINVAL);
+
+ m = rte_pktmbuf_alloc(dpdk->pktmbuf_pool);
+ if (!m) {
+ printf("\n%s rte_pktmbuf_alloc failed. exiting.\n", __FUNCTION__);
+ return (ENOMEM);
+ }
+ rte_memcpy((char*)m->buf_addr, pkt_buf, pkt_sz);
+ m->data_off = 0;
+ m->data_len = m->pkt_len = pkt_sz;
+ m->nb_segs = 1;
+ m->next = NULL;
+
+ /* set the refcnt to the wanted number of runs, avoiding to free
+ mbuf struct on first tx burst */
+ rte_mbuf_refcnt_set(m, nbruns);
+
+ /* check that the crafted packet is valid */
+ rte_mbuf_sanity_check(m, 1);
+
+ /* assign new cached pkt to list */
+ dpdk->pcap_caches[cache_index].mbufs[cpt] = m;
+ return (0);
+}
+
+int preload_pcap(const struct cmd_opts* opts, struct pcap_ctx* pcap)
+{
+ unsigned char pkt_buf[MAX_PKT_SZ];
+ pcaprec_hdr_t pcap_rechdr;
+ struct stat s;
+ unsigned int cpt;
+ size_t nb_read;
+ long int total_read;
+ float percent;
+ int ret;
+
+ if (!opts || !pcap)
+ return (EINVAL);
+
+ /* open wanted file */
+ pcap->fd = open(opts->trace, O_RDONLY);
+ if (pcap->fd < 0) {
+ printf("open of %s failed: %s\n", opts->trace, strerror(errno));
+ return (errno);
+ }
+
+ /* check pcap header */
+ ret = check_pcap_hdr(pcap->fd);
+ if (ret)
+ goto preload_pcapErrorInit;
+
+ /* get file informations */
+ ret = stat(opts->trace, &s);
+ if (ret)
+ goto preload_pcapErrorInit;
+ s.st_size -= sizeof(pcap_hdr_t);
+ printf("preloading %s file (of size: %li bytes)\n", opts->trace, s.st_size);
+ pcap->cap_sz = s.st_size;
+
+ /* loop on file to read all saved packets */
+ for (total_read = 0, cpt = 0; ; cpt++) {
+ /* get packet pcap header */
+ nb_read = read(pcap->fd, &pcap_rechdr, sizeof(pcap_rechdr));
+ if (!nb_read) /* EOF :) */
+ break;
+ else if (nb_read == (unsigned long)(-1)) {
+ printf("\n%s: read failed (%s)\n", __FUNCTION__, strerror(errno));
+ ret = errno;
+ goto preload_pcapError;
+ } else if (nb_read != sizeof(pcap_rechdr)) {
+ printf("\nread pkt hdr misssize: %lu / %lu\n",
+ nb_read, sizeof(pcap_rechdr));
+ ret = EIO;
+ goto preload_pcapError;
+ }
+ total_read += nb_read;
+
+#ifdef DEBUG
+ if (pcap_rechdr.incl_len != pcap_rechdr.orig_len)
+ printf("\npkt %i size: %u/%u\n", cpt,
+ pcap_rechdr.incl_len, pcap_rechdr.orig_len);
+#endif /* DEBUG */
+
+ /* update max pkt size (to be able to calculate the needed memory) */
+ if (pcap_rechdr.incl_len > pcap->max_pkt_sz)
+ pcap->max_pkt_sz = pcap_rechdr.incl_len;
+
+ /* get packet */
+ nb_read = read(pcap->fd, pkt_buf, pcap_rechdr.incl_len);
+ if (nb_read == (unsigned long)(-1)) {
+ printf("\n%s: read failed (%s)\n", __FUNCTION__, strerror(errno));
+ ret = errno;
+ goto preload_pcapError;
+ } else if (nb_read != pcap_rechdr.incl_len) {
+ printf("\nread pkt %i payload misssize: %u / %u\n", cpt,
+ (unsigned int)nb_read, pcap_rechdr.incl_len);
+ ret = EIO;
+ goto preload_pcapError;
+ }
+ total_read += nb_read;
+
+ /* calcul & print progression every 1024 pkts */
+ if ((cpt % 1024) == 0) {
+ percent = 100 * (float)total_read / (float)s.st_size;
+ printf("\rfile read at %02.2f%%", percent);
+ }
+ }
+
+preload_pcapError:
+ percent = 100 * (float)total_read / (float)s.st_size;
+ printf("%sfile read at %02.2f%%\n", (ret ? "\n" : "\r"), percent);
+ printf("read %u pkts (for a total of %li bytes). max paket length = %u bytes.\n",
+ cpt, total_read, pcap->max_pkt_sz);
+preload_pcapErrorInit:
+ if (ret) {
+ close(pcap->fd);
+ pcap->fd = 0;
+ } else
+ pcap->nb_pkts = cpt;
+ return (ret);
+}
+
+int load_pcap(const struct cmd_opts* opts, struct pcap_ctx* pcap,
+ const struct cpus_bindings* cpus, struct dpdk_ctx* dpdk)
+{
+ pcaprec_hdr_t pcap_rechdr;
+ unsigned char pkt_buf[MAX_PKT_SZ];
+ unsigned int cpt = 0;
+ size_t nb_read;
+ long int total_read = 0;
+ float percent;
+ unsigned int i;
+ int ret;
+
+ if (!opts || !pcap || !cpus || !dpdk)
+ return (EINVAL);
+
+ /* alloc needed pkt caches and bzero them */
+ dpdk->pcap_caches = malloc(sizeof(*(dpdk->pcap_caches)) * (cpus->nb_needed_cpus));
+ if (!dpdk->pcap_caches) {
+ printf("malloc of pcap_caches failed.\n");
+ return (ENOMEM);
+ }
+ bzero(dpdk->pcap_caches, sizeof(*(dpdk->pcap_caches)) * (cpus->nb_needed_cpus));
+ for (i = 0; i < cpus->nb_needed_cpus; i++) {
+ dpdk->pcap_caches[i].mbufs = malloc(sizeof(*(dpdk->pcap_caches[i].mbufs)) *
+ pcap->nb_pkts);
+ if (dpdk->pcap_caches[i].mbufs == NULL) {
+ fprintf(stderr, "%s: malloc of mbufs failed.\n", __FUNCTION__);
+ return (ENOMEM);
+ }
+ bzero(dpdk->pcap_caches[i].mbufs,
+ sizeof(*(dpdk->pcap_caches[i].mbufs)) * pcap->nb_pkts);
+ }
+
+ /* seek again to the beginning */
+ if (lseek(pcap->fd, 0, SEEK_SET) == (off_t)(-1)) {
+ printf("%s: lseek failed (%s)\n", __FUNCTION__, strerror(errno));
+ ret = errno;
+ goto load_pcapError;
+ }
+ ret = check_pcap_hdr(pcap->fd);
+ if (ret)
+ goto load_pcapError;
+
+ printf("-> Will cache %i pkts.\n", pcap->nb_pkts);
+ for (; cpt < pcap->nb_pkts; cpt++) {
+ /* get packet pcap header */
+ nb_read = read(pcap->fd, &pcap_rechdr, sizeof(pcap_rechdr));
+ if (!nb_read) /* EOF :) */
+ break;
+ else if (nb_read == (unsigned long)(-1)) {
+ printf("\n%s: read failed (%s)\n", __FUNCTION__, strerror(errno));
+ ret = errno;
+ goto load_pcapError;
+ } else if (nb_read != sizeof(pcap_rechdr)) {
+ printf("\nread pkt hdr misssize: %u / %lu\n",
+ (unsigned int)nb_read, sizeof(pcap_rechdr));
+ ret = EIO;
+ goto load_pcapError;
+ }
+ total_read += nb_read;
+
+ /* get packet */
+ nb_read = read(pcap->fd, pkt_buf, pcap_rechdr.incl_len);
+ if (nb_read == (unsigned long)(-1)) {
+ printf("\n%s: read failed (%s)\n", __FUNCTION__, strerror(errno));
+ ret = errno;
+ goto load_pcapError;
+ } else if (nb_read != pcap_rechdr.incl_len) {
+ printf("\nread pkt %u payload misssize: %u / %u\n", cpt,
+ (unsigned int)nb_read, pcap_rechdr.incl_len);
+ ret = EIO;
+ goto load_pcapError;
+ }
+ total_read += nb_read;
+
+ /* add packet to caches */
+ for (i = 0; i < cpus->nb_needed_cpus; i++) {
+ ret = add_pkt_to_cache(dpdk, i, pkt_buf, nb_read, cpt, opts->nbruns);
+ if (ret) {
+ fprintf(stderr, "\nadd_pkt_to_cache failed on pkt.\n");
+ goto load_pcapError;
+ }
+ }
+
+ /* calcul & print progression every 1024 pkts */
+ if ((cpt % 1024) == 0) {
+ percent = 100 * cpt / pcap->nb_pkts;
+ printf("\rfile read at %02.2f%%", percent);
+ }
+ }
+
+load_pcapError:
+ percent = 100 * cpt / pcap->nb_pkts;
+ printf("%sfile read at %02.2f%%\n", (ret ? "\n" : "\r"), percent);
+ printf("read %u pkts (for a total of %li bytes).\n", cpt, total_read);
+ dpdk->pcap_sz = total_read;
+ close(pcap->fd);
+ pcap->fd = 0;
+ return (ret);
+}
+
+void clean_pcap_ctx(struct pcap_ctx* pcap)
+{
+ if (!pcap)
+ return ;
+
+ if (pcap->fd) {
+ close(pcap->fd);
+ pcap->fd = 0;
+ }
+ return ;
+}
diff --git a/src/utils.c b/src/utils.c
new file mode 100644
index 0000000..c9134dd
--- /dev/null
+++ b/src/utils.c
@@ -0,0 +1,54 @@
+/*
+ Copyright 2018 Jonathan Ribas, FraudBuster. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without modification,
+ are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation and/or
+ other materials provided with the distribution.
+
+ 3. Neither the name of the copyright holder nor the names of its contributors
+ may be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#define _GNU_SOURCE
+#include <stdio.h>
+
+char* nb_oct_to_human_str(float size)
+{
+ char* disp_unit[] = { "o", "Mo", "Mo", "Go", "To" };
+ int i;
+ char* buf = NULL;
+
+ for (i = 0; i < 5; i++, size /= 1024)
+ if (size / 1024 < 1) break;
+ if (asprintf(&buf, "%.3f %s", size, disp_unit[i]) == -1) {
+ fprintf(stderr, "%s: asprintf failed.\n", __FUNCTION__);
+ return (NULL);
+ }
+ return (buf);
+}
+
+unsigned int get_next_power_of_2(const unsigned int nb)
+{
+ unsigned int i;
+
+ for (i = 0; (unsigned int)((1 << i)) < nb; i++) ;
+ return (1 << i);
+}