--- /dev/null
+#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 */
--- /dev/null
- 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
--- /dev/null
--- /dev/null
++#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 */
--- /dev/null
- 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
--- /dev/null
--- /dev/null
++#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 */
--- /dev/null
- 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
--- /dev/null
--- /dev/null
++/* 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);
++}
--- /dev/null
+/* 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(>i->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 = >i->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(>i->wq, 64);
+ gti->wq.write_cb = &gsmtap_wq_w_cb;
+
+ osmo_fd_register(>i->wq.bfd);
+ }
+
+ return gti;
+}
+
+#endif /* HAVE_SYS_SOCKET_H */
--- /dev/null
- 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 */
--- /dev/null
- 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;
+ }
+}
+