/* OpenBSC Abis input driver for ip.access */ /* (C) 2009 by Harald Welte * (C) 2010 by Holger Hans Peter Freyther * (C) 2010 by On-Waves * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #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]; } int ipa_ccm_idtag_parse(struct tlv_parsed *dec, unsigned char *buf, int len) { uint8_t t_len; uint8_t t_tag; uint8_t *cur = buf; memset(dec, 0, sizeof(*dec)); while (len >= 2) { len -= 2; t_len = *cur++; t_tag = *cur++; if (t_len > len + 1) { LOGP(DLMI, LOGL_ERROR, "The tag does not fit: %d\n", t_len); return -EINVAL; } DEBUGPC(DLMI, "%s='%s' ", ipa_ccm_idtag_name(t_tag), cur); dec->lv[t_tag].len = t_len; dec->lv[t_tag].val = cur; cur += t_len; len -= t_len; } 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; } 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)) ud->serno = talloc_strdup(ud, (char *) TLVP_VAL(tp, IPAC_IDTAG_SERNR)); if (TLVP_PRES_LEN(tp, IPAC_IDTAG_UNITNAME, 1)) ud->unit_name = talloc_strdup(ud, (char *) TLVP_VAL(tp, IPAC_IDTAG_UNITNAME)); if (TLVP_PRES_LEN(tp, IPAC_IDTAG_LOCATION1, 1)) ud->location1 = talloc_strdup(ud, (char *) TLVP_VAL(tp, IPAC_IDTAG_LOCATION1)); if (TLVP_PRES_LEN(tp, IPAC_IDTAG_LOCATION2, 1)) ud->location2 = talloc_strdup(ud, (char *) TLVP_VAL(tp, IPAC_IDTAG_LOCATION2)); if (TLVP_PRES_LEN(tp, IPAC_IDTAG_EQUIPVERS, 1)) ud->equipvers = talloc_strdup(ud, (char *) TLVP_VAL(tp, IPAC_IDTAG_EQUIPVERS)); if (TLVP_PRES_LEN(tp, IPAC_IDTAG_SWVERSION, 1)) ud->swversion = talloc_strdup(ud, (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; } int ipa_send(int fd, const void *msg, size_t msglen) { int ret; ret = write(fd, msg, msglen); if (ret < 0) return ret; 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 PING " "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 = htons(msg->len - sizeof(*hh)); hh->proto = proto; } 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; } 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 = 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; } struct msgb *ipa_msg_alloc(int headroom) { struct msgb *nmsg; headroom += sizeof(struct ipaccess_head); nmsg = msgb_alloc_headroom(1200 + headroom, headroom, "Abis/IP"); if (!nmsg) return NULL; return nmsg; }