summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/gsm/Makefile.am2
-rw-r--r--src/gsm/ipaccess.c447
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;
+}