diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | include/osmo-bts/Makefile.am | 2 | ||||
-rw-r--r-- | include/osmo-bts/oml_router_ctrl.h | 30 | ||||
-rw-r--r-- | include/osmo-bts/oml_routing.h | 51 | ||||
-rw-r--r-- | include/osmo-bts/vty.h | 1 | ||||
-rw-r--r-- | src/common/Makefile.am | 6 | ||||
-rw-r--r-- | src/common/msg_utils.c | 18 | ||||
-rw-r--r-- | src/common/oml_router.c | 875 | ||||
-rw-r--r-- | src/common/oml_router_ctrl.c | 95 | ||||
-rw-r--r-- | src/common/oml_routing.c | 294 |
10 files changed, 1364 insertions, 9 deletions
@@ -24,6 +24,7 @@ core.* contrib/sysmobts-calib/sysmobts-calib +src/common/osmobts-omlrouter src/osmo-bts-sysmo/l1fwd-proxy src/osmo-bts-sysmo/sysmobts src/osmo-bts-sysmo/sysmobts-remote diff --git a/include/osmo-bts/Makefile.am b/include/osmo-bts/Makefile.am index b2941444..7fb14f7f 100644 --- a/include/osmo-bts/Makefile.am +++ b/include/osmo-bts/Makefile.am @@ -1,3 +1,3 @@ noinst_HEADERS = abis.h bts.h bts_model.h gsm_data.h logging.h measurement.h \ oml.h paging.h rsl.h signal.h vty.h amr.h pcu_if.h pcuif_proto.h \ - handover.h msg_utils.h tx_power.h + handover.h msg_utils.h tx_power.h oml_routing.h oml_router_ctrl.h diff --git a/include/osmo-bts/oml_router_ctrl.h b/include/osmo-bts/oml_router_ctrl.h new file mode 100644 index 00000000..7fe71dff --- /dev/null +++ b/include/osmo-bts/oml_router_ctrl.h @@ -0,0 +1,30 @@ +#ifndef OML_ROUTER_CTRL_H +#define OML_ROUTER_CTRL_H + +enum osmo_omlrctrl_msgtype { + OSMO_ORC_MSGT_REGISTER_REQ = 1, + OSMO_ORC_MSGT_REGISTER_ACK = 2, + OSMO_ORC_MSGT_REGISTER_NACK = 3, + + OSMO_ORC_MSGT_ROUTE_ADD_REQ = 4, + OSMO_ORC_MSGT_ROUTE_ADD_ACK = 5, + OSMO_ORC_MSGT_ROUTE_ADD_NACK = 6, + + OSMO_ORC_MSGT_ROUTE_DEL_REQ = 7, + OSMO_ORC_MSGT_ROUTE_DEL_ACK = 8, + OSMO_ORC_MSGT_ROUTE_DEL_NACK = 9, +}; + +struct osmo_omlrctrl_hdr { + uint8_t version; + uint8_t msg_type; + uint16_t data_len; + uint8_t data[0]; +} __attribute__((packed)); + +struct osmo_omlrctrl_register_req { + uint8_t name_len; + char name[0]; +} __attribute__((packed)); + +#endif diff --git a/include/osmo-bts/oml_routing.h b/include/osmo-bts/oml_routing.h new file mode 100644 index 00000000..9d50881f --- /dev/null +++ b/include/osmo-bts/oml_routing.h @@ -0,0 +1,51 @@ +#ifndef OML_ROUTING_H +#define OML_ROUTING_H + +#include <stdint.h> +#include <osmocom/gsm/abis_nm.h> + +enum oml_routing_flags { + OML_RTF_MDISC = 0x00000001, + OML_RTF_OBJ_CLASS = 0x00000002, + OML_RTF_BTS_NR = 0x00000004, + OML_RTF_TRX_NR = 0x00000008, + OML_RTF_TS_NR = 0x00000010, + OML_RTF_VENDOR = 0x00000020, +}; + +struct oml_routing_key { + uint8_t mdisc; /* abis_om_hdr.mdisc */ + uint8_t obj_class; /* abis_om_fom_hdr.obj_class */ + struct abis_om_obj_inst obj_inst; /* abis_om_fom_hdr.obj_inst */ + uint8_t vendor_lv[64]; /* vendor length-value */ +} __attribute__ ((packed)); + +struct oml_route { + uint32_t flags; /* bitmask of oml_routing_flags */ + struct oml_routing_key key; +} __attribute ((packed)); + +struct oml_routing_inst; +struct oml_client; + +/* an OML route as it is used internally */ +struct oml_route_entry { + struct llist_head list; + struct oml_route route; + struct oml_client *client; +}; + +int oml_route_add(struct oml_routing_inst *inst, const struct oml_route *route, + struct oml_client *client); + +int oml_route_del(struct oml_routing_inst *inst, const struct oml_route *route); +int oml_route_del_client(struct oml_routing_inst *inst, const struct oml_client *client); + +void *oml_route(struct oml_routing_inst *inst, + const struct oml_routing_key *key); + +struct oml_routing_inst *oml_route_init(void *ctx, void *priv); + +char *oml_route_client_name(struct oml_client *client); + +#endif diff --git a/include/osmo-bts/vty.h b/include/osmo-bts/vty.h index 1bc7e718..48303d45 100644 --- a/include/osmo-bts/vty.h +++ b/include/osmo-bts/vty.h @@ -7,6 +7,7 @@ enum bts_vty_node { BTS_NODE = _LAST_OSMOVTY_NODE + 1, TRX_NODE, + OMLR_NODE, }; extern struct cmd_element ournode_exit_cmd; diff --git a/src/common/Makefile.am b/src/common/Makefile.am index 10627e2a..c9173158 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -8,3 +8,9 @@ libbts_a_SOURCES = gsm_data_shared.c sysinfo.c logging.c abis.c oml.c bts.c \ load_indication.c pcu_sock.c handover.c msg_utils.c \ load_indication.c pcu_sock.c handover.c msg_utils.c \ tx_power.c bts_ctrl_commands.c bts_ctrl_lookup.c + +bin_PROGRAMS = osmobts-omlrouter + +osmobts_omlrouter_SOURCES = oml_router.c msg_utils.c oml_routing.c +osmobts_omlrouter_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOGSM_LIBS) + diff --git a/src/common/msg_utils.c b/src/common/msg_utils.c index f3cba090..22a3012a 100644 --- a/src/common/msg_utils.c +++ b/src/common/msg_utils.c @@ -103,13 +103,6 @@ int msg_verify_ipa_structure(struct msgb *msg) hh = (struct ipaccess_head *) msg->l1h; - if (hh->proto != IPAC_PROTO_OML) { - LOGP(DL1C, LOGL_ERROR, - "Incorrect ipa header protocol 0x%x 0x%x\n", - hh->proto, IPAC_PROTO_OML); - return -1; - } - if (ntohs(hh->len) != msgb_l1len(msg) - sizeof(struct ipaccess_head)) { LOGP(DL1C, LOGL_ERROR, "Incorrect ipa header msg size %d %d\n", @@ -117,7 +110,16 @@ int msg_verify_ipa_structure(struct msgb *msg) return -1; } - msg->l2h = hh->data; + if (hh->proto == IPAC_PROTO_OSMO) { + struct ipaccess_head_ext *hh_ext = (struct ipaccess_head_ext *) hh->data; + if (ntohs(hh->len) < sizeof(*hh_ext)) { + LOGP(DL1C, LOGL_ERROR, "IPA length shorter than OSMO header\n"); + return -1; + } + msg->l2h = hh_ext->data; + } else + msg->l2h = hh->data; + return 0; } diff --git a/src/common/oml_router.c b/src/common/oml_router.c new file mode 100644 index 00000000..b9dbdb1b --- /dev/null +++ b/src/common/oml_router.c @@ -0,0 +1,875 @@ +/* OML Message Router (server side) */ + +/* (C) 2014 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 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 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 <stdio.h> + +#include <sys/socket.h> +#include <sys/signal.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/application.h> +#include <osmocom/core/select.h> +#include <osmocom/core/macaddr.h> +#include <osmocom/gsm/tlv.h> +#include <osmocom/gsm/protocol/gsm_12_21.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/abis/abis.h> +#include <osmocom/abis/e1_input.h> +#include <osmocom/abis/ipa.h> +#include <osmocom/gsm/protocol/ipaccess.h> +#include <osmocom/gsm/ipa.h> + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/telnet_interface.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/msg_utils.h> +#include <osmo-bts/oml_routing.h> +#include <osmo-bts/oml_router_ctrl.h> +#include <osmo-bts/vty.h> + +#include "../../btsconfig.h" + +static const char *config_file = "osmobts-omlrouter.cfg"; +static int daemonize = 0; + +enum bsc_link_state { + BSC_LS_WAIT_RECONNECT, + BSC_LS_CONNECTING, + BSC_LS_CONNECTED, +}; + +static const struct value_string bsc_ls_names[] = { + { BSC_LS_WAIT_RECONNECT, "waiting for reconnect" }, + { BSC_LS_CONNECTING, "attempting to connect" }, + { BSC_LS_CONNECTED, "connected" }, + { 0, NULL } +}; + +struct oml_router { + /* config */ + char *bsc_oml_host; + uint16_t bsc_oml_port; + char *listen_host; + uint16_t listen_port; + unsigned int bsc_reconnect_secs; + + /* state */ + + struct oml_routing_inst *routing; + /* state of the BSC connection */ + enum bsc_link_state bsc_link_state; + /* BSC connection */ + struct ipa_client_conn *bsc_conn; + /* BSC re-connection timer */ + struct osmo_timer_list bsc_recon_timer; + /* server listening for clients */ + struct ipa_server_link *server_link; + /* list of clients connected to us */ + struct llist_head clients; +}; + +struct oml_client { + struct llist_head list; + struct ipa_server_conn *conn; + struct oml_router *router; + struct ipaccess_unit *unit_data; + char *name; +}; + +char *oml_route_client_name(struct oml_client *cl) +{ + struct ipaccess_unit *ud = cl->unit_data; + static char outbuf[256]; + + snprintf(outbuf, sizeof(outbuf)-1, "%d/%d/%d(%s)", + ud->site_id, ud->bts_id, ud->trx_id, + cl->name ? cl->name : "NULL"); + outbuf[sizeof(outbuf)-1] = '\0'; + + return outbuf; +} + +char *oml_route_client_addr(struct oml_client *cl) +{ + static char outbuf[256]; + struct sockaddr_in sin; + socklen_t slen = sizeof(sin); + + getpeername(cl->conn->ofd.fd, (struct sockaddr *) &sin, &slen); + snprintf(outbuf, sizeof(outbuf)-1, "%s:%u", + inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); + outbuf[sizeof(outbuf)-1] = '\0'; + + return outbuf; +} + +static struct ipaccess_unit bts_dev_info = { + .unit_name = "sysmoBTS", + .equipvers = "", /* FIXME: read this from hw */ + .swversion = PACKAGE_VERSION, + .location1 = "", + .location2 = "", + .serno = "", /* FIXME: read this from hw */ +}; + +/* We shouldn't need this, but there's no 'private data' inside an e1inp_line */ +static struct oml_router *g_inst; + +#define ORC_HEADROOM 20 + +static struct msgb *orc_msgb_alloc(void) +{ + unsigned int headroom; + + headroom = ORC_HEADROOM + sizeof(struct ipaccess_head) + + sizeof(struct ipaccess_head_ext); + + return msgb_alloc_headroom(1200 + headroom, headroom, "OML Router Ctrl"); +} + +/* push ipa_ext and ipa header */ +static void orc_push_header(struct msgb *msg) +{ + struct ipaccess_head_ext *he; + + he = (struct ipaccess_head_ext *) msgb_push(msg, sizeof(*he)); + he->proto = IPAC_PROTO_EXT_ORC; + + ipa_msg_push_header(msg, IPAC_PROTO_OSMO); +} + +/* push ipa_ext and ipa header + send to client */ +static void orc_client_send(struct oml_client *client, struct msgb *msg) +{ + orc_push_header(msg); + ipa_server_conn_send(client->conn, msg); +} + +/* Send ORC ACK to client */ +static void client_send_ack(struct oml_client *client, + enum osmo_omlrctrl_msgtype type) +{ + struct msgb *msg = orc_msgb_alloc(); + struct osmo_omlrctrl_hdr *oh; + + oh = (struct osmo_omlrctrl_hdr *) msgb_put(msg, sizeof(*oh)); + oh->version = 0; + oh->msg_type = type; + oh->data_len = 0; + + orc_client_send(client, msg); +} + +/* Send ORC NACK to client */ +static void client_send_nack(struct oml_client *client, + enum osmo_omlrctrl_msgtype type, + uint8_t cause, char *diag) +{ + struct msgb *msg = orc_msgb_alloc(); + struct osmo_omlrctrl_hdr *oh; + unsigned int d_len = 0, payload_len = 1; + + /* optional diagnostics LV at end of message */ + if (diag) { + d_len = strlen(diag); + payload_len += d_len + 2; /* NULL + Length */ + } + + oh = (struct osmo_omlrctrl_hdr *) + msgb_put(msg, sizeof(*oh)+payload_len); + oh->version = 0; + oh->msg_type = type; + oh->data_len = payload_len; + /* first byte is cause */ + oh->data[0] = cause; + /* followed by optional diagnostics string as LV */ + if (d_len) { + oh->data[1] = d_len; + memcpy(oh->data+2, diag, d_len); + } + + orc_client_send(client, msg); +} + +/* process incoming ORC REGISTER recquest from client */ +static int client_rx_orc_register(struct oml_client *client, + struct msgb *msg) +{ + struct osmo_omlrctrl_register_req *rr = + (struct osmo_omlrctrl_register_req *) msgb_l3(msg); + + if (msgb_l3len(msg) < sizeof(*rr)) { + client_send_nack(client, OSMO_ORC_MSGT_REGISTER_NACK, + 0xff, "Short header"); + return -1; + } + + if (rr->name_len && rr->name[rr->name_len-1] != '\0') { + client_send_nack(client, OSMO_ORC_MSGT_REGISTER_NACK, + 0xff, "Name without NULL termination"); + return -1; + } + + /* store client identity parameters */ + if (rr->name_len) { + talloc_free(client->name); + client->name = talloc_strndup(client, rr->name, rr->name_len); + } + + LOGP(DOML, LOGL_INFO, "Client %s registered as %s\n", + oml_route_client_addr(client), + oml_route_client_name(client)); + + client_send_ack(client, OSMO_ORC_MSGT_REGISTER_ACK); + + return 0; +} + +/* process incoming ORC message from client */ +static int ipa_omlrouter_rcvmsg(struct oml_client *client, struct msgb *msg) +{ + struct osmo_omlrctrl_hdr *oh = + (struct osmo_omlrctrl_hdr *) msgb_l2(msg); + struct oml_route *rt; + int rc; + + /* FIXME: size / consistency checks */ + msg->l3h = oh->data; + + switch (oh->msg_type) { + case OSMO_ORC_MSGT_REGISTER_REQ: + rc = client_rx_orc_register(client, msg); + break; + case OSMO_ORC_MSGT_ROUTE_ADD_REQ: + DEBUGP(DOML, "route add request\n"); + if (oh->data_len < sizeof(*rt)) { + LOGP(DOML, LOGL_ERROR, "route too small (%u < %lu)\n", + oh->data_len, sizeof(*rt)); + client_send_nack(client, OSMO_ORC_MSGT_ROUTE_ADD_NACK, + 0, "Route size mismatch"); + goto err; + } + rt = (struct oml_route *) &oh->data; + rc = oml_route_add(client->router->routing, rt, client); + if (rc < 0) + client_send_nack(client, OSMO_ORC_MSGT_ROUTE_ADD_NACK, + -rc, "Could not add route"); + else + client_send_ack(client, OSMO_ORC_MSGT_ROUTE_ADD_ACK); + break; + case OSMO_ORC_MSGT_ROUTE_DEL_REQ: + if (oh->data_len <= sizeof(*rt)) + goto err; + rt = (struct oml_route *) &oh->data; + rc = oml_route_del(client->router->routing, rt); + if (rc < 0) + client_send_nack(client, OSMO_ORC_MSGT_ROUTE_DEL_NACK, + -rc, "Could not delete route"); + else + client_send_ack(client, OSMO_ORC_MSGT_ROUTE_DEL_ACK); + break; + } + +err: + msgb_free(msg); + return rc; +} + +static int client_rx_ipa_ccm(struct oml_client *client, struct msgb *msg) +{ + struct tlv_parsed tp; + uint8_t msg_type = *(msg->l2h); + int rc; + + /* deal with PING/PONG/ACK */ + rc = ipa_ccm_rcvmsg_base(msg, &client->conn->ofd); + if (rc == 1) { + /* message was handled by rcvmsg_base */ + return 0; + } + + /* messages that we have to handle locally */ + switch (msg_type) { + case IPAC_MSGT_ID_RESP: + rc = ipa_ccm_idtag_parse(&tp, msgb_l2(msg)+2, + msgb_l2len(msg)-2); + if (rc < 0) + return rc; + + /* store result in client structure */ + rc = ipa_ccm_tlv_to_unitdata(client->unit_data, &tp); + if (rc < 0) + return rc; + + if (client->unit_data->site_id != bts_dev_info.site_id) { + LOGP(DOML, LOGL_ERROR, "Client %s Wrong Site ID " + "(client %u != our %u); Closing\n", + oml_route_client_addr(client), + client->unit_data->site_id, bts_dev_info.site_id); + ipa_server_conn_destroy(client->conn); + return -1; + } + break; + default: + LOGP(DOML, LOGL_NOTICE, "Not handled IPA CCM from client\n"); + return -1; + } + + return rc; +} + +/* one of the clients has sent data to us */ +static int client_data_cb(struct ipa_server_conn *conn, struct msgb *msg) +{ + struct oml_client *client = conn->data; + struct ipaccess_head *hh = (struct ipaccess_head *) msgb_l1(msg); + struct ipaccess_head_ext *hh_ext; + int rc; + + DEBUGP(DOML, "Received data from client: %s\n", + osmo_hexdump(msgb_data(msg), msgb_length(msg))); + + /* regular message handling */ + rc = msg_verify_ipa_structure(msg); + if (rc < 0) { + LOGP(DOML, LOGL_ERROR, + "Invalid IPA message from client (rc=%d)\n", rc); + goto err; + } + + switch (hh->proto) { + case IPAC_PROTO_IPACCESS: + rc = client_rx_ipa_ccm(client, msg); + msgb_free(msg); + break; + case IPAC_PROTO_OSMO: + hh_ext = (struct ipaccess_head_ext *) hh->data; + switch (hh_ext->proto) { + case IPAC_PROTO_EXT_ORC: + rc = ipa_omlrouter_rcvmsg(client, msg); + break; + default: + LOGP(DOML, LOGL_NOTICE, + "Unknown IPA-EXT protocol %u\n", hh_ext->proto); + msgb_free(msg); + break; + } + break; + case IPAC_PROTO_OML: + rc = msg_verify_oml_structure(msg); + if (rc < 0) { + LOGP(DOML, LOGL_ERROR, + "Invalid OML message from Client (rc=%d)\n", rc); + goto err; + } + /* forward message to BSC */ + ipa_client_conn_send(client->router->bsc_conn, msg); + break; + default: + LOGP(DOML, LOGL_NOTICE, "Unhandled stream ID %u from client\n", hh->proto); + msgb_free(msg); + break; + } + + return 0; +err: + msgb_free(msg); + return -1; +} + +/* a client connection was lost */ +static int client_closed_cb(struct ipa_server_conn *conn) +{ + struct oml_client *client = conn->data; + + oml_route_del_client(client->router->routing, client); + + llist_del(&client->list); + talloc_free(client); + + return 0; +} + +/* a new client has connected to our server */ +static int server_accept_cb(struct ipa_server_link *link, int fd) +{ + struct oml_router *inst = link->data; + struct oml_client *client = talloc_zero(link->data, struct oml_client); + + client->unit_data = talloc_zero(client, struct ipaccess_unit); + client->router = inst; + + client->conn = ipa_server_conn_create(link, link, fd, client_data_cb, + client_closed_cb, client); + if (!client->conn) { + talloc_free(client); + return -1; + } + + llist_add_tail(&client->list, &inst->clients); + + /* send IPA ID REQ */ + ipa_ccm_send_id_req(fd); + + return 0; +} + +/* schedule a re-connect towards the BSC */ +static void reschedule_bsc_connect(struct oml_router *inst) +{ + DEBUGP(DOML, "Re-scheduling BSC connect\n"); + osmo_timer_schedule(&inst->bsc_recon_timer, inst->bsc_reconnect_secs, 0); + inst->bsc_link_state = BSC_LS_CONNECTING; +} + +/* BSC re-connect timer call-back */ +static void bsc_recon_timer_cb(void *data) +{ + struct oml_router *inst = data; + + if (ipa_client_conn_open(inst->bsc_conn) < 0) { + LOGP(DOML, LOGL_NOTICE, "failed to connect to BSC\n"); + reschedule_bsc_connect(inst); + } +} + + +/* link to BSC has gone up or down */ +static void bsc_updown_cb(struct ipa_client_conn *link, int up) +{ + struct oml_router *inst = link->data; + + LOGP(DOML, LOGL_INFO, "BSC connection %s\n", up ? "up" : "down"); + + if (up) { + inst->bsc_link_state = BSC_LS_CONNECTED; + /* FIXME */ + } else { + reschedule_bsc_connect(link->data); + inst->bsc_link_state = BSC_LS_WAIT_RECONNECT; + } +} + +/* derive routing key from OML message */ +static void key_from_omlmsg(struct oml_routing_key *key, const struct msgb *msg) +{ + struct abis_om_hdr *oh = (struct abis_om_hdr *) msgb_l2(msg); + struct abis_om_fom_hdr *foh; + + memset(key, 0, sizeof(*key)); + + key->mdisc = oh->mdisc; + switch (oh->mdisc) { + case ABIS_OM_MDISC_FOM: + foh = (struct abis_om_fom_hdr *) oh->data; + key->obj_class = foh->obj_class; + memcpy(&key->obj_inst, &foh->obj_inst, sizeof(key->obj_inst)); + break; + case ABIS_OM_MDISC_MANUF: + break; + } +} + +/* incoming data from the BSC */ +static int bsc_read_cb(struct ipa_client_conn *link, struct msgb *msg) +{ + int rc; + struct ipaccess_head *hh = (struct ipaccess_head *) msgb_l1(msg); + struct oml_router *inst = link->data; + struct oml_client *client; + struct oml_routing_key key; + + DEBUGP(DOML, "Received data from BSC: %s\n", + osmo_hexdump(msgb_data(msg), msgb_length(msg))); + + /* regular message handling */ + rc = msg_verify_ipa_structure(msg); + if (rc < 0) { + LOGP(DOML, LOGL_ERROR, + "Invalid IPA message from BSC (rc=%d)\n", rc); + goto err; + } + + switch (hh->proto) { + case IPAC_PROTO_IPACCESS: + /* handle the core IPA CCM messages in libosmoabis */ + rc = ipaccess_bts_handle_ccm(link, &bts_dev_info, msg); + if (rc == 0) + LOGP(DOML, LOGL_NOTICE, "Not handled IPA CCM from BSC\n"); + else if (rc < 0) + LOGP(DOML, LOGL_ERROR, "Error handling IPA CCM from BSC\n"); + msgb_free(msg); + break; + case IPAC_PROTO_OML: + rc = msg_verify_oml_structure(msg); + if (rc < 0) { + LOGP(DOML, LOGL_ERROR, + "Invalid OML message from BSC (rc=%d)\n", rc); + goto err; + } + + /* find route */ + key_from_omlmsg(&key, msg); + client = oml_route(inst->routing, &key); + if (!client) { + LOGP(DOML, LOGL_ERROR, "Cannot find route!\n"); + goto err; + } + /* send OML message to respective client */ + DEBUGP(DOML, "Routed to client %s\n", + oml_route_client_name(client)); + ipa_server_conn_send(client->conn, msg); + break; + default: + LOGP(DOML, LOGL_NOTICE, + "Unhandled stream ID %u from BSC\n", hh->proto); + msgb_free(msg); + break; + } + + return 0; +err: + msgb_free(msg); + return -1; +} + +DEFUN(show_bsc_link, show_bsc_link_cmd, + "show bsc-link", + SHOW_STR "Display information about our link to the BSC\n") +{ + vty_out(vty, "BSC OML destination at %s:%u, State: %s%s", + g_inst->bsc_oml_host, g_inst->bsc_oml_port, + get_value_string(bsc_ls_names, g_inst->bsc_link_state), + VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(show_clients, show_clients_cmd, + "show oml-clients", + SHOW_STR "Display information about OML clients\n") +{ + struct oml_client *cl; + + llist_for_each_entry(cl, &g_inst->clients, list) { + struct ipaccess_unit *ud = cl->unit_data; + vty_out(vty, "Client '%s' from %s%s", + oml_route_client_name(cl), + oml_route_client_addr(cl), VTY_NEWLINE); + vty_out(vty, " Name '%s', Serno '%s'%s", + ud->unit_name, ud->serno, VTY_NEWLINE); + vty_out(vty, " CCM Equipment Version: '%s', SW Version: '%s'%s", + ud->equipvers, ud->swversion, VTY_NEWLINE); + vty_out(vty, " CCM Location: '%s' / '%s'%s", + ud->location1, ud->location2, VTY_NEWLINE); + + } + + return CMD_SUCCESS; +} + +static struct cmd_node omlr_node = { + OMLR_NODE, + "%s(oml-router)# ", + 1, +}; + +DEFUN(cfg_omlr, cfg_omlr_cmd, + "oml-router", + "Configure the OML Router\n") +{ + vty->index = g_inst; + vty->node = OMLR_NODE; + + return CMD_SUCCESS; +} + +#define REMOTE_STR "Configuration of outbound OML connection\n" +#define LOCAL_STR "Configuration of OML server for inbound connections\n" + +DEFUN(cfg_oml_remote_ip, cfg_oml_remote_ip_cmd, + "remote connect-ip A.B.C.D", REMOTE_STR + "Configure remote (BSC) IP address for OML\n" + "IP address of BSC\n") +{ + struct oml_router *inst = vty->index; + + if (inst->bsc_oml_host) + talloc_free(inst->bsc_oml_host); + inst->bsc_oml_host = talloc_strdup(inst, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_oml_remote_port, cfg_oml_remote_port_cmd, + "remote connect-port <0-65535>", REMOTE_STR + "Configure remote (BSC) TCP port for OML\n") +{ + struct oml_router *inst = vty->index; + + inst->bsc_oml_port = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_oml_reconnect_secs, cfg_oml_reconnect_secs_cmd, + "remote reconnect-timer <0-65535>", REMOTE_STR + "Reconnect interval for OML connection to BSC\n") +{ + struct oml_router *inst = vty->index; + + inst->bsc_reconnect_secs = atoi(argv[0]); + + return CMD_SUCCESS; +} + + +DEFUN(cfg_oml_local_ip, cfg_oml_local_ip_cmd, + "local listen-ip A.B.C.D", LOCAL_STR + "Configure local listen IP address for incoming OML connections\n" + "local IP address for incoming OML connections\n") +{ + struct oml_router *inst = vty->index; + + if (inst->listen_host) + talloc_free(inst->listen_host); + inst->listen_host = talloc_strdup(inst, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_oml_local_port, cfg_oml_local_port_cmd, + "local listen-port <0-65535>", LOCAL_STR + "Configure local listen TCP port for incoming OML connections\n" + "local TCP port for incoming OML connections\n") +{ + struct oml_router *inst = vty->index; + + inst->listen_port = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ipa_unit_id, cfg_ipa_unit_id_cmd, + "ipa unit-id <0-65535> <0-255>", + "Configure IPA parameters\n" + "Site ID for this OML router\n" + "BTS ID for this OML router\n") +{ + bts_dev_info.site_id = atoi(argv[0]); + bts_dev_info.bts_id = atoi(argv[1]); + + return CMD_SUCCESS; +} + +static int config_write_omlr(struct vty *vty) +{ + struct oml_router *inst = g_inst; + + vty_out(vty, "oml-router%s", VTY_NEWLINE); + vty_out(vty, " ipa unit-id %u %u%s", bts_dev_info.site_id, + bts_dev_info.bts_id, VTY_NEWLINE); + vty_out(vty, " local listen-ip %s%s", + inst->listen_host, VTY_NEWLINE); + vty_out(vty, " local listen-port %u%s", + inst->listen_port, VTY_NEWLINE); + vty_out(vty, " remote connect-ip %s%s", + inst->bsc_oml_host, VTY_NEWLINE); + vty_out(vty, " remote connect-port %u%s", + inst->bsc_oml_port, VTY_NEWLINE); + vty_out(vty, " remote reconnect-timer %u%s", + inst->bsc_reconnect_secs, VTY_NEWLINE); + + return CMD_SUCCESS; +} + +static void *router_ctx; + +struct log_info_cat router_cat[] = { + [DOML] = { + .name = "OML", + .description = "A-bis OML (TS 12.21)", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, +}; + +static struct log_info router_log_info = { + .filter_fn = NULL, + .cat = router_cat, + .num_cat = ARRAY_SIZE(router_cat), +}; + +static enum node_type omlr_vty_go_parent(struct vty *vty) +{ + switch (vty->node) { + case OMLR_NODE: + vty->node = CONFIG_NODE; + break; + default: + vty->node = CONFIG_NODE; + } + return vty->node; +} + +static int omlr_vty_is_config_node(struct vty *vty, int node) +{ + switch (node) { + case OMLR_NODE: + return 1; + default: + return 0; + } +} + +static const char copyright[] = + "Copyright (C) 2014 by Harald Welte\r\n" + "License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n" + "This is free software: you are free to change and redistribute it.\r\n" + "There is NO WARRANTY, to the extent permitted by law.\r\n"; + + +static struct vty_app_info vty_info = { + .name = "OsmoBTS-OMLrouter", + .version = PACKAGE_VERSION, + .copyright = copyright, + .go_parent_cb = omlr_vty_go_parent, + .is_config_node = omlr_vty_is_config_node, +}; + +static void signal_handler(int signal) +{ + fprintf(stderr, "signal %u received\n", signal); + + switch (signal) { + case SIGUSR1: + case SIGUSR2: + talloc_report_full(router_ctx, stderr); + break; + default: + break; + } +} + +int main(int argc, char **argv) +{ + struct oml_router *inst; + int rc; + + router_ctx = talloc_named_const(NULL, 1, "OML router"); + + g_inst = inst = talloc_zero(router_ctx, struct oml_router); + + osmo_get_macaddr(bts_dev_info.mac_addr, "eth0"); + + inst->bsc_oml_host = talloc_strdup(inst, ""); + inst->bsc_oml_port = 3002; + inst->listen_host = talloc_strdup(inst, "0.0.0.0"); + inst->listen_port = 3002; + inst->bsc_reconnect_secs = 10; + + INIT_LLIST_HEAD(&inst->clients); + inst->bsc_recon_timer.cb = bsc_recon_timer_cb; + inst->bsc_recon_timer.data = inst; + + libosmo_abis_init(router_ctx); + + osmo_init_logging(&router_log_info); + + //vty_info.tall_ctx = router_ctx; + vty_init(&vty_info); + + install_node(&omlr_node, config_write_omlr); + install_element(CONFIG_NODE, &cfg_omlr_cmd); + install_element(OMLR_NODE, &cfg_oml_remote_ip_cmd); + install_element(OMLR_NODE, &cfg_oml_remote_port_cmd); + install_element(OMLR_NODE, &cfg_oml_reconnect_secs_cmd); + install_element(OMLR_NODE, &cfg_oml_local_ip_cmd); + install_element(OMLR_NODE, &cfg_oml_local_port_cmd); + install_element(OMLR_NODE, &cfg_ipa_unit_id_cmd); + + install_element_ve(&show_bsc_link_cmd); + install_element_ve(&show_clients_cmd); + telnet_init(router_ctx, NULL, 4230); + + rc = vty_read_config_file(config_file, NULL); + if (rc < 0) { + fprintf(stderr, "Failed to parse the config file: '%s'\n", + config_file); + exit(1); + } + + signal(SIGUSR1, &signal_handler); + signal(SIGUSR2, &signal_handler); + osmo_init_ignore_signals(); + + inst->routing = oml_route_init(g_inst, g_inst); + + inst->server_link = ipa_server_link_create(router_ctx, NULL, + inst->listen_host, inst->listen_port, + server_accept_cb, inst); + if (!inst->server_link) { + LOGP(DOML, LOGL_ERROR, "Cannot create server instance\n"); + return -1; + } + + + if (ipa_server_link_open(inst->server_link) < 0) { + LOGP(DOML, LOGL_ERROR, "Cannot bind server to TCP port %u\n", + inst->listen_port); + return -1; + } + + inst->bsc_conn = ipa_client_conn_create(router_ctx, NULL, 0, + inst->bsc_oml_host, + inst->bsc_oml_port, + bsc_updown_cb, bsc_read_cb, + NULL, inst); + if (!inst->bsc_conn) { + LOGP(DOML, LOGL_ERROR, "Cannot create client instance\n"); + return -1; + } + + /* attempt tp connect and re-schedule connect if needed */ + bsc_recon_timer_cb(inst); + + if (daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + exit(1); + } + } + + LOGP(DOML, LOGL_NOTICE, "entering main loop\n"); + + while (1) { + log_reset_context(); + osmo_select_main(0); + } + + return 0; +} diff --git a/src/common/oml_router_ctrl.c b/src/common/oml_router_ctrl.c new file mode 100644 index 00000000..018c5041 --- /dev/null +++ b/src/common/oml_router_ctrl.c @@ -0,0 +1,95 @@ + +#include <osmocom/core/msgb.h> +#include <osmocom/gsm/protocol/ipaccess.h> + +#include <osmo-bts/oml_routing.h> +#include <osmo-bts/oml_router_ctrl.h> +#include <osmo-bts/gsm_data.h> + +int abis_orc_sendmsg(struct msgb *msg) +{ + struct gsm_bts *bts = msg->trx->bts; + struct ipaccess_head_ext *he; + + he = (struct ipaccess_head_ext *) msgb_push(msg, sizeof(*he)); + he->proto = IPAC_PROTO_EXT_ORC; + + /* osmo-bts uses msg->trx internally, but libosmo-abis uses the + * signalling like at msg->dst */ + msg->dst = bts->osmo_link; + return abis_sendmsg(msg); +} + +static struct msgb *orc_msgb_alloc(void) +{ + return msgb_alloc_headroom(1000+40, 40, "OML Router Ctrl"); +} + +static struct msgb *__gen_orc_route(const struct oml_route *rt_in, int del) +{ + struct msgb *msg = orc_msgb_alloc(); + struct osmo_omlrctrl_hdr *oh; + struct oml_route *rt; + + oh = (struct osmo_omlrctrl_hdr *) msgb_put(msg, sizeof(*oh)); + rt = (struct oml_route *) msgb_put(msg, sizeof(*rt)); + + oh->version = 0; + if (del) + oh->msg_type = OSMO_ORC_MSGT_ROUTE_DEL_REQ; + else + oh->msg_type = OSMO_ORC_MSGT_ROUTE_ADD_REQ; + oh->data_len = sizeof(*rt); + + memcpy(rt, rt_in, sizeof (*rt)); + + return msg; +} + +struct msgb *gen_orc_route_add(const struct oml_route *rt_in) +{ + return __gen_orc_route(rt_in, 0); +} + +struct msgb *gen_orc_route_del(const struct oml_route *rt_in) +{ + return __gen_orc_route(rt_in, 1); +} + +int orc_add_route_mo(struct gsm_abis_mo *mo) +{ + struct oml_route rt; + struct msgb *msg; + + rt.flags = OML_RTF_MDISC | OML_RTF_OBJ_CLASS; + rt.key.mdisc = ABIS_OM_MDISC_FOM; + rt.key.obj_class = mo->obj_class; + + memcpy(&rt.key.obj_inst, &mo->obj_inst, sizeof(rt.key.obj_inst)); + + /* do w really need this? why not simply register for 0xFF ... */ + switch (mo->obj_class) { + case NM_OC_SITE_MANAGER: + break; + case NM_OC_BTS: + case NM_OC_GPRS_NSE: + case NM_OC_GPRS_CELL: + rt.flags |= OML_RTF_BTS_NR; + break; + case NM_OC_RADIO_CARRIER: + case NM_OC_BASEB_TRANSC: + case NM_OC_GPRS_NSVC: + rt.flags |= OML_RTF_BTS_NR | OML_RTF_TRX_NR; + break; + case NM_OC_CHANNEL: + rt.flags |= OML_RTF_BTS_NR | OML_RTF_TRX_NR | OML_RTF_TS_NR; + break; + } + + msg = gen_orc_route_add(&rt); + if (!msg) + return -1; + + msg->trx = mo->bts->c0; + return abis_orc_sendmsg(msg); +} diff --git a/src/common/oml_routing.c b/src/common/oml_routing.c new file mode 100644 index 00000000..3cab76ee --- /dev/null +++ b/src/common/oml_routing.c @@ -0,0 +1,294 @@ +/* OML Message routing for osmo-bts */ + +/* (C) 2014 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 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 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 <stdint.h> +#include <unistd.h> +#include <errno.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> + +#include <osmocom/gsm/abis_nm.h> +#include <osmocom/gsm/protocol/gsm_12_21.h> + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/oml_routing.h> + + +/* an OML router instance */ +struct oml_routing_inst { + struct llist_head routes; + void *priv; +}; + +/* FIXME: This must go! */ +static struct oml_routing_inst *g_inst; + +/* match given routing key against given route */ +static int oml_route_match(const struct oml_routing_key *key, + const struct oml_route *route) +{ + if (route->flags & OML_RTF_MDISC) + if (route->key.mdisc != key->mdisc) + return 0; + + if (route->flags & OML_RTF_OBJ_CLASS) { + if (route->key.obj_class != key->obj_class) + return 0; + if (route->key.obj_class == ABIS_OM_MDISC_MANUF && + route->flags & OML_RTF_VENDOR) { + if (route->key.vendor_lv[0] != key->vendor_lv[0]) + return 0; + if (memcmp(route->key.vendor_lv+1, key->vendor_lv+1, + OSMO_MIN(sizeof(route->key.vendor_lv)-1, + route->key.vendor_lv[0]))) + return 0; + } + } + + if (route->flags & OML_RTF_BTS_NR) + if (route->key.obj_inst.bts_nr != key->obj_inst.bts_nr) + return 0; + + if (route->flags & OML_RTF_TRX_NR) + if (route->key.obj_inst.trx_nr != key->obj_inst.trx_nr) + return 0; + + if (route->flags & OML_RTF_TS_NR) + if (route->key.obj_inst.ts_nr != key->obj_inst.ts_nr) + return 0; + + return 1; +} + +/* are two given routes identical? */ +static int oml_route_ident(const struct oml_route *a, const struct oml_route *b) +{ + if (a->flags != b->flags) + return 0; + + return oml_route_match(&a->key, b); +} + +/* add a route from a router instance */ +int oml_route_add(struct oml_routing_inst *inst, const struct oml_route *route, + struct oml_client *client) +{ + struct oml_route_entry *e; + + llist_for_each_entry(e, &inst->routes, list) { + if (oml_route_ident(route, &e->route)) + return -EEXIST; + } + + e = talloc_zero(inst, struct oml_route_entry); + memcpy(&e->route, route, sizeof(e->route)); + e->client = client; + /* FIXME: insert in order of integer of routing_flags */ + llist_add(&e->list, &inst->routes); + + return 0; +} + +/* delete a route from a router instance */ +int oml_route_del(struct oml_routing_inst *inst, const struct oml_route *route) +{ + struct oml_route_entry *e; + + /* no safe iteration needed as we stop at first match */ + llist_for_each_entry(e, &inst->routes, list) { + if (oml_route_ident(route, &e->route)) { + llist_del(&e->list); + talloc_free(e); + return 1; + } + } + + return -ENODEV; +} + +int oml_route_del_client(struct oml_routing_inst *inst, + const struct oml_client *client) +{ + struct oml_route_entry *e, *f; + int num = 0; + + /* no safe iteration needed as we stop at first match */ + llist_for_each_entry_safe(e, f, &inst->routes, list) { + if (e->client == client) { + llist_del(&e->list); + talloc_free(e); + num++; + } + } + return num; +} + + +/* perform routing of 'key' against router instance */ +void *oml_route(struct oml_routing_inst *inst, + const struct oml_routing_key *key) +{ + struct oml_route_entry *e; + + llist_for_each_entry(e, &inst->routes, list) { + if (oml_route_match(key, &e->route)) + return e->client; + } + + return NULL; +} + +DEFUN(show_oml_routing, show_oml_routing_cmd, + "show oml-routes", + SHOW_STR "Show the currently configured OML routing") +{ + struct oml_route_entry *e; + + llist_for_each_entry(e, &g_inst->routes, list) { + struct oml_route *rt = &e->route; + + vty_out(vty, " %s OC=%s INST=(%02x/%02x/%02x) -> %s/%s%s", + rt->flags & OML_RTF_MDISC ? + get_value_string(abis_nm_msg_disc_names, + rt->key.mdisc) : "ANY", + rt->flags & OML_RTF_OBJ_CLASS ? + get_value_string(abis_nm_obj_class_names, + rt->key.obj_class) : "ANY", + rt->flags & OML_RTF_BTS_NR ? rt->key.obj_inst.bts_nr : 255, + rt->flags & OML_RTF_TRX_NR ? rt->key.obj_inst.trx_nr : 255, + rt->flags & OML_RTF_TS_NR ? rt->key.obj_inst.ts_nr : 255, + oml_route_client_addr(e->client), + oml_route_client_name(e->client), VTY_NEWLINE); + } + return CMD_SUCCESS; +} + +#define MDISC_STR "(any|fom|mmi|trau|manuf|<0-255>)" +#define CLASS_STR "(any|site-manager|bts|radio-carrier|baseband-transceiver|channel|gprs-nse|gprs-cell|gprs-nsvc|<0-255>)" + +#define OMLROUTE_CMD "oml-route (add|del) mdisc "MDISC_STR" class "CLASS_STR" bts (any|<0-255>) trx (any|<0-255>]) ts (any|<0-255>) DEST" + +#define OMLROUTE_HELP \ + "OML-Routing\n" \ + "Add a route\n" \ + "Delete a route\n" \ + "Message Discriminator\n" \ + "Message Discriminator: Match any message discriminator\n" \ + "Message Discriminator: Formatted OML messages\n" \ + "Message Discriminator: Man-Machine-Interface OML messages\n" \ + "Message Discriminator: Transcoder OML Message\n" \ + "Message Discriminator: Manufacturer-specific OML messages\n" \ + "Message Discriminator: Specific numeric message discrimniator\n" \ + "Object Class\n" \ + "Object Class: Match any object class\n" \ + "Object Class: Site Manager\n" \ + "Object Class: Base Transceiver Station\n" \ + "Object Class: Radio Carrier\n" \ + "Object Class: Baseband Transceiver\n" \ + "Object Class: Channel (Um Timeslot)\n" \ + "Object Class: GPRS NS Entity\n" \ + "Object Class: GPRS Cell\n" \ + "Object Class: GPRS NS Virtual Circuit\n" \ + "Object Class: Specific numeric object class\n" \ + "Object Instance(BTS)\n" \ + "Object Instance(BTS): Any BTS number\n" \ + "Object Instance(BTS): Specific BTS number\n" \ + "Object Instance(TRX)\n" \ + "Object Instance(TRX): Any TRX number\n" \ + "Object Instance(TRX): Specific TRX number\n" \ + "Object Instance(TS)\n" \ + "Object Instance(TS): Any TS number\n" \ + "Object Instance(TS): Specific TS number\n" \ + "Name of destination for this route\n" + +DEFUN(cfg_omlr_route, cfg_omlr_route_cmd, + OMLROUTE_CMD, OMLROUTE_HELP) +{ + struct oml_route _r, *r = &_r; + int rc; + + memset(r, 0, sizeof(*r)); + + if (strcmp(argv[1], "any")) { + r->flags |= OML_RTF_MDISC; + rc = get_string_value(abis_nm_msg_disc_names, argv[1]); + if (rc < 0) + rc = atoi(argv[1]); + r->key.mdisc = rc; + } + + if (strcmp(argv[2], "any")) { + r->flags |= OML_RTF_OBJ_CLASS; + rc = get_string_value(abis_nm_obj_class_names, argv[2]); + if (rc < 0) + rc = atoi(argv[2]); + r->key.obj_class = rc; + } + + if (strcmp(argv[3], "any")) { + r->flags |= OML_RTF_BTS_NR; + r->key.obj_inst.bts_nr = atoi(argv[3]); + }; + + if (strcmp(argv[4], "any")) { + r->flags |= OML_RTF_TRX_NR; + r->key.obj_inst.trx_nr = atoi(argv[4]); + }; + + if (strcmp(argv[5], "any")) { + r->flags |= OML_RTF_TS_NR; + r->key.obj_inst.ts_nr = atoi(argv[5]); + }; + + if (!strcmp(argv[0], "add")) + rc = oml_route_add(g_inst, r, NULL); + else + rc = oml_route_del(g_inst, r); + + if (rc < 0) { + vty_out(vty, "Error %sing route: %d%s", + !strcmp(argv[0], "add") ? "add" : "delet", + rc, VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +struct oml_routing_inst *oml_route_init(void *ctx, void *priv) +{ + g_inst = talloc_zero(ctx, struct oml_routing_inst); + + INIT_LLIST_HEAD(&g_inst->routes); + + g_inst->priv = priv; + + /* FIXME: add separate node, as this is the only way to make settings + * persistent via the node-specific save callback */ + install_element_ve(&show_oml_routing_cmd); + install_element(ENABLE_NODE, &cfg_omlr_route_cmd); + + return g_inst; +} |