123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653 |
- /*
- * Copyright (c) 2013 Fred Klassen <fklassen at appneta dot com> - AppNeta
- *
- * The Tcpreplay Suite of tools is free software: you can redistribute it
- * and/or modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or with the authors permission any later version.
- *
- * The Tcpreplay Suite is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with the Tcpreplay Suite. If not, see <http://www.gnu.org/licenses/>.
- */
- #include "netmap.h"
- #include "config.h"
- #include "common.h"
- #include "tcpreplay_api.h"
- #include <errno.h>
- #include <stdlib.h>
- #include <string.h>
- #include <sys/ioctl.h>
- #include <sys/mman.h>
- #include <sys/socket.h>
- #include <sys/types.h>
- #include <unistd.h>
- static int nm_do_ioctl(sendpacket_t *sp, u_long what, int subcmd);
- /**
- * Method takes an open "/dev/netmap" file descriptor and returns
- * the netmap version.
- *
- * Returns -1 on error
- */
- int
- get_netmap_version()
- {
- u_int32_t netmap_version = -1;
- nmreq_t nmr;
- int fd;
- if ((fd = open("/dev/netmap", O_RDWR)) < 0)
- return -1;
- /* netmap version discovery */
- bzero(&nmr, sizeof(nmr));
- nmr.nr_version = NETMAP_API;
- /* attempt using the netmap API version that this was compiled under */
- if (ioctl(fd, NIOCGINFO, &nmr) == 0) {
- netmap_version = nmr.nr_version;
- dbgx(1, "netmap detected API version %d which matches compiled version\n", netmap_version);
- } else {
- /* failed.
- *
- * Try other versions in an attempt to find the version
- * that matches this system.
- */
- int x;
- for (x = 0; x < 50; ++x) {
- bzero(&nmr, sizeof(nmr));
- nmr.nr_version = x;
- if (ioctl(fd, NIOCGINFO, &nmr) == 0) {
- netmap_version = nmr.nr_version;
- dbgx(1,
- "netmap detected API version %d which doesn't match compiled version %d\n",
- netmap_version,
- NETMAP_API);
- break;
- }
- }
- }
- close(fd);
- return (int)netmap_version;
- }
- /**
- * ioctl support for netmap
- */
- static int
- nm_do_ioctl(sendpacket_t *sp, u_long what, int subcmd)
- {
- struct ifreq ifr;
- int error;
- int fd;
- #ifdef linux
- struct ethtool_value eval;
- #endif
- assert(sp);
- fd = socket(AF_INET, SOCK_DGRAM, 0);
- if (fd < 0) {
- dbg(1, "ioctl error: cannot get device control socket.\n");
- return -1;
- }
- bzero(&ifr, sizeof(ifr));
- strncpy(ifr.ifr_name, sp->device, sizeof(ifr.ifr_name));
- switch (what) {
- case SIOCSIFFLAGS:
- ifr.ifr_flags = sp->if_flags >> 16;
- ifr.ifr_flags = sp->if_flags & 0xffff;
- break;
- #ifdef linux
- case SIOCETHTOOL:
- eval.cmd = subcmd;
- eval.data = sp->data;
- ifr.ifr_data = (caddr_t)&eval;
- dbgx(1, "ioctl SIOCETHTOOL subcmd=%d data=%u", subcmd, eval.data);
- break;
- #endif
- }
- error = ioctl(fd, what, &ifr);
- if (error)
- goto done;
- switch (what) {
- case SIOCGIFFLAGS:
- sp->if_flags = (ifr.ifr_flags << 16) | (0xffff & ifr.ifr_flags);
- dbgx(1, "SIOCGIFFLAGS flags are 0x%x", sp->if_flags);
- break;
- #ifdef linux
- case SIOCETHTOOL:
- switch (subcmd) {
- case ETHTOOL_GGSO:
- sp->gso = eval.data;
- dbgx(1, "ioctl SIOCETHTOOL ETHTOOL_GGSO=%u", eval.data);
- break;
- case ETHTOOL_GTSO:
- sp->tso = eval.data;
- dbgx(1, "ioctl SIOCETHTOOL ETHTOOL_GTSO=%u", eval.data);
- break;
- case ETHTOOL_GRXCSUM:
- sp->rxcsum = eval.data;
- dbgx(1, "ioctl SIOCETHTOOL ETHTOOL_GRXCSUM=%u", eval.data);
- break;
- case ETHTOOL_GTXCSUM:
- sp->txcsum = eval.data;
- dbgx(1, "ioctl SIOCETHTOOL ETHTOOL_GTXCSUM=%u", eval.data);
- break;
- default:
- return -1;
- }
- break;
- #endif
- default:
- return -1;
- }
- done:
- close(fd);
- if (error)
- warnx("ioctl error %d %lu:%d", error, what, subcmd);
- return error;
- }
- /**
- * Inner sendpacket_open() method for using netmap
- */
- void *
- sendpacket_open_netmap(const char *device, char *errbuf, void *arg)
- {
- tcpreplay_t *ctx = (tcpreplay_t *)arg;
- sendpacket_t *sp = NULL;
- nmreq_t nmr;
- char ifname_buf[MAX_IFNAMELEN];
- const char *ifname;
- const char *port = NULL;
- size_t namelen;
- u_int32_t nr_ringid = 0;
- u_int32_t nr_flags = NR_REG_DEFAULT;
- int is_default = 0;
- assert(device);
- assert(errbuf);
- dbg(1, "sendpacket_open_netmap: using netmap");
- bzero(&nmr, sizeof(nmr));
- /* prep & return our sp handle */
- sp = (sendpacket_t *)safe_malloc(sizeof(sendpacket_t));
- if (strlen(device) > MAX_IFNAMELEN - 8) {
- snprintf(errbuf, SENDPACKET_ERRBUF_SIZE, "Interface name is to long: %s\n", device);
- goto IFACENAME_INVALID;
- }
- /* get the version of the netmap driver. If < 0, driver is not installed */
- sp->netmap_version = get_netmap_version();
- if (sp->netmap_version < 0) {
- snprintf(errbuf,
- SENDPACKET_ERRBUF_SIZE,
- "Unable to determine the running netmap version.\n"
- "See INSTALL document for details on installing or upgrading netmap.");
- goto NETMAP_NOT_INSTALLED;
- }
- /*
- * Sort out interface names
- *
- * ifname (foo, netmap:foo or vale:foo) is the port name
- * foo bind to a single NIC hardware queue
- * netmap:foo bind to a single NIC hardware queue
- * vale:foo bind to the Vale virtual interface
- *
- * for netmap version 10+ a suffix can indicate the following:
- * netmap:foo! bind to all NIC hardware queues (may cause TX reordering)
- * netmap:foo^ bind to the host (sw) ring pair
- * netmap:foo* bind to the host (sw) and NIC ring pairs (transparent)
- * netmap:foo-NN bind to the individual NIC ring pair (queue) where NN = the ring number
- * netmap:foo{NN bind to the master side of pipe NN
- * netmap:foo}NN bind to the slave side of pipe NN
- */
- if (strncmp(device, "netmap:", 7) && strncmp(device, "vale", 4)) {
- snprintf(ifname_buf, sizeof(ifname_buf), "netmap:%s", device);
- ifname = ifname_buf;
- } else {
- ifname = device;
- }
- if (!strncmp("vale", device, 4))
- sp->is_vale = 1;
- if (ifname[0] == 'n')
- ifname += 7;
- /* scan for a separator */
- for (port = ifname; *port && !index("!-*^{}", *port); port++)
- ;
- namelen = port - ifname;
- if (namelen > sizeof(nmr.nr_name)) {
- snprintf(errbuf, SENDPACKET_ERRBUF_SIZE, "Interface name is to long: %s\n", device);
- goto IFACENAME_INVALID;
- }
- /*
- * Open the netmap device to fetch the number of queues of our
- * interface.
- *
- * The first NIOCREGIF also detaches the card from the
- * protocol stack and may cause a reset of the card,
- * which in turn may take some time for the PHY to
- * reconfigure.
- */
- if ((sp->handle.fd = open("/dev/netmap", O_RDWR)) < 0) {
- dbg(1, "sendpacket_open_netmap: Unable to access netmap");
- snprintf(errbuf,
- SENDPACKET_ERRBUF_SIZE,
- "Unable to access netmap.\n"
- "See INSTALL to learn which NICs are supported and\n"
- "how to set up netmap-capable network drivers.");
- goto OPEN_FAILED;
- }
- /*
- * The nmreq structure must have the NETMAP_API version for the running machine.
- * However the binary may have been compiled on a different machine than the
- * running machine. Discover the true netmap API version, and be careful to call
- * functions that are available on all netmap versions.
- */
- if (sp->netmap_version >= 10) {
- switch (*port) {
- case '-': /* one NIC */
- nr_flags = NR_REG_ONE_NIC;
- nr_ringid = strtol(port + 1, NULL, 10);
- break;
- case '*': /* NIC and SW, ignore port */
- nr_flags = NR_REG_NIC_SW;
- if (port[1]) {
- snprintf(errbuf, SENDPACKET_ERRBUF_SIZE, "invalid netmap port for nic+sw");
- goto NETMAP_IF_PARSE_FAIL;
- }
- break;
- case '^': /* only sw ring */
- nr_flags = NR_REG_SW;
- if (port[1]) {
- snprintf(errbuf, SENDPACKET_ERRBUF_SIZE, "invalid port for sw ring");
- goto NETMAP_IF_PARSE_FAIL;
- }
- break;
- case '{':
- nr_flags = NR_REG_PIPE_MASTER;
- nr_ringid = strtol(port + 1, NULL, 10);
- break;
- case '}':
- nr_flags = NR_REG_PIPE_SLAVE;
- nr_ringid = strtol(port + 1, NULL, 10);
- break;
- case '!':
- nr_flags = NR_REG_ALL_NIC;
- break;
- default: /* '\0', no suffix */
- nr_flags = NR_REG_ALL_NIC;
- is_default = 1;
- break;
- }
- if (nr_ringid >= NETMAP_RING_MASK) {
- snprintf(errbuf, SENDPACKET_ERRBUF_SIZE, "invalid ringid");
- goto NETMAP_IF_PARSE_FAIL;
- }
- nmr.nr_ringid = nr_ringid;
- nmr.nr_flags = nr_flags;
- }
- nmr.nr_version = sp->netmap_version;
- memcpy(nmr.nr_name, ifname, namelen);
- nmr.nr_name[namelen] = '\0';
- strlcpy(sp->device, nmr.nr_name, sizeof(sp->device));
- /*
- * Register the interface on the netmap device: from now on,
- * we can operate on the network interface without any
- * interference from the legacy network stack.
- *
- * Cards take a long time to reset the PHY.
- */
- fprintf(stderr, "Switching network driver for %s to netmap bypass mode... ", sp->device);
- fflush(NULL);
- sleep(1); /* ensure message prints when user is connected via ssh */
- if (ioctl(sp->handle.fd, NIOCREGIF, &nmr)) {
- snprintf(errbuf,
- SENDPACKET_ERRBUF_SIZE,
- "Failure accessing netmap.\n"
- "\tRequest for netmap version %d failed.\n\tCompiled netmap driver is version %d.\n\tError=%s\n",
- sp->netmap_version,
- NETMAP_API,
- strerror(errno));
- goto NETMAP_IF_FAILED;
- }
- if (!nmr.nr_memsize) {
- snprintf(errbuf, SENDPACKET_ERRBUF_SIZE, "Netmap interface '%s' not configured.\n", device);
- goto NETMAP_IF_FAILED;
- }
- sp->mmap_size = nmr.nr_memsize;
- sp->mmap_addr = (struct netmap_d *)mmap(0, sp->mmap_size, PROT_WRITE | PROT_READ, MAP_SHARED, sp->handle.fd, 0);
- if (!sp->mmap_addr || sp->mmap_addr == MAP_FAILED) {
- snprintf(errbuf, SENDPACKET_ERRBUF_SIZE, "mmap: %s", strerror(errno));
- goto MMAP_FAILED;
- }
- dbgx(1, "sendpacket_open_netmap: mapping %d Kbytes queues=%d", sp->mmap_size >> 10, nmr.nr_tx_rings);
- sp->nm_if = NETMAP_IF(sp->mmap_addr, nmr.nr_offset);
- sp->nmr = nmr;
- sp->handle_type = SP_TYPE_NETMAP;
- /* set up ring IDs */
- sp->cur_tx_ring = 0;
- switch (nr_flags) {
- case NR_REG_DEFAULT: /* only use one queue to prevent TX reordering */
- sp->first_tx_ring = sp->last_tx_ring = sp->cur_tx_ring = 0;
- break;
- case NR_REG_ALL_NIC:
- if (is_default) {
- sp->first_tx_ring = sp->last_tx_ring = sp->cur_tx_ring = 0;
- } else {
- sp->first_tx_ring = sp->cur_tx_ring = 0;
- sp->last_tx_ring = nmr.nr_tx_rings - 1;
- }
- break;
- case NR_REG_SW:
- sp->first_tx_ring = sp->last_tx_ring = sp->cur_tx_ring = nmr.nr_tx_rings;
- break;
- case NR_REG_NIC_SW:
- sp->first_tx_ring = sp->cur_tx_ring = 0;
- sp->last_tx_ring = nmr.nr_tx_rings;
- break;
- case NR_REG_ONE_NIC:
- sp->first_tx_ring = sp->last_tx_ring = sp->cur_tx_ring = nr_ringid;
- break;
- default:
- sp->first_tx_ring = sp->last_tx_ring = sp->cur_tx_ring = 0;
- }
- {
- /* debugging code */
- int i;
- dbgx(1, "%s tx first=%d last=%d num=%d", ifname, sp->first_tx_ring, sp->last_tx_ring, sp->nmr.nr_tx_rings);
- for (i = 0; i <= sp->nmr.nr_tx_rings; i++) {
- #ifdef HAVE_NETMAP_RING_HEAD_TAIL
- dbgx(1,
- "TX%d 0x%p head=%d cur=%d tail=%d",
- i,
- NETMAP_TXRING(sp->nm_if, i),
- (NETMAP_TXRING(sp->nm_if, i))->head,
- (NETMAP_TXRING(sp->nm_if, i))->cur,
- (NETMAP_TXRING(sp->nm_if, i))->tail);
- #else
- dbgx(1,
- "TX%d 0x%p cur=%d avail=%d",
- i,
- NETMAP_TXRING(sp->nm_if, i),
- (NETMAP_TXRING(sp->nm_if, i))->cur,
- (NETMAP_TXRING(sp->nm_if, i))->avail);
- #endif
- }
- }
- dbgx(2, "Waiting %d seconds for phy reset...", ctx->options->netmap_delay);
- sleep(ctx->options->netmap_delay);
- dbg(2, "Ready!");
- if (!sp->is_vale) {
- if (nm_do_ioctl(sp, SIOCGIFFLAGS, 0) < 0)
- goto NM_DO_IOCTL_FAILED;
- if ((sp->if_flags & IFF_RUNNING) == 0) {
- dbgx(1, "sendpacket_open_netmap: %s is not running", sp->device);
- snprintf(errbuf, SENDPACKET_ERRBUF_SIZE, "interface %s is not running - check cables\n", sp->device);
- goto NETMAP_IF_NOT_RUNNING;
- }
- if ((sp->if_flags & IFF_UP) == 0) {
- dbgx(1, "%s is down, bringing up...", sp->device);
- sp->if_flags |= IFF_UP;
- }
- /* set promiscuous mode */
- sp->if_flags |= IFF_PROMISC;
- if (nm_do_ioctl(sp, SIOCSIFFLAGS, 0) < 0)
- goto NM_DO_IOCTL_FAILED;
- #ifdef linux
- /* disable:
- * - generic-segmentation-offload
- * - tcp-segmentation-offload
- * - rx-checksumming
- * - tx-checksumming
- */
- if (nm_do_ioctl(sp, SIOCETHTOOL, ETHTOOL_GGSO) < 0 || nm_do_ioctl(sp, SIOCETHTOOL, ETHTOOL_GTSO) < 0 ||
- nm_do_ioctl(sp, SIOCETHTOOL, ETHTOOL_GRXCSUM) < 0 || nm_do_ioctl(sp, SIOCETHTOOL, ETHTOOL_GTXCSUM) < 0)
- goto NM_DO_IOCTL_FAILED;
- sp->data = 0;
- if (nm_do_ioctl(sp, SIOCETHTOOL, ETHTOOL_SGSO) < 0 || nm_do_ioctl(sp, SIOCETHTOOL, ETHTOOL_STSO) < 0 ||
- nm_do_ioctl(sp, SIOCETHTOOL, ETHTOOL_SRXCSUM) < 0 || nm_do_ioctl(sp, SIOCETHTOOL, ETHTOOL_STXCSUM))
- goto NM_DO_IOCTL_FAILED;
- #endif
- }
- if (sp->abort)
- goto NETMAP_ABORT;
- notice("done!");
- return sp;
- NM_DO_IOCTL_FAILED:
- snprintf(errbuf, SENDPACKET_ERRBUF_SIZE, "nm_do_ioctl: %s", strerror(errno));
- NETMAP_IF_NOT_RUNNING:
- notice("failed!");
- NETMAP_ABORT:
- fprintf(stderr, " Switching network driver for %s to normal mode... ", sp->device);
- fflush(NULL);
- munmap(sp->mmap_addr, sp->mmap_size);
- MMAP_FAILED:
- #if NETMAP_API < 10
- ioctl(sp->handle.fd, NIOCUNREGIF, NULL);
- #endif
- NETMAP_IF_FAILED:
- NETMAP_IF_PARSE_FAIL:
- close(sp->handle.fd);
- OPEN_FAILED:
- safe_free(sp);
- IFACENAME_INVALID:
- NETMAP_NOT_INSTALLED:
- return NULL;
- }
- void
- sendpacket_close_netmap(void *p)
- {
- sendpacket_t *sp = p;
- fprintf(stderr, "Switching network driver for %s to normal mode... ", sp->device);
- fflush(NULL);
- /* flush any remaining packets */
- ioctl(sp->handle.fd, NIOCTXSYNC, NULL);
- /* wait for traffic to be sent */
- dbgx(2, "Waiting %d seconds for phy reset...", sp->netmap_delay);
- sleep(sp->netmap_delay);
- dbg(2, "Ready!");
- #ifdef linux
- if (!sp->is_vale) {
- /* restore original settings:
- * - generic-segmentation-offload
- * - tcp-segmentation-offload
- * - rx-checksumming
- * - tx-checksumming
- */
- sp->data = sp->gso;
- nm_do_ioctl(sp, SIOCETHTOOL, ETHTOOL_SGSO);
- sp->data = sp->tso;
- nm_do_ioctl(sp, SIOCETHTOOL, ETHTOOL_STSO);
- sp->data = sp->rxcsum;
- nm_do_ioctl(sp, SIOCETHTOOL, ETHTOOL_SRXCSUM);
- sp->data = sp->txcsum;
- nm_do_ioctl(sp, SIOCETHTOOL, ETHTOOL_STXCSUM);
- }
- #endif /* linux */
- /* restore interface to normal mode */
- #if NETMAP_API < 10
- ioctl(sp->handle.fd, NIOCUNREGIF, NULL);
- #endif
- if (sp->mmap_addr)
- munmap(sp->mmap_addr, sp->mmap_size);
- close(sp->handle.fd);
- notice("done!");
- }
- bool
- netmap_tx_queues_empty(void *p)
- {
- sendpacket_t *sp = p;
- struct netmap_ring *txring;
- assert(sp);
- sp->cur_tx_ring = 0;
- txring = NETMAP_TXRING(sp->nm_if, sp->cur_tx_ring);
- while (NETMAP_TX_RING_EMPTY(txring)) {
- /* current ring is empty- go to next */
- ++sp->cur_tx_ring;
- if (sp->cur_tx_ring > sp->last_tx_ring) {
- /* last ring */
- sp->cur_tx_ring = 0;
- return true;
- }
- txring = NETMAP_TXRING(sp->nm_if, sp->cur_tx_ring);
- }
- /*
- * send TX interrupt signal
- */
- ioctl(sp->handle.fd, NIOCTXSYNC, NULL);
- return false;
- }
- int
- sendpacket_send_netmap(void *p, const u_char *data, size_t len)
- {
- sendpacket_t *sp = p;
- struct netmap_ring *txring;
- struct netmap_slot *slot;
- char *pkt;
- uint32_t cur, avail;
- if (sp->abort)
- return 0;
- txring = NETMAP_TXRING(sp->nm_if, sp->cur_tx_ring);
- while ((avail = nm_ring_space(txring)) == 0) {
- /* out of space on current TX queue - go to next */
- ++sp->cur_tx_ring;
- if (sp->cur_tx_ring > sp->last_tx_ring) {
- /*
- * out of space on all queues
- *
- * we have looped through all configured TX queues
- * so we have to reset to the first queue and
- * wait for available space
- */
- sp->cur_tx_ring = sp->first_tx_ring;
- /* send TX interrupt signal
- *
- * On Linux this makes one slot free on the
- * ring, which increases speed by about 10Mbps.
- *
- * But it will never free up all the slots. For
- * that we must poll and call again.
- */
- ioctl(sp->handle.fd, NIOCTXSYNC, NULL);
- /* loop again */
- return -2;
- }
- txring = NETMAP_TXRING(sp->nm_if, sp->cur_tx_ring);
- }
- /*
- * send
- */
- cur = txring->cur;
- slot = &txring->slot[cur];
- slot->flags = 0;
- pkt = NETMAP_BUF(txring, slot->buf_idx);
- memcpy(pkt, data, min(len, txring->nr_buf_size));
- slot->len = len;
- if (avail <= 1)
- slot->flags = NS_REPORT;
- dbgx(3,
- "netmap cur=%d slot index=%d flags=0x%x empty=%d avail=%u bufsize=%d\n",
- cur,
- slot->buf_idx,
- slot->flags,
- NETMAP_TX_RING_EMPTY(txring),
- nm_ring_space(txring),
- txring->nr_buf_size);
- /* let kernel know that packet is available */
- cur = NETMAP_RING_NEXT(txring, cur);
- #ifdef HAVE_NETMAP_RING_HEAD_TAIL
- txring->head = cur;
- #else
- txring->avail--;
- #endif
- txring->cur = cur;
- return len;
- }
|