/* Bloated main routine, refactor */ /* * (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 General Public License as published by * the Free Software Foundation; either version 2 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 General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #undef PACKAGE_NAME #undef PACKAGE_VERSION #undef PACKAGE_BUGREPORT #undef PACKAGE_TARNAME #undef PACKAGE_STRING #include static struct log_target *stderr_target; static char *config = "cellmgr_ng.cfg"; static int flood = 0; static struct timer_list flood_timer; struct bsc_data bsc; extern void cell_vty_init(void); static void send_reset_ack(struct mtp_link *link, int sls); static void bsc_resources_released(struct bsc_data *bsc); static void handle_local_sccp(struct mtp_link *link, struct msgb *inp, struct sccp_parse_result *res, int sls); static void clear_connections(struct bsc_data *bsc); static void send_local_rlsd(struct mtp_link *link, struct sccp_parse_result *res); static void start_flood(); int link_c7_init(struct link_data *data) __attribute__((__weak__)); int link_c7_init(struct link_data *data) { return -1; } /* send a RSIP to the MGCP GW */ static void mgcp_reset(struct bsc_data *bsc) { static const char mgcp_reset[] = { "RSIP 1 13@mgw MGCP 1.0\r\n" }; mgcp_forward(bsc, (const uint8_t *) mgcp_reset, strlen(mgcp_reset)); } /* * methods called from the MTP Level3 part */ void mtp_link_submit(struct mtp_link *link, struct msgb *msg) { bsc.link.write(&bsc.link, msg); } void mtp_link_restart(struct mtp_link *link) { LOGP(DINP, LOGL_ERROR, "Need to restart the SS7 link.\n"); bsc.link.reset(&bsc.link); } void mtp_link_sccp_down(struct mtp_link *link) { msc_clear_queue(&bsc); } void mtp_link_forward_sccp(struct mtp_link *link, struct msgb *_msg, int sls) { int rc; struct sccp_parse_result result; rc = bss_patch_filter_msg(_msg, &result); if (rc == BSS_FILTER_RESET) { LOGP(DMSC, LOGL_NOTICE, "Filtering BSS Reset from the BSC\n"); msc_clear_queue(&bsc); mgcp_reset(&bsc); send_reset_ack(link, sls); return; } /* special responder */ if (bsc.msc_link_down) { if (rc == BSS_FILTER_RESET_ACK && bsc.reset_count > 0) { LOGP(DMSC, LOGL_ERROR, "Received reset ack for closing.\n"); clear_connections(&bsc); bsc_resources_released(&bsc); return; } if (rc != 0 && rc != BSS_FILTER_RLSD && rc != BSS_FILTER_RLC) { LOGP(DMSC, LOGL_ERROR, "Ignoring unparsable msg during closedown.\n"); return; } return handle_local_sccp(link, _msg, &result, sls); } /* update the connection state */ update_con_state(link, rc, &result, _msg, 0, sls); if (rc == BSS_FILTER_CLEAR_COMPL) { send_local_rlsd(link, &result); } else if (rc == BSS_FILTER_RLC || rc == BSS_FILTER_RLSD) { LOGP(DMSC, LOGL_DEBUG, "Not forwarding RLC/RLSD to the MSC.\n"); return; } msc_send_msg(&bsc, rc, &result, _msg); } /* * handle local message in close down mode */ static void handle_local_sccp(struct mtp_link *link, struct msgb *inpt, struct sccp_parse_result *result, int sls) { /* Handle msg with a reject */ if (inpt->l2h[0] == SCCP_MSG_TYPE_CR) { struct sccp_connection_request *cr; struct msgb *msg; LOGP(DINP, LOGL_NOTICE, "Handling CR localy.\n"); cr = (struct sccp_connection_request *) inpt->l2h; msg = create_sccp_refuse(&cr->source_local_reference); if (msg) { mtp_link_submit_sccp_data(link, sls, msg->l2h, msgb_l2len(msg)); msgb_free(msg); } return; } else if (inpt->l2h[0] == SCCP_MSG_TYPE_DT1 && result->data_len >= 3) { struct active_sccp_con *con; struct sccp_data_form1 *form1; struct msgb *msg; if (inpt->l3h[0] == 0 && inpt->l3h[2] == BSS_MAP_MSG_CLEAR_COMPLETE) { LOGP(DINP, LOGL_DEBUG, "Received Clear Complete. Sending Release.\n"); form1 = (struct sccp_data_form1 *) inpt->l2h; llist_for_each_entry(con, &bsc.sccp_connections, entry) { if (memcmp(&form1->destination_local_reference, &con->dst_ref, sizeof(con->dst_ref)) == 0) { LOGP(DINP, LOGL_DEBUG, "Sending a release request now.\n"); msg = create_sccp_rlsd(&con->dst_ref, &con->src_ref); if (msg) { mtp_link_submit_sccp_data(link, con->sls, msg->l2h, msgb_l2len(msg)); msgb_free(msg); } return; } } LOGP(DINP, LOGL_ERROR, "Could not find connection for the Clear Command.\n"); } } else if (inpt->l2h[0] == SCCP_MSG_TYPE_UDT && result->data_len >= 3) { if (inpt->l3h[0] == 0 && inpt->l3h[2] == BSS_MAP_MSG_RESET_ACKNOWLEDGE) { LOGP(DINP, LOGL_NOTICE, "Reset ACK. Connecting to the MSC again.\n"); bsc_resources_released(&bsc); return; } } /* Update the state, maybe the connection was released? */ update_con_state(link, 0, result, inpt, 0, sls); if (llist_empty(&bsc.sccp_connections)) bsc_resources_released(&bsc); return; } static void clear_connections(struct bsc_data *bsc) { struct active_sccp_con *tmp, *con; llist_for_each_entry_safe(con, tmp, &bsc->sccp_connections, entry) { free_con(con); } bsc->link.clear_queue(&bsc->link); } void bsc_resources_released(struct bsc_data *bsc) { bsc_del_timer(&bsc->reset_timeout); } static void bsc_reset_timeout(void *_data) { struct msgb *msg; struct bsc_data *bsc = (struct bsc_data *) _data; /* no reset */ if (bsc->reset_count > 0) { LOGP(DINP, LOGL_ERROR, "The BSC did not answer the GSM08.08 reset. Restart MTP\n"); mtp_link_stop(bsc->link.the_link); clear_connections(bsc); bsc->link.reset(&bsc->link); bsc_resources_released(bsc); return; } msg = create_reset(); if (!msg) { bsc_schedule_timer(&bsc->reset_timeout, 10, 0); return; } ++bsc->reset_count; mtp_link_submit_sccp_data(bsc->link.the_link, 13, msg->l2h, msgb_l2len(msg)); msgb_free(msg); bsc_schedule_timer(&bsc->reset_timeout, 20, 0); } /* * We have lost the connection to the MSC. This is tough. We * can not just bring down the MTP link as this will disable * the BTS radio. We will have to do the following: * * 1.) Bring down all open SCCP connections. As this will close * all radio resources * 2.) Bring down all MGCP endpoints * 3.) Clear the connection data. * * To make things worse we need to buffer the BSC messages... atfer * everything has been sent we will try to connect to the MSC again. * * We will have to veriy that all connections are closed properly.. * this means we need to parse response message. In the case the * MTP link is going down while we are sending. We will simply * reconnect to the MSC. */ void release_bsc_resources(struct bsc_data *bsc) { struct active_sccp_con *tmp; struct active_sccp_con *con; bsc_del_timer(&bsc->reset_timeout); /* 2. clear the MGCP endpoints */ mgcp_reset(bsc); /* 1. send BSSMAP Cleanup.. if we have any connection */ llist_for_each_entry_safe(con, tmp, &bsc->sccp_connections, entry) { if (!con->has_dst_ref) { free_con(con); continue; } struct msgb *msg = create_clear_command(&con->src_ref); if (!msg) continue; /* wait for the clear commands */ mtp_link_submit_sccp_data(bsc->link.the_link, con->sls, msg->l2h, msgb_l2len(msg)); msgb_free(msg); } if (llist_empty(&bsc->sccp_connections)) { bsc_resources_released(bsc); } else { /* Send a reset in 20 seconds if we fail to bring everything down */ bsc->reset_timeout.cb = bsc_reset_timeout; bsc->reset_timeout.data = bsc; bsc->reset_count = 0; bsc_schedule_timer(&bsc->reset_timeout, 10, 0); } /* clear pending messages from the MSC */ msc_clear_queue(bsc); } void bsc_link_down(struct link_data *data) { int was_up; struct mtp_link *link = data->the_link; link->available = 0; was_up = link->sccp_up; mtp_link_stop(link); clear_connections(data->bsc); mgcp_reset(data->bsc); data->clear_queue(data); /* clear pending messages from the MSC */ msc_clear_queue(data->bsc); /* If we have an A link send a reset to the MSC */ msc_send_reset(data->bsc); } void bsc_link_up(struct link_data *data) { data->the_link->available = 1; /* we have not gone through link down */ if (data->bsc->msc_link_down) { clear_connections(data->bsc); bsc_resources_released(data->bsc); } mtp_link_reset(data->the_link); if (flood) start_flood(); } /** * update the connection state and helpers below */ static void send_rlc_to_bsc(unsigned int sls, struct sccp_source_reference *src, struct sccp_source_reference *dst) { struct msgb *msg; msg = create_sccp_rlc(src, dst); if (!msg) return; mtp_link_submit_sccp_data(bsc.link.the_link, sls, msg->l2h, msgb_l2len(msg)); msgb_free(msg); } static void handle_rlsd(struct sccp_connection_released *rlsd, int from_msc) { struct active_sccp_con *con; if (from_msc) { /* search for a connection, reverse src/dest for MSC */ con = find_con_by_src_dest_ref(&rlsd->destination_local_reference, &rlsd->source_local_reference); if (con) { LOGP(DINP, LOGL_DEBUG, "RLSD conn still alive: local: 0x%x remote: 0x%x\n", sccp_src_ref_to_int(&con->src_ref), sccp_src_ref_to_int(&con->dst_ref)); con->released_from_msc = 1; } else { /* send RLC */ LOGP(DINP, LOGL_DEBUG, "Sending RLC for MSC: src: 0x%x dst: 0x%x\n", sccp_src_ref_to_int(&rlsd->destination_local_reference), sccp_src_ref_to_int(&rlsd->source_local_reference)); msc_send_rlc(&bsc, &rlsd->destination_local_reference, &rlsd->source_local_reference); } } else { unsigned int sls = 13; con = find_con_by_src_dest_ref(&rlsd->source_local_reference, &rlsd->destination_local_reference); if (con) { LOGP(DINP, LOGL_DEBUG, "Timeout on BSC. Sending RLC. src: 0x%x\n", sccp_src_ref_to_int(&rlsd->source_local_reference)); if (con->released_from_msc) msc_send_rlc(&bsc, &con->src_ref, &con->dst_ref); sls = con->sls; free_con(con); } else { LOGP(DINP, LOGL_ERROR, "Timeout on BSC for unknown connection. src: 0x%x\n", sccp_src_ref_to_int(&rlsd->source_local_reference)); } /* now send a rlc back to the BSC */ send_rlc_to_bsc(sls, &rlsd->destination_local_reference, &rlsd->source_local_reference); } } /** * Update connection state and also send message..... * * RLSD from MSC: * 1.) We don't find the entry in this case we will send a * forged RLC to the MSC and we are done. * 2.) We find an entry in this we will need to register that * we need to send a RLC and we are done for now. * RLSD from BSC: * 1.) This is an error we are ignoring for now. * RLC from BSC: * 1.) We are destroying the connection, we might send a RLC to * the MSC if we are waiting for one. */ void update_con_state(struct mtp_link *link, int rc, struct sccp_parse_result *res, struct msgb *msg, int from_msc, int sls) { struct active_sccp_con *con; struct sccp_connection_request *cr; struct sccp_connection_confirm *cc; struct sccp_connection_release_complete *rlc; struct sccp_connection_refused *cref; /* was the header okay? */ if (rc < 0) return; /* the header was size checked */ switch (msg->l2h[0]) { case SCCP_MSG_TYPE_CR: if (from_msc) { LOGP(DMSC, LOGL_ERROR, "CR from MSC is not handled.\n"); return; } cr = (struct sccp_connection_request *) msg->l2h; con = find_con_by_src_ref(&cr->source_local_reference); if (con) { LOGP(DINP, LOGL_ERROR, "Duplicate SRC reference for: 0x%x. Reusing\n", sccp_src_ref_to_int(&con->src_ref)); free_con(con); } con = talloc_zero(NULL, struct active_sccp_con); if (!con) { LOGP(DINP, LOGL_ERROR, "Failed to allocate\n"); return; } con->src_ref = cr->source_local_reference; con->sls = sls; con->link = link; llist_add_tail(&con->entry, &bsc.sccp_connections); LOGP(DINP, LOGL_DEBUG, "Adding CR: local ref: 0x%x\n", sccp_src_ref_to_int(&con->src_ref)); break; case SCCP_MSG_TYPE_CC: if (!from_msc) { LOGP(DINP, LOGL_ERROR, "CC from BSC is not handled.\n"); return; } cc = (struct sccp_connection_confirm *) msg->l2h; con = find_con_by_src_ref(&cc->destination_local_reference); if (con) { con->dst_ref = cc->source_local_reference; con->has_dst_ref = 1; LOGP(DINP, LOGL_DEBUG, "Updating CC: local: 0x%x remote: 0x%x\n", sccp_src_ref_to_int(&con->src_ref), sccp_src_ref_to_int(&con->dst_ref)); return; } LOGP(DINP, LOGL_ERROR, "CCed connection can not be found: 0x%x\n", sccp_src_ref_to_int(&cc->destination_local_reference)); break; case SCCP_MSG_TYPE_CREF: if (!from_msc) { LOGP(DINP, LOGL_ERROR, "CREF from BSC is not handled.\n"); return; } cref = (struct sccp_connection_refused *) msg->l2h; con = find_con_by_src_ref(&cref->destination_local_reference); if (con) { LOGP(DINP, LOGL_DEBUG, "Releasing local: 0x%x\n", sccp_src_ref_to_int(&con->src_ref)); free_con(con); return; } LOGP(DINP, LOGL_ERROR, "CREF from BSC is not handled.\n"); break; case SCCP_MSG_TYPE_RLSD: handle_rlsd((struct sccp_connection_released *) msg->l2h, from_msc); break; case SCCP_MSG_TYPE_RLC: if (from_msc) { LOGP(DINP, LOGL_ERROR, "RLC from MSC is wrong.\n"); return; } rlc = (struct sccp_connection_release_complete *) msg->l2h; con = find_con_by_src_dest_ref(&rlc->source_local_reference, &rlc->destination_local_reference); if (con) { LOGP(DINP, LOGL_DEBUG, "Releasing local: 0x%x\n", sccp_src_ref_to_int(&con->src_ref)); if (con->released_from_msc) msc_send_rlc(&bsc, &con->src_ref, &con->dst_ref); free_con(con); return; } LOGP(DINP, LOGL_ERROR, "RLC can not be found. 0x%x 0x%x\n", sccp_src_ref_to_int(&rlc->source_local_reference), sccp_src_ref_to_int(&rlc->destination_local_reference)); break; } } static void send_local_rlsd_for_con(void *data) { struct msgb *rlsd; struct active_sccp_con *con = (struct active_sccp_con *) data; /* try again in three seconds */ con->rlc_timeout.data = con; con->rlc_timeout.cb = send_local_rlsd_for_con; bsc_schedule_timer(&con->rlc_timeout, 3, 0); /* we send this to the BSC so we need to switch src and dest */ rlsd = create_sccp_rlsd(&con->dst_ref, &con->src_ref); if (!rlsd) return; ++con->rls_tries; LOGP(DINP, LOGL_DEBUG, "Sending RLSD for 0x%x the %d time.\n", sccp_src_ref_to_int(&con->src_ref), con->rls_tries); mtp_link_submit_sccp_data(bsc.link.the_link, con->sls, rlsd->l2h, msgb_l2len(rlsd)); msgb_free(rlsd); } static void send_local_rlsd(struct mtp_link *link, struct sccp_parse_result *res) { struct active_sccp_con *con; LOGP(DINP, LOGL_DEBUG, "Received GSM Clear Complete. Sending RLSD locally.\n"); con = find_con_by_dest_ref(res->destination_local_reference); if (!con) return; con->rls_tries = 0; send_local_rlsd_for_con(con); } static void send_reset_ack(struct mtp_link *link, int sls) { static const uint8_t reset_ack[] = { 0x09, 0x00, 0x03, 0x05, 0x7, 0x02, 0x42, 0xfe, 0x02, 0x42, 0xfe, 0x03, 0x00, 0x01, 0x31 }; mtp_link_submit_sccp_data(link, sls, reset_ack, sizeof(reset_ack)); } static void start_flood() { static unsigned int i = 0; static const uint8_t paging_cmd[] = { 0x09, 0x00, 0x03, 0x07, 0x0b, 0x04, 0x43, 0x0a, 0x00, 0xfe, 0x04, 0x43, 0x5c, 0x00, 0xfe, 0x10, 0x00, 0x0e, 0x52, 0x08, 0x08, 0x29, 0x80, 0x10, 0x76, 0x10, 0x77, 0x46, 0x05, 0x1a, 0x01, 0x06 }; /* change the imsi slightly */ if (bsc.link.the_link->sltm_pending) { LOGP(DINP, LOGL_ERROR, "Not sending due clash with SLTM.\n"); } else { struct msgb *msg; msg = msgb_alloc_headroom(4096, 128, "paging"); if (msg) { LOGP(DINP, LOGL_NOTICE, "Flooding BSC with one paging requests.\n"); msg->l2h = msgb_put(msg, sizeof(paging_cmd)); memcpy(msg->l2h, paging_cmd, msgb_l2len(msg)); bss_rewrite_header_to_bsc(msg, bsc.link.the_link->opc, bsc.link.the_link->dpc); mtp_link_submit_sccp_data(bsc.link.the_link, i++, msg->l2h, msgb_l2len(msg)); msgb_free(msg); } } /* try again in five seconds */ flood_timer.cb = start_flood; bsc_schedule_timer(&flood_timer, 2, 0); } static void print_usage() { printf("Usage: cellmgr_ng\n"); } static void sigint() { static pthread_mutex_t exit_mutex = PTHREAD_MUTEX_INITIALIZER; static int handled = 0; /* failed to lock */ if (pthread_mutex_trylock(&exit_mutex) != 0) return; if (handled) goto out; printf("Terminating.\n"); handled = 1; if (bsc.setup) bsc.link.shutdown(&bsc.link); exit(0); out: pthread_mutex_unlock(&exit_mutex); } static void sigusr2() { printf("Closing the MSC connection on demand.\n"); msc_close_connection(&bsc); } static void print_help() { printf(" Some useful help...\n"); printf(" -h --help this text\n"); printf(" -c --config=CFG The config file to use.\n"); printf(" -p --pcap=FILE. Write MSUs to the PCAP file.\n"); printf(" -c --once. Send the SLTM msg only once.\n"); printf(" -f --flood. Send flood of paging requests to the BSC.\n"); printf(" -v --version. Print the version number\n"); } static void handle_options(int argc, char **argv) { while (1) { int option_index = 0, c; static struct option long_options[] = { {"help", 0, 0, 'h'}, {"config", 1, 0, 'c'}, {"pcap", 1, 0, 'p'}, {"flood", 0, 0, 'f'}, {"version", 0, 0, 0}, {0, 0, 0, 0}, }; c = getopt_long(argc, argv, "hc:p:fv", long_options, &option_index); if (c == -1) break; switch (c) { case 'h': print_usage(); print_help(); exit(0); case 'p': if (bsc.link.pcap_fd >= 0) close(bsc.link.pcap_fd); bsc.link.pcap_fd = open(optarg, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP| S_IROTH); if (bsc.link.pcap_fd < 0) { fprintf(stderr, "Failed to open PCAP file.\n"); exit(0); } mtp_pcap_write_header(bsc.link.pcap_fd); break; case 'c': config = optarg; break; case 'f': flood = 1; break; case 'v': printf("This is %s version %s.\n", PACKAGE, VERSION); exit(0); break; default: fprintf(stderr, "Unknown option.\n"); break; } } } static void start_rest(void *start) { bsc.setup = 1; if (msc_init(&bsc, 1) != 0) { fprintf(stderr, "Failed to init MSC part.\n"); exit(3); } bsc.link.start(&bsc.link); } int main(int argc, char **argv) { int rc; INIT_LLIST_HEAD(&bsc.sccp_connections); bsc.dpc = 1; bsc.opc = 0; bsc.udp_port = 3456; bsc.udp_ip = NULL; bsc.src_port = 1313; bsc.ni_ni = MTP_NI_NATION_NET; bsc.ni_spare = 0; mtp_link_init(); thread_init(); log_init(&log_info); stderr_target = log_target_create_stderr(); log_add_target(stderr_target); /* enable filters */ log_set_all_filter(stderr_target, 1); log_set_category_filter(stderr_target, DINP, 1, LOGL_INFO); log_set_category_filter(stderr_target, DSCCP, 1, LOGL_INFO); log_set_category_filter(stderr_target, DMSC, 1, LOGL_INFO); log_set_category_filter(stderr_target, DMGCP, 1, LOGL_INFO); log_set_print_timestamp(stderr_target, 1); log_set_use_color(stderr_target, 0); sccp_set_log_area(DSCCP); bsc.setup = 0; bsc.msc_address = "127.0.0.1"; bsc.link.pcap_fd = -1; bsc.link.udp.reset_timeout = 180; bsc.ping_time = 20; bsc.pong_time = 5; bsc.msc_time = 20; handle_options(argc, argv); signal(SIGPIPE, SIG_IGN); signal(SIGINT, sigint); signal(SIGUSR2, sigusr2); srand(time(NULL)); cell_vty_init(); if (vty_read_config_file(config, NULL) < 0) { fprintf(stderr, "Failed to read the VTY config.\n"); return -1; } rc = telnet_init(NULL, NULL, 4242); if (rc < 0) return rc; bsc.link.the_link = mtp_link_alloc(); bsc.link.the_link->dpc = bsc.dpc; bsc.link.the_link->opc = bsc.opc; bsc.link.the_link->link = 0; bsc.link.the_link->sltm_once = bsc.once; bsc.link.the_link->ni = bsc.ni_ni; bsc.link.the_link->spare = bsc.ni_spare; bsc.link.bsc = &bsc; if (bsc.udp_ip) { LOGP(DINP, LOGL_NOTICE, "Using UDP MTP mode.\n"); /* setup SNMP first, it is blocking */ bsc.link.udp.session = snmp_mtp_session_create(bsc.udp_ip); if (!bsc.link.udp.session) return -1; /* now connect to the transport */ if (link_udp_init(&bsc.link, bsc.src_port, bsc.udp_ip, bsc.udp_port) != 0) return -1; /* * We will ask the MTP link to be taken down for two * timeouts of the BSC to make sure we are missing the * SLTM and it begins a reset. Then we will take it up * again and do the usual business. */ snmp_mtp_deactivate(bsc.link.udp.session); bsc.start_timer.cb = start_rest; bsc.start_timer.data = &bsc; bsc_schedule_timer(&bsc.start_timer, bsc.link.udp.reset_timeout, 0); LOGP(DMSC, LOGL_NOTICE, "Making sure SLTM will timeout.\n"); } else { LOGP(DINP, LOGL_NOTICE, "Using NexusWare C7 input.\n"); if (link_c7_init(&bsc.link) != 0) return -1; /* give time to things to start*/ bsc.start_timer.cb = start_rest; bsc.start_timer.data = &bsc; bsc_schedule_timer(&bsc.start_timer, 30, 0); LOGP(DMSC, LOGL_NOTICE, "Waiting to continue to startup.\n"); } while (1) { bsc_select_main(0); } return 0; }