Merge commit '3036612d59a5c8d97b2086a5e7817613f45948ef'
authorHarald Welte <laforge@gnumonks.org>
Mon, 23 May 2011 20:17:26 +0000 (22:17 +0200)
committerHarald Welte <laforge@gnumonks.org>
Mon, 23 May 2011 20:17:26 +0000 (22:17 +0200)
1  2 
src/shared/libosmocore/include/osmocom/core/socket.h
src/shared/libosmocore/include/osmocom/gsm/Makefile.am
src/shared/libosmocore/include/osmocom/gsm/abis_nm.h
src/shared/libosmocore/include/osmocom/gsm/protocol/Makefile.am
src/shared/libosmocore/include/osmocom/gsm/protocol/ipaccess.h
src/shared/libosmocore/src/gsm/Makefile.am
src/shared/libosmocore/src/gsm/abis_nm.c
src/shared/libosmocore/src/gsmtap_util.c
src/shared/libosmocore/src/socket.c
src/shared/libosmocore/src/vty/telnet_interface.c

index a3baa9d,0000000..b2601c7
mode 100644,000000..100644
--- /dev/null
@@@ -1,17 -1,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 */
index a39d2d0,0000000..c3670ec
mode 100644,000000..100644
--- /dev/null
@@@ -1,6 -1,0 +1,6 @@@
-                 gsm0480.h gsm48.h gsm_utils.h rsl.h tlv.h
 +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 abis_nm.h
 +
 +SUBDIRS = protocol
 +
 +osmogsmdir = $(includedir)/osmocom/gsm
index 0000000,0000000..720b603
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,25 @@@
++#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];
++
++extern const struct value_string abis_nm_obj_class_names[];
++extern const struct value_string abis_nm_adm_state_names[];
++
++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_opstate_name(uint8_t os);
++const char *abis_nm_avail_name(uint8_t avail);
++const char *abis_nm_test_name(uint8_t test);
++void abis_nm_debugp_foh(int ss, struct abis_om_fom_hdr *foh);
++
++#endif /* _OSMO_GSM_ABIS_NM_H */
index 8483f10,0000000..7f6de63
mode 100644,000000..100644
--- /dev/null
@@@ -1,6 -1,0 +1,6 @@@
-                       gsm_12_21.h
 +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 ipaccess.h
 +
 +osmogsm_protodir = $(includedir)/osmocom/gsm/protocol
index 0000000,0000000..2792572
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,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 */
index fb4a8cb,0000000..94f137e
mode 100644,000000..100644
--- /dev/null
@@@ -1,14 -1,0 +1,14 @@@
-                       gprs_cipher_core.c gsm0480.c
 +# 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=0:0:0
 +
 +INCLUDES = $(all_includes) -I$(top_srcdir)/include
 +AM_CFLAGS = -fPIC -Wall
 +
 +lib_LTLIBRARIES = libosmogsm.la
 +
 +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 abis_nm.c
 +libosmogsm_la_LDFLAGS = -version-info $(LIBVERSION)
 +libosmogsm_la_LIBADD = $(top_builddir)/src/libosmocore.la
index 0000000,0000000..109c3bb
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,396 @@@
++/* 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 },
++      },
++};
++
++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_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);
++}
++
++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 }
++};
++
++void abis_nm_debugp_foh(int ss, struct abis_om_fom_hdr *foh)
++{
++      DEBUGP(ss, "OC=%s(%02x) INST=(%02x,%02x,%02x) ",
++              get_value_string(abis_nm_obj_class_names, foh->obj_class),
++              foh->obj_class, foh->obj_inst.bts_nr, foh->obj_inst.trx_nr,
++              foh->obj_inst.ts_nr);
++}
index 5c68b6a,0000000..3d20bfc
mode 100644,000000..100644
--- /dev/null
@@@ -1,275 -1,0 +1,281 @@@
 +/* GSMTAP support code in libmsomcore */
 +/*
 + * (C) 2010-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, write to the Free Software Foundation, Inc.,
 + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 + *
 + */
 +
 +#include "../config.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 <stdio.h>
 +#include <unistd.h>
 +#include <stdint.h>
 +#include <string.h>
 +#include <errno.h>
 +
 +uint8_t chantype_rsl2gsmtap(uint8_t rsl_chantype, uint8_t link_id)
 +{
 +      uint8_t ret = GSMTAP_CHANNEL_UNKNOWN;
 +
 +      switch (rsl_chantype) {
 +      case RSL_CHAN_Bm_ACCHs:
 +              ret = GSMTAP_CHANNEL_TCH_F;
 +              break;
 +      case RSL_CHAN_Lm_ACCHs:
 +              ret = GSMTAP_CHANNEL_TCH_H;
 +              break;
 +      case RSL_CHAN_SDCCH4_ACCH:
 +              ret = GSMTAP_CHANNEL_SDCCH4;
 +              break;
 +      case RSL_CHAN_SDCCH8_ACCH:
 +              ret = GSMTAP_CHANNEL_SDCCH8;
 +              break;
 +      case RSL_CHAN_BCCH:
 +              ret = GSMTAP_CHANNEL_BCCH;
 +              break;
 +      case RSL_CHAN_RACH:
 +              ret = GSMTAP_CHANNEL_RACH;
 +              break;
 +      case RSL_CHAN_PCH_AGCH:
 +              /* it could also be AGCH... */
 +              ret = GSMTAP_CHANNEL_PCH;
 +              break;
 +      }
 +
 +      if (link_id & 0x40)
 +              ret |= GSMTAP_CHANNEL_ACCH;
 +
 +      return ret;
 +}
 +
 +/* receive a message from L1/L2 and put it in GSMTAP */
 +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)
 +{
 +      struct msgb *msg;
 +      struct gsmtap_hdr *gh;
 +      uint8_t *dst;
 +
 +      msg = msgb_alloc(sizeof(*gh) + len, "gsmtap_tx");
 +      if (!msg)
 +              return NULL;
 +
 +      gh = (struct gsmtap_hdr *) msgb_put(msg, sizeof(*gh));
 +
 +      gh->version = GSMTAP_VERSION;
 +      gh->hdr_len = sizeof(*gh)/4;
 +      gh->type = GSMTAP_TYPE_UM;
 +      gh->timeslot = ts;
 +      gh->sub_slot = ss;
 +      gh->arfcn = htons(arfcn);
 +      gh->snr_db = snr;
 +      gh->signal_dbm = signal_dbm;
 +      gh->frame_number = htonl(fn);
 +      gh->sub_type = chan_type;
 +      gh->antenna_nr = 0;
 +
 +      dst = msgb_put(msg, len);
 +      memcpy(dst, data, len);
 +
 +      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_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;
 +
++      if (!gti)
++              return -ENODEV;
++
 +      msg = gsmtap_makemsg(arfcn, ts, chan_type, ss, fn, signal_dbm,
 +                           snr, data, len);
 +      if (!msg)
 +              return -ENOMEM;
 +
 +      return gsmtap_sendmsg(gti, msg);
 +}
 +
 +/* Callback from select layer if we can write to the socket */
 +static int gsmtap_wq_w_cb(struct osmo_fd *ofd, struct msgb *msg)
 +{
 +      int rc;
 +
 +      rc = write(ofd->fd, msg->data, msg->len);
 +      if (rc < 0) {
 +              perror("writing msgb to gsmtap fd");
 +              msgb_free(msg);
 +              return rc;
 +      }
 +      if (rc != msg->len) {
 +              perror("short write to gsmtap fd");
 +              msgb_free(msg);
 +              return -EIO;
 +      }
 +
 +      msgb_free(msg);
 +      return 0;
 +}
 +
 +/* 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)
 +{
 +      int rc;
 +      uint8_t buf[4096];
 +
 +      if (!(flags & BSC_FD_READ))
 +              return 0;
 +
 +      rc = read(fd->fd, buf, sizeof(buf));
 +      if (rc < 0) {
 +              perror("reading from gsmtap sink fd");
 +              return rc;
 +      }
 +      /* simply discard any data arriving on the socket */
 +
 +      return 0;
 +}
 +
 +/* Add a local sink to an existing GSMTAP source instance */
 +int gsmtap_source_add_sink(struct gsmtap_inst *gti)
 +{
 +      int fd;
 +
 +      fd = gsmtap_source_add_sink_fd(gsmtap_inst_fd(gti));
 +      if (fd < 0)
 +              return fd;
 +
 +      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);
 +      }
 +
 +      return fd;
 +}
 +
 +/* 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 */
index e053c24,0000000..66907c8
mode 100644,000000..100644
--- /dev/null
@@@ -1,147 -1,0 +1,179 @@@
-       int sfd, rc;
 +#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;
-       fprintf(stderr, "==> PORT = %u\n", port);
++      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 */
index 7845994,0000000..c08a256
mode 100644,000000..100644
--- /dev/null
@@@ -1,206 -1,0 +1,212 @@@
-       static char *msg =
-               "Welcome to the OpenBSC Control interface\r\n";
 +/* minimalistic telnet/network interface it might turn into a wire interface */
 +/* (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.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, write to the Free Software Foundation, Inc.,
 + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 + *
 + */
 +
 +#include <sys/socket.h>
 +#include <netinet/in.h>
 +#include <stdlib.h>
 +#include <stdio.h>
 +#include <string.h>
 +#include <unistd.h>
 +
 +#include <osmocom/core/msgb.h>
 +#include <osmocom/core/talloc.h>
 +#include <osmocom/core/logging.h>
 +
 +#include <osmocom/vty/telnet_interface.h>
 +#include <osmocom/vty/buffer.h>
 +#include <osmocom/vty/command.h>
 +
 +/* per connection data */
 +LLIST_HEAD(active_connections);
 +
 +static void *tall_telnet_ctx;
 +
 +/* per network data */
 +static int telnet_new_connection(struct osmo_fd *fd, unsigned int what);
 +
 +static struct osmo_fd server_socket = {
 +      .when       = BSC_FD_READ,
 +      .cb         = telnet_new_connection,
 +      .priv_nr    = 0,
 +};
 +
 +int telnet_init(void *tall_ctx, void *priv, int port)
 +{
 +      struct sockaddr_in sock_addr;
 +      int fd, rc, on = 1;
 +
 +      tall_telnet_ctx = talloc_named_const(tall_ctx, 1,
 +                                           "telnet_connection");
 +
 +      fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
 +
 +      if (fd < 0) {
 +              LOGP(0, LOGL_ERROR, "Telnet interface socket creation failed\n");
 +              return fd;
 +      }
 +
 +      setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
 +
 +      memset(&sock_addr, 0, sizeof(sock_addr));
 +      sock_addr.sin_family = AF_INET;
 +      sock_addr.sin_port = htons(port);
 +      sock_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
 +
 +      rc = bind(fd, (struct sockaddr*)&sock_addr, sizeof(sock_addr));
 +      if (rc < 0) {
 +              LOGP(0, LOGL_ERROR, "Telnet interface failed to bind\n");
 +              close(fd);
 +              return rc;
 +      }
 +
 +      rc = listen(fd, 0);
 +      if (rc < 0) {
 +              LOGP(0, LOGL_ERROR, "Telnet interface failed to listen\n");
 +              close(fd);
 +              return rc;
 +      }
 +
 +      server_socket.data = priv;
 +      server_socket.fd = fd;
 +      osmo_fd_register(&server_socket);
 +
 +      return 0;
 +}
 +
 +extern struct host host;
 +
 +static void print_welcome(int fd)
 +{
 +      int ret;
-       ret = write(fd, msg, strlen(msg));
++      static const char *msg1 = "Welcome to the ";
++      static const char *msg2 = " control interface\r\n";
++      char *app_name = "<unnamed>";
 +
++      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));
 +}
 +
 +int telnet_close_client(struct osmo_fd *fd)
 +{
 +      struct telnet_connection *conn = (struct telnet_connection*)fd->data;
 +
 +      close(fd->fd);
 +      osmo_fd_unregister(fd);
 +
 +      if (conn->dbg) {
 +              log_del_target(conn->dbg);
 +              talloc_free(conn->dbg);
 +      }
 +
 +      llist_del(&conn->entry);
 +      talloc_free(conn);
 +      return 0;
 +}
 +
 +static int client_data(struct osmo_fd *fd, unsigned int what)
 +{
 +      struct telnet_connection *conn = fd->data;
 +      int rc = 0;
 +
 +      if (what & BSC_FD_READ) {
 +              conn->fd.when &= ~BSC_FD_READ;
 +              rc = vty_read(conn->vty);
 +      }
 +
 +      /* vty might have been closed from vithin vty_read() */
 +      if (!conn->vty)
 +              return rc;
 +
 +      if (what & BSC_FD_WRITE) {
 +              rc = buffer_flush_all(conn->vty->obuf, fd->fd);
 +              if (rc == BUFFER_EMPTY)
 +                      conn->fd.when &= ~BSC_FD_WRITE;
 +      }
 +
 +      return rc;
 +}
 +
 +static int telnet_new_connection(struct osmo_fd *fd, unsigned int what)
 +{
 +      struct telnet_connection *connection;
 +      struct sockaddr_in sockaddr;
 +      socklen_t len = sizeof(sockaddr);
 +      int new_connection = accept(fd->fd, (struct sockaddr*)&sockaddr, &len);
 +
 +      if (new_connection < 0) {
 +              LOGP(0, LOGL_ERROR, "telnet accept failed\n");
 +              return new_connection;
 +      }
 +
 +      connection = talloc_zero(tall_telnet_ctx, struct telnet_connection);
 +      connection->priv = fd->data;
 +      connection->fd.data = connection;
 +      connection->fd.fd = new_connection;
 +      connection->fd.when = BSC_FD_READ;
 +      connection->fd.cb = client_data;
 +      osmo_fd_register(&connection->fd);
 +      llist_add_tail(&connection->entry, &active_connections);
 +
 +      print_welcome(new_connection);
 +
 +      connection->vty = vty_create(new_connection, connection);
 +      if (!connection->vty) {
 +              LOGP(0, LOGL_ERROR, "couldn't create VTY\n");
 +              close(new_connection);
 +              talloc_free(connection);
 +              return -1;
 +      }
 +
 +      return 0;
 +}
 +
 +/* callback from VTY code */
 +void vty_event(enum event event, int sock, struct vty *vty)
 +{
 +      struct telnet_connection *connection = vty->priv;
 +      struct osmo_fd *bfd = &connection->fd;
 +
 +      if (vty->type != VTY_TERM)
 +              return;
 +
 +      switch (event) {
 +      case VTY_READ:
 +              bfd->when |= BSC_FD_READ;
 +              break;
 +      case VTY_WRITE:
 +              bfd->when |= BSC_FD_WRITE;
 +              break;
 +      case VTY_CLOSED:
 +              /* vty layer is about to free() vty */
 +              connection->vty = NULL;
 +              telnet_close_client(bfd);
 +              break;
 +      default:
 +              break;
 +      }
 +}
 +