mctp-demux-daemon: Add packet capture option

Optionally enable libpcap support in mctp-demux-daemon to capture
packets both from the Unix domain socket and binding interfaces.
Providing the two capture points allows for tracking down issues with
packets being dropped during binding initialisation.

As there's no formal linktype defined for MCTP or higher-level DMTF
protocols command-line switches provide the ability to specify one of
the private linktype values in the range 147-162.

Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Change-Id: I593d9e4be80c0198e643758f216e774169668a8c
diff --git a/Makefile.am b/Makefile.am
index ab3c80e..e91e13c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -23,7 +23,12 @@
 endif
 
 bin_PROGRAMS = utils/mctp-demux-daemon
-utils_mctp_demux_daemon_LDADD = libmctp.la
+utils_mctp_demux_daemon_SOURCES = utils/mctp-demux-daemon.c
+if HAVE_PCAP
+utils_mctp_demux_daemon_SOURCES += utils/mctp-capture.c
+endif
+utils_mctp_demux_daemon_LDADD = libmctp.la $(pcap_LIBS)
+utils_mctp_demux_daemon_CFLAGS = $(pcap_CFLAGS)
 
 pkgconfig_DATA = libmctp.pc
 
diff --git a/configure.ac b/configure.ac
index bbdc7b7..fc688f3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -28,6 +28,19 @@
 
 AC_SUBST([udevrulesdir], [$udevdir/rules.d])
 
+AC_ARG_ENABLE([capture],
+              [AC_HELP_STRING([--enable-capture],
+                              [Use libpcap to capture messages and packets])])
+AS_IF([test "x$enable_capture" = "xyes"],
+       [PKG_CHECK_MODULES(pcap, libpcap,
+                          [AC_DEFINE([HAVE_PCAP], [1],
+                                     [Define to 1 if you have libpcap])],
+                          [])],
+       [])
+AC_SUBST([pcap_CFLAGS])
+AC_SUBST([pcap_LIBS])
+AM_CONDITIONAL([HAVE_PCAP], [test "x$enable_capture" = "xyes"])
+
 AC_ARG_ENABLE([astlpc-raw-kcs],
               [AS_HELP_STRING([--enable-astlpc-raw-kcs],
                               [Use udev rules to symlink raw-kcs device nodes for the astlpc binding])])
diff --git a/utils/mctp-capture.c b/utils/mctp-capture.c
new file mode 100644
index 0000000..e1c270e
--- /dev/null
+++ b/utils/mctp-capture.c
@@ -0,0 +1,78 @@
+#include "utils/mctp-capture.h"
+
+#include <stdio.h>
+#include <sys/time.h>
+
+int capture_init(void)
+{
+	char errbuf[PCAP_ERRBUF_SIZE];
+	int rc;
+
+	if ((rc = pcap_init(PCAP_CHAR_ENC_UTF_8, errbuf)) == -1) {
+		fprintf(stderr, "pcap_init: %s\n", errbuf);
+		return -1;
+	}
+
+	return 0;
+}
+
+int capture_prepare(struct capture *cap)
+{
+	int rc;
+
+	if (cap->linktype < CAPTURE_LINKTYPE_FIRST ||
+			cap->linktype > CAPTURE_LINKTYPE_LAST) {
+		fprintf(stderr,
+			"Invalid private linktype value %d: see https://www.tcpdump.org/linktypes.html\n",
+			cap->linktype);
+		return -1;
+	}
+
+	if (!(cap->pcap = pcap_open_dead(cap->linktype, UINT16_MAX))) {
+		fprintf(stderr, "pcap_open_dead: failed\n");
+		return -1;
+	}
+
+	if (!(cap->dumper = pcap_dump_open(cap->pcap, cap->path))) {
+		fprintf(stderr, "pcap_dump_open: failed\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+void capture_close(struct capture *cap)
+{
+	pcap_dump_close(cap->dumper);
+
+	pcap_close(cap->pcap);
+}
+
+void capture_binding(struct mctp_pktbuf *pkt, void *user)
+{
+	pcap_dumper_t *dumper = user;
+	struct pcap_pkthdr hdr;
+	int rc;
+
+	if ((rc = gettimeofday(&hdr.ts, NULL)) == -1)
+		return;
+
+	hdr.caplen = mctp_pktbuf_size(pkt);
+	hdr.len = mctp_pktbuf_size(pkt);
+
+	pcap_dump((u_char *)dumper, &hdr, (const u_char *)mctp_pktbuf_hdr(pkt));
+}
+
+void capture_socket(pcap_dumper_t *dumper, const void *buf, size_t len)
+{
+	struct pcap_pkthdr hdr;
+	int rc;
+
+	if ((rc = gettimeofday(&hdr.ts, NULL)) == -1)
+		return;
+
+	hdr.caplen = len;
+	hdr.len = len;
+
+	pcap_dump((u_char *)dumper, &hdr, buf);
+}
diff --git a/utils/mctp-capture.h b/utils/mctp-capture.h
new file mode 100644
index 0000000..90feb66
--- /dev/null
+++ b/utils/mctp-capture.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+
+#ifndef _UTILS_MCTP_CAPTURE_H
+#define _UTILS_MCTP_CAPTURE_H
+
+#include "config.h"
+
+#include "libmctp.h"
+
+#include <sys/types.h>
+
+#if HAVE_PCAP
+#include <pcap/pcap.h>
+#else
+typedef void pcap_t;
+typedef void pcap_dumper_t;
+#endif
+
+#define CAPTURE_LINKTYPE_FIRST	147
+#define CAPTURE_LINKTYPE_LAST	162
+
+#define __unused __attribute__((unused))
+
+struct capture {
+	const char	*path;
+	int		linktype;
+	pcap_t		*pcap;
+	pcap_dumper_t	*dumper;
+};
+
+#if HAVE_PCAP
+int capture_init(void);
+int capture_prepare(struct capture *cap);
+void capture_close(struct capture *cap);
+void capture_binding(struct mctp_pktbuf *pkt, void *user);
+void capture_socket(pcap_dumper_t *dumper, const void *buf, size_t len);
+#else
+#include <stdio.h>
+static inline int
+capture_init(void)
+{
+	fprintf(stderr, "libpcap support is disabled, cannot initialise libpcap\n");
+	return 0;
+}
+
+static inline int
+capture_prepare(struct capture *cap)
+{
+	fprintf(stderr, "libpcap support is disabled, cannot capture to %s\n",
+		cap->path);
+	return 0;
+}
+
+static inline void capture_close(struct capture *cap __unused)
+{
+}
+
+static inline void
+capture_binding(struct mctp_pktbuf *pkt __unused, void *user __unused)
+{
+}
+
+static inline void capture_socket(pcap_dumper_t *dumper __unused,
+				  const void *buf __unused,
+				  size_t len __unused)
+{
+}
+#endif
+#endif
diff --git a/utils/mctp-demux-daemon.c b/utils/mctp-demux-daemon.c
index bbf7796..dd719de 100644
--- a/utils/mctp-demux-daemon.c
+++ b/utils/mctp-demux-daemon.c
@@ -3,6 +3,7 @@
 #define _GNU_SOURCE
 
 #include "config.h"
+#include "utils/mctp-capture.h"
 
 #include <assert.h>
 #include <err.h>
@@ -71,6 +72,11 @@
 
 	struct client	*clients;
 	int		n_clients;
+
+	struct {
+		struct capture binding;
+		struct capture socket;
+	} pcap;
 };
 
 static void tx_message(struct ctx *ctx, mctp_eid_t eid, void *msg, size_t len)
@@ -380,6 +386,9 @@
 		goto out_close;
 	}
 
+	if (ctx->pcap.socket.path)
+		capture_socket(ctx->pcap.socket.dumper, ctx->buf, rc);
+
 	eid = *(uint8_t *)ctx->buf;
 
 	if (ctx->verbose)
@@ -537,6 +546,10 @@
 }
 
 static const struct option options[] = {
+	{ "capture-binding", required_argument, 0, 'b' },
+	{ "capture-socket", required_argument, 0, 's' },
+	{ "binding-linktype", required_argument, 0, 'B' },
+	{ "socket-linktype", required_argument, 0, 'S' },
 	{ "verbose", no_argument, 0, 'v' },
 	{ "eid", required_argument, 0, 'e' },
 	{ 0 },
@@ -562,12 +575,26 @@
 	ctx->n_clients = 0;
 	ctx->local_eid = local_eid_default;
 	ctx->verbose = false;
+	ctx->pcap.binding.path = NULL;
+	ctx->pcap.socket.path = NULL;
 
 	for (;;) {
-		rc = getopt_long(argc, argv, "e:v", options, NULL);
+		rc = getopt_long(argc, argv, "b:es::v", options, NULL);
 		if (rc == -1)
 			break;
 		switch (rc) {
+		case 'b':
+			ctx->pcap.binding.path = optarg;
+			break;
+		case 's':
+			ctx->pcap.socket.path = optarg;
+			break;
+		case 'B':
+			ctx->pcap.binding.linktype = atoi(optarg);
+			break;
+		case 'S':
+			ctx->pcap.socket.linktype = atoi(optarg);
+			break;
 		case 'v':
 			ctx->verbose = true;
 			break;
@@ -595,21 +622,65 @@
 	ctx->mctp = mctp_init();
 	assert(ctx->mctp);
 
+	if (ctx->pcap.binding.path || ctx->pcap.socket.path) {
+		if (capture_init()) {
+			rc = EXIT_FAILURE;
+			goto cleanup_mctp;
+		}
+	}
+
+	if (ctx->pcap.binding.path) {
+		rc = capture_prepare(&ctx->pcap.binding);
+		if (rc == -1) {
+			fprintf(stderr, "Failed to initialise capture: %d\n", rc);
+			rc = EXIT_FAILURE;
+			goto cleanup_mctp;
+		}
+
+		mctp_set_capture_handler(ctx->mctp, capture_binding,
+					 ctx->pcap.binding.dumper);
+	}
+
+	if (ctx->pcap.socket.path) {
+		rc = capture_prepare(&ctx->pcap.socket);
+		if (rc == -1) {
+			fprintf(stderr, "Failed to initialise capture: %d\n", rc);
+			rc = EXIT_FAILURE;
+			goto cleanup_pcap_binding;
+		}
+	}
+
 	rc = binding_init(ctx, argv[optind], argc - optind - 1, argv + optind + 1);
-	if (rc)
-		return EXIT_FAILURE;
+	if (rc) {
+		fprintf(stderr, "Failed to initialise binding: %d\n", rc);
+		rc = EXIT_FAILURE;
+		goto cleanup_pcap_socket;
+	}
 
 	rc = sd_listen_fds(true);
 	if (rc <= 0) {
 		rc = socket_init(ctx);
-		if (rc)
+		if (rc) {
+			fprintf(stderr, "Failed to initialse socket: %d\n", rc);
 			return EXIT_FAILURE;
+		}
 	} else {
 		ctx->sock = SD_LISTEN_FDS_START;
 	}
 
 	rc = run_daemon(ctx);
 
-	return rc ? EXIT_FAILURE : EXIT_SUCCESS;
+cleanup_pcap_socket:
+	if (ctx->pcap.socket.path)
+		capture_close(&ctx->pcap.socket);
+
+cleanup_pcap_binding:
+	if (ctx->pcap.binding.path)
+		capture_close(&ctx->pcap.binding);
+
+	rc = rc ? EXIT_FAILURE : EXIT_SUCCESS;
+cleanup_mctp:
+
+	return rc;
 
 }