/* OML Message Router (server side) */ /* (C) 2014 by Harald Welte * * 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 . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 \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; }