diff options
Diffstat (limited to 'src/gsm')
-rw-r--r-- | src/gsm/Makefile.am | 2 | ||||
-rw-r--r-- | src/gsm/ipaccess.c | 447 |
2 files changed, 448 insertions, 1 deletions
diff --git a/src/gsm/Makefile.am b/src/gsm/Makefile.am index 94729c91..06b1f18a 100644 --- a/src/gsm/Makefile.am +++ b/src/gsm/Makefile.am @@ -19,7 +19,7 @@ libosmogsm_la_SOURCES = a5.c rxlev_stat.c tlv_parser.c comp128.c comp128v23.c \ auth_core.c auth_comp128v1.c auth_comp128v23.c \ auth_milenage.c milenage/aes-encblock.c \ milenage/aes-internal.c milenage/aes-internal-enc.c \ - milenage/milenage.c gan.c + milenage/milenage.c gan.c ipaccess.c libosmogsm_la_LDFLAGS = $(LTLDFLAGS_OSMOGSM) -version-info $(LIBVERSION) -no-undefined libosmogsm_la_LIBADD = $(top_builddir)/src/libosmocore.la diff --git a/src/gsm/ipaccess.c b/src/gsm/ipaccess.c new file mode 100644 index 00000000..cddbd534 --- /dev/null +++ b/src/gsm/ipaccess.c @@ -0,0 +1,447 @@ +/* OpenBSC Abis input driver for ip.access */ + +/* (C) 2009 by Harald Welte <laforge@gnumonks.org> + * (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 <http://www.gnu.org/licenses/>. + * + */ + +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <stdlib.h> + +#include <arpa/inet.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/macaddr.h> +#include <osmocom/core/select.h> + +#include <osmocom/gsm/tlv.h> +#include <osmocom/gsm/protocol/ipaccess.h> +#include <osmocom/gsm/ipaccess.h> + +#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 *ipaccess_idtag_name(uint8_t tag) +{ + if (tag >= ARRAY_SIZE(idtag_names)) + return "unknown"; + + return idtag_names[tag]; +} + +int ipaccess_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' ", ipaccess_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 ipaccess_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 ipaccess_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 = ipaccess_parse_unitid((char *) + TLVP_VAL(tp, IPAC_IDTAG_UNIT), ud); + +out: + return rc; +} + +int ipaccess_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, "ipaccess_send: short write\n"); + return -EIO; + } + return ret; +} + +int ipaccess_send_pong(int fd) +{ + return ipaccess_send(fd, ipa_pong_msg, sizeof(ipa_pong_msg)); +} + +int ipaccess_send_id_ack(int fd) +{ + return ipaccess_send(fd, ipa_id_ack_msg, sizeof(ipa_id_ack_msg)); +} + +int ipaccess_send_id_req(int fd) +{ + return ipaccess_send(fd, ipa_id_req_msg, sizeof(ipa_id_req_msg)); +} + +/* base handling of the ip.access protocol */ +int ipaccess_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 = ipaccess_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 = ipaccess_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 ipaccess_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 = ipaccess_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 ipaccess_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 ipaccess_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/%d)\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; +} |