Merge branch 'master' of git.osmocom.org:libosmocore
diff --git a/configure.in b/configure.in
index ead18f0..2e22bb2 100644
--- a/configure.in
+++ b/configure.in
@@ -18,7 +18,7 @@
 
 dnl checks for header files
 AC_HEADER_STDC
-AC_CHECK_HEADERS(execinfo.h sys/select.h syslog.h ctype.h)
+AC_CHECK_HEADERS(execinfo.h sys/select.h sys/socket.h syslog.h ctype.h)
 
 # The following test is taken from WebKit's webkit.m4
 saved_CFLAGS="$CFLAGS"
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..b2601c7
--- /dev/null
+++ b/include/osmocom/core/socket.h
@@ -0,0 +1,20 @@
+#ifndef _OSMOCORE_SOCKET_H
+#define _OSMOCORE_SOCKET_H
+
+#include <stdint.h>
+
+struct sockaddr;
+
+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_ofd(struct osmo_fd *ofd, int family, int type, int 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, unsigned int addrlen);
+
+#endif /* _OSMOCORE_SOCKET_H */
diff --git a/include/osmocom/gsm/Makefile.am b/include/osmocom/gsm/Makefile.am
index a39d2d0..c3670ec 100644
--- a/include/osmocom/gsm/Makefile.am
+++ b/include/osmocom/gsm/Makefile.am
@@ -1,5 +1,5 @@
 osmogsm_HEADERS = a5.h comp128.h gsm0808.h gsm48_ie.h mncc.h rxlev_stat.h \
-		  gsm0480.h gsm48.h gsm_utils.h rsl.h tlv.h
+		  gsm0480.h gsm48.h gsm_utils.h rsl.h tlv.h abis_nm.h
 
 SUBDIRS = protocol
 
diff --git a/include/osmocom/gsm/abis_nm.h b/include/osmocom/gsm/abis_nm.h
new file mode 100644
index 0000000..04e4575
--- /dev/null
+++ b/include/osmocom/gsm/abis_nm.h
@@ -0,0 +1,24 @@
+#ifndef _OSMO_GSM_ABIS_NM_H
+#define _OSMO_GSM_ABIS_NM_H
+
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+const enum abis_nm_msgtype abis_nm_reports[4];
+const enum abis_nm_msgtype abis_nm_no_ack_nack[3];
+const enum abis_nm_msgtype abis_nm_sw_load_msgs[9];
+const enum abis_nm_msgtype abis_nm_nacks[33];
+
+const char *abis_nm_nack_cause_name(uint8_t cause);
+const char *abis_nm_nack_name(uint8_t nack);
+const char *abis_nm_event_type_name(uint8_t cause);
+const char *abis_nm_severity_name(uint8_t cause);
+const struct tlv_definition abis_nm_att_tlvdef;
+const char *abis_nm_obj_class_name(uint8_t oc);
+const char *abis_nm_opstate_name(uint8_t os);
+const char *abis_nm_avail_name(uint8_t avail);
+const char *abis_nm_test_name(uint8_t test);
+const char *abis_nm_adm_name(uint8_t adm);
+void abis_nm_debugp_foh(int ss, struct abis_om_fom_hdr *foh);
+
+#endif /* _OSMO_GSM_ABIS_NM_H */
diff --git a/include/osmocom/gsm/protocol/Makefile.am b/include/osmocom/gsm/protocol/Makefile.am
index 8483f10..7f6de63 100644
--- a/include/osmocom/gsm/protocol/Makefile.am
+++ b/include/osmocom/gsm/protocol/Makefile.am
@@ -1,6 +1,6 @@
 osmogsm_proto_HEADERS = gsm_03_41.h \
 			gsm_04_08.h gsm_04_11.h gsm_04_12.h gsm_04_80.h \
 			gsm_08_08.h gsm_08_58.h \
-			gsm_12_21.h
+			gsm_12_21.h ipaccess.h
 
 osmogsm_protodir = $(includedir)/osmocom/gsm/protocol
diff --git a/include/osmocom/gsm/protocol/ipaccess.h b/include/osmocom/gsm/protocol/ipaccess.h
new file mode 100644
index 0000000..2792572
--- /dev/null
+++ b/include/osmocom/gsm/protocol/ipaccess.h
@@ -0,0 +1,93 @@
+#ifndef _OSMO_PROTO_IPACCESS_H
+#define _OSMO_PROTO_IPACCESS_H
+
+#include <stdint.h>
+
+#define IPA_TCP_PORT_OML	3002
+#define IPA_TCP_PORT_RSL	3003
+
+struct ipaccess_head {
+	uint16_t len;	/* network byte order */
+	uint8_t proto;
+	uint8_t data[0];
+} __attribute__ ((packed));
+
+struct ipaccess_head_ext {
+	uint8_t proto;
+	uint8_t data[0];
+} __attribute__ ((packed));
+
+enum ipaccess_proto {
+	IPAC_PROTO_RSL		= 0x00,
+	IPAC_PROTO_IPACCESS	= 0xfe,
+	IPAC_PROTO_SCCP		= 0xfd,
+	IPAC_PROTO_OML		= 0xff,
+
+
+	/* OpenBSC extensions */
+	IPAC_PROTO_OSMO		= 0xee,
+	IPAC_PROTO_MGCP_OLD	= 0xfc,
+};
+
+enum ipaccess_proto_ext {
+	IPAC_PROTO_EXT_CTRL	= 0x00,
+	IPAC_PROTO_EXT_MGCP	= 0x01,
+	IPAC_PROTO_EXT_LAC	= 0x02,
+};
+
+enum ipaccess_msgtype {
+	IPAC_MSGT_PING		= 0x00,
+	IPAC_MSGT_PONG		= 0x01,
+	IPAC_MSGT_ID_GET	= 0x04,
+	IPAC_MSGT_ID_RESP	= 0x05,
+	IPAC_MSGT_ID_ACK	= 0x06,
+
+	/* OpenBSC extension */
+	IPAC_MSGT_SCCP_OLD	= 0xff,
+};
+
+enum ipaccess_id_tags {
+	IPAC_IDTAG_SERNR		= 0x00,
+	IPAC_IDTAG_UNITNAME		= 0x01,
+	IPAC_IDTAG_LOCATION1		= 0x02,
+	IPAC_IDTAG_LOCATION2		= 0x03,
+	IPAC_IDTAG_EQUIPVERS		= 0x04,
+	IPAC_IDTAG_SWVERSION		= 0x05,
+	IPAC_IDTAG_IPADDR		= 0x06,
+	IPAC_IDTAG_MACADDR		= 0x07,
+	IPAC_IDTAG_UNIT			= 0x08,
+};
+
+/*
+ * Firmware specific header
+ */
+struct sdp_firmware {
+	char magic[4];
+	char more_magic[2];
+	uint16_t more_more_magic;
+	uint32_t header_length;
+	uint32_t file_length;
+	char sw_part[20];
+	char text1[64];
+	char time[12];
+	char date[14];
+	char text2[10];
+	char version[20];
+	uint16_t table_offset;
+	/* stuff i don't know */
+} __attribute__((packed));
+
+struct sdp_header_entry {
+	uint16_t something1;
+	char text1[64];
+	char time[12];
+	char date[14];
+	char text2[10];
+	char version[20];
+	uint32_t length;
+	uint32_t addr1;
+	uint32_t addr2;
+	uint32_t start;
+} __attribute__((packed));
+
+#endif /* _OSMO_PROTO_IPACCESS_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/gsm/Makefile.am b/src/gsm/Makefile.am
index fb4a8cb..94f137e 100644
--- a/src/gsm/Makefile.am
+++ b/src/gsm/Makefile.am
@@ -9,6 +9,6 @@
 
 libosmogsm_la_SOURCES = a5.c rxlev_stat.c tlv_parser.c comp128.c gsm_utils.c \
                         rsl.c gsm48.c gsm48_ie.c gsm0808.c \
-			gprs_cipher_core.c gsm0480.c
+			gprs_cipher_core.c gsm0480.c abis_nm.c
 libosmogsm_la_LDFLAGS = -version-info $(LIBVERSION)
 libosmogsm_la_LIBADD = $(top_builddir)/src/libosmocore.la
diff --git a/src/gsm/abis_nm.c b/src/gsm/abis_nm.c
new file mode 100644
index 0000000..acea4ed
--- /dev/null
+++ b/src/gsm/abis_nm.c
@@ -0,0 +1,406 @@
+/* GSM Network Management (OML) messages on the A-bis interface
+ * 3GPP TS 12.21 version 8.0.0 Release 1999 / ETSI TS 100 623 V8.0.0 */
+
+/* (C) 2008-2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+#include <osmocom/gsm/abis_nm.h>
+
+/* unidirectional messages from BTS to BSC */
+const enum abis_nm_msgtype abis_nm_reports[4] = {
+	NM_MT_SW_ACTIVATED_REP,
+	NM_MT_TEST_REP,
+	NM_MT_STATECHG_EVENT_REP,
+	NM_MT_FAILURE_EVENT_REP,
+};
+
+/* messages without ACK/NACK */
+const enum abis_nm_msgtype abis_nm_no_ack_nack[3] = {
+	NM_MT_MEAS_RES_REQ,
+	NM_MT_STOP_MEAS,
+	NM_MT_START_MEAS,
+};
+
+/* Messages related to software load */
+const enum abis_nm_msgtype abis_nm_sw_load_msgs[9] = {
+	NM_MT_LOAD_INIT_ACK,
+	NM_MT_LOAD_INIT_NACK,
+	NM_MT_LOAD_SEG_ACK,
+	NM_MT_LOAD_ABORT,
+	NM_MT_LOAD_END_ACK,
+	NM_MT_LOAD_END_NACK,
+	//NM_MT_SW_ACT_REQ,
+	NM_MT_ACTIVATE_SW_ACK,
+	NM_MT_ACTIVATE_SW_NACK,
+	NM_MT_SW_ACTIVATED_REP,
+};
+
+const enum abis_nm_msgtype abis_nm_nacks[33] = {
+	NM_MT_LOAD_INIT_NACK,
+	NM_MT_LOAD_END_NACK,
+	NM_MT_SW_ACT_REQ_NACK,
+	NM_MT_ACTIVATE_SW_NACK,
+	NM_MT_ESTABLISH_TEI_NACK,
+	NM_MT_CONN_TERR_SIGN_NACK,
+	NM_MT_DISC_TERR_SIGN_NACK,
+	NM_MT_CONN_TERR_TRAF_NACK,
+	NM_MT_DISC_TERR_TRAF_NACK,
+	NM_MT_CONN_MDROP_LINK_NACK,
+	NM_MT_DISC_MDROP_LINK_NACK,
+	NM_MT_SET_BTS_ATTR_NACK,
+	NM_MT_SET_RADIO_ATTR_NACK,
+	NM_MT_SET_CHAN_ATTR_NACK,
+	NM_MT_PERF_TEST_NACK,
+	NM_MT_SEND_TEST_REP_NACK,
+	NM_MT_STOP_TEST_NACK,
+	NM_MT_STOP_EVENT_REP_NACK,
+	NM_MT_REST_EVENT_REP_NACK,
+	NM_MT_CHG_ADM_STATE_NACK,
+	NM_MT_CHG_ADM_STATE_REQ_NACK,
+	NM_MT_REP_OUTST_ALARMS_NACK,
+	NM_MT_CHANGEOVER_NACK,
+	NM_MT_OPSTART_NACK,
+	NM_MT_REINIT_NACK,
+	NM_MT_SET_SITE_OUT_NACK,
+	NM_MT_CHG_HW_CONF_NACK,
+	NM_MT_GET_ATTR_NACK,
+	NM_MT_SET_ALARM_THRES_NACK,
+	NM_MT_BS11_BEGIN_DB_TX_NACK,
+	NM_MT_BS11_END_DB_TX_NACK,
+	NM_MT_BS11_CREATE_OBJ_NACK,
+	NM_MT_BS11_DELETE_OBJ_NACK,
+};
+
+static const struct value_string nack_names[] = {
+	{ NM_MT_LOAD_INIT_NACK,		"SOFTWARE LOAD INIT" },
+	{ NM_MT_LOAD_END_NACK,		"SOFTWARE LOAD END" },
+	{ NM_MT_SW_ACT_REQ_NACK,	"SOFTWARE ACTIVATE REQUEST" },
+	{ NM_MT_ACTIVATE_SW_NACK,	"ACTIVATE SOFTWARE" },
+	{ NM_MT_ESTABLISH_TEI_NACK,	"ESTABLISH TEI" },
+	{ NM_MT_CONN_TERR_SIGN_NACK,	"CONNECT TERRESTRIAL SIGNALLING" },
+	{ NM_MT_DISC_TERR_SIGN_NACK,	"DISCONNECT TERRESTRIAL SIGNALLING" },
+	{ NM_MT_CONN_TERR_TRAF_NACK,	"CONNECT TERRESTRIAL TRAFFIC" },
+	{ NM_MT_DISC_TERR_TRAF_NACK,	"DISCONNECT TERRESTRIAL TRAFFIC" },
+	{ NM_MT_CONN_MDROP_LINK_NACK,	"CONNECT MULTI-DROP LINK" },
+	{ NM_MT_DISC_MDROP_LINK_NACK,	"DISCONNECT MULTI-DROP LINK" },
+	{ NM_MT_SET_BTS_ATTR_NACK,	"SET BTS ATTRIBUTE" },
+	{ NM_MT_SET_RADIO_ATTR_NACK,	"SET RADIO ATTRIBUTE" },
+	{ NM_MT_SET_CHAN_ATTR_NACK,	"SET CHANNEL ATTRIBUTE" },
+	{ NM_MT_PERF_TEST_NACK,		"PERFORM TEST" },
+	{ NM_MT_SEND_TEST_REP_NACK,	"SEND TEST REPORT" },
+	{ NM_MT_STOP_TEST_NACK,		"STOP TEST" },
+	{ NM_MT_STOP_EVENT_REP_NACK,	"STOP EVENT REPORT" },
+	{ NM_MT_REST_EVENT_REP_NACK,	"RESET EVENT REPORT" },
+	{ NM_MT_CHG_ADM_STATE_NACK,	"CHANGE ADMINISTRATIVE STATE" },
+	{ NM_MT_CHG_ADM_STATE_REQ_NACK,
+				"CHANGE ADMINISTRATIVE STATE REQUEST" },
+	{ NM_MT_REP_OUTST_ALARMS_NACK,	"REPORT OUTSTANDING ALARMS" },
+	{ NM_MT_CHANGEOVER_NACK,	"CHANGEOVER" },
+	{ NM_MT_OPSTART_NACK,		"OPSTART" },
+	{ NM_MT_REINIT_NACK,		"REINIT" },
+	{ NM_MT_SET_SITE_OUT_NACK,	"SET SITE OUTPUT" },
+	{ NM_MT_CHG_HW_CONF_NACK,	"CHANGE HARDWARE CONFIGURATION" },
+	{ NM_MT_GET_ATTR_NACK,		"GET ATTRIBUTE" },
+	{ NM_MT_SET_ALARM_THRES_NACK,	"SET ALARM THRESHOLD" },
+	{ NM_MT_BS11_BEGIN_DB_TX_NACK,	"BS11 BEGIN DATABASE TRANSMISSION" },
+	{ NM_MT_BS11_END_DB_TX_NACK,	"BS11 END DATABASE TRANSMISSION" },
+	{ NM_MT_BS11_CREATE_OBJ_NACK,	"BS11 CREATE OBJECT" },
+	{ NM_MT_BS11_DELETE_OBJ_NACK,	"BS11 DELETE OBJECT" },
+	{ 0,				NULL }
+};
+
+const char *abis_nm_nack_name(uint8_t nack)
+{
+	return get_value_string(nack_names, nack);
+}
+
+/* Chapter 9.4.36 */
+static const struct value_string nack_cause_names[] = {
+	/* General Nack Causes */
+	{ NM_NACK_INCORR_STRUCT,	"Incorrect message structure" },
+	{ NM_NACK_MSGTYPE_INVAL,	"Invalid message type value" },
+	{ NM_NACK_OBJCLASS_INVAL,	"Invalid Object class value" },
+	{ NM_NACK_OBJCLASS_NOTSUPP,	"Object class not supported" },
+	{ NM_NACK_BTSNR_UNKN,		"BTS no. unknown" },
+	{ NM_NACK_TRXNR_UNKN,		"Baseband Transceiver no. unknown" },
+	{ NM_NACK_OBJINST_UNKN,		"Object Instance unknown" },
+	{ NM_NACK_ATTRID_INVAL,		"Invalid attribute identifier value" },
+	{ NM_NACK_ATTRID_NOTSUPP,	"Attribute identifier not supported" },
+	{ NM_NACK_PARAM_RANGE,		"Parameter value outside permitted range" },
+	{ NM_NACK_ATTRLIST_INCONSISTENT,"Inconsistency in attribute list" },
+	{ NM_NACK_SPEC_IMPL_NOTSUPP,	"Specified implementation not supported" },
+	{ NM_NACK_CANT_PERFORM,		"Message cannot be performed" },
+	/* Specific Nack Causes */
+	{ NM_NACK_RES_NOTIMPL,		"Resource not implemented" },
+	{ NM_NACK_RES_NOTAVAIL,		"Resource not available" },
+	{ NM_NACK_FREQ_NOTAVAIL,	"Frequency not available" },
+	{ NM_NACK_TEST_NOTSUPP,		"Test not supported" },
+	{ NM_NACK_CAPACITY_RESTR,	"Capacity restrictions" },
+	{ NM_NACK_PHYSCFG_NOTPERFORM,	"Physical configuration cannot be performed" },
+	{ NM_NACK_TEST_NOTINIT,		"Test not initiated" },
+	{ NM_NACK_PHYSCFG_NOTRESTORE,	"Physical configuration cannot be restored" },
+	{ NM_NACK_TEST_NOSUCH,		"No such test" },
+	{ NM_NACK_TEST_NOSTOP,		"Test cannot be stopped" },
+	{ NM_NACK_MSGINCONSIST_PHYSCFG,	"Message inconsistent with physical configuration" },
+	{ NM_NACK_FILE_INCOMPLETE,	"Complete file notreceived" },
+	{ NM_NACK_FILE_NOTAVAIL,	"File not available at destination" },
+	{ NM_NACK_FILE_NOTACTIVATE,	"File cannot be activate" },
+	{ NM_NACK_REQ_NOT_GRANT,	"Request not granted" },
+	{ NM_NACK_WAIT,			"Wait" },
+	{ NM_NACK_NOTH_REPORT_EXIST,	"Nothing reportable existing" },
+	{ NM_NACK_MEAS_NOTSUPP,		"Measurement not supported" },
+	{ NM_NACK_MEAS_NOTSTART,	"Measurement not started" },
+	{ 0,				NULL }
+};
+
+const char *abis_nm_nack_cause_name(uint8_t cause)
+{
+	return get_value_string(nack_cause_names, cause);
+}
+
+/* Chapter 9.4.16: Event Type */
+static const struct value_string event_type_names[] = {
+	{ NM_EVT_COMM_FAIL,		"communication failure" },
+	{ NM_EVT_QOS_FAIL,		"quality of service failure" },
+	{ NM_EVT_PROC_FAIL,		"processing failure" },
+	{ NM_EVT_EQUIP_FAIL,		"equipment failure" },
+	{ NM_EVT_ENV_FAIL,		"environment failure" },
+	{ 0,				NULL }
+};
+
+const char *abis_nm_event_type_name(uint8_t cause)
+{
+	return get_value_string(event_type_names, cause);
+}
+
+/* Chapter 9.4.63: Perceived Severity */
+static const struct value_string severity_names[] = {
+	{ NM_SEVER_CEASED,		"failure ceased" },
+	{ NM_SEVER_CRITICAL,		"critical failure" },
+	{ NM_SEVER_MAJOR,		"major failure" },
+	{ NM_SEVER_MINOR,		"minor failure" },
+	{ NM_SEVER_WARNING,		"warning level failure" },
+	{ NM_SEVER_INDETERMINATE,	"indeterminate failure" },
+	{ 0,				NULL }
+};
+
+const char *abis_nm_severity_name(uint8_t cause)
+{
+	return get_value_string(severity_names, cause);
+}
+
+/* Attributes that the BSC can set, not only get, according to Section 9.4 */
+const enum abis_nm_attr abis_nm_att_settable[] = {
+	NM_ATT_ADD_INFO,
+	NM_ATT_ADD_TEXT,
+	NM_ATT_DEST,
+	NM_ATT_EVENT_TYPE,
+	NM_ATT_FILE_DATA,
+	NM_ATT_GET_ARI,
+	NM_ATT_HW_CONF_CHG,
+	NM_ATT_LIST_REQ_ATTR,
+	NM_ATT_MDROP_LINK,
+	NM_ATT_MDROP_NEXT,
+	NM_ATT_NACK_CAUSES,
+	NM_ATT_OUTST_ALARM,
+	NM_ATT_PHYS_CONF,
+	NM_ATT_PROB_CAUSE,
+	NM_ATT_RAD_SUBC,
+	NM_ATT_SOURCE,
+	NM_ATT_SPEC_PROB,
+	NM_ATT_START_TIME,
+	NM_ATT_TEST_DUR,
+	NM_ATT_TEST_NO,
+	NM_ATT_TEST_REPORT,
+	NM_ATT_WINDOW_SIZE,
+	NM_ATT_SEVERITY,
+	NM_ATT_MEAS_RES,
+	NM_ATT_MEAS_TYPE,
+};
+
+const struct tlv_definition abis_nm_att_tlvdef = {
+	.def = {
+		[NM_ATT_ABIS_CHANNEL] =		{ TLV_TYPE_FIXED, 3 },
+		[NM_ATT_ADD_INFO] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_ADD_TEXT] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_ADM_STATE] =		{ TLV_TYPE_TV },
+		[NM_ATT_ARFCN_LIST]=		{ TLV_TYPE_TL16V },
+		[NM_ATT_AUTON_REPORT] =		{ TLV_TYPE_TV },
+		[NM_ATT_AVAIL_STATUS] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_BCCH_ARFCN] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_BSIC] =			{ TLV_TYPE_TV },
+		[NM_ATT_BTS_AIR_TIMER] =	{ TLV_TYPE_TV },
+		[NM_ATT_CCCH_L_I_P] =		{ TLV_TYPE_TV },
+		[NM_ATT_CCCH_L_T] =		{ TLV_TYPE_TV },
+		[NM_ATT_CHAN_COMB] =		{ TLV_TYPE_TV },
+		[NM_ATT_CONN_FAIL_CRIT] =	{ TLV_TYPE_TL16V },
+		[NM_ATT_DEST] =			{ TLV_TYPE_TL16V },
+		[NM_ATT_EVENT_TYPE] =		{ TLV_TYPE_TV },
+		[NM_ATT_FILE_DATA] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_FILE_ID] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_FILE_VERSION] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_GSM_TIME] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_HSN] =			{ TLV_TYPE_TV },
+		[NM_ATT_HW_CONFIG] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_HW_DESC] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_INTAVE_PARAM] =		{ TLV_TYPE_TV },
+		[NM_ATT_INTERF_BOUND] =		{ TLV_TYPE_FIXED, 6 },
+		[NM_ATT_LIST_REQ_ATTR] =	{ TLV_TYPE_TL16V },
+		[NM_ATT_MAIO] =			{ TLV_TYPE_TV },
+		[NM_ATT_MANUF_STATE] =		{ TLV_TYPE_TV },
+		[NM_ATT_MANUF_THRESH] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_MANUF_ID] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_MAX_TA] =		{ TLV_TYPE_TV },
+		[NM_ATT_MDROP_LINK] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_MDROP_NEXT] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_NACK_CAUSES] =		{ TLV_TYPE_TV },
+		[NM_ATT_NY1] =			{ TLV_TYPE_TV },
+		[NM_ATT_OPER_STATE] =		{ TLV_TYPE_TV },
+		[NM_ATT_OVERL_PERIOD] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_PHYS_CONF] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_POWER_CLASS] =		{ TLV_TYPE_TV },
+		[NM_ATT_POWER_THRESH] =		{ TLV_TYPE_FIXED, 3 },
+		[NM_ATT_PROB_CAUSE] =		{ TLV_TYPE_FIXED, 3 },
+		[NM_ATT_RACH_B_THRESH] =	{ TLV_TYPE_TV },
+		[NM_ATT_LDAVG_SLOTS] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_RAD_SUBC] =		{ TLV_TYPE_TV },
+		[NM_ATT_RF_MAXPOWR_R] =		{ TLV_TYPE_TV },
+		[NM_ATT_SITE_INPUTS] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_SITE_OUTPUTS] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_SOURCE] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_SPEC_PROB] =		{ TLV_TYPE_TV },
+		[NM_ATT_START_TIME] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_T200] =			{ TLV_TYPE_FIXED, 7 },
+		[NM_ATT_TEI] =			{ TLV_TYPE_TV },
+		[NM_ATT_TEST_DUR] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_TEST_NO] =		{ TLV_TYPE_TV },
+		[NM_ATT_TEST_REPORT] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_VSWR_THRESH] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_WINDOW_SIZE] = 		{ TLV_TYPE_TV },
+		[NM_ATT_TSC] =			{ TLV_TYPE_TV },
+		[NM_ATT_SW_CONFIG] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_SEVERITY] = 		{ TLV_TYPE_TV },
+		[NM_ATT_GET_ARI] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_HW_CONF_CHG] = 		{ TLV_TYPE_TL16V },
+		[NM_ATT_OUTST_ALARM] =		{ TLV_TYPE_TV },
+		[NM_ATT_MEAS_RES] =		{ TLV_TYPE_TL16V },
+	},
+};
+
+static const struct value_string abis_nm_obj_class_names[] = {
+	{ NM_OC_SITE_MANAGER,	"SITE-MANAGER" },
+	{ NM_OC_BTS,		"BTS" },
+	{ NM_OC_RADIO_CARRIER,	"RADIO-CARRIER" },
+	{ NM_OC_BASEB_TRANSC,	"BASEBAND-TRANSCEIVER" },
+	{ NM_OC_CHANNEL,	"CHANNEL" },
+	{ NM_OC_BS11_ADJC,	"ADJC" },
+	{ NM_OC_BS11_HANDOVER,	"HANDOVER" },
+	{ NM_OC_BS11_PWR_CTRL,	"POWER-CONTROL" },
+	{ NM_OC_BS11_BTSE,	"BTSE" },
+	{ NM_OC_BS11_RACK,	"RACK" },
+	{ NM_OC_BS11_TEST,	"TEST" },
+	{ NM_OC_BS11_ENVABTSE,	"ENVABTSE" },
+	{ NM_OC_BS11_BPORT,	"BPORT" },
+	{ NM_OC_GPRS_NSE,	"GPRS-NSE" },
+	{ NM_OC_GPRS_CELL,	"GPRS-CELL" },
+	{ NM_OC_GPRS_NSVC,	"GPRS-NSVC" },
+	{ NM_OC_BS11,		"SIEMENSHW" },
+	{ 0,			NULL }
+};
+
+const char *abis_nm_obj_class_name(uint8_t oc)
+{
+	return get_value_string(abis_nm_obj_class_names, oc);
+}
+
+const char *abis_nm_opstate_name(uint8_t os)
+{
+	switch (os) {
+	case NM_OPSTATE_DISABLED:
+		return "Disabled";
+	case NM_OPSTATE_ENABLED:
+		return "Enabled";
+	case NM_OPSTATE_NULL:
+		return "NULL";
+	default:
+		return "RFU";
+	}
+}
+
+/* Chapter 9.4.7 */
+static const struct value_string avail_names[] = {
+	{ 0, 	"In test" },
+	{ 1,	"Failed" },
+	{ 2,	"Power off" },
+	{ 3,	"Off line" },
+	/* Not used */
+	{ 5,	"Dependency" },
+	{ 6,	"Degraded" },
+	{ 7,	"Not installed" },
+	{ 0xff, "OK" },
+	{ 0,	NULL }
+};
+
+const char *abis_nm_avail_name(uint8_t avail)
+{
+	return get_value_string(avail_names, avail);
+}
+
+static struct value_string test_names[] = {
+	/* FIXME: standard test names */
+	{ NM_IPACC_TESTNO_CHAN_USAGE, "Channel Usage" },
+	{ NM_IPACC_TESTNO_BCCH_CHAN_USAGE, "BCCH Channel Usage" },
+	{ NM_IPACC_TESTNO_FREQ_SYNC, "Frequency Synchronization" },
+	{ NM_IPACC_TESTNO_BCCH_INFO, "BCCH Info" },
+	{ NM_IPACC_TESTNO_TX_BEACON, "Transmit Beacon" },
+	{ NM_IPACC_TESTNO_SYSINFO_MONITOR, "System Info Monitor" },
+	{ NM_IPACC_TESTNO_BCCCH_MONITOR, "BCCH Monitor" },
+	{ 0, NULL }
+};
+
+const char *abis_nm_test_name(uint8_t test)
+{
+	return get_value_string(test_names, test);
+}
+
+static const struct value_string abis_nm_adm_state_names[] = {
+	{ NM_STATE_LOCKED,	"Locked" },
+	{ NM_STATE_UNLOCKED,	"Unlocked" },
+	{ NM_STATE_SHUTDOWN,	"Shutdown" },
+	{ NM_STATE_NULL,	"NULL" },
+	{ 0, NULL }
+};
+
+const char *abis_nm_adm_name(uint8_t adm)
+{
+	return get_value_string(abis_nm_adm_state_names, adm);
+}
+
+void abis_nm_debugp_foh(int ss, struct abis_om_fom_hdr *foh)
+{
+	DEBUGP(ss, "OC=%s(%02x) INST=(%02x,%02x,%02x) ",
+		abis_nm_obj_class_name(foh->obj_class), foh->obj_class,
+		foh->obj_inst.bts_nr, foh->obj_inst.trx_nr,
+		foh->obj_inst.ts_nr);
+}
diff --git a/src/gsmtap_util.c b/src/gsmtap_util.c
index e0bc848..3d20bfc 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,19 +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 <sys/types.h>
+
 #include <arpa/inet.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
 
 #include <stdio.h>
 #include <unistd.h>
@@ -42,10 +42,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 +110,91 @@
 	return msg;
 }
 
+#ifdef HAVE_SYS_SOCKET_H
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+/* 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;
+}
+
+int gsmtap_sendmsg(struct gsmtap_inst *gti, struct msgb *msg)
+{
+	if (!gti)
+		return -ENODEV;
+
+	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;
+	if (!gti)
+		return -ENODEV;
 
 	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 +210,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 +229,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 osmo_fd_register(&gsmtap_sink_bfd);
-
+	return fd;
 }
 
-#endif /* HAVE_SYS_SELECT_H */
+/* 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_SOCKET_H */
diff --git a/src/socket.c b/src/socket.c
new file mode 100644
index 0000000..66907c8
--- /dev/null
+++ b/src/socket.c
@@ -0,0 +1,179 @@
+#include "../config.h"
+
+#ifdef HAVE_SYS_SOCKET_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, on = 1;
+	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;
+	}
+
+	setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+	/* Make sure to call 'listen' on a bound, connection-oriented sock */
+	if (connect0_bind1 == 1) {
+		switch (type) {
+		case SOCK_STREAM:
+		case SOCK_SEQPACKET:
+			listen(sfd, 10);
+			break;
+		}
+	}
+	return sfd;
+}
+
+int osmo_sock_init_ofd(struct osmo_fd *ofd, int family, int type, int proto,
+			const char *host, uint16_t port, int connect0_bind1)
+{
+	int sfd, rc;
+
+	sfd = osmo_sock_init(family, type, proto, host, port, connect0_bind1);
+	if (sfd < 0)
+		return sfd;
+
+	ofd->fd = sfd;
+	ofd->when = BSC_FD_READ;
+
+	rc = osmo_fd_register(ofd);
+	if (rc < 0) {
+		close(sfd);
+		return rc;
+	}
+
+	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;
+	}
+
+	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, unsigned int 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;
+}
+
+#endif /* HAVE_SYS_SOCKET_H */
diff --git a/src/vty/telnet_interface.c b/src/vty/telnet_interface.c
index 7845994..c08a256 100644
--- a/src/vty/telnet_interface.c
+++ b/src/vty/telnet_interface.c
@@ -95,10 +95,16 @@
 static void print_welcome(int fd)
 {
 	int ret;
-	static char *msg =
-		"Welcome to the OpenBSC Control interface\r\n";
+	static const char *msg1 = "Welcome to the ";
+	static const char *msg2 = " control interface\r\n";
+	char *app_name = "<unnamed>";
 
-	ret = write(fd, msg, strlen(msg));
+	if (host.app_info->name)
+		app_name = host.app_info->name;
+
+	ret = write(fd, msg1, strlen(msg1));
+	ret = write(fd, app_name, strlen(app_name));
+	ret = write(fd, msg2, strlen(msg2));
 
 	if (host.app_info->copyright)
 		ret = write(fd, host.app_info->copyright, strlen(host.app_info->copyright));