Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port netmap code to Linux #800

Merged
merged 1 commit into from Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.netmap.md
Expand Up @@ -8,9 +8,9 @@ information on netmap.

Netmap is available by default on FreeBSD on many architectures, including
amd64 and arm64, and is easy to add to the kernel config on architectures where
it is not built by default. While netmap has been ported to Linux, ZMap's
netmap mode currently only supports FreeBSD and will require porting to build
and run on Linux.
it is not built by default. On Linux, netmap itself and netmap-aware drivers
can be installed by following the instructions in
[netmap/LINUX/README.md](https://github.com/luigirizzo/netmap/blob/master/LINUX/README.md).


### Prerequisites
Expand Down
7 changes: 7 additions & 0 deletions src/CMakeLists.txt
Expand Up @@ -124,6 +124,13 @@ if(WITH_PFRING)
elseif(WITH_NETMAP)
set(SOURCES ${SOURCES} socket-netmap.c send-netmap.c)
set(ZTESTSOURCES ${ZTESTSOURCES} socket-netmap.c send-netmap.c)
if(APPLE OR BSD)
set(SOURCES ${SOURCES} if-netmap-bsd.c)
set(ZTESTSOURCES ${ZTESTSOURCES} if-netmap-bsd.c)
else()
set(SOURCES ${SOURCES} if-netmap-linux.c)
set(ZTESTSOURCES ${ZTESTSOURCES} if-netmap-linux.c)
endif()
elseif (APPLE OR BSD)
set(SOURCES ${SOURCES} socket-bsd.c send-bsd.c)
set(ZTESTSOURCES ${ZTESTSOURCES} socket-bsd.c send-bsd.c)
Expand Down
141 changes: 141 additions & 0 deletions src/if-netmap-bsd.c
@@ -0,0 +1,141 @@
/*
* ZMap Copyright 2013 Regents of the University of Michigan
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*/

#ifndef __FreeBSD__
#error "NETMAP requires FreeBSD or Linux"
#endif

#include "if-netmap.h"

#include "../lib/includes.h"
#include "../lib/logger.h"

#include <sys/ioctl.h>
#include <net/if_types.h>
#include <unistd.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <errno.h>

static void
fetch_if_data(struct if_data *ifd, char const *ifname, int fd)
{
struct ifreq ifr;
bzero(&ifr, sizeof(ifr));
strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
ifr.ifr_data = (caddr_t)ifd;
if (ioctl(fd, SIOCGIFDATA, &ifr) == -1) {
log_fatal("if-netmap-bsd", "unable to retrieve if_data: %d: %s",
errno, strerror(errno));
}
}

void
if_wait_for_phy_reset(char const *ifname, int fd)
{
struct if_data ifd;
bzero(&ifd, sizeof(ifd));
for (size_t i = 0; i < 40 /* 10s */; i++) {
fetch_if_data(&ifd, ifname, fd);
if (ifd.ifi_link_state == LINK_STATE_UP) {
return;
}
usleep(250000);
}
log_fatal("if-netmap-bsd", "timeout waiting for PHY reset to complete");
}

size_t
if_get_data_link_size(char const *ifname, int fd)
{
struct if_data ifd;
bzero(&ifd, sizeof(ifd));
fetch_if_data(&ifd, ifname, fd);

switch (ifd.ifi_type) {
case IFT_ETHER:
log_debug("if-netmap-bsd", "IFT_ETHER");
return sizeof(struct ether_header);
default:
log_fatal("if-netmap-bsd", "Unsupported if type %u", ifd.ifi_type);
}
}

// Notes on if counters:
// On interfaces without hardware counters (HWSTATS), ipackets misses
// packets that we do not forward to the host ring pair.
// oqdrops counts packets the host OS could not send due to netmap mode.

struct if_stats_ctx {
char *ifname; // owned
int fd; // borrowed
bool hwstats;
uint64_t ifi_ipackets;
uint64_t ifi_iqdrops;
uint64_t ifi_ierrors;
uint64_t ifi_oerrors;
};

if_stats_ctx_t *
if_stats_init(char const *ifname, int fd)
{
if_stats_ctx_t *ctx = malloc(sizeof(struct if_stats_ctx));
bzero(ctx, sizeof(struct if_stats_ctx));

ctx->ifname = strdup(ifname);
ctx->fd = fd;

struct if_data ifd;
bzero(&ifd, sizeof(ifd));
fetch_if_data(&ifd, ctx->ifname, ctx->fd);

ctx->hwstats = (ifd.ifi_hwassist & IFCAP_HWSTATS) != 0;
if (ctx->hwstats) {
ctx->ifi_ipackets = ifd.ifi_ipackets;
} else {
ctx->ifi_ipackets = 0;
}
ctx->ifi_iqdrops = ifd.ifi_iqdrops;
ctx->ifi_ierrors = ifd.ifi_ierrors;
ctx->ifi_oerrors = ifd.ifi_oerrors;
return ctx;
}

void
if_stats_fini(if_stats_ctx_t *ctx)
{
free(ctx->ifname);
free(ctx);
}

bool
if_stats_have_recv_ctr(if_stats_ctx_t *ctx)
{
return ctx->hwstats;
}

int
if_stats_get(if_stats_ctx_t *ctx, uint32_t *ps_recv, uint32_t *ps_drop, uint32_t *ps_ifdrop)
{
struct if_data ifd;
bzero(&ifd, sizeof(ifd));
fetch_if_data(&ifd, ctx->ifname, ctx->fd);

if (ctx->hwstats) {
*ps_recv = (uint32_t)(ifd.ifi_ipackets - ctx->ifi_ipackets);
}
*ps_drop = (uint32_t)(ifd.ifi_iqdrops - ctx->ifi_iqdrops);
*ps_ifdrop = (uint32_t)(ifd.ifi_ierrors - ctx->ifi_ierrors +
ifd.ifi_oerrors - ctx->ifi_oerrors);
return 0;
}




223 changes: 223 additions & 0 deletions src/if-netmap-linux.c
@@ -0,0 +1,223 @@
/*
* ZMap Copyright 2013 Regents of the University of Michigan
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*/

#include "if-netmap.h"

#include "../lib/includes.h"
#include "../lib/logger.h"

#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/ethtool.h>
#include <linux/sockios.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <net/if.h>

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <stdint.h>
#include <inttypes.h>
#include <assert.h>
#include <errno.h>

static int
nlrt_socket(void)
{
int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (fd == -1) {
log_fatal("if-netmap-linux", "socket(NETLINK_ROUTE): %d: %s", errno, strerror(errno));
}

int one = 1;
(void)setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &one, sizeof(one));

struct sockaddr_nl sanl;
memset(&sanl, 0, sizeof(sanl));
sanl.nl_family = AF_NETLINK;

if (bind(fd, (struct sockaddr *)&sanl, sizeof(sanl)) == -1) {
log_fatal("if-netmap-linux", "bind(AF_NETLINK): %d: %s", errno, strerror(errno));
}

return fd;
}

static void
fetch_stats64(struct rtnl_link_stats64 *rtlstats64, char const *ifname, int nlrtfd)
{
struct {
struct nlmsghdr nlh;
struct if_stats_msg ifsm;
} nlreq;
memset(&nlreq, 0, sizeof(nlreq));
nlreq.nlh.nlmsg_len = sizeof(nlreq);
nlreq.nlh.nlmsg_type = RTM_GETSTATS;
nlreq.nlh.nlmsg_flags = NLM_F_REQUEST;
nlreq.ifsm.ifindex = if_nametoindex(ifname);
nlreq.ifsm.filter_mask = IFLA_STATS_LINK_64;

struct iovec iov[2];
memset(&iov[0], 0, sizeof(iov[0]));
iov[0].iov_base = (void *)&nlreq;
iov[0].iov_len = sizeof(nlreq);

struct msghdr msg;
memset(&msg, 0, sizeof(msg));
msg.msg_iov = &iov[0];
msg.msg_iovlen = 1;

if (sendmsg(nlrtfd, &msg, 0) == -1) {
log_fatal("if-netmap-linux", "sendmsg(RTM_GETSTATS): %d: %s", errno, strerror(errno));
}

struct nlresp {
struct nlmsghdr nlh;
union {
struct {
struct rtmsg rth;
struct rtnl_link_stats64 rtlstats64;
} ans;
struct {
struct nlmsgerr nlerr;
} err;
};
} nlresp;
_Static_assert(sizeof(nlresp.ans) >= sizeof(nlresp.err));
static const size_t ans_size = offsetof(struct nlresp, ans) + sizeof(nlresp.ans);
static const size_t err_size = offsetof(struct nlresp, err) + sizeof(nlresp.err);

memset(iov, 0, sizeof(iov));
iov[0].iov_base = (void *)&nlresp;
iov[0].iov_len = offsetof(struct nlresp, ans.rtlstats64);
iov[1].iov_base = (void *)rtlstats64; // caller-provided
iov[1].iov_len = sizeof(struct rtnl_link_stats64);
memset(&msg, 0, sizeof(msg));
msg.msg_iov = iov;
msg.msg_iovlen = 2;

ssize_t n = recvmsg(nlrtfd, &msg, 0);
if (n == -1) {
log_fatal("if-netmap-linux", "recvmsg(RTM_GETSTATS): %d: %s", errno, strerror(errno));
}
if ((size_t)n < err_size) {
log_fatal("if-netmap-linux", "received %zu expected %zu or larger", (size_t)n, err_size);
}

if (nlresp.nlh.nlmsg_type == NLMSG_ERROR) {
// copy second iov into ans in first iov to get contiguous struct nlmsgerr
nlresp.ans.rtlstats64 = *rtlstats64;
assert(nlresp.err.nlerr.error < 0);
errno = -nlresp.err.nlerr.error;
log_fatal("if-netmap-linux", "received NLMSG_ERROR: %d: %s", errno, strerror(errno));
}
if (nlresp.nlh.nlmsg_type != RTM_NEWSTATS) {
log_fatal("if-netmap-linux", "received unexpected nlmsg_type %u", nlresp.nlh.nlmsg_type);
}
if ((size_t)n != ans_size) {
log_fatal("if-netmap-linux", "received %zu expected %zu", (size_t)n, ans_size);
}
}

void
if_wait_for_phy_reset(char const *ifname, int fd)
{
// clobber deliberately
fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
if (fd == -1) {
log_fatal("if-netmap-linux", "socket(AF_INET): %d: %s", errno, strerror(errno));
}

for (size_t i = 0; i < 40 /* 10s */; i++) {
struct ifreq ifr;
strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
struct ethtool_value etv;
etv.cmd = ETHTOOL_GLINK;
ifr.ifr_data = (void *)&etv;
if (ioctl(fd, SIOCETHTOOL, &ifr) == -1) {
log_fatal("if-netmap-linux", "ioctl(SIOCETHTOOL): %d: %s", errno, strerror(errno));
}
if (etv.data != 0 /* carrier */) {
close(fd);
return;
}
usleep(250000);
}
log_fatal("if-netmap-linux", "timeout waiting for PHY reset to complete");
}

size_t
if_get_data_link_size(UNUSED char const *ifname, UNUSED int fd)
{
// Assuming Ethernet is not entirely unreasonable, as that's
// the only thing we support on the send path anyway.
// TODO figure out actual link type or link header size
return sizeof(struct ether_header);
}

struct if_stats_ctx {
char *ifname; // owned
int nlrtfd; // owned
// uint64_t rx_packets;
uint64_t rx_dropped;
uint64_t rx_errors;
uint64_t tx_errors;
};

if_stats_ctx_t *
if_stats_init(char const *ifname, UNUSED int fd)
{
if_stats_ctx_t *ctx = malloc(sizeof(struct if_stats_ctx));
memset(ctx, 0, sizeof(struct if_stats_ctx));

ctx->ifname = strdup(ifname);
ctx->nlrtfd = nlrt_socket();

struct rtnl_link_stats64 rtlstats64;
memset(&rtlstats64, 0, sizeof(rtlstats64));
fetch_stats64(&rtlstats64, ctx->ifname, ctx->nlrtfd);

//ctx->rx_packets = rtlstats64.rx_packets;
ctx->rx_dropped = rtlstats64.rx_dropped;
ctx->rx_errors = rtlstats64.rx_errors;
ctx->tx_errors = rtlstats64.tx_errors;
return ctx;
}

void
if_stats_fini(if_stats_ctx_t *ctx)
{
free(ctx->ifname);
close(ctx->nlrtfd);
free(ctx);
}

bool
if_stats_have_recv_ctr(UNUSED if_stats_ctx_t *ctx)
{
return false;
}

int
if_stats_get(if_stats_ctx_t *ctx, UNUSED uint32_t *ps_recv, uint32_t *ps_drop, uint32_t *ps_ifdrop)
{
struct rtnl_link_stats64 rtlstats64;
memset(&rtlstats64, 0, sizeof(rtlstats64));
fetch_stats64(&rtlstats64, ctx->ifname, ctx->nlrtfd);

//*ps_recv = (uint32_t)(rtlstats64.rx_packets - ctx->rx_packets);
*ps_drop = (uint32_t)(rtlstats64.rx_dropped - ctx->rx_dropped);
*ps_ifdrop = (uint32_t)(rtlstats64.rx_errors - ctx->rx_errors +
rtlstats64.tx_errors - ctx->tx_errors);
return 0;
}