input: add generic PCAP interface for LAPD

This patch allows you to create PCAP traces between the BSC and BTS including
the LAPD frames. Useful for debugging communication issues.

So far, it was only possible to create layer 3 traces containing
OML/RSL. LAPD traces can be also interesting to debug communication
issues between the BSC and the BTS.

To enable PCAP of LAPD, you only have to invoke:

li->pcap_fd = osmo_pcap_lapd_open("/tmp/file.pcap", 0600);

By default, li->pcap_fd is set to -1 which means disabled.

openBSC needs a patch to replace all usage of e1_set_pcap_fd by
osmo_pcap_lapd_open.
diff --git a/src/input/lapd_pcap.c b/src/input/lapd_pcap.c
new file mode 100644
index 0000000..c83bc60
--- /dev/null
+++ b/src/input/lapd_pcap.c
@@ -0,0 +1,159 @@
+/* (C) 2008-2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * Author: Harald Welte <laforge@gnumonks.org>
+ *         Pablo Neira Ayuso <pablo@gnumonks.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <errno.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/abis/lapd_pcap.h>
+
+/*
+ * pcap writing of the mlapd load
+ * pcap format is from http://wiki.wireshark.org/Development/LibpcapFileFormat
+ */
+#define DLT_LINUX_LAPD		177
+
+struct pcap_hdr {
+	uint32_t magic_number;
+	uint16_t version_major;
+	uint16_t version_minor;
+	int32_t  thiszone;
+	uint32_t sigfigs;
+	uint32_t snaplen;
+	uint32_t network;
+} __attribute__((packed));
+
+struct pcap_rechdr {
+	uint32_t ts_sec;
+	uint32_t ts_usec;
+	uint32_t incl_len;
+	uint32_t orig_len;
+} __attribute__((packed));
+
+struct pcap_lapdhdr {
+	uint16_t pkttype;
+	uint16_t hatype;
+	uint16_t halen;
+	uint64_t addr;
+	int16_t protocol;
+} __attribute__((packed));
+
+osmo_static_assert(offsetof(struct pcap_lapdhdr, hatype) == 2, hatype_offset);
+osmo_static_assert(offsetof(struct pcap_lapdhdr, halen) == 4, halen_offset);
+osmo_static_assert(offsetof(struct pcap_lapdhdr, addr) == 6, addr_offset);
+osmo_static_assert(offsetof(struct pcap_lapdhdr, protocol) == 14, proto_offset);
+osmo_static_assert(sizeof(struct pcap_lapdhdr) == 16, lapd_header_size);
+
+int osmo_pcap_lapd_open(char *filename, mode_t mode)
+{
+	int fd;
+	struct pcap_hdr pcap_header = {
+		.magic_number	= 0xa1b2c3d4,
+		.version_major	= 2,
+		.version_minor	= 4,
+		.thiszone	= 0,
+		.sigfigs	= 0,
+		.snaplen	= 65535,
+		.network	= DLT_LINUX_LAPD,
+	};
+
+	LOGP(DLLAPD, LOGL_NOTICE, "opening LAPD pcap file `%s'\n", filename);
+
+	fd = open(filename, O_WRONLY|O_TRUNC|O_CREAT, mode);
+	if (fd < 0) {
+		LOGP(DLLAPD, LOGL_ERROR, "failed to open PCAP file: %s\n",
+			strerror(errno));
+		return -1;
+	}
+	if (write(fd, &pcap_header, sizeof(pcap_header))
+					!= sizeof(pcap_header)) {
+		LOGP(DLLAPD, LOGL_ERROR, "cannot write PCAP header: %s\n",
+			strerror(errno));
+		close(fd);
+		return -1;
+	}
+	return fd;
+}
+
+/* This currently only works for the D-Channel */
+int osmo_pcap_lapd_write(int fd, int direction, struct msgb *msg)
+{
+	int numbytes = 0;
+	struct timeval tv;
+	struct pcap_rechdr pcap_rechdr;
+	struct pcap_lapdhdr header;
+	char buf[sizeof(struct pcap_rechdr) +
+		 sizeof(struct pcap_lapdhdr) + msg->len];
+
+	/* PCAP file has not been opened, skip. */
+	if (fd < 0)
+		return 0;
+
+	pcap_rechdr.ts_sec	= 0;
+	pcap_rechdr.ts_usec	= 0;
+	pcap_rechdr.incl_len   = msg->len + sizeof(struct pcap_lapdhdr);
+	pcap_rechdr.orig_len   = msg->len + sizeof(struct pcap_lapdhdr);
+
+	header.pkttype		= 4;
+	header.hatype		= 0;
+	header.halen		= 0;
+	header.addr		= direction == OSMO_LAPD_PCAP_OUTPUT ? 0x0 : 0x1;
+	header.protocol		= ntohs(48);
+
+	gettimeofday(&tv, NULL);
+	pcap_rechdr.ts_sec = tv.tv_sec;
+	pcap_rechdr.ts_usec = tv.tv_usec;
+
+	memcpy(buf + numbytes, &pcap_rechdr, sizeof(pcap_rechdr));
+	numbytes += sizeof(pcap_rechdr);
+
+	memcpy(buf + numbytes, &header, sizeof(header));
+	numbytes += sizeof(header);
+
+	memcpy(buf + numbytes, msg->data, msg->len);
+	numbytes += msg->len;
+
+	if (write(fd, buf, numbytes) != numbytes) {
+		LOGP(DLLAPD, LOGL_ERROR, "cannot write packet to PCAP: %s\n",
+			strerror(errno));
+		return -1;
+	}
+	return numbytes;
+}
+
+int osmo_pcap_lapd_close(int fd)
+{
+	LOGP(DLLAPD, LOGL_NOTICE, "closing LAPD pcap file\n");
+	return close(fd);
+}