gsmtap: rework GSMTAP API to be more future-proof

* use write_queue where applicable
* provide functions that work on raw FD and those with osmo_fd
* add support for multiple gsmtap instances (no global variables)
diff --git a/include/osmocom/core/Makefile.am b/include/osmocom/core/Makefile.am
index 3698873..3c30362 100644
--- a/include/osmocom/core/Makefile.am
+++ b/include/osmocom/core/Makefile.am
@@ -1,5 +1,5 @@
 osmocore_HEADERS = signal.h linuxlist.h timer.h select.h msgb.h bits.h \
-		   bitvec.h statistics.h utils.h \
+		   bitvec.h statistics.h utils.h socket.h \
 		   gsmtap.h write_queue.h \
 		   logging.h rate_ctr.h gsmtap_util.h \
 		   plugin.h crc16.h panic.h process.h msgfile.h \
diff --git a/include/osmocom/core/gsmtap_util.h b/include/osmocom/core/gsmtap_util.h
index 785f5e5..f553c17 100644
--- a/include/osmocom/core/gsmtap_util.h
+++ b/include/osmocom/core/gsmtap_util.h
@@ -2,24 +2,52 @@
 #define _GSMTAP_UTIL_H
 
 #include <stdint.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/select.h>
 
 /* convert RSL channel number to GSMTAP channel type */
 uint8_t chantype_rsl2gsmtap(uint8_t rsl_chantype, uint8_t rsl_link_id);
 
-/* receive a message from L1/L2 and put it in GSMTAP */
+/* generate msgb from data + metadata */
 struct msgb *gsmtap_makemsg(uint16_t arfcn, uint8_t ts, uint8_t chan_type,
 			    uint8_t ss, uint32_t fn, int8_t signal_dbm,
 			    uint8_t snr, const uint8_t *data, unsigned int len);
 
-/* receive a message from L1/L2 and put it in GSMTAP */
-int gsmtap_sendmsg(uint16_t arfcn, uint8_t ts, uint8_t chan_type, uint8_t ss,
-		   uint32_t fn, int8_t signal_dbm, uint8_t snr,
-		   const uint8_t *data, unsigned int len);
+/* one gsmtap instance */
+struct gsmtap_inst {
+	int ofd_wq_mode;
+	struct osmo_wqueue wq;
+	struct osmo_fd sink_ofd;
+};
 
-int gsmtap_init(uint32_t dst_ip);
+static inline int gsmtap_inst_fd(struct gsmtap_inst *gti)
+{
+	return gti->wq.bfd.fd;
+}
 
-/* Create a local 'gsmtap sink' avoiding the UDP packets being rejected
- * with ICMP reject messages */
-int gsmtap_sink_init(uint32_t bind_ip);
+/* Open a GSMTAP source (sending) socket, conncet it to host/port and
+ * return resulting fd */
+int gsmtap_source_init_fd(const char *host, uint16_t port);
+
+/* Add a local sink to an existing GSMTAP source and return fd */
+int gsmtap_source_add_sink_fd(int gsmtap_fd);
+
+/* Open GSMTAP source (sending) socket, connect it to host/port,
+ * allocate 'struct gsmtap_inst' and optionally osmo_fd/osmo_wqueue
+ * registration */
+struct gsmtap_inst *gsmtap_source_init(const char *host, uint16_t port,
+					int ofd_wq_mode);
+
+/* Add a local sink to an existing GSMTAP source instance */
+int gsmtap_source_add_sink(struct gsmtap_inst *gti);
+
+/* Send a msgb through a GSMTAP source */
+int gsmtap_sendmsg(struct gsmtap_inst *gti, struct msgb *msg);
+
+/* generate a message and send it via GSMTAP */
+int gsmtap_send(struct gsmtap_inst *gti, uint16_t arfcn, uint8_t ts,
+		uint8_t chan_type, uint8_t ss, uint32_t fn,
+		int8_t signal_dbm, uint8_t snr, const uint8_t *data,
+		unsigned int len);
 
 #endif /* _GSMTAP_UTIL_H */
diff --git a/include/osmocom/core/socket.h b/include/osmocom/core/socket.h
new file mode 100644
index 0000000..3ede524
--- /dev/null
+++ b/include/osmocom/core/socket.h
@@ -0,0 +1,16 @@
+#ifndef _OSMOCORE_SOCKET_H
+#define _OSMOCORE_SOCKET_H
+
+#include <stdint.h>
+#include <sys/socket.h>
+
+int osmo_sock_init(uint16_t family, uint16_t type, uint8_t proto,
+		   const char *host, uint16_t port, int connect0_bind1);
+
+int osmo_sock_init_sa(struct sockaddr *ss, uint16_t type,
+		      uint8_t proto, int connect0_bind1);
+
+/* determine if the given address is a local address */
+int osmo_sockaddr_is_local(struct sockaddr *addr, socklen_t addrlen);
+
+#endif /* _OSMOCORE_SOCKET_H */
diff --git a/src/Makefile.am b/src/Makefile.am
index e58bc28..1ae3cff 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2,7 +2,7 @@
 
 # This is _NOT_ the library release version, it's an API version.
 # Please read Chapter 6 "Library interface versions" of the libtool documentation before making any modification
-LIBVERSION=1:0:1
+LIBVERSION=2:0:0
 
 INCLUDES = $(all_includes) -I$(top_srcdir)/include
 AM_CFLAGS = -fPIC -Wall
@@ -11,7 +11,7 @@
 
 libosmocore_la_SOURCES = timer.c select.c signal.c msgb.c bits.c \
 			 bitvec.c statistics.c \
-			 write_queue.c utils.c \
+			 write_queue.c utils.c socket.c \
 			 logging.c logging_syslog.c rate_ctr.c \
 			 gsmtap_util.c crc16.c panic.c backtrace.c \
 			 process.c conv.c application.c
diff --git a/src/gsmtap_util.c b/src/gsmtap_util.c
index e0bc848..1542635 100644
--- a/src/gsmtap_util.c
+++ b/src/gsmtap_util.c
@@ -1,6 +1,6 @@
-/* GSMTAP output for Osmocom Layer2 (will only work on the host PC) */
+/* GSMTAP support code in libmsomcore */
 /*
- * (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010-2011 by Harald Welte <laforge@gnumonks.org>
  *
  * All Rights Reserved
  *
@@ -22,18 +22,19 @@
 
 #include "../config.h"
 
-#ifdef HAVE_SYS_SELECT_H
-
 #include <osmocom/core/gsmtap_util.h>
 #include <osmocom/core/logging.h>
 #include <osmocom/core/gsmtap.h>
 #include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
 #include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
 #include <osmocom/gsm/protocol/gsm_04_08.h>
 #include <osmocom/gsm/rsl.h>
 
 #include <arpa/inet.h>
 #include <sys/socket.h>
+#include <sys/types.h>
 #include <netinet/in.h>
 
 #include <stdio.h>
@@ -42,10 +43,6 @@
 #include <string.h>
 #include <errno.h>
 
-static struct osmo_fd gsmtap_bfd = { .fd = -1 };
-static struct osmo_fd gsmtap_sink_bfd = { .fd = -1 };
-static LLIST_HEAD(gsmtap_txqueue);
-
 uint8_t chantype_rsl2gsmtap(uint8_t rsl_chantype, uint8_t link_id)
 {
 	uint8_t ret = GSMTAP_CHANNEL_UNKNOWN;
@@ -114,44 +111,82 @@
 	return msg;
 }
 
+/* Open a GSMTAP source (sending) socket, conncet it to host/port and
+ * return resulting fd */
+int gsmtap_source_init_fd(const char *host, uint16_t port)
+{
+	if (port == 0)
+		port = GSMTAP_UDP_PORT;
+	if (host == NULL)
+		host = "localhost";
+
+	return osmo_sock_init(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, host, port, 0);
+}
+
+int gsmtap_source_add_sink_fd(int gsmtap_fd)
+{
+	struct sockaddr_storage ss;
+	socklen_t ss_len = sizeof(ss);
+	int rc;
+
+	rc = getpeername(gsmtap_fd, (struct sockaddr *)&ss, &ss_len);
+	if (rc < 0)
+		return rc;
+
+	if (osmo_sockaddr_is_local((struct sockaddr *)&ss, ss_len) == 1) {
+		rc = osmo_sock_init_sa((struct sockaddr *)&ss, SOCK_DGRAM, IPPROTO_UDP, 1);
+		if (rc >= 0)
+			return rc;
+	}
+
+	return -ENODEV;
+}
+
+#ifdef HAVE_SYS_SELECT_H
+
+int gsmtap_sendmsg(struct gsmtap_inst *gti, struct msgb *msg)
+{
+	if (gti->ofd_wq_mode)
+		return osmo_wqueue_enqueue(&gti->wq, msg);
+	else {
+		/* try immediate send and return error if any */
+		int rc;
+
+		rc = write(gsmtap_inst_fd(gti), msg->data, msg->len);
+		if (rc <= 0) {
+			return rc;
+		} else if (rc >= msg->len) {
+			msgb_free(msg);
+			return 0;
+		} else {
+			/* short write */
+			return -EIO;
+		}
+	}
+}
+
 /* receive a message from L1/L2 and put it in GSMTAP */
-int gsmtap_sendmsg(uint16_t arfcn, uint8_t ts, uint8_t chan_type, uint8_t ss,
-		   uint32_t fn, int8_t signal_dbm, uint8_t snr,
-		   const uint8_t *data, unsigned int len)
+int gsmtap_send(struct gsmtap_inst *gti, uint16_t arfcn, uint8_t ts,
+		uint8_t chan_type, uint8_t ss, uint32_t fn,
+		int8_t signal_dbm, uint8_t snr, const uint8_t *data,
+		unsigned int len)
 {
 	struct msgb *msg;
 
-	/* gsmtap was never initialized, so don't try to send anything */
-	if (gsmtap_bfd.fd == -1)
-		return 0;
-
 	msg = gsmtap_makemsg(arfcn, ts, chan_type, ss, fn, signal_dbm,
 			     snr, data, len);
 	if (!msg)
 		return -ENOMEM;
 
-	msgb_enqueue(&gsmtap_txqueue, msg);
-	gsmtap_bfd.when |= BSC_FD_WRITE;
-
-	return 0;
+	return gsmtap_sendmsg(gti, msg);
 }
 
 /* Callback from select layer if we can write to the socket */
-static int gsmtap_fd_cb(struct osmo_fd *fd, unsigned int flags)
+static int gsmtap_wq_w_cb(struct osmo_fd *ofd, struct msgb *msg)
 {
-	struct msgb *msg;
 	int rc;
 
-	if (!(flags & BSC_FD_WRITE))
-		return 0;
-
-	msg = msgb_dequeue(&gsmtap_txqueue);
-	if (!msg) {
-		/* no more messages in the queue, disable READ cb */
-		gsmtap_bfd.when = 0;
-		return 0;
-	}
-	rc = write(gsmtap_bfd.fd, msg->data, msg->len);
+	rc = write(ofd->fd, msg->data, msg->len);
 	if (rc < 0) {
 		perror("writing msgb to gsmtap fd");
 		msgb_free(msg);
@@ -167,37 +202,6 @@
 	return 0;
 }
 
-int gsmtap_init(uint32_t dst_ip)
-{
-	int rc;
-	struct sockaddr_in sin;
-
-	sin.sin_family = AF_INET;
-	sin.sin_port = htons(GSMTAP_UDP_PORT);
-	sin.sin_addr.s_addr = htonl(dst_ip);
-
-	/* create socket */
-	rc = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
-	if (rc < 0) {
-		perror("creating UDP socket");
-		return rc;
-	}
-	gsmtap_bfd.fd = rc;
-	rc = connect(rc, (struct sockaddr *)&sin, sizeof(sin));
-	if (rc < 0) {
-		perror("connecting UDP socket");
-		close(gsmtap_bfd.fd);
-		gsmtap_bfd.fd = -1;
-		return rc;
-	}
-
-	gsmtap_bfd.when = BSC_FD_WRITE;
-	gsmtap_bfd.cb = gsmtap_fd_cb;
-	gsmtap_bfd.data = NULL;
-
-	return osmo_fd_register(&gsmtap_bfd);
-}
-
 /* Callback from select layer if we can read from the sink socket */
 static int gsmtap_sink_fd_cb(struct osmo_fd *fd, unsigned int flags)
 {
@@ -217,37 +221,53 @@
 	return 0;
 }
 
-/* Create a local 'gsmtap sink' avoiding the UDP packets being rejected
- * with ICMP reject messages */
-int gsmtap_sink_init(uint32_t bind_ip)
+/* Add a local sink to an existing GSMTAP source instance */
+int gsmtap_source_add_sink(struct gsmtap_inst *gti)
 {
-	int rc;
-	struct sockaddr_in sin;
+	int fd;
 
-	sin.sin_family = AF_INET;
-	sin.sin_port = htons(GSMTAP_UDP_PORT);
-	sin.sin_addr.s_addr = htonl(bind_ip);
+	fd = gsmtap_source_add_sink_fd(gsmtap_inst_fd(gti));
+	if (fd < 0)
+		return fd;
 
-	rc = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
-	if (rc < 0) {
-		perror("creating UDP socket");
-		return rc;
-	}
-	gsmtap_sink_bfd.fd = rc;
-	rc = bind(rc, (struct sockaddr *)&sin, sizeof(sin));
-	if (rc < 0) {
-		perror("binding UDP socket");
-		close(gsmtap_sink_bfd.fd);
-		gsmtap_sink_bfd.fd = -1;
-		return rc;
+	if (gti->ofd_wq_mode) {
+		struct osmo_fd *sink_ofd;
+
+		sink_ofd = &gti->sink_ofd;
+		sink_ofd->fd = fd;
+		sink_ofd->when = BSC_FD_READ;
+		sink_ofd->cb = gsmtap_sink_fd_cb;
+
+		osmo_fd_register(sink_ofd);
 	}
 
-	gsmtap_sink_bfd.when = BSC_FD_READ;
-	gsmtap_sink_bfd.cb = gsmtap_sink_fd_cb;
-	gsmtap_sink_bfd.data = NULL;
+	return fd;
+}
 
-	return osmo_fd_register(&gsmtap_sink_bfd);
+/* like gsmtap_init2() but integrated with libosmocore select.c */
+struct gsmtap_inst *gsmtap_source_init(const char *host, uint16_t port,
+					int ofd_wq_mode)
+{
+	struct gsmtap_inst *gti;
+	int fd;
 
+	fd = gsmtap_source_init_fd(host, port);
+	if (fd < 0)
+		return NULL;
+
+	gti = talloc_zero(NULL, struct gsmtap_inst);
+	gti->ofd_wq_mode = ofd_wq_mode;
+	gti->wq.bfd.fd = fd;
+	gti->sink_ofd.fd = -1;
+
+	if (ofd_wq_mode) {
+		osmo_wqueue_init(&gti->wq, 64);
+		gti->wq.write_cb = &gsmtap_wq_w_cb;
+
+		osmo_fd_register(&gti->wq.bfd);
+	}
+
+	return gti;
 }
 
 #endif /* HAVE_SYS_SELECT_H */
diff --git a/src/socket.c b/src/socket.c
new file mode 100644
index 0000000..bd4914f
--- /dev/null
+++ b/src/socket.c
@@ -0,0 +1,143 @@
+#include "../config.h"
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <netdb.h>
+#include <ifaddrs.h>
+
+int osmo_sock_init(uint16_t family, uint16_t type, uint8_t proto,
+		   const char *host, uint16_t port, int connect0_bind1)
+{
+	struct addrinfo hints, *result, *rp;
+	int sfd, rc;
+	char portbuf[16];
+
+	sprintf(portbuf, "%u", port);
+	memset(&hints, 0, sizeof(struct addrinfo));
+	hints.ai_family = family;
+	hints.ai_socktype = type;
+	hints.ai_flags = 0;
+	hints.ai_protocol = proto;
+
+	rc = getaddrinfo(host, portbuf, &hints, &result);
+	if (rc != 0) {
+		perror("getaddrinfo returned NULL");
+		return -EINVAL;
+	}
+
+	for (rp = result; rp != NULL; rp = rp->ai_next) {
+		sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+		if (sfd == -1)
+			continue;
+		if (connect0_bind1 == 0) {
+			if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
+				break;
+		} else {
+			if (bind(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
+				break;
+		}
+		close(sfd);
+	}
+	freeaddrinfo(result);
+
+	if (rp == NULL) {
+		perror("unable to connect/bind socket");
+		return -ENODEV;
+	}
+	return sfd;
+}
+
+int osmo_sock_init_sa(struct sockaddr *ss, uint16_t type,
+		      uint8_t proto, int connect0_bind1)
+{
+	char host[NI_MAXHOST];
+	uint16_t port;
+	struct sockaddr_in *sin;
+	struct sockaddr_in6 *sin6;
+	int s, sa_len;
+
+	/* determine port and host from ss */
+	switch (ss->sa_family) {
+	case AF_INET:
+		sin = (struct sockaddr_in *) ss;
+		sa_len = sizeof(struct sockaddr_in);
+		port = ntohs(sin->sin_port);
+		break;
+	case AF_INET6:
+		sin6 = (struct sockaddr_in6 *) ss;
+		sa_len = sizeof(struct sockaddr_in6);
+		port = ntohs(sin6->sin6_port);
+		break;
+	default:
+		return -EINVAL;
+	}
+	fprintf(stderr, "==> PORT = %u\n", port);
+
+	s = getnameinfo(ss, sa_len, host, NI_MAXHOST,
+			NULL, 0, NI_NUMERICHOST);
+	if (s != 0) {
+		perror("getnameinfo failed");
+		return s;
+	}
+
+	return osmo_sock_init(ss->sa_family, type, proto, host,
+			      port, connect0_bind1);
+}
+
+static int sockaddr_equal(const struct sockaddr *a,
+			  const struct sockaddr *b, socklen_t len)
+{
+	struct sockaddr_in *sin_a, *sin_b;
+	struct sockaddr_in6 *sin6_a, *sin6_b;
+
+	if (a->sa_family != b->sa_family)
+		return 0;
+
+	switch (a->sa_family) {
+	case AF_INET:
+		sin_a = (struct sockaddr_in *)a;
+		sin_b = (struct sockaddr_in *)b;
+		if (!memcmp(&sin_a->sin_addr, &sin_b->sin_addr,
+			    sizeof(struct in_addr)))
+			return 1;
+		break;
+	case AF_INET6:
+		sin6_a = (struct sockaddr_in6 *)a;
+		sin6_b = (struct sockaddr_in6 *)b;
+		if (!memcmp(&sin6_a->sin6_addr, &sin6_b->sin6_addr,
+			    sizeof(struct in6_addr)))
+			return 1;
+		break;
+	}
+	return 0;
+}
+
+/* determine if the given address is a local address */
+int osmo_sockaddr_is_local(struct sockaddr *addr, socklen_t addrlen)
+{
+	struct ifaddrs *ifaddr, *ifa;
+
+	if (getifaddrs(&ifaddr) == -1) {
+		perror("getifaddrs");
+		return -EIO;
+	}
+
+	for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
+		if (sockaddr_equal(ifa->ifa_addr, addr, addrlen))
+			return 1;
+	}
+
+	return 0;
+}