From fbcc42561094fe68efb028e0ff81374bfa7875a0 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Fri, 11 Jun 2021 18:53:43 -0700 Subject: [PATCH] net: Add a new libpcap-based client --- net/clients.h | 4 ++ net/meson.build | 1 + net/net.c | 1 + net/pcap.c | 178 ++++++++++++++++++++++++++++++++++++++++++++++++ net/pcap.h | 29 ++++++++ qapi/net.json | 19 +++++- 6 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 net/pcap.c create mode 100644 net/pcap.h diff --git a/net/clients.h b/net/clients.h index 92f9b59aed..166355eded 100644 --- a/net/clients.h +++ b/net/clients.h @@ -63,4 +63,8 @@ int net_init_vhost_user(const Netdev *netdev, const char *name, int net_init_vhost_vdpa(const Netdev *netdev, const char *name, NetClientState *peer, Error **errp); + +int net_init_pcap(const Netdev *netdev, const char *name, + NetClientState *peer, Error **errp); + #endif /* QEMU_NET_CLIENTS_H */ diff --git a/net/meson.build b/net/meson.build index 1076b0a7ab..3b2e9cddab 100644 --- a/net/meson.build +++ b/net/meson.build @@ -37,5 +37,6 @@ endif softmmu_ss.add(when: 'CONFIG_POSIX', if_true: files(tap_posix)) softmmu_ss.add(when: 'CONFIG_WIN32', if_true: files('tap-win32.c')) softmmu_ss.add(when: 'CONFIG_VHOST_NET_VDPA', if_true: files('vhost-vdpa.c')) +softmmu_ss.add([libpcap, files('pcap.c')]) subdir('can') diff --git a/net/net.c b/net/net.c index edf9b95418..1cd154083d 100644 --- a/net/net.c +++ b/net/net.c @@ -1002,6 +1002,7 @@ static int (* const net_client_init_fun[NET_CLIENT_DRIVER__MAX])( #ifdef CONFIG_L2TPV3 [NET_CLIENT_DRIVER_L2TPV3] = net_init_l2tpv3, #endif + [NET_CLIENT_DRIVER_PCAP] = net_init_pcap, }; diff --git a/net/pcap.c b/net/pcap.c new file mode 100644 index 0000000000..121f8ae07a --- /dev/null +++ b/net/pcap.c @@ -0,0 +1,178 @@ +/* + * QEMU libpcap network client + * + * Copyright (C) 2021 Matt Borgerson + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program. If not, see . + */ + +#include "qemu/osdep.h" +#include "net/net.h" +#include "net/eth.h" +#include "net/clients.h" +#include "clients.h" +#include "sysemu/sysemu.h" +#include "qemu/error-report.h" +#include "qapi/error.h" +#include "qemu/iov.h" +#include "qemu/cutils.h" +#include "qemu/main-loop.h" +#include "net/pcap.h" + +#if 0 +#define LOG(fmt, ...) fprintf(stderr, "%s: " fmt "\n", __func__, ##__VA_ARGS__) +#else +#define LOG(...) do {} while (0) +#endif + +typedef struct NetPcapState { + NetClientState nc; + char *ifname; + pcap_t *p; +#ifdef WIN32 + HANDLE fd; +#else + int fd; + bool read_poll; +#endif +} NetPcapState; + +static ssize_t net_pcap_receive(NetClientState *nc, const uint8_t *buf, + size_t size) +{ + NetPcapState *s = DO_UPCAST(NetPcapState, nc, nc); + LOG("qemu->pcap %zd bytes...", size); + + if (pcap_sendpacket(s->p, buf, size)) { + LOG("pcap_sendpacket failed!\n"); + return -1; + } + + return size; +} + +static void net_pcap_cleanup(NetClientState *nc) +{ + NetPcapState *s = DO_UPCAST(NetPcapState, nc, nc); +#if defined(_WIN32) + qemu_del_wait_object(s->fd, NULL, NULL); +#endif + pcap_close(s->p); + free(s->ifname); +} + +static NetClientInfo net_pcap_info = { + .type = NET_CLIENT_DRIVER_PCAP, + .size = sizeof(NetPcapState), + .receive = net_pcap_receive, + .cleanup = net_pcap_cleanup, +}; + +static void net_pcap_send(void *opaque) +{ + NetPcapState *s = opaque; + struct pcap_pkthdr *pkt_header; + const u_char *buf; + uint8_t min_pkt[ETH_ZLEN]; + size_t min_pktsz = sizeof(min_pkt); + + int status = pcap_next_ex(s->p, &pkt_header, &buf); + if (status == 1) { + /* Success */ + } else if (status == 0) { + /* Timeout */ + return; + } else if (status == -1) { + LOG("pcap_next_ex error: %s", pcap_geterr(s->p)); + return; + } else { + LOG("unknown pcap error %d\n", status); + return; + } + + size_t size = pkt_header->len; + assert(size >= 14); + assert(pkt_header->caplen == size); + + if (size > 0) { + if (net_peer_needs_padding(&s->nc)) { + if (eth_pad_short_frame(min_pkt, &min_pktsz, buf, size)) { + buf = min_pkt; + size = min_pktsz; + } + } + + LOG("pcap->qemu %zd bytes", size); + qemu_send_packet(&s->nc, buf, size); + } +} + +#if !defined(_WIN32) +static void net_pcap_update_fd_handler(NetPcapState *s) +{ + qemu_set_fd_handler(s->fd, s->read_poll ? net_pcap_send : NULL, NULL, s); +} + +static void net_pcap_read_poll(NetPcapState *s, bool enable) +{ + s->read_poll = enable; + net_pcap_update_fd_handler(s); +} +#endif + +int net_init_pcap(const Netdev *netdev, const char *name, NetClientState *peer, + Error **errp) +{ + const NetdevPcapOptions *pcap_opts = &netdev->u.pcap; + NetClientState *nc; + NetPcapState *s; + char err[PCAP_ERRBUF_SIZE]; + const int promisc = 1; + int status; + + pcap_t *p = pcap_open_live(pcap_opts->ifname, 65536, promisc, 1, err); + if (p == NULL) { + error_setg(errp, "failed to open interface '%s' for capture: %s", + pcap_opts->ifname, err); + return -1; + } + + status = pcap_set_datalink(p, DLT_EN10MB); + if (status != 0) { + error_setg(errp, "failed to set data link format to DLT_EN10MB"); + return -1; + } + +#ifdef WIN32 + pcap_setmintocopy(p, 40); +#endif + + nc = qemu_new_net_client(&net_pcap_info, peer, "pcap", name); + s = DO_UPCAST(NetPcapState, nc, nc); + s->ifname = strdup(pcap_opts->ifname); + s->p = p; + + LOG("Initialized with interface %s", s->ifname); + +#ifdef WIN32 + s->fd = pcap_getevent(p); + qemu_add_wait_object(s->fd, net_pcap_send, s); +#else + s->fd = pcap_get_selectable_fd(p); + assert(s->fd >= 0); + net_pcap_read_poll(s, true); +#endif + + return 0; +} diff --git a/net/pcap.h b/net/pcap.h new file mode 100644 index 0000000000..d474423e2b --- /dev/null +++ b/net/pcap.h @@ -0,0 +1,29 @@ +/* + * QEMU libpcap network client + * + * Copyright (C) 2021 Matt Borgerson + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program. If not, see . + */ + +#ifndef NET_PCAP_H +#define NET_PCAP_H + +#include + +#if defined(_WIN32) +#include "net/capture_win_ifnames.h" +#endif + +#endif diff --git a/qapi/net.json b/qapi/net.json index af3f5b0fda..45242933df 100644 --- a/qapi/net.json +++ b/qapi/net.json @@ -450,6 +450,19 @@ '*vhostdev': 'str', '*queues': 'int' } } +## +# @NetdevPcapOptions: +# +# Connect a client to a libpcap interface +# +# @ifname: Name of host interface +# +# Since: 6.0 +## +{ 'struct': 'NetdevPcapOptions', + 'data': { + 'ifname': 'str' } } + ## # @NetClientDriver: # @@ -461,7 +474,8 @@ ## { 'enum': 'NetClientDriver', 'data': [ 'none', 'nic', 'user', 'tap', 'l2tpv3', 'socket', 'vde', - 'bridge', 'hubport', 'netmap', 'vhost-user', 'vhost-vdpa' ] } + 'bridge', 'hubport', 'netmap', 'vhost-user', 'vhost-vdpa', + 'pcap' ] } ## # @Netdev: @@ -490,7 +504,8 @@ 'hubport': 'NetdevHubPortOptions', 'netmap': 'NetdevNetmapOptions', 'vhost-user': 'NetdevVhostUserOptions', - 'vhost-vdpa': 'NetdevVhostVDPAOptions' } } + 'vhost-vdpa': 'NetdevVhostVDPAOptions', + 'pcap': 'NetdevPcapOptions' } } ## # @RxState: