Merge branch 'zecke/feature/move-calls'

Be able to move a call from one MSC to another MSC based on a regexp
for the phone number and pre-defined dial plan.
diff --git a/openbsc/include/openbsc/osmo_msc_data.h b/openbsc/include/openbsc/osmo_msc_data.h
index 0bda662..6eebcdd 100644
--- a/openbsc/include/openbsc/osmo_msc_data.h
+++ b/openbsc/include/openbsc/osmo_msc_data.h
@@ -27,6 +27,8 @@
 
 #include <osmocom/core/timer.h>
 
+#include <regex.h>
+
 struct osmo_bsc_rf;
 struct gsm_network;
 
@@ -49,6 +51,11 @@
 	int allow_emerg;
 	int type;
 
+	/* local call routing */
+	char *local_pref;
+	regex_t local_pref_reg;
+
+
 	/* Connection data */
 	char *bsc_token;
 	int ping_timeout;
diff --git a/openbsc/src/osmo-bsc/osmo_bsc_api.c b/openbsc/src/osmo-bsc/osmo_bsc_api.c
index 7acb00a..87d3f6e 100644
--- a/openbsc/src/osmo-bsc/osmo_bsc_api.c
+++ b/openbsc/src/osmo-bsc/osmo_bsc_api.c
@@ -45,6 +45,10 @@
 	} \
 	bsc_queue_for_msc(conn->sccp_con, resp);
 
+static int bsc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause);
+static int complete_layer3(struct gsm_subscriber_connection *conn,
+			   struct msgb *msg, struct osmo_msc_data *msc);
+
 static uint16_t get_network_code_for_msc(struct osmo_msc_data *msc)
 {
 	if (msc->core_ncc != -1)
@@ -88,10 +92,7 @@
 static int bsc_compl_l3(struct gsm_subscriber_connection *conn, struct msgb *msg,
 			uint16_t chosen_channel)
 {
-	struct msgb *resp;
 	struct osmo_msc_data *msc;
-	uint16_t network_code;
-	uint16_t country_code;
 
 	LOGP(DMSC, LOGL_INFO, "Tx MSC COMPL L3\n");
 
@@ -102,6 +103,16 @@
 		return -1;
 	}
 
+	return complete_layer3(conn, msg, msc);
+}
+
+static int complete_layer3(struct gsm_subscriber_connection *conn,
+			   struct msgb *msg, struct osmo_msc_data *msc)
+{
+	struct msgb *resp;
+	uint16_t network_code;
+	uint16_t country_code;
+
 	/* allocate resource for a new connection */
 	if (bsc_create_new_connection(conn, msc) != 0)
 		return BSC_API_CONN_POL_REJECT;
@@ -130,6 +141,96 @@
 	return BSC_API_CONN_POL_ACCEPT;
 }
 
+/*
+ * Plastic surgery... we want to give up the current connection
+ */
+static int move_to_msc(struct gsm_subscriber_connection *_conn,
+		       struct msgb *msg, struct osmo_msc_data *msc)
+{
+	struct osmo_bsc_sccp_con *old_con = _conn->sccp_con;
+
+	/*
+	 * 1. Give up the old connection.
+	 * This happens by sending a clear request to the MSC,
+	 * it should end with the MSC releasing the connection.
+	 */
+	old_con->conn = NULL;
+	bsc_clear_request(_conn, 0);
+
+	/*
+	 * 2. Attempt to create a new connection to the local
+	 * MSC. If it fails the caller will need to handle this
+	 * properly.
+	 */
+	_conn->sccp_con = NULL;
+	if (complete_layer3(_conn, msg, msc) != BSC_API_CONN_POL_ACCEPT) {
+		gsm0808_clear(_conn);
+		subscr_con_free(_conn);
+		return 1;
+	}
+
+	return 2;
+}
+
+static int handle_cc_setup(struct gsm_subscriber_connection *conn,
+			   struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	uint8_t pdisc = gh->proto_discr & 0x0f;
+	uint8_t mtype = gh->msg_type & 0xbf;
+
+	struct osmo_msc_data *msc;
+	struct gsm_mncc_number called;
+	struct tlv_parsed tp;
+	unsigned payload_len;
+
+	char _dest_nr[35];
+
+	/*
+	 * Do we have a setup message here? if not return fast.
+	 */
+	if (pdisc != GSM48_PDISC_CC || mtype != GSM48_MT_CC_SETUP)
+		return 0;
+
+	payload_len = msgb_l3len(msg) - sizeof(*gh);
+
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	if (!TLVP_PRESENT(&tp, GSM48_IE_CALLED_BCD)) {
+		LOGP(DMSC, LOGL_ERROR, "Called BCD not present in setup.\n");
+		return -1;
+	}
+
+	memset(&called, 0, sizeof(called));
+	gsm48_decode_called(&called,
+			    TLVP_VAL(&tp, GSM48_IE_CALLED_BCD) - 1);
+
+	if (called.plan != 1 && called.plan != 0)
+		return 0;
+
+	if (called.plan == 1 && called.type == 1) {
+		_dest_nr[0] = _dest_nr[1] = '0';
+		memcpy(_dest_nr + 2, called.number, sizeof(called.number));
+	} else
+		memcpy(_dest_nr, called.number, sizeof(called.number));
+
+	/*
+	 * Check if the connection should be moved...
+	 */
+	llist_for_each_entry(msc, &conn->bts->network->bsc_data->mscs, entry) {
+		if (msc->type != MSC_CON_TYPE_LOCAL)
+			continue;
+		if (!msc->local_pref)
+			continue;
+		if (regexec(&msc->local_pref_reg, _dest_nr, 0, NULL, 0) != 0)
+			continue;
+
+		return move_to_msc(conn, msg, msc);
+	}
+
+	return 0;
+}
+
+
 static void bsc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg)
 {
 	struct msgb *resp;
@@ -137,7 +238,16 @@
 
 	LOGP(DMSC, LOGL_INFO, "Tx MSC DTAP LINK_ID=0x%02x\n", link_id);
 
+	/*
+	 * We might want to move this connection to a new MSC. Ask someone
+	 * to handle it. If it was handled we will return.
+	 */
+	if (handle_cc_setup(conn, msg) >= 1)
+		return;
+
 	bsc_scan_bts_msg(conn, msg);
+
+
 	resp = gsm0808_create_dtap(msg, link_id);
 	queue_msg_or_return(resp);
 }
diff --git a/openbsc/src/osmo-bsc/osmo_bsc_vty.c b/openbsc/src/osmo-bsc/osmo_bsc_vty.c
index 9f1eb69..a3bf5af 100644
--- a/openbsc/src/osmo-bsc/osmo_bsc_vty.c
+++ b/openbsc/src/osmo-bsc/osmo_bsc_vty.c
@@ -119,6 +119,9 @@
 					"normal" : "local", VTY_NEWLINE);
 	vty_out(vty, " allow-emergency %s%s", msc->allow_emerg ?
 					"allow" : "deny", VTY_NEWLINE);
+
+	if (msc->local_pref)
+		vty_out(vty, " local-prefix %s%s", msc->local_pref, VTY_NEWLINE);
 }
 
 static int config_write_msc(struct vty *vty)
@@ -364,6 +367,22 @@
 	return CMD_SUCCESS;
 }
 
+DEFUN(cfg_net_msc_local_prefix,
+      cfg_net_msc_local_prefix_cmd,
+      "local-prefix REGEXP",
+      "Prefix for local numbers\n" "REGEXP used\n")
+{
+	struct osmo_msc_data *msc = osmo_msc_data(vty);
+
+	if (gsm_parse_reg(msc, &msc->local_pref_reg, &msc->local_pref, argc, argv) != 0) {
+		vty_out(vty, "%%Failed to parse the regexp: '%s'%s",
+			argv[0], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	return CMD_SUCCESS;
+}
+
 DEFUN(cfg_net_bsc_mid_call_text,
       cfg_net_bsc_mid_call_text_cmd,
       "mid-call-text .TEXT",
@@ -453,6 +472,7 @@
 	install_element(MSC_NODE, &cfg_net_msc_welcome_ussd_cmd);
 	install_element(MSC_NODE, &cfg_net_msc_type_cmd);
 	install_element(MSC_NODE, &cfg_net_msc_emerg_cmd);
+	install_element(MSC_NODE, &cfg_net_msc_local_prefix_cmd);
 
 	install_element_ve(&show_statistics_cmd);
 	install_element_ve(&show_mscs_cmd);