diff mbox

[PATCHv2,3/4] linux-generic: pktio: add pcap pktio type

Message ID 1441372829-12886-4-git-send-email-stuart.haslam@linaro.org
State New
Headers show

Commit Message

Stuart Haslam Sept. 4, 2015, 1:20 p.m. UTC
Create a new pktio type that allows for reading from and writing to a
pcap capture file. This is intended to be used as a simple way of
injecting test packets into an application for functional testing and
can be used as it is with some of the existing example applications.

To use this interface the name passed to odp_pktio_open() must begin
with "pcap:" and be in the format;

 pcap:in=test.pcap:out=test_out.pcap:loops=10

   in      the name of the input pcap file. If no input file is given
           attempts to receive from the pktio will just return no
           packets.
   out     the name of the output pcap file. If no output file is
           given any packets transmitted over the interface will just
           be freed.
   loops   the number of times to iterate through the input file, set
           to 0 to loop indefinitely. The default value is 1.

Signed-off-by: Stuart Haslam <stuart.haslam@linaro.org>
---
 platform/linux-generic/Makefile.am                 |   4 +
 .../linux-generic/include/odp_packet_io_internal.h |  21 ++
 platform/linux-generic/m4/configure.m4             |  16 +
 platform/linux-generic/pktio/io_ops.c              |   3 +
 platform/linux-generic/pktio/pcap.c                | 334 +++++++++++++++++++++
 5 files changed, 378 insertions(+)
 create mode 100644 platform/linux-generic/pktio/pcap.c

Comments

Nicolas Morey-Chaisemartin Sept. 8, 2015, 3:25 p.m. UTC | #1
On 09/04/2015 03:20 PM, Stuart Haslam wrote:
> Create a new pktio type that allows for reading from and writing to a
> pcap capture file. This is intended to be used as a simple way of
> injecting test packets into an application for functional testing and
> can be used as it is with some of the existing example applications.
>
> To use this interface the name passed to odp_pktio_open() must begin
> with "pcap:" and be in the format;
>
>  pcap:in=test.pcap:out=test_out.pcap:loops=10
>
>    in      the name of the input pcap file. If no input file is given
>            attempts to receive from the pktio will just return no
>            packets.
>    out     the name of the output pcap file. If no output file is
>            given any packets transmitted over the interface will just
>            be freed.
>    loops   the number of times to iterate through the input file, set
>            to 0 to loop indefinitely. The default value is 1.
>
> Signed-off-by: Stuart Haslam <stuart.haslam@linaro.org>
> ---
>  platform/linux-generic/Makefile.am                 |   4 +
>  .../linux-generic/include/odp_packet_io_internal.h |  21 ++
>  platform/linux-generic/m4/configure.m4             |  16 +
>  platform/linux-generic/pktio/io_ops.c              |   3 +
>  platform/linux-generic/pktio/pcap.c                | 334 +++++++++++++++++++++
>  5 files changed, 378 insertions(+)
>  create mode 100644 platform/linux-generic/pktio/pcap.c
>
> diff --git a/platform/linux-generic/Makefile.am b/platform/linux-generic/Makefile.am
> index 4c79730..8d14477 100644
> --- a/platform/linux-generic/Makefile.am
> +++ b/platform/linux-generic/Makefile.am
> @@ -173,3 +173,7 @@ EXTRA_DIST = \
>  	     arch/linux/odp_time_cycles.c \
>  	     arch/mips64/odp_time_cycles.c \
>  	     arch/x86/odp_time_cycles.c
> +
> +if HAVE_PCAP
> +__LIB__libodp_la_SOURCES += pktio/pcap.c
> +endif
> diff --git a/platform/linux-generic/include/odp_packet_io_internal.h b/platform/linux-generic/include/odp_packet_io_internal.h
> index bdffc15..7faa4f0 100644
> --- a/platform/linux-generic/include/odp_packet_io_internal.h
> +++ b/platform/linux-generic/include/odp_packet_io_internal.h
> @@ -38,6 +38,21 @@ typedef struct {
>  	odp_bool_t promisc;		/**< promiscuous mode state */
>  } pkt_loop_t;
>  
> +#ifdef HAVE_PCAP
> +typedef struct {
> +	char *fname_rx;		/**< name of pcap file for rx */
> +	char *fname_tx;		/**< name of pcap file for tx */
> +	void *rx;		/**< rx pcap handle */
> +	void *tx;		/**< tx pcap handle */
> +	void *tx_dump;		/**< tx pcap dumper handle */
> +	odp_pool_t pool;	/**< rx pool */
> +	unsigned char *buf;	/**< per-pktio temp buffer */
> +	odp_bool_t promisc;	/**< promiscuous mode requested state */
> +	int loops;		/**< number of times to loop rx pcap */
> +	int loop_cnt;		/**< number of loops completed */
> +} pkt_pcap_t;
> +#endif
> +
>  struct pktio_entry {
>  	const struct pktio_if_ops *ops; /**< Implementation specific methods */
>  	odp_spinlock_t lock;		/**< entry spinlock */
> @@ -51,6 +66,9 @@ struct pktio_entry {
>  		pkt_sock_t pkt_sock;		/**< using socket API for IO */
>  		pkt_sock_mmap_t pkt_sock_mmap;	/**< using socket mmap
>  						 *   API for IO */
> +#ifdef HAVE_PCAP
> +		pkt_pcap_t pkt_pcap;		/**< Using pcap for IO */
> +#endif
>  	};
>  	enum {
>  		STATE_START = 0,
> @@ -116,6 +134,9 @@ int pktin_poll(pktio_entry_t *entry);
>  extern const pktio_if_ops_t sock_mmsg_pktio_ops;
>  extern const pktio_if_ops_t sock_mmap_pktio_ops;
>  extern const pktio_if_ops_t loopback_pktio_ops;
> +#ifdef HAVE_PCAP
> +extern const pktio_if_ops_t pcap_pktio_ops;
> +#endif
>  extern const pktio_if_ops_t * const pktio_if_ops[];
>  
>  #ifdef __cplusplus
> diff --git a/platform/linux-generic/m4/configure.m4 b/platform/linux-generic/m4/configure.m4
> index 9658274..4e4fa00 100644
> --- a/platform/linux-generic/m4/configure.m4
> +++ b/platform/linux-generic/m4/configure.m4
> @@ -22,3 +22,19 @@ m4_include([platform/linux-generic/m4/odp_openssl.m4])
>  AC_CONFIG_FILES([platform/linux-generic/Makefile
>  		 platform/linux-generic/test/Makefile
>  		 platform/linux-generic/test/pktio/Makefile])
> +
> +#########################################################################
> +# Check for libpcap availability
> +#########################################################################
> +have_pcap=no
> +AC_CHECK_HEADER(pcap/pcap.h,
> +    [AC_CHECK_HEADER(pcap/bpf.h,
> +        [AC_CHECK_LIB(pcap, pcap_open_offline, have_pcap=yes, [])],
> +    [])],
> +[])
> +
> +AM_CONDITIONAL([HAVE_PCAP], [test $have_pcap = yes])
> +if test $have_pcap == yes; then
> +    AM_CFLAGS="$AM_CFLAGS -DHAVE_PCAP"
> +    LIBS="$LIBS -lpcap"
> +fi
> diff --git a/platform/linux-generic/pktio/io_ops.c b/platform/linux-generic/pktio/io_ops.c
> index 1d47e74..bd53a5c 100644
> --- a/platform/linux-generic/pktio/io_ops.c
> +++ b/platform/linux-generic/pktio/io_ops.c
> @@ -12,6 +12,9 @@
>   * Array must be NULL terminated */
>  const pktio_if_ops_t * const pktio_if_ops[]  = {
>  	&loopback_pktio_ops,
> +#ifdef HAVE_PCAP
> +	&pcap_pktio_ops,
> +#endif
>  	&sock_mmap_pktio_ops,
>  	&sock_mmsg_pktio_ops,
>  	NULL
> diff --git a/platform/linux-generic/pktio/pcap.c b/platform/linux-generic/pktio/pcap.c
> new file mode 100644
> index 0000000..d4173f9
> --- /dev/null
> +++ b/platform/linux-generic/pktio/pcap.c
> @@ -0,0 +1,334 @@
> +/* Copyright (c) 2015, Linaro Limited
> + * All rights reserved.
> + *
> + * SPDX-License-Identifier:     BSD-3-Clause
> + */
> +
> +/**
> + * @file
> + *
> + * PCAP pktio type
> + *
> + * This file provides a pktio interface that allows for reading from
> + * and writing to pcap capture files. It is intended to be used as
> + * simple way of injecting test packets into an application for the
> + * purpose of functional testing.
> + *
> + * To use this interface the name passed to odp_pktio_open() must begin
> + * with "pcap:" and be in the format;
> + *
> + * pcap:in=test.pcap:out=test_out.pcap:loops=10
> + *
> + *   in      the name of the input pcap file. If no input file is given
> + *           attempts to receive from the pktio will just return no
> + *           packets. If an input file is specified it must exist and be
> + *           a readable pcap file with a link type of DLT_EN10MB.
> + *   out     the name of the output pcap file. If no output file is
> + *           given any packets transmitted over the interface will just
> + *           be freed. If an output file is specified and the file
> + *           doesn't exist it will be created, if it does exist it will
> + *           be overwritten.
> + *   loops   the number of times to iterate through the input file, set
> + *           to 0 to loop indefinitely. The default value is 1.
> + *
> + * The total length of the string is limited by PKTIO_NAME_LEN.
> + */
> +
> +#ifndef _GNU_SOURCE
> +#define _GNU_SOURCE
> +#endif
> +
> +#include <odp.h>
> +#include <odp_packet_internal.h>
> +#include <odp_packet_io_internal.h>
> +
> +#include <odp/helper/eth.h>
> +
> +#include <pcap/pcap.h>
> +#include <pcap/bpf.h>
> +
> +#define PKTIO_PCAP_MTU (64 * 1024)
> +
> +static int _pcapif_parse_devname(pkt_pcap_t *pcap, const char *devname)
> +{
> +	char *tok;
> +	char in[PKTIO_NAME_LEN];
> +
> +	if (strncmp(devname, "pcap:", 5) != 0)
> +		return -1;
> +
> +	snprintf(in, sizeof(in), "%s", devname);
> +
> +	for (tok = strtok(in + 5, ":"); tok; tok = strtok(NULL, ":")) {
> +		if (strncmp(tok, "in=", 3) == 0 && !pcap->fname_rx) {
> +			tok += 3;
> +			pcap->fname_rx = strdup(tok);
> +		} else if (strncmp(tok, "out=", 4) == 0 && !pcap->fname_tx) {
> +			tok += 4;
> +			pcap->fname_tx = strdup(tok);
> +		} else if (strncmp(tok, "loops=", 6) == 0) {
> +			pcap->loops = atoi(tok + 6);
> +			if (pcap->loops < 0) {
> +				ODP_ERR("invalid loop count\n");
> +				return -1;
> +			}
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int _pcapif_init_rx(pkt_pcap_t *pcap)
> +{
> +	char errbuf[PCAP_ERRBUF_SIZE];
> +	int linktype;
> +
> +	pcap->rx = pcap_open_offline(pcap->fname_rx, errbuf);
> +	if (!pcap->rx) {
> +		ODP_ERR("failed to open pcap file %s (%s)\n",
> +			pcap->fname_rx, errbuf);
> +		return -1;
> +	}
> +
> +	linktype = pcap_datalink(pcap->rx);
> +	if (linktype != DLT_EN10MB) {
> +		ODP_ERR("unsupported datalink type: %d\n", linktype);
> +		return -1;
> +	}
> +
> +	return 0;
> +}
> +
> +static int _pcapif_init_tx(pkt_pcap_t *pcap)
> +{
> +	pcap_t *tx = pcap->rx;
> +
> +	if (!tx) {
> +		/* if there is no rx pcap_t already open for rx, a dummy
> +		 * one needs to be opened for writing the dump */
> +		tx = pcap_open_dead(DLT_EN10MB, PKTIO_PCAP_MTU);
> +		if (!tx) {
> +			ODP_ERR("failed to open TX dump\n");
> +			return -1;
> +		}
> +
> +		pcap->tx = tx;
> +	}
> +
> +	pcap->buf = malloc(PKTIO_PCAP_MTU);
> +	if (!pcap->buf) {
> +		ODP_ERR("failed to malloc temp buffer\n");
> +		return -1;
> +	}
> +
> +	pcap->tx_dump = pcap_dump_open(tx, pcap->fname_tx);
> +	if (!pcap->tx_dump) {
> +		ODP_ERR("failed to open dump file %s (%s)\n",
> +			pcap->fname_tx, pcap_geterr(tx));
> +		return -1;
> +	}
> +
> +	return pcap_dump_flush(pcap->tx_dump);
> +}
> +
> +static int pcapif_init(odp_pktio_t id ODP_UNUSED, pktio_entry_t *pktio_entry,
> +		       const char *devname, odp_pool_t pool)
> +{
> +	pkt_pcap_t *pcap = &pktio_entry->s.pkt_pcap;
> +	int ret;
> +
> +	memset(pcap, 0, sizeof(pkt_pcap_t));
> +	pcap->loop_cnt = 1;
> +	pcap->loops = 1;
> +	pcap->pool = pool;
> +
> +	ret = _pcapif_parse_devname(pcap, devname);
> +
> +	if (ret == 0 && pcap->fname_rx)
> +		ret = _pcapif_init_rx(pcap);
> +
> +	if (ret == 0 && pcap->fname_tx)
> +		ret = _pcapif_init_tx(pcap);
> +
> +	if (ret == 0 && (!pcap->rx && !pcap->tx_dump))
> +		ret = -1;
> +
> +	return ret;
> +}
> +
> +static int pcapif_close(pktio_entry_t *pktio_entry)
> +{
> +	pkt_pcap_t *pcap = &pktio_entry->s.pkt_pcap;
> +
> +	if (pcap->tx_dump)
> +		pcap_dump_close(pcap->tx_dump);
> +
> +	if (pcap->tx)
> +		pcap_close(pcap->tx);
> +
> +	if (pcap->rx)
> +		pcap_close(pcap->rx);
> +
> +	free(pcap->buf);
> +	free(pcap->fname_rx);
> +	free(pcap->fname_tx);
> +
> +	return 0;
> +}
> +
> +static int _pcapif_reopen(pkt_pcap_t *pcap)
> +{
> +	char errbuf[PCAP_ERRBUF_SIZE];
> +
> +	if (pcap->loops != 0 && ++pcap->loop_cnt >= pcap->loops)
> +		return 1;
> +
> +	if (pcap->rx)
> +		pcap_close(pcap->rx);
> +
> +	pcap->rx = pcap_open_offline(pcap->fname_rx, errbuf);
> +	if (!pcap->rx) {
> +		ODP_ERR("failed to reopen pcap file %s (%s)\n",
> +			pcap->fname_rx, errbuf);
> +		return 1;
> +	}
> +
> +	return 0;
> +}
> +
> +static int pcapif_recv_pkt(pktio_entry_t *pktio_entry, odp_packet_t pkts[],
> +			   unsigned len)
> +{
> +	unsigned i;
> +	struct pcap_pkthdr *hdr;
> +	const u_char *data;
> +	pkt_pcap_t *pcap = &pktio_entry->s.pkt_pcap;
> +
> +	ODP_ASSERT(pktio_entry->s.state == STATE_START);
> +
> +	if (!pcap->rx)
> +		return 0;
> +
> +	for (i = 0; i < len; ) {
> +		int ret = pcap_next_ex(pcap->rx, &hdr, &data);
> +
> +		/* end of file, attempt to reopen if within loop limit */
> +		if (ret == -2 && _pcapif_reopen(pcap) == 0)
> +			continue;
> +
> +		if (ret != 1)
> +			break;
> +
> +		pkts[i] = odp_packet_alloc(pcap->pool, hdr->caplen);
> +		if (odp_unlikely(pkts[i] == ODP_PACKET_INVALID))
> +			break;
> +
> +		if (odp_packet_copydata_in(pkts[i], 0, hdr->caplen, data) != 0)
> +			break;
> +
> +		_odp_packet_reset_parse(pkts[i]);
> +
> +		i++;
> +	}
> +
> +	return i;
> +}
> +
> +static int _pcapif_dump_pkt(pkt_pcap_t *pcap, odp_packet_t pkt)
> +{
> +	struct pcap_pkthdr hdr;
> +
> +	if (!pcap->tx_dump)
> +		return 0;
> +
> +	hdr.caplen = odp_packet_len(pkt);
> +	hdr.len = hdr.caplen;
> +	(void)gettimeofday(&hdr.ts, NULL);
> +
> +	if (odp_packet_copydata_out(pkt, 0, hdr.len, pcap->buf) != 0)
> +		return -1;
> +
> +	pcap_dump(pcap->tx_dump, &hdr, pcap->buf);
> +	(void)pcap_dump_flush(pcap->tx_dump);
> +
> +	return 0;
> +}
> +
> +static int pcapif_send_pkt(pktio_entry_t *pktio_entry, odp_packet_t pkts[],
> +			   unsigned len)
> +{
> +	pkt_pcap_t *pcap = &pktio_entry->s.pkt_pcap;
> +	unsigned i;
> +
> +	ODP_ASSERT(pktio_entry->s.state == STATE_START);
> +
> +	for (i = 0; i < len; ++i) {
> +		if (odp_packet_len(pkts[i]) > PKTIO_PCAP_MTU) {
> +			if (i == 0)
> +				return -1;
> +			break;
> +		}
> +
> +		if (_pcapif_dump_pkt(pcap, pkts[i]) != 0)
> +			break;
> +
> +		odp_packet_free(pkts[i]);
> +	}
> +
> +	return i;
> +}
> +
> +static int pcapif_mtu_get(pktio_entry_t *pktio_entry ODP_UNUSED)
> +{
> +	return PKTIO_PCAP_MTU;
> +}
> +
> +static int pcapif_mac_addr_get(pktio_entry_t *pktio_entry ODP_UNUSED,
> +			       void *mac_addr)
> +{
> +	/* this isn't used for anything, it's just here to keep callers of this
> +	 * API happy in cases where the pcap interface is used as a test mock */
> +	static const char pcap_mac[] = {0x02, 0xe9, 0x34, 0x80, 0x73, 0x02};
> +
> +	memcpy(mac_addr, pcap_mac, ODPH_ETHADDR_LEN);
> +
> +	return ODPH_ETHADDR_LEN;
> +}
Isn't returning 0 the appropriate thing to do?
<0 is failure but 0 just means the mac address is NULL sized.

> +
> +static int pcapif_promisc_mode_set(pktio_entry_t *pktio_entry,
> +				   odp_bool_t enable)
> +{
> +	pktio_entry->s.pkt_pcap.promisc = enable;
> +
> +	return 0;
> +}
> +
> +static int pcapif_promisc_mode_get(pktio_entry_t *pktio_entry)
> +{
> +	return pktio_entry->s.pkt_pcap.promisc;
> +}
> +
> +static int pcapif_start(pktio_entry_t *pktio_entry)
> +{
> +	pktio_entry->s.state = STATE_START;
> +	return 0;
> +}
> +
> +static int pcapif_stop(pktio_entry_t *pktio_entry)
> +{
> +	pktio_entry->s.state = STATE_STOP;
> +	return 0;
> +}
> +
Now that 45259bf4d4b0390d8baad03e942c0f49206643e6 (linux-generic: pktio: factor state management into packet_io) made it in,
changing state inside the pktio implementation is not useful anymore. You can even drop the start/stop function.

However it's not problematic so it could be done in a later patch.

> +const pktio_if_ops_t pcap_pktio_ops = {
> +	.open = pcapif_init,
> +	.close = pcapif_close,
> +	.recv = pcapif_recv_pkt,
> +	.send = pcapif_send_pkt,
> +	.mtu_get = pcapif_mtu_get,
> +	.promisc_mode_set = pcapif_promisc_mode_set,
> +	.promisc_mode_get = pcapif_promisc_mode_get,
> +	.mac_get = pcapif_mac_addr_get,
> +	.start = pcapif_start,
> +	.stop = pcapif_stop
> +};
Stuart Haslam Sept. 10, 2015, 11:53 a.m. UTC | #2
On Tue, Sep 08, 2015 at 05:25:57PM +0200, Nicolas Morey-Chaisemartin wrote:
> 
> 
> On 09/04/2015 03:20 PM, Stuart Haslam wrote:
> > Create a new pktio type that allows for reading from and writing to a
> > pcap capture file. This is intended to be used as a simple way of
> > injecting test packets into an application for functional testing and
> > can be used as it is with some of the existing example applications.
> >
> > To use this interface the name passed to odp_pktio_open() must begin
> > with "pcap:" and be in the format;
> >
> >  pcap:in=test.pcap:out=test_out.pcap:loops=10
> >
> >    in      the name of the input pcap file. If no input file is given
> >            attempts to receive from the pktio will just return no
> >            packets.
> >    out     the name of the output pcap file. If no output file is
> >            given any packets transmitted over the interface will just
> >            be freed.
> >    loops   the number of times to iterate through the input file, set
> >            to 0 to loop indefinitely. The default value is 1.
> >
> > Signed-off-by: Stuart Haslam <stuart.haslam@linaro.org>
> > ---
> >  platform/linux-generic/Makefile.am                 |   4 +
> >  .../linux-generic/include/odp_packet_io_internal.h |  21 ++
> >  platform/linux-generic/m4/configure.m4             |  16 +
> >  platform/linux-generic/pktio/io_ops.c              |   3 +
> >  platform/linux-generic/pktio/pcap.c                | 334 +++++++++++++++++++++
> >  5 files changed, 378 insertions(+)
> >  create mode 100644 platform/linux-generic/pktio/pcap.c
> >
> > diff --git a/platform/linux-generic/Makefile.am b/platform/linux-generic/Makefile.am
> > index 4c79730..8d14477 100644
> > --- a/platform/linux-generic/Makefile.am
> > +++ b/platform/linux-generic/Makefile.am
> > @@ -173,3 +173,7 @@ EXTRA_DIST = \
> >  	     arch/linux/odp_time_cycles.c \
> >  	     arch/mips64/odp_time_cycles.c \
> >  	     arch/x86/odp_time_cycles.c
> > +
> > +if HAVE_PCAP
> > +__LIB__libodp_la_SOURCES += pktio/pcap.c
> > +endif
> > diff --git a/platform/linux-generic/include/odp_packet_io_internal.h b/platform/linux-generic/include/odp_packet_io_internal.h
> > index bdffc15..7faa4f0 100644
> > --- a/platform/linux-generic/include/odp_packet_io_internal.h
> > +++ b/platform/linux-generic/include/odp_packet_io_internal.h
> > @@ -38,6 +38,21 @@ typedef struct {
> >  	odp_bool_t promisc;		/**< promiscuous mode state */
> >  } pkt_loop_t;
> >  
> > +#ifdef HAVE_PCAP
> > +typedef struct {
> > +	char *fname_rx;		/**< name of pcap file for rx */
> > +	char *fname_tx;		/**< name of pcap file for tx */
> > +	void *rx;		/**< rx pcap handle */
> > +	void *tx;		/**< tx pcap handle */
> > +	void *tx_dump;		/**< tx pcap dumper handle */
> > +	odp_pool_t pool;	/**< rx pool */
> > +	unsigned char *buf;	/**< per-pktio temp buffer */
> > +	odp_bool_t promisc;	/**< promiscuous mode requested state */
> > +	int loops;		/**< number of times to loop rx pcap */
> > +	int loop_cnt;		/**< number of loops completed */
> > +} pkt_pcap_t;
> > +#endif
> > +
> >  struct pktio_entry {
> >  	const struct pktio_if_ops *ops; /**< Implementation specific methods */
> >  	odp_spinlock_t lock;		/**< entry spinlock */
> > @@ -51,6 +66,9 @@ struct pktio_entry {
> >  		pkt_sock_t pkt_sock;		/**< using socket API for IO */
> >  		pkt_sock_mmap_t pkt_sock_mmap;	/**< using socket mmap
> >  						 *   API for IO */
> > +#ifdef HAVE_PCAP
> > +		pkt_pcap_t pkt_pcap;		/**< Using pcap for IO */
> > +#endif
> >  	};
> >  	enum {
> >  		STATE_START = 0,
> > @@ -116,6 +134,9 @@ int pktin_poll(pktio_entry_t *entry);
> >  extern const pktio_if_ops_t sock_mmsg_pktio_ops;
> >  extern const pktio_if_ops_t sock_mmap_pktio_ops;
> >  extern const pktio_if_ops_t loopback_pktio_ops;
> > +#ifdef HAVE_PCAP
> > +extern const pktio_if_ops_t pcap_pktio_ops;
> > +#endif
> >  extern const pktio_if_ops_t * const pktio_if_ops[];
> >  
> >  #ifdef __cplusplus
> > diff --git a/platform/linux-generic/m4/configure.m4 b/platform/linux-generic/m4/configure.m4
> > index 9658274..4e4fa00 100644
> > --- a/platform/linux-generic/m4/configure.m4
> > +++ b/platform/linux-generic/m4/configure.m4
> > @@ -22,3 +22,19 @@ m4_include([platform/linux-generic/m4/odp_openssl.m4])
> >  AC_CONFIG_FILES([platform/linux-generic/Makefile
> >  		 platform/linux-generic/test/Makefile
> >  		 platform/linux-generic/test/pktio/Makefile])
> > +
> > +#########################################################################
> > +# Check for libpcap availability
> > +#########################################################################
> > +have_pcap=no
> > +AC_CHECK_HEADER(pcap/pcap.h,
> > +    [AC_CHECK_HEADER(pcap/bpf.h,
> > +        [AC_CHECK_LIB(pcap, pcap_open_offline, have_pcap=yes, [])],
> > +    [])],
> > +[])
> > +
> > +AM_CONDITIONAL([HAVE_PCAP], [test $have_pcap = yes])
> > +if test $have_pcap == yes; then
> > +    AM_CFLAGS="$AM_CFLAGS -DHAVE_PCAP"
> > +    LIBS="$LIBS -lpcap"
> > +fi
> > diff --git a/platform/linux-generic/pktio/io_ops.c b/platform/linux-generic/pktio/io_ops.c
> > index 1d47e74..bd53a5c 100644
> > --- a/platform/linux-generic/pktio/io_ops.c
> > +++ b/platform/linux-generic/pktio/io_ops.c
> > @@ -12,6 +12,9 @@
> >   * Array must be NULL terminated */
> >  const pktio_if_ops_t * const pktio_if_ops[]  = {
> >  	&loopback_pktio_ops,
> > +#ifdef HAVE_PCAP
> > +	&pcap_pktio_ops,
> > +#endif
> >  	&sock_mmap_pktio_ops,
> >  	&sock_mmsg_pktio_ops,
> >  	NULL
> > diff --git a/platform/linux-generic/pktio/pcap.c b/platform/linux-generic/pktio/pcap.c
> > new file mode 100644
> > index 0000000..d4173f9
> > --- /dev/null
> > +++ b/platform/linux-generic/pktio/pcap.c
> > @@ -0,0 +1,334 @@
> > +/* Copyright (c) 2015, Linaro Limited
> > + * All rights reserved.
> > + *
> > + * SPDX-License-Identifier:     BSD-3-Clause
> > + */
> > +
> > +/**
> > + * @file
> > + *
> > + * PCAP pktio type
> > + *
> > + * This file provides a pktio interface that allows for reading from
> > + * and writing to pcap capture files. It is intended to be used as
> > + * simple way of injecting test packets into an application for the
> > + * purpose of functional testing.
> > + *
> > + * To use this interface the name passed to odp_pktio_open() must begin
> > + * with "pcap:" and be in the format;
> > + *
> > + * pcap:in=test.pcap:out=test_out.pcap:loops=10
> > + *
> > + *   in      the name of the input pcap file. If no input file is given
> > + *           attempts to receive from the pktio will just return no
> > + *           packets. If an input file is specified it must exist and be
> > + *           a readable pcap file with a link type of DLT_EN10MB.
> > + *   out     the name of the output pcap file. If no output file is
> > + *           given any packets transmitted over the interface will just
> > + *           be freed. If an output file is specified and the file
> > + *           doesn't exist it will be created, if it does exist it will
> > + *           be overwritten.
> > + *   loops   the number of times to iterate through the input file, set
> > + *           to 0 to loop indefinitely. The default value is 1.
> > + *
> > + * The total length of the string is limited by PKTIO_NAME_LEN.
> > + */
> > +
> > +#ifndef _GNU_SOURCE
> > +#define _GNU_SOURCE
> > +#endif
> > +
> > +#include <odp.h>
> > +#include <odp_packet_internal.h>
> > +#include <odp_packet_io_internal.h>
> > +
> > +#include <odp/helper/eth.h>
> > +
> > +#include <pcap/pcap.h>
> > +#include <pcap/bpf.h>
> > +
> > +#define PKTIO_PCAP_MTU (64 * 1024)
> > +
> > +static int _pcapif_parse_devname(pkt_pcap_t *pcap, const char *devname)
> > +{
> > +	char *tok;
> > +	char in[PKTIO_NAME_LEN];
> > +
> > +	if (strncmp(devname, "pcap:", 5) != 0)
> > +		return -1;
> > +
> > +	snprintf(in, sizeof(in), "%s", devname);
> > +
> > +	for (tok = strtok(in + 5, ":"); tok; tok = strtok(NULL, ":")) {
> > +		if (strncmp(tok, "in=", 3) == 0 && !pcap->fname_rx) {
> > +			tok += 3;
> > +			pcap->fname_rx = strdup(tok);
> > +		} else if (strncmp(tok, "out=", 4) == 0 && !pcap->fname_tx) {
> > +			tok += 4;
> > +			pcap->fname_tx = strdup(tok);
> > +		} else if (strncmp(tok, "loops=", 6) == 0) {
> > +			pcap->loops = atoi(tok + 6);
> > +			if (pcap->loops < 0) {
> > +				ODP_ERR("invalid loop count\n");
> > +				return -1;
> > +			}
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int _pcapif_init_rx(pkt_pcap_t *pcap)
> > +{
> > +	char errbuf[PCAP_ERRBUF_SIZE];
> > +	int linktype;
> > +
> > +	pcap->rx = pcap_open_offline(pcap->fname_rx, errbuf);
> > +	if (!pcap->rx) {
> > +		ODP_ERR("failed to open pcap file %s (%s)\n",
> > +			pcap->fname_rx, errbuf);
> > +		return -1;
> > +	}
> > +
> > +	linktype = pcap_datalink(pcap->rx);
> > +	if (linktype != DLT_EN10MB) {
> > +		ODP_ERR("unsupported datalink type: %d\n", linktype);
> > +		return -1;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int _pcapif_init_tx(pkt_pcap_t *pcap)
> > +{
> > +	pcap_t *tx = pcap->rx;
> > +
> > +	if (!tx) {
> > +		/* if there is no rx pcap_t already open for rx, a dummy
> > +		 * one needs to be opened for writing the dump */
> > +		tx = pcap_open_dead(DLT_EN10MB, PKTIO_PCAP_MTU);
> > +		if (!tx) {
> > +			ODP_ERR("failed to open TX dump\n");
> > +			return -1;
> > +		}
> > +
> > +		pcap->tx = tx;
> > +	}
> > +
> > +	pcap->buf = malloc(PKTIO_PCAP_MTU);
> > +	if (!pcap->buf) {
> > +		ODP_ERR("failed to malloc temp buffer\n");
> > +		return -1;
> > +	}
> > +
> > +	pcap->tx_dump = pcap_dump_open(tx, pcap->fname_tx);
> > +	if (!pcap->tx_dump) {
> > +		ODP_ERR("failed to open dump file %s (%s)\n",
> > +			pcap->fname_tx, pcap_geterr(tx));
> > +		return -1;
> > +	}
> > +
> > +	return pcap_dump_flush(pcap->tx_dump);
> > +}
> > +
> > +static int pcapif_init(odp_pktio_t id ODP_UNUSED, pktio_entry_t *pktio_entry,
> > +		       const char *devname, odp_pool_t pool)
> > +{
> > +	pkt_pcap_t *pcap = &pktio_entry->s.pkt_pcap;
> > +	int ret;
> > +
> > +	memset(pcap, 0, sizeof(pkt_pcap_t));
> > +	pcap->loop_cnt = 1;
> > +	pcap->loops = 1;
> > +	pcap->pool = pool;
> > +
> > +	ret = _pcapif_parse_devname(pcap, devname);
> > +
> > +	if (ret == 0 && pcap->fname_rx)
> > +		ret = _pcapif_init_rx(pcap);
> > +
> > +	if (ret == 0 && pcap->fname_tx)
> > +		ret = _pcapif_init_tx(pcap);
> > +
> > +	if (ret == 0 && (!pcap->rx && !pcap->tx_dump))
> > +		ret = -1;
> > +
> > +	return ret;
> > +}
> > +
> > +static int pcapif_close(pktio_entry_t *pktio_entry)
> > +{
> > +	pkt_pcap_t *pcap = &pktio_entry->s.pkt_pcap;
> > +
> > +	if (pcap->tx_dump)
> > +		pcap_dump_close(pcap->tx_dump);
> > +
> > +	if (pcap->tx)
> > +		pcap_close(pcap->tx);
> > +
> > +	if (pcap->rx)
> > +		pcap_close(pcap->rx);
> > +
> > +	free(pcap->buf);
> > +	free(pcap->fname_rx);
> > +	free(pcap->fname_tx);
> > +
> > +	return 0;
> > +}
> > +
> > +static int _pcapif_reopen(pkt_pcap_t *pcap)
> > +{
> > +	char errbuf[PCAP_ERRBUF_SIZE];
> > +
> > +	if (pcap->loops != 0 && ++pcap->loop_cnt >= pcap->loops)
> > +		return 1;
> > +
> > +	if (pcap->rx)
> > +		pcap_close(pcap->rx);
> > +
> > +	pcap->rx = pcap_open_offline(pcap->fname_rx, errbuf);
> > +	if (!pcap->rx) {
> > +		ODP_ERR("failed to reopen pcap file %s (%s)\n",
> > +			pcap->fname_rx, errbuf);
> > +		return 1;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int pcapif_recv_pkt(pktio_entry_t *pktio_entry, odp_packet_t pkts[],
> > +			   unsigned len)
> > +{
> > +	unsigned i;
> > +	struct pcap_pkthdr *hdr;
> > +	const u_char *data;
> > +	pkt_pcap_t *pcap = &pktio_entry->s.pkt_pcap;
> > +
> > +	ODP_ASSERT(pktio_entry->s.state == STATE_START);
> > +
> > +	if (!pcap->rx)
> > +		return 0;
> > +
> > +	for (i = 0; i < len; ) {
> > +		int ret = pcap_next_ex(pcap->rx, &hdr, &data);
> > +
> > +		/* end of file, attempt to reopen if within loop limit */
> > +		if (ret == -2 && _pcapif_reopen(pcap) == 0)
> > +			continue;
> > +
> > +		if (ret != 1)
> > +			break;
> > +
> > +		pkts[i] = odp_packet_alloc(pcap->pool, hdr->caplen);
> > +		if (odp_unlikely(pkts[i] == ODP_PACKET_INVALID))
> > +			break;
> > +
> > +		if (odp_packet_copydata_in(pkts[i], 0, hdr->caplen, data) != 0)
> > +			break;
> > +
> > +		_odp_packet_reset_parse(pkts[i]);
> > +
> > +		i++;
> > +	}
> > +
> > +	return i;
> > +}
> > +
> > +static int _pcapif_dump_pkt(pkt_pcap_t *pcap, odp_packet_t pkt)
> > +{
> > +	struct pcap_pkthdr hdr;
> > +
> > +	if (!pcap->tx_dump)
> > +		return 0;
> > +
> > +	hdr.caplen = odp_packet_len(pkt);
> > +	hdr.len = hdr.caplen;
> > +	(void)gettimeofday(&hdr.ts, NULL);
> > +
> > +	if (odp_packet_copydata_out(pkt, 0, hdr.len, pcap->buf) != 0)
> > +		return -1;
> > +
> > +	pcap_dump(pcap->tx_dump, &hdr, pcap->buf);
> > +	(void)pcap_dump_flush(pcap->tx_dump);
> > +
> > +	return 0;
> > +}
> > +
> > +static int pcapif_send_pkt(pktio_entry_t *pktio_entry, odp_packet_t pkts[],
> > +			   unsigned len)
> > +{
> > +	pkt_pcap_t *pcap = &pktio_entry->s.pkt_pcap;
> > +	unsigned i;
> > +
> > +	ODP_ASSERT(pktio_entry->s.state == STATE_START);
> > +
> > +	for (i = 0; i < len; ++i) {
> > +		if (odp_packet_len(pkts[i]) > PKTIO_PCAP_MTU) {
> > +			if (i == 0)
> > +				return -1;
> > +			break;
> > +		}
> > +
> > +		if (_pcapif_dump_pkt(pcap, pkts[i]) != 0)
> > +			break;
> > +
> > +		odp_packet_free(pkts[i]);
> > +	}
> > +
> > +	return i;
> > +}
> > +
> > +static int pcapif_mtu_get(pktio_entry_t *pktio_entry ODP_UNUSED)
> > +{
> > +	return PKTIO_PCAP_MTU;
> > +}
> > +
> > +static int pcapif_mac_addr_get(pktio_entry_t *pktio_entry ODP_UNUSED,
> > +			       void *mac_addr)
> > +{
> > +	/* this isn't used for anything, it's just here to keep callers of this
> > +	 * API happy in cases where the pcap interface is used as a test mock */
> > +	static const char pcap_mac[] = {0x02, 0xe9, 0x34, 0x80, 0x73, 0x02};
> > +
> > +	memcpy(mac_addr, pcap_mac, ODPH_ETHADDR_LEN);
> > +
> > +	return ODPH_ETHADDR_LEN;
> > +}
> Isn't returning 0 the appropriate thing to do?
> <0 is failure but 0 just means the mac address is NULL sized.
> 

That sounds reasonable to me, it would also imply that promiscuous mode
can't be disabled. Both of these require changes in the validation test
to prevent them being treated as failures.

> > +
> > +static int pcapif_promisc_mode_set(pktio_entry_t *pktio_entry,
> > +				   odp_bool_t enable)
> > +{
> > +	pktio_entry->s.pkt_pcap.promisc = enable;
> > +
> > +	return 0;
> > +}
> > +
> > +static int pcapif_promisc_mode_get(pktio_entry_t *pktio_entry)
> > +{
> > +	return pktio_entry->s.pkt_pcap.promisc;
> > +}
> > +
> > +static int pcapif_start(pktio_entry_t *pktio_entry)
> > +{
> > +	pktio_entry->s.state = STATE_START;
> > +	return 0;
> > +}
> > +
> > +static int pcapif_stop(pktio_entry_t *pktio_entry)
> > +{
> > +	pktio_entry->s.state = STATE_STOP;
> > +	return 0;
> > +}
> > +
> Now that 45259bf4d4b0390d8baad03e942c0f49206643e6 (linux-generic: pktio: factor state management into packet_io) made it in,
> changing state inside the pktio implementation is not useful anymore. You can even drop the start/stop function.
> 
> However it's not problematic so it could be done in a later patch.
> 

I'm sending a v2 anyway so I'll include those changes.
Maxim Uvarov Sept. 10, 2015, 3:52 p.m. UTC | #3
On 09/10/15 14:53, Stuart Haslam wrote:
> +#########################################################################
> > >+# Check for libpcap availability
> > >+#########################################################################
> > >+have_pcap=no
> > >+AC_CHECK_HEADER(pcap/pcap.h,
> > >+    [AC_CHECK_HEADER(pcap/bpf.h,
> > >+        [AC_CHECK_LIB(pcap, pcap_open_offline, have_pcap=yes, [])],
> > >+    [])],
> > >+[])
> > >+
> > >+AM_CONDITIONAL([HAVE_PCAP], [test $have_pcap = yes])
> > >+if test $have_pcap == yes; then
> > >+    AM_CFLAGS="$AM_CFLAGS -DHAVE_PCAP"
> > >+    LIBS="$LIBS -lpcap"
> > >+fi
please move that to pcap.m4 as all others checks.

Maxim.
Stuart Haslam Sept. 10, 2015, 4:15 p.m. UTC | #4
On Thu, Sep 10, 2015 at 06:52:44PM +0300, Maxim Uvarov wrote:
> On 09/10/15 14:53, Stuart Haslam wrote:
> >+#########################################################################
> >> >+# Check for libpcap availability
> >> >+#########################################################################
> >> >+have_pcap=no
> >> >+AC_CHECK_HEADER(pcap/pcap.h,
> >> >+    [AC_CHECK_HEADER(pcap/bpf.h,
> >> >+        [AC_CHECK_LIB(pcap, pcap_open_offline, have_pcap=yes, [])],
> >> >+    [])],
> >> >+[])
> >> >+
> >> >+AM_CONDITIONAL([HAVE_PCAP], [test $have_pcap = yes])
> >> >+if test $have_pcap == yes; then
> >> >+    AM_CFLAGS="$AM_CFLAGS -DHAVE_PCAP"
> >> >+    LIBS="$LIBS -lpcap"
> >> >+fi
> please move that to pcap.m4 as all others checks.
> 
> Maxim.

OK - the others didn't exist when I initially made this change.
diff mbox

Patch

diff --git a/platform/linux-generic/Makefile.am b/platform/linux-generic/Makefile.am
index 4c79730..8d14477 100644
--- a/platform/linux-generic/Makefile.am
+++ b/platform/linux-generic/Makefile.am
@@ -173,3 +173,7 @@  EXTRA_DIST = \
 	     arch/linux/odp_time_cycles.c \
 	     arch/mips64/odp_time_cycles.c \
 	     arch/x86/odp_time_cycles.c
+
+if HAVE_PCAP
+__LIB__libodp_la_SOURCES += pktio/pcap.c
+endif
diff --git a/platform/linux-generic/include/odp_packet_io_internal.h b/platform/linux-generic/include/odp_packet_io_internal.h
index bdffc15..7faa4f0 100644
--- a/platform/linux-generic/include/odp_packet_io_internal.h
+++ b/platform/linux-generic/include/odp_packet_io_internal.h
@@ -38,6 +38,21 @@  typedef struct {
 	odp_bool_t promisc;		/**< promiscuous mode state */
 } pkt_loop_t;
 
+#ifdef HAVE_PCAP
+typedef struct {
+	char *fname_rx;		/**< name of pcap file for rx */
+	char *fname_tx;		/**< name of pcap file for tx */
+	void *rx;		/**< rx pcap handle */
+	void *tx;		/**< tx pcap handle */
+	void *tx_dump;		/**< tx pcap dumper handle */
+	odp_pool_t pool;	/**< rx pool */
+	unsigned char *buf;	/**< per-pktio temp buffer */
+	odp_bool_t promisc;	/**< promiscuous mode requested state */
+	int loops;		/**< number of times to loop rx pcap */
+	int loop_cnt;		/**< number of loops completed */
+} pkt_pcap_t;
+#endif
+
 struct pktio_entry {
 	const struct pktio_if_ops *ops; /**< Implementation specific methods */
 	odp_spinlock_t lock;		/**< entry spinlock */
@@ -51,6 +66,9 @@  struct pktio_entry {
 		pkt_sock_t pkt_sock;		/**< using socket API for IO */
 		pkt_sock_mmap_t pkt_sock_mmap;	/**< using socket mmap
 						 *   API for IO */
+#ifdef HAVE_PCAP
+		pkt_pcap_t pkt_pcap;		/**< Using pcap for IO */
+#endif
 	};
 	enum {
 		STATE_START = 0,
@@ -116,6 +134,9 @@  int pktin_poll(pktio_entry_t *entry);
 extern const pktio_if_ops_t sock_mmsg_pktio_ops;
 extern const pktio_if_ops_t sock_mmap_pktio_ops;
 extern const pktio_if_ops_t loopback_pktio_ops;
+#ifdef HAVE_PCAP
+extern const pktio_if_ops_t pcap_pktio_ops;
+#endif
 extern const pktio_if_ops_t * const pktio_if_ops[];
 
 #ifdef __cplusplus
diff --git a/platform/linux-generic/m4/configure.m4 b/platform/linux-generic/m4/configure.m4
index 9658274..4e4fa00 100644
--- a/platform/linux-generic/m4/configure.m4
+++ b/platform/linux-generic/m4/configure.m4
@@ -22,3 +22,19 @@  m4_include([platform/linux-generic/m4/odp_openssl.m4])
 AC_CONFIG_FILES([platform/linux-generic/Makefile
 		 platform/linux-generic/test/Makefile
 		 platform/linux-generic/test/pktio/Makefile])
+
+#########################################################################
+# Check for libpcap availability
+#########################################################################
+have_pcap=no
+AC_CHECK_HEADER(pcap/pcap.h,
+    [AC_CHECK_HEADER(pcap/bpf.h,
+        [AC_CHECK_LIB(pcap, pcap_open_offline, have_pcap=yes, [])],
+    [])],
+[])
+
+AM_CONDITIONAL([HAVE_PCAP], [test $have_pcap = yes])
+if test $have_pcap == yes; then
+    AM_CFLAGS="$AM_CFLAGS -DHAVE_PCAP"
+    LIBS="$LIBS -lpcap"
+fi
diff --git a/platform/linux-generic/pktio/io_ops.c b/platform/linux-generic/pktio/io_ops.c
index 1d47e74..bd53a5c 100644
--- a/platform/linux-generic/pktio/io_ops.c
+++ b/platform/linux-generic/pktio/io_ops.c
@@ -12,6 +12,9 @@ 
  * Array must be NULL terminated */
 const pktio_if_ops_t * const pktio_if_ops[]  = {
 	&loopback_pktio_ops,
+#ifdef HAVE_PCAP
+	&pcap_pktio_ops,
+#endif
 	&sock_mmap_pktio_ops,
 	&sock_mmsg_pktio_ops,
 	NULL
diff --git a/platform/linux-generic/pktio/pcap.c b/platform/linux-generic/pktio/pcap.c
new file mode 100644
index 0000000..d4173f9
--- /dev/null
+++ b/platform/linux-generic/pktio/pcap.c
@@ -0,0 +1,334 @@ 
+/* Copyright (c) 2015, Linaro Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ */
+
+/**
+ * @file
+ *
+ * PCAP pktio type
+ *
+ * This file provides a pktio interface that allows for reading from
+ * and writing to pcap capture files. It is intended to be used as
+ * simple way of injecting test packets into an application for the
+ * purpose of functional testing.
+ *
+ * To use this interface the name passed to odp_pktio_open() must begin
+ * with "pcap:" and be in the format;
+ *
+ * pcap:in=test.pcap:out=test_out.pcap:loops=10
+ *
+ *   in      the name of the input pcap file. If no input file is given
+ *           attempts to receive from the pktio will just return no
+ *           packets. If an input file is specified it must exist and be
+ *           a readable pcap file with a link type of DLT_EN10MB.
+ *   out     the name of the output pcap file. If no output file is
+ *           given any packets transmitted over the interface will just
+ *           be freed. If an output file is specified and the file
+ *           doesn't exist it will be created, if it does exist it will
+ *           be overwritten.
+ *   loops   the number of times to iterate through the input file, set
+ *           to 0 to loop indefinitely. The default value is 1.
+ *
+ * The total length of the string is limited by PKTIO_NAME_LEN.
+ */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <odp.h>
+#include <odp_packet_internal.h>
+#include <odp_packet_io_internal.h>
+
+#include <odp/helper/eth.h>
+
+#include <pcap/pcap.h>
+#include <pcap/bpf.h>
+
+#define PKTIO_PCAP_MTU (64 * 1024)
+
+static int _pcapif_parse_devname(pkt_pcap_t *pcap, const char *devname)
+{
+	char *tok;
+	char in[PKTIO_NAME_LEN];
+
+	if (strncmp(devname, "pcap:", 5) != 0)
+		return -1;
+
+	snprintf(in, sizeof(in), "%s", devname);
+
+	for (tok = strtok(in + 5, ":"); tok; tok = strtok(NULL, ":")) {
+		if (strncmp(tok, "in=", 3) == 0 && !pcap->fname_rx) {
+			tok += 3;
+			pcap->fname_rx = strdup(tok);
+		} else if (strncmp(tok, "out=", 4) == 0 && !pcap->fname_tx) {
+			tok += 4;
+			pcap->fname_tx = strdup(tok);
+		} else if (strncmp(tok, "loops=", 6) == 0) {
+			pcap->loops = atoi(tok + 6);
+			if (pcap->loops < 0) {
+				ODP_ERR("invalid loop count\n");
+				return -1;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int _pcapif_init_rx(pkt_pcap_t *pcap)
+{
+	char errbuf[PCAP_ERRBUF_SIZE];
+	int linktype;
+
+	pcap->rx = pcap_open_offline(pcap->fname_rx, errbuf);
+	if (!pcap->rx) {
+		ODP_ERR("failed to open pcap file %s (%s)\n",
+			pcap->fname_rx, errbuf);
+		return -1;
+	}
+
+	linktype = pcap_datalink(pcap->rx);
+	if (linktype != DLT_EN10MB) {
+		ODP_ERR("unsupported datalink type: %d\n", linktype);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int _pcapif_init_tx(pkt_pcap_t *pcap)
+{
+	pcap_t *tx = pcap->rx;
+
+	if (!tx) {
+		/* if there is no rx pcap_t already open for rx, a dummy
+		 * one needs to be opened for writing the dump */
+		tx = pcap_open_dead(DLT_EN10MB, PKTIO_PCAP_MTU);
+		if (!tx) {
+			ODP_ERR("failed to open TX dump\n");
+			return -1;
+		}
+
+		pcap->tx = tx;
+	}
+
+	pcap->buf = malloc(PKTIO_PCAP_MTU);
+	if (!pcap->buf) {
+		ODP_ERR("failed to malloc temp buffer\n");
+		return -1;
+	}
+
+	pcap->tx_dump = pcap_dump_open(tx, pcap->fname_tx);
+	if (!pcap->tx_dump) {
+		ODP_ERR("failed to open dump file %s (%s)\n",
+			pcap->fname_tx, pcap_geterr(tx));
+		return -1;
+	}
+
+	return pcap_dump_flush(pcap->tx_dump);
+}
+
+static int pcapif_init(odp_pktio_t id ODP_UNUSED, pktio_entry_t *pktio_entry,
+		       const char *devname, odp_pool_t pool)
+{
+	pkt_pcap_t *pcap = &pktio_entry->s.pkt_pcap;
+	int ret;
+
+	memset(pcap, 0, sizeof(pkt_pcap_t));
+	pcap->loop_cnt = 1;
+	pcap->loops = 1;
+	pcap->pool = pool;
+
+	ret = _pcapif_parse_devname(pcap, devname);
+
+	if (ret == 0 && pcap->fname_rx)
+		ret = _pcapif_init_rx(pcap);
+
+	if (ret == 0 && pcap->fname_tx)
+		ret = _pcapif_init_tx(pcap);
+
+	if (ret == 0 && (!pcap->rx && !pcap->tx_dump))
+		ret = -1;
+
+	return ret;
+}
+
+static int pcapif_close(pktio_entry_t *pktio_entry)
+{
+	pkt_pcap_t *pcap = &pktio_entry->s.pkt_pcap;
+
+	if (pcap->tx_dump)
+		pcap_dump_close(pcap->tx_dump);
+
+	if (pcap->tx)
+		pcap_close(pcap->tx);
+
+	if (pcap->rx)
+		pcap_close(pcap->rx);
+
+	free(pcap->buf);
+	free(pcap->fname_rx);
+	free(pcap->fname_tx);
+
+	return 0;
+}
+
+static int _pcapif_reopen(pkt_pcap_t *pcap)
+{
+	char errbuf[PCAP_ERRBUF_SIZE];
+
+	if (pcap->loops != 0 && ++pcap->loop_cnt >= pcap->loops)
+		return 1;
+
+	if (pcap->rx)
+		pcap_close(pcap->rx);
+
+	pcap->rx = pcap_open_offline(pcap->fname_rx, errbuf);
+	if (!pcap->rx) {
+		ODP_ERR("failed to reopen pcap file %s (%s)\n",
+			pcap->fname_rx, errbuf);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int pcapif_recv_pkt(pktio_entry_t *pktio_entry, odp_packet_t pkts[],
+			   unsigned len)
+{
+	unsigned i;
+	struct pcap_pkthdr *hdr;
+	const u_char *data;
+	pkt_pcap_t *pcap = &pktio_entry->s.pkt_pcap;
+
+	ODP_ASSERT(pktio_entry->s.state == STATE_START);
+
+	if (!pcap->rx)
+		return 0;
+
+	for (i = 0; i < len; ) {
+		int ret = pcap_next_ex(pcap->rx, &hdr, &data);
+
+		/* end of file, attempt to reopen if within loop limit */
+		if (ret == -2 && _pcapif_reopen(pcap) == 0)
+			continue;
+
+		if (ret != 1)
+			break;
+
+		pkts[i] = odp_packet_alloc(pcap->pool, hdr->caplen);
+		if (odp_unlikely(pkts[i] == ODP_PACKET_INVALID))
+			break;
+
+		if (odp_packet_copydata_in(pkts[i], 0, hdr->caplen, data) != 0)
+			break;
+
+		_odp_packet_reset_parse(pkts[i]);
+
+		i++;
+	}
+
+	return i;
+}
+
+static int _pcapif_dump_pkt(pkt_pcap_t *pcap, odp_packet_t pkt)
+{
+	struct pcap_pkthdr hdr;
+
+	if (!pcap->tx_dump)
+		return 0;
+
+	hdr.caplen = odp_packet_len(pkt);
+	hdr.len = hdr.caplen;
+	(void)gettimeofday(&hdr.ts, NULL);
+
+	if (odp_packet_copydata_out(pkt, 0, hdr.len, pcap->buf) != 0)
+		return -1;
+
+	pcap_dump(pcap->tx_dump, &hdr, pcap->buf);
+	(void)pcap_dump_flush(pcap->tx_dump);
+
+	return 0;
+}
+
+static int pcapif_send_pkt(pktio_entry_t *pktio_entry, odp_packet_t pkts[],
+			   unsigned len)
+{
+	pkt_pcap_t *pcap = &pktio_entry->s.pkt_pcap;
+	unsigned i;
+
+	ODP_ASSERT(pktio_entry->s.state == STATE_START);
+
+	for (i = 0; i < len; ++i) {
+		if (odp_packet_len(pkts[i]) > PKTIO_PCAP_MTU) {
+			if (i == 0)
+				return -1;
+			break;
+		}
+
+		if (_pcapif_dump_pkt(pcap, pkts[i]) != 0)
+			break;
+
+		odp_packet_free(pkts[i]);
+	}
+
+	return i;
+}
+
+static int pcapif_mtu_get(pktio_entry_t *pktio_entry ODP_UNUSED)
+{
+	return PKTIO_PCAP_MTU;
+}
+
+static int pcapif_mac_addr_get(pktio_entry_t *pktio_entry ODP_UNUSED,
+			       void *mac_addr)
+{
+	/* this isn't used for anything, it's just here to keep callers of this
+	 * API happy in cases where the pcap interface is used as a test mock */
+	static const char pcap_mac[] = {0x02, 0xe9, 0x34, 0x80, 0x73, 0x02};
+
+	memcpy(mac_addr, pcap_mac, ODPH_ETHADDR_LEN);
+
+	return ODPH_ETHADDR_LEN;
+}
+
+static int pcapif_promisc_mode_set(pktio_entry_t *pktio_entry,
+				   odp_bool_t enable)
+{
+	pktio_entry->s.pkt_pcap.promisc = enable;
+
+	return 0;
+}
+
+static int pcapif_promisc_mode_get(pktio_entry_t *pktio_entry)
+{
+	return pktio_entry->s.pkt_pcap.promisc;
+}
+
+static int pcapif_start(pktio_entry_t *pktio_entry)
+{
+	pktio_entry->s.state = STATE_START;
+	return 0;
+}
+
+static int pcapif_stop(pktio_entry_t *pktio_entry)
+{
+	pktio_entry->s.state = STATE_STOP;
+	return 0;
+}
+
+const pktio_if_ops_t pcap_pktio_ops = {
+	.open = pcapif_init,
+	.close = pcapif_close,
+	.recv = pcapif_recv_pkt,
+	.send = pcapif_send_pkt,
+	.mtu_get = pcapif_mtu_get,
+	.promisc_mode_set = pcapif_promisc_mode_set,
+	.promisc_mode_get = pcapif_promisc_mode_get,
+	.mac_get = pcapif_mac_addr_get,
+	.start = pcapif_start,
+	.stop = pcapif_stop
+};