/*! \file ipa.c * OpenBSC Abis input driver for ip.access */ /* * (C) 2009-2017 by Harald Welte * (C) 2010 by Holger Hans Peter Freyther * (C) 2010 by On-Waves * * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ * * 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 . * */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*! \addtogroup ipa * @{ * IPA Multiplex utility routines */ #define IPA_ALLOC_SIZE 1200 /* * Common propietary IPA messages: * - PONG: in reply to PING. * - ID_REQUEST: first messages once OML has been established. * - ID_ACK: in reply to ID_ACK. */ static const uint8_t ipa_pong_msg[] = { 0, 1, IPAC_PROTO_IPACCESS, IPAC_MSGT_PONG }; static const uint8_t ipa_id_ack_msg[] = { 0, 1, IPAC_PROTO_IPACCESS, IPAC_MSGT_ID_ACK }; static const uint8_t ipa_id_req_msg[] = { 0, 17, IPAC_PROTO_IPACCESS, IPAC_MSGT_ID_GET, 0x01, IPAC_IDTAG_UNIT, 0x01, IPAC_IDTAG_MACADDR, 0x01, IPAC_IDTAG_LOCATION1, 0x01, IPAC_IDTAG_LOCATION2, 0x01, IPAC_IDTAG_EQUIPVERS, 0x01, IPAC_IDTAG_SWVERSION, 0x01, IPAC_IDTAG_UNITNAME, 0x01, IPAC_IDTAG_SERNR, }; static const char *idtag_names[] = { [IPAC_IDTAG_SERNR] = "Serial_Number", [IPAC_IDTAG_UNITNAME] = "Unit_Name", [IPAC_IDTAG_LOCATION1] = "Location_1", [IPAC_IDTAG_LOCATION2] = "Location_2", [IPAC_IDTAG_EQUIPVERS] = "Equipment_Version", [IPAC_IDTAG_SWVERSION] = "Software_Version", [IPAC_IDTAG_IPADDR] = "IP_Address", [IPAC_IDTAG_MACADDR] = "MAC_Address", [IPAC_IDTAG_UNIT] = "Unit_ID", }; const char *ipa_ccm_idtag_name(uint8_t tag) { if (tag >= ARRAY_SIZE(idtag_names)) return "unknown"; return idtag_names[tag]; } /*! Parse the payload part of an IPA CCM ID GET, return \ref tlv_parsed format. */ int ipa_ccm_idtag_parse(struct tlv_parsed *dec, unsigned char *buf, int len) { return ipa_ccm_idtag_parse_off(dec, buf, len, 1); } /*! Parse the payload part of an IPA CCM ID GET, return \ref tlv_parsed format. * WARNING: This function can only parse correctly IPA CCM ID GET/REQUEST * messages, and only when len_offset is passed value of 1. * \param[out] dec Caller-provided/allocated output structure for parsed payload * \param[in] buf Buffer containing the payload (excluding 1 byte msg_type) of the message * \param[in] len Length of \a buf in octets * \param[in] len_offset Offset from end of len field to start of value (ommiting tag). Must be 1! * \returns 0 on success; negative on error */ int ipa_ccm_idtag_parse_off(struct tlv_parsed *dec, unsigned char *buf, int len, const int len_offset) { uint8_t t_len; uint8_t t_tag; uint8_t *cur = buf; memset(dec, 0, sizeof(*dec)); LOGP(DLMI, LOGL_DEBUG, "Rx IPA CCM ID_GET: "); while (len >= 2) { len -= 2; t_len = *cur++; t_tag = *cur++; if (t_len < len_offset) { LOGPC(DLMI, LOGL_DEBUG, "\n"); LOGP(DLMI, LOGL_ERROR, "minimal offset not included: %d < %d\n", t_len, len_offset); return -EINVAL; } if (t_len > len + 1) { LOGPC(DLMI, LOGL_DEBUG, "\n"); LOGP(DLMI, LOGL_ERROR, "The tag does not fit: %d > %d\n", t_len, len + 1); return -EINVAL; } LOGPC(DLMI, LOGL_DEBUG, "%s='%s' ", ipa_ccm_idtag_name(t_tag), cur); dec->lv[t_tag].len = t_len - len_offset; dec->lv[t_tag].val = cur; cur += t_len - len_offset; len -= t_len - len_offset; } LOGPC(DLMI, LOGL_DEBUG, "\n"); return 0; } /*! Parse the payload part of an IPA CCM ID GET, return \ref tlv_parsed format. * The odd payload format of those messages is structured as follows: * * 8bit length value (length of payload *and tag*) * * 8bit tag value * * optional, variable-length payload * \param[out] dec Caller-provided/allocated output structure for parsed payload * \param[in] buf Buffer containing the payload (excluding 1 byte msg_type) of the message * \param[in] len Length of \a buf in octets * \returns 0 on success; negative on error */ int ipa_ccm_id_get_parse(struct tlv_parsed *dec, const uint8_t *buf, unsigned int len) { uint8_t t_len; uint8_t t_tag; const uint8_t *cur = buf; memset(dec, 0, sizeof(*dec)); LOGP(DLMI, LOGL_DEBUG, "Rx IPA CCM ID_GET: "); while (len >= 2) { len -= 2; t_len = *cur++; t_tag = *cur++; if (t_len > len + 1) { LOGPC(DLMI, LOGL_DEBUG, "\n"); LOGP(DLMI, LOGL_ERROR, "The tag does not fit: %d > %d\n", t_len, len + 1); return -EINVAL; } LOGPC(DLMI, LOGL_DEBUG, "%s='%s' ", ipa_ccm_idtag_name(t_tag), cur); dec->lv[t_tag].len = t_len-1; dec->lv[t_tag].val = cur; cur += t_len-1; len -= t_len-1; } LOGPC(DLMI, LOGL_DEBUG, "\n"); return 0; } /*! Parse the payload part of an IPA CCM ID RESP, return \ref tlv_parsed format. * The odd payload format of those messages is structured as follows: * * 16bit length value (length of payload *and tag*) * * 8bit tag value * * optional, variable-length payload * \param[out] dec Caller-provided/allocated output structure for parsed payload * \param[in] buf Buffer containing the payload (excluding 1 byte msg_type) of the message * \param[in] len Length of \a buf in octets * \returns 0 on success; negative on error */ int ipa_ccm_id_resp_parse(struct tlv_parsed *dec, const uint8_t *buf, unsigned int len) { uint8_t t_len; uint8_t t_tag; const uint8_t *cur = buf; memset(dec, 0, sizeof(*dec)); LOGP(DLMI, LOGL_DEBUG, "Rx IPA CCM ID_RESP: "); while (len >= 3) { len -= 3; t_len = osmo_load16be(cur); cur += 2; t_tag = *cur++; if (t_len > len + 1) { LOGPC(DLMI, LOGL_DEBUG, "\n"); LOGP(DLMI, LOGL_ERROR, "The tag does not fit: %d > %d\n", t_len, len + 1); return -EINVAL; } DEBUGPC(DLMI, "%s='%s' ", ipa_ccm_idtag_name(t_tag), cur); dec->lv[t_tag].len = t_len-1; dec->lv[t_tag].val = cur; cur += t_len-1; len -= t_len-1; } LOGPC(DLMI, LOGL_DEBUG, "\n"); return 0; } int ipa_parse_unitid(const char *str, struct ipaccess_unit *unit_data) { unsigned long ul; char *endptr; const char *nptr; nptr = str; ul = strtoul(nptr, &endptr, 10); if (endptr <= nptr) return -EINVAL; unit_data->site_id = ul & 0xffff; if (*endptr++ != '/') return -EINVAL; nptr = endptr; ul = strtoul(nptr, &endptr, 10); if (endptr <= nptr) return -EINVAL; unit_data->bts_id = ul & 0xffff; if (*endptr++ != '/') return -EINVAL; nptr = endptr; ul = strtoul(nptr, &endptr, 10); if (endptr <= nptr) return -EINVAL; unit_data->trx_id = ul & 0xffff; return 0; } /*! Fill ud struct from tp structure. * \param[in,out] ud ipaccess_unit to fill * \param[in] tp the decoded TLV structure from eg. ID_RESP message * \returns zero on success, negative on error * * This function expects parameter ud's fields to be initialized to zero if not yet set. * Existing incoming string pointer fields are expected to be allocated using * talloc and will be deallocated as such if replaced with the content of tp. **/ int ipa_ccm_tlv_to_unitdata(struct ipaccess_unit *ud, const struct tlv_parsed *tp) { int rc = 0; if (TLVP_PRES_LEN(tp, IPAC_IDTAG_SERNR, 1)) osmo_talloc_replace_string(ud, &ud->serno, (char *)TLVP_VAL(tp, IPAC_IDTAG_SERNR)); if (TLVP_PRES_LEN(tp, IPAC_IDTAG_UNITNAME, 1)) osmo_talloc_replace_string(ud, &ud->unit_name, (char *)TLVP_VAL(tp, IPAC_IDTAG_UNITNAME)); if (TLVP_PRES_LEN(tp, IPAC_IDTAG_LOCATION1, 1)) osmo_talloc_replace_string(ud, &ud->location1, (char *)TLVP_VAL(tp, IPAC_IDTAG_LOCATION1)); if (TLVP_PRES_LEN(tp, IPAC_IDTAG_LOCATION2, 1)) osmo_talloc_replace_string(ud, &ud->location2, (char *)TLVP_VAL(tp, IPAC_IDTAG_LOCATION2)); if (TLVP_PRES_LEN(tp, IPAC_IDTAG_EQUIPVERS, 1)) osmo_talloc_replace_string(ud, &ud->equipvers, (char *)TLVP_VAL(tp, IPAC_IDTAG_EQUIPVERS)); if (TLVP_PRES_LEN(tp, IPAC_IDTAG_SWVERSION, 1)) osmo_talloc_replace_string(ud, &ud->swversion, (char *)TLVP_VAL(tp, IPAC_IDTAG_SWVERSION)); if (TLVP_PRES_LEN(tp, IPAC_IDTAG_MACADDR, 17)) { rc = osmo_macaddr_parse(ud->mac_addr, (char *) TLVP_VAL(tp, IPAC_IDTAG_MACADDR)); if (rc < 0) goto out; } if (TLVP_PRES_LEN(tp, IPAC_IDTAG_UNIT, 1)) rc = ipa_parse_unitid((char *) TLVP_VAL(tp, IPAC_IDTAG_UNIT), ud); out: return rc; } #define IPA_STRING_MAX 64 /*! Generate IPA CCM ID RESP based on list of IEs * \param[in] dev Descriptor describing identity data for response * \param[in] ies_req List of IEIs to include in response * \param[in] num_ies_req Number of IEIs in \a ies_req * \returns Message buffer with IPA CCM ID RESP */ struct msgb *ipa_ccm_make_id_resp(const struct ipaccess_unit *dev, const uint8_t *ies_req, unsigned int num_ies_req) { struct msgb *msg = ipa_msg_alloc(16); char str[IPA_STRING_MAX]; unsigned int i; if (!msg) return NULL; *msgb_put(msg, 1) = IPAC_MSGT_ID_RESP; for (i = 0; i < num_ies_req; i++) { uint8_t *tag; str[0] = '\0'; switch (ies_req[i]) { case IPAC_IDTAG_UNIT: snprintf(str, sizeof(str), "%u/%u/%u", dev->site_id, dev->bts_id, dev->trx_id); break; case IPAC_IDTAG_MACADDR: snprintf(str, sizeof(str), "%02x:%02x:%02x:%02x:%02x:%02x", dev->mac_addr[0], dev->mac_addr[1], dev->mac_addr[2], dev->mac_addr[3], dev->mac_addr[4], dev->mac_addr[5]); break; case IPAC_IDTAG_LOCATION1: if (dev->location1) osmo_strlcpy(str, dev->location1, sizeof(str)); break; case IPAC_IDTAG_LOCATION2: if (dev->location2) osmo_strlcpy(str, dev->location2, sizeof(str)); break; case IPAC_IDTAG_EQUIPVERS: if (dev->equipvers) osmo_strlcpy(str, dev->equipvers, sizeof(str)); break; case IPAC_IDTAG_SWVERSION: if (dev->swversion) osmo_strlcpy(str, dev->swversion, sizeof(str)); break; case IPAC_IDTAG_UNITNAME: if (dev->unit_name) { snprintf(str, sizeof(str), "%s", dev->unit_name); } else { snprintf(str, sizeof(str), "%02x-%02x-%02x-%02x-%02x-%02x", dev->mac_addr[0], dev->mac_addr[1], dev->mac_addr[2], dev->mac_addr[3], dev->mac_addr[4], dev->mac_addr[5]); } break; case IPAC_IDTAG_SERNR: if (dev->serno) osmo_strlcpy(str, dev->serno, sizeof(str)); break; default: LOGP(DLINP, LOGL_NOTICE, "Unknown ipaccess tag 0x%02x\n", ies_req[i]); msgb_free(msg); return NULL; } LOGP(DLINP, LOGL_INFO, " tag %d: %s\n", ies_req[i], str); tag = msgb_put(msg, 3 + strlen(str) + 1); tag[0] = 0x00; tag[1] = 1 + strlen(str) + 1; tag[2] = ies_req[i]; memcpy(tag + 3, str, strlen(str) + 1); } ipa_prepend_header(msg, IPAC_PROTO_IPACCESS); return msg; } /*! Generate IPA CCM ID RESP based on request payload * \param[in] dev Descriptor describing identity data for response * \param[in] data Payload of the IPA CCM ID GET request * \param[in] len Length of \a data in octets * \returns Message buffer with IPA CCM ID RESP */ struct msgb *ipa_ccm_make_id_resp_from_req(const struct ipaccess_unit *dev, const uint8_t *data, unsigned int len) { uint8_t ies[len/2]; unsigned int num_ies = 0; const uint8_t *cur = data; memset(ies, 0, sizeof(ies)); /* build a array of the IEIs */ while (len >= 2) { uint8_t t_len, t_tag; len -= 2; /* subtract the length of the two bytes read below */ t_len = *cur++; t_tag = *cur++; /* as the 'tag' is included in the length of t_len, this cannot happen */ if (t_len == 0) break; if (t_len > len + 1) { LOGP(DLINP, LOGL_ERROR, "IPA CCM tag 0x%02x does not fit\n", t_tag); break; } ies[num_ies++] = t_tag; /* we need to subtract one from t_len to account for the tag */ cur += t_len - 1; /* prevent any unsigned integer underflow due to somebody sending us * messages with wrong length values */ if (len <= t_len) len = 0; else len -= t_len - 1; } return ipa_ccm_make_id_resp(dev, ies, num_ies); } int ipa_send(int fd, const void *msg, size_t msglen) { int ret; ret = write(fd, msg, msglen); if (ret < 0) return -errno; if (ret < msglen) { LOGP(DLINP, LOGL_ERROR, "ipa_send: short write\n"); return -EIO; } return ret; } int ipa_ccm_send_pong(int fd) { return ipa_send(fd, ipa_pong_msg, sizeof(ipa_pong_msg)); } int ipa_ccm_send_id_ack(int fd) { return ipa_send(fd, ipa_id_ack_msg, sizeof(ipa_id_ack_msg)); } int ipa_ccm_send_id_req(int fd) { return ipa_send(fd, ipa_id_req_msg, sizeof(ipa_id_req_msg)); } /* base handling of the ip.access protocol */ int ipa_ccm_rcvmsg_base(struct msgb *msg, struct osmo_fd *bfd) { uint8_t msg_type = *(msg->l2h); int ret; switch (msg_type) { case IPAC_MSGT_PING: ret = ipa_ccm_send_pong(bfd->fd); if (ret < 0) { LOGP(DLINP, LOGL_ERROR, "Cannot send PONG " "message. Reason: %s\n", strerror(errno)); break; } ret = 1; break; case IPAC_MSGT_PONG: DEBUGP(DLMI, "PONG!\n"); ret = 1; break; case IPAC_MSGT_ID_ACK: DEBUGP(DLMI, "ID_ACK? -> ACK!\n"); ret = ipa_ccm_send_id_ack(bfd->fd); if (ret < 0) { LOGP(DLINP, LOGL_ERROR, "Cannot send ID_ACK " "message. Reason: %s\n", strerror(errno)); break; } ret = 1; break; default: /* This is not an IPA PING, PONG or ID_ACK message */ ret = 0; break; } return ret; } /* base handling of the ip.access protocol */ int ipa_ccm_rcvmsg_bts_base(struct msgb *msg, struct osmo_fd *bfd) { uint8_t msg_type = *(msg->l2h); int ret = 0; switch (msg_type) { case IPAC_MSGT_PING: ret = ipa_ccm_send_pong(bfd->fd); if (ret < 0) { LOGP(DLINP, LOGL_ERROR, "Cannot send PONG " "message. Reason: %s\n", strerror(errno)); } break; case IPAC_MSGT_PONG: DEBUGP(DLMI, "PONG!\n"); break; case IPAC_MSGT_ID_ACK: DEBUGP(DLMI, "ID_ACK\n"); break; } return ret; } void ipa_prepend_header_ext(struct msgb *msg, int proto) { struct ipaccess_head_ext *hh_ext; /* prepend the osmo ip.access header extension */ hh_ext = (struct ipaccess_head_ext *) msgb_push(msg, sizeof(*hh_ext)); hh_ext->proto = proto; } void ipa_prepend_header(struct msgb *msg, int proto) { struct ipaccess_head *hh; /* prepend the ip.access header */ hh = (struct ipaccess_head *) msgb_push(msg, sizeof(*hh)); hh->len = osmo_htons(msg->len - sizeof(*hh)); hh->proto = proto; } #ifdef HAVE_SYS_SOCKET_H #include /*! Read one ipa message from socket fd without caching not fully received * messages. See \ref ipa_msg_recv_buffered for further information. */ int ipa_msg_recv(int fd, struct msgb **rmsg) { int rc = ipa_msg_recv_buffered(fd, rmsg, NULL); if (rc < 0) { errno = -rc; rc = -1; } return rc; } /*! Read one ipa message from socket fd or store part if still not fully received. * \param[in] fd The fd for the socket to read from. * \param[out] rmsg internally allocated msgb containing a fully received ipa message. * \param[inout] tmp_msg internally allocated msgb caching data for not yet fully received message. * * As ipa can run on top of stream based protocols such as TCP, there's the * possibility that such lower layers split ipa messages in several low level * packets. If a low layer packet is received containing several ipa frames, * this function will pull from the socket and return only the first one * available in the stream. As the socket will remain with data, it will * trigger again during next select() and then this function will fetch the * next ipa message, and so on. * * \returns -EAGAIN and allocated tmp_msg if message was not yet fully * received. Other negative values indicate an error and cached msgb will be * freed. 0 if socket is found dead. Positive value indicating l2 msgb len and * rmsg pointing to internally allocated msgb containing the ipa frame on * scucess. */ int ipa_msg_recv_buffered(int fd, struct msgb **rmsg, struct msgb **tmp_msg) { struct msgb *msg = tmp_msg ? *tmp_msg : NULL; struct ipaccess_head *hh; int len, ret; int needed; if (msg == NULL) { msg = ipa_msg_alloc(0); if (msg == NULL) { ret = -ENOMEM; goto discard_msg; } msg->l1h = msg->tail; } if (msg->l2h == NULL) { /* first read our 3-byte header */ needed = sizeof(*hh) - msg->len; ret = recv(fd, msg->tail, needed, 0); if (ret == 0) goto discard_msg; if (ret < 0) { if (errno == EAGAIN || errno == EINTR) ret = 0; else { ret = -errno; goto discard_msg; } } msgb_put(msg, ret); if (ret < needed) { if (msg->len == 0) { ret = -EAGAIN; goto discard_msg; } LOGP(DLINP, LOGL_INFO, "Received part of IPA message header (%d/%zu)\n", msg->len, sizeof(*hh)); if (!tmp_msg) { ret = -EIO; goto discard_msg; } *tmp_msg = msg; return -EAGAIN; } msg->l2h = msg->tail; } hh = (struct ipaccess_head *) msg->data; /* then read the length as specified in header */ len = osmo_ntohs(hh->len); if (len < 0 || IPA_ALLOC_SIZE < len + sizeof(*hh)) { LOGP(DLINP, LOGL_ERROR, "bad message length of %d bytes, " "received %d bytes\n", len, msg->len); ret = -EIO; goto discard_msg; } needed = len - msgb_l2len(msg); if (needed > 0) { ret = recv(fd, msg->tail, needed, 0); if (ret == 0) goto discard_msg; if (ret < 0) { if (errno == EAGAIN || errno == EINTR) ret = 0; else { ret = -errno; goto discard_msg; } } msgb_put(msg, ret); if (ret < needed) { LOGP(DLINP, LOGL_INFO, "Received part of IPA message L2 data (%d/%d)\n", msgb_l2len(msg), len); if (!tmp_msg) { ret = -EIO; goto discard_msg; } *tmp_msg = msg; return -EAGAIN; } } ret = msgb_l2len(msg); if (ret == 0) { LOGP(DLINP, LOGL_INFO, "Discarding IPA message without payload\n"); ret = -EAGAIN; goto discard_msg; } if (tmp_msg) *tmp_msg = NULL; *rmsg = msg; return ret; discard_msg: if (tmp_msg) *tmp_msg = NULL; msgb_free(msg); return ret; } #endif /* SYS_SOCKET_H */ struct msgb *ipa_msg_alloc(int headroom) { struct msgb *nmsg; headroom += sizeof(struct ipaccess_head); nmsg = msgb_alloc_headroom(1200 + headroom, headroom, "IPA Multiplex"); if (!nmsg) return NULL; return nmsg; } /*! @} */