diff options
Diffstat (limited to 'src/libmsc')
-rw-r--r-- | src/libmsc/Makefile.am | 58 | ||||
-rw-r--r-- | src/libmsc/auth.c | 157 | ||||
-rw-r--r-- | src/libmsc/ctrl_commands.c | 212 | ||||
-rw-r--r-- | src/libmsc/db.c | 1752 | ||||
-rw-r--r-- | src/libmsc/gsm_04_08.c | 4047 | ||||
-rw-r--r-- | src/libmsc/gsm_04_11.c | 1070 | ||||
-rw-r--r-- | src/libmsc/gsm_04_80.c | 155 | ||||
-rw-r--r-- | src/libmsc/gsm_subscriber.c | 422 | ||||
-rw-r--r-- | src/libmsc/meas_feed.c | 167 | ||||
-rw-r--r-- | src/libmsc/meas_feed.h | 12 | ||||
-rw-r--r-- | src/libmsc/mncc.c | 107 | ||||
-rw-r--r-- | src/libmsc/mncc_builtin.c | 426 | ||||
-rw-r--r-- | src/libmsc/mncc_sock.c | 318 | ||||
-rw-r--r-- | src/libmsc/osmo_msc.c | 177 | ||||
-rw-r--r-- | src/libmsc/rrlp.c | 104 | ||||
-rw-r--r-- | src/libmsc/silent_call.c | 152 | ||||
-rw-r--r-- | src/libmsc/smpp_openbsc.c | 758 | ||||
-rw-r--r-- | src/libmsc/smpp_smsc.c | 1030 | ||||
-rw-r--r-- | src/libmsc/smpp_smsc.h | 166 | ||||
-rw-r--r-- | src/libmsc/smpp_utils.c | 62 | ||||
-rw-r--r-- | src/libmsc/smpp_vty.c | 612 | ||||
-rw-r--r-- | src/libmsc/sms_queue.c | 544 | ||||
-rw-r--r-- | src/libmsc/token_auth.c | 160 | ||||
-rw-r--r-- | src/libmsc/transaction.c | 163 | ||||
-rw-r--r-- | src/libmsc/ussd.c | 95 | ||||
-rw-r--r-- | src/libmsc/vty_interface_layer3.c | 1210 |
26 files changed, 14136 insertions, 0 deletions
diff --git a/src/libmsc/Makefile.am b/src/libmsc/Makefile.am new file mode 100644 index 000000000..9d966dbc1 --- /dev/null +++ b/src/libmsc/Makefile.am @@ -0,0 +1,58 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_builddir) \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOABIS_CFLAGS) \ + $(COVERAGE_CFLAGS) \ + $(LIBCRYPTO_CFLAGS) \ + $(LIBSMPP34_CFLAGS) \ + $(NULL) + +noinst_HEADERS = \ + meas_feed.h \ + $(NULL) + +noinst_LIBRARIES = \ + libmsc.a \ + $(NULL) + +libmsc_a_SOURCES = \ + auth.c \ + db.c \ + gsm_04_08.c \ + gsm_04_11.c \ + gsm_04_80.c \ + gsm_subscriber.c \ + mncc.c \ + mncc_builtin.c \ + mncc_sock.c \ + rrlp.c \ + silent_call.c \ + sms_queue.c \ + token_auth.c \ + ussd.c \ + vty_interface_layer3.c \ + transaction.c \ + osmo_msc.c \ + ctrl_commands.c \ + meas_feed.c \ + $(NULL) + +if BUILD_SMPP +noinst_HEADERS += \ + smpp_smsc.h \ + $(NULL) + +libmsc_a_SOURCES += \ + smpp_smsc.c \ + smpp_openbsc.c \ + smpp_vty.c \ + smpp_utils.c \ + $(NULL) +endif diff --git a/src/libmsc/auth.c b/src/libmsc/auth.c new file mode 100644 index 000000000..19def1ec1 --- /dev/null +++ b/src/libmsc/auth.c @@ -0,0 +1,157 @@ +/* Authentication related functions */ + +/* + * (C) 2010 by Sylvain Munaut <tnt@246tNt.com> + * + * 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 <openbsc/db.h> +#include <openbsc/debug.h> +#include <openbsc/auth.h> +#include <openbsc/gsm_data.h> + +#include <osmocom/gsm/comp128.h> +#include <osmocom/core/utils.h> + +#include <openssl/rand.h> + +#include <stdlib.h> + +const struct value_string auth_action_names[] = { + OSMO_VALUE_STRING(AUTH_ERROR), + OSMO_VALUE_STRING(AUTH_NOT_AVAIL), + OSMO_VALUE_STRING(AUTH_DO_AUTH_THEN_CIPH), + OSMO_VALUE_STRING(AUTH_DO_CIPH), + OSMO_VALUE_STRING(AUTH_DO_AUTH), + { 0, NULL } +}; + +static int +_use_xor(struct gsm_auth_info *ainfo, struct gsm_auth_tuple *atuple) +{ + int i, l = ainfo->a3a8_ki_len; + + if ((l > A38_XOR_MAX_KEY_LEN) || (l < A38_XOR_MIN_KEY_LEN)) { + LOGP(DMM, LOGL_ERROR, "Invalid XOR key (len=%d) %s\n", + ainfo->a3a8_ki_len, + osmo_hexdump(ainfo->a3a8_ki, ainfo->a3a8_ki_len)); + return -1; + } + + for (i=0; i<4; i++) + atuple->vec.sres[i] = atuple->vec.rand[i] ^ ainfo->a3a8_ki[i]; + for (i=4; i<12; i++) + atuple->vec.kc[i-4] = atuple->vec.rand[i] ^ ainfo->a3a8_ki[i]; + + return 0; +} + +static int +_use_comp128_v1(struct gsm_auth_info *ainfo, struct gsm_auth_tuple *atuple) +{ + if (ainfo->a3a8_ki_len != A38_COMP128_KEY_LEN) { + LOGP(DMM, LOGL_ERROR, "Invalid COMP128v1 key (len=%d) %s\n", + ainfo->a3a8_ki_len, + osmo_hexdump(ainfo->a3a8_ki, ainfo->a3a8_ki_len)); + return -1; + } + + comp128(ainfo->a3a8_ki, atuple->vec.rand, atuple->vec.sres, atuple->vec.kc); + + return 0; +} + +/* Return values + * -1 -> Internal error + * 0 -> Not available + * 1 -> Tuple returned, need to do auth, then enable cipher + * 2 -> Tuple returned, need to enable cipher + */ +int auth_get_tuple_for_subscr(struct gsm_auth_tuple *atuple, + struct gsm_subscriber *subscr, int key_seq) +{ + struct gsm_auth_info ainfo; + int rc; + + /* Get subscriber info (if any) */ + rc = db_get_authinfo_for_subscr(&ainfo, subscr); + if (rc < 0) { + LOGP(DMM, LOGL_NOTICE, + "No retrievable Ki for subscriber %s, skipping auth\n", + subscr_name(subscr)); + return rc == -ENOENT ? AUTH_NOT_AVAIL : AUTH_ERROR; + } + + /* If possible, re-use the last tuple and skip auth */ + rc = db_get_lastauthtuple_for_subscr(atuple, subscr); + if ((rc == 0) && + (key_seq != GSM_KEY_SEQ_INVAL) && + (key_seq == atuple->key_seq) && + (atuple->use_count < 3)) + { + atuple->use_count++; + db_sync_lastauthtuple_for_subscr(atuple, subscr); + DEBUGP(DMM, "Auth tuple use < 3, just doing ciphering\n"); + return AUTH_DO_CIPH; + } + + /* Generate a new one */ + if (rc != 0) { + /* If db_get_lastauthtuple_for_subscr() returned nothing, make + * sure the atuple memory is initialized to zero and thus start + * off with key_seq = 0. */ + memset(atuple, 0, sizeof(*atuple)); + } else { + /* If db_get_lastauthtuple_for_subscr() returned a previous + * tuple, use the next key_seq. */ + atuple->key_seq = (atuple->key_seq + 1) % 7; + } + atuple->use_count = 1; + + if (RAND_bytes(atuple->vec.rand, sizeof(atuple->vec.rand)) != 1) { + LOGP(DMM, LOGL_NOTICE, "RAND_bytes failed, can't generate new auth tuple\n"); + return AUTH_ERROR; + } + + switch (ainfo.auth_algo) { + case AUTH_ALGO_NONE: + DEBUGP(DMM, "No authentication for subscriber\n"); + return AUTH_NOT_AVAIL; + + case AUTH_ALGO_XOR: + if (_use_xor(&ainfo, atuple)) + return AUTH_NOT_AVAIL; + break; + + case AUTH_ALGO_COMP128v1: + if (_use_comp128_v1(&ainfo, atuple)) + return AUTH_NOT_AVAIL; + break; + + default: + DEBUGP(DMM, "Unsupported auth type algo_id=%d\n", + ainfo.auth_algo); + return AUTH_NOT_AVAIL; + } + + db_sync_lastauthtuple_for_subscr(atuple, subscr); + + DEBUGP(DMM, "Need to do authentication and ciphering\n"); + return AUTH_DO_AUTH_THEN_CIPH; +} + diff --git a/src/libmsc/ctrl_commands.c b/src/libmsc/ctrl_commands.c new file mode 100644 index 000000000..c99dde44c --- /dev/null +++ b/src/libmsc/ctrl_commands.c @@ -0,0 +1,212 @@ +/* + * (C) 2014 by Holger Hans Peter Freyther + * (C) 2014 by sysmocom s.f.m.c. GmbH + * + * 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 <osmocom/ctrl/control_cmd.h> +#include <osmocom/core/utils.h> +#include <openbsc/gsm_data.h> +#include <openbsc/gsm_subscriber.h> +#include <openbsc/db.h> +#include <openbsc/debug.h> + +#include <stdbool.h> + +static bool alg_supported(const char *alg) +{ + /* + * TODO: share this with the vty_interface and extend to all + * algorithms supported by libosmocore now. Make it table based + * as well. + */ + if (strcasecmp(alg, "none") == 0) + return true; + if (strcasecmp(alg, "xor") == 0) + return true; + if (strcasecmp(alg, "comp128v1") == 0) + return true; + return false; +} + +static int verify_subscriber_modify(struct ctrl_cmd *cmd, const char *value, void *d) +{ + char *tmp, *imsi, *msisdn, *alg, *ki, *saveptr = NULL; + int rc = 0; + + tmp = talloc_strdup(cmd, value); + if (!tmp) + return 1; + + imsi = strtok_r(tmp, ",", &saveptr); + msisdn = strtok_r(NULL, ",", &saveptr); + alg = strtok_r(NULL, ",", &saveptr); + ki = strtok_r(NULL, ",", &saveptr); + + if (!imsi || !msisdn) + rc = 1; + else if (strlen(imsi) > GSM23003_IMSI_MAX_DIGITS) + rc = 1; + else if (strlen(msisdn) >= GSM_EXTENSION_LENGTH) + rc = 1; + else if (alg) { + if (!alg_supported(alg)) + rc = 1; + else if (strcasecmp(alg, "none") != 0 && !ki) + rc = 1; + } + + talloc_free(tmp); + return rc; +} + +static int set_subscriber_modify(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_network *net = cmd->node; + char *tmp, *imsi, *msisdn, *alg, *ki, *saveptr = NULL; + struct gsm_subscriber* subscr; + int rc; + + tmp = talloc_strdup(cmd, cmd->value); + if (!tmp) + return 1; + + imsi = strtok_r(tmp, ",", &saveptr); + msisdn = strtok_r(NULL, ",", &saveptr); + alg = strtok_r(NULL, ",", &saveptr); + ki = strtok_r(NULL, ",", &saveptr); + + subscr = subscr_get_by_imsi(net->subscr_group, imsi); + if (!subscr) + subscr = subscr_create_subscriber(net->subscr_group, imsi); + if (!subscr) + goto fail; + + subscr->authorized = 1; + osmo_strlcpy(subscr->extension, msisdn, sizeof(subscr->extension)); + + /* put it back to the db */ + rc = db_sync_subscriber(subscr); + db_subscriber_update(subscr); + + /* handle optional ciphering */ + if (alg) { + if (strcasecmp(alg, "none") == 0) + db_sync_authinfo_for_subscr(NULL, subscr); + else { + struct gsm_auth_info ainfo = { 0, }; + /* the verify should make sure that this is okay */ + OSMO_ASSERT(alg); + OSMO_ASSERT(ki); + + if (strcasecmp(alg, "xor") == 0) + ainfo.auth_algo = AUTH_ALGO_XOR; + else if (strcasecmp(alg, "comp128v1") == 0) + ainfo.auth_algo = AUTH_ALGO_COMP128v1; + + rc = osmo_hexparse(ki, ainfo.a3a8_ki, sizeof(ainfo.a3a8_ki)); + if (rc < 0) { + subscr_put(subscr); + talloc_free(tmp); + cmd->reply = "Failed to parse KI"; + return CTRL_CMD_ERROR; + } + + ainfo.a3a8_ki_len = rc; + db_sync_authinfo_for_subscr(&ainfo, subscr); + rc = 0; + } + db_sync_lastauthtuple_for_subscr(NULL, subscr); + } + subscr_put(subscr); + + talloc_free(tmp); + + if (rc != 0) { + cmd->reply = "Failed to store the record in the DB"; + return CTRL_CMD_ERROR; + } + + cmd->reply = "OK"; + return CTRL_CMD_REPLY; + +fail: + talloc_free(tmp); + cmd->reply = "Failed to create subscriber"; + return CTRL_CMD_ERROR; +} + +CTRL_CMD_DEFINE_WO(subscriber_modify, "subscriber-modify-v1"); + +static int set_subscriber_delete(struct ctrl_cmd *cmd, void *data) +{ + int was_used = 0; + int rc; + struct gsm_subscriber *subscr; + struct gsm_network *net = cmd->node; + + subscr = subscr_get_by_imsi(net->subscr_group, cmd->value); + if (!subscr) { + cmd->reply = "Failed to find subscriber"; + return CTRL_CMD_ERROR; + } + + if (subscr->use_count != 1) { + LOGP(DCTRL, LOGL_NOTICE, "Going to remove active subscriber.\n"); + was_used = 1; + } + + rc = db_subscriber_delete(subscr); + subscr_put(subscr); + + if (rc != 0) { + cmd->reply = "Failed to remove subscriber"; + return CTRL_CMD_ERROR; + } + + cmd->reply = was_used ? "Removed active subscriber" : "Removed"; + return CTRL_CMD_REPLY; +} +CTRL_CMD_DEFINE_WO_NOVRF(subscriber_delete, "subscriber-delete-v1"); + +static void list_cb(struct gsm_subscriber *subscr, void *d) +{ + char **data = (char **) d; + *data = talloc_asprintf_append(*data, "%s,%s\n", + subscr->imsi, subscr->extension); +} + +static int get_subscriber_list(struct ctrl_cmd *cmd, void *d) +{ + cmd->reply = talloc_strdup(cmd, ""); + + db_subscriber_list_active(list_cb, &cmd->reply); + printf("%s\n", cmd->reply); + return CTRL_CMD_REPLY; +} +CTRL_CMD_DEFINE_RO(subscriber_list, "subscriber-list-active-v1"); + +int msc_ctrl_cmds_install(void) +{ + int rc = 0; + + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_subscriber_modify); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_subscriber_delete); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_subscriber_list); + return rc; +} diff --git a/src/libmsc/db.c b/src/libmsc/db.c new file mode 100644 index 000000000..5fe2a3c6b --- /dev/null +++ b/src/libmsc/db.c @@ -0,0 +1,1752 @@ +/* Simple HLR/VLR database backend using dbi */ +/* (C) 2008 by Jan Luebbe <jluebbe@debian.org> + * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009 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 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 <stdint.h> +#include <inttypes.h> +#include <libgen.h> +#include <stdio.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <dbi/dbi.h> + +#include <openbsc/gsm_data.h> +#include <openbsc/gsm_subscriber.h> +#include <openbsc/gsm_04_11.h> +#include <openbsc/db.h> +#include <openbsc/debug.h> + +#include <osmocom/gsm/protocol/gsm_23_003.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/statistics.h> +#include <osmocom/core/rate_ctr.h> +#include <osmocom/core/utils.h> + +#include <openssl/rand.h> + +/* Semi-Private-Interface (SPI) for the subscriber code */ +void subscr_direct_free(struct gsm_subscriber *subscr); + +static char *db_basename = NULL; +static char *db_dirname = NULL; +static dbi_conn conn; + +#define SCHEMA_REVISION "4" + +enum { + SCHEMA_META, + INSERT_META, + SCHEMA_SUBSCRIBER, + SCHEMA_AUTH, + SCHEMA_EQUIPMENT, + SCHEMA_EQUIPMENT_WATCH, + SCHEMA_SMS, + SCHEMA_VLR, + SCHEMA_APDU, + SCHEMA_COUNTERS, + SCHEMA_RATE, + SCHEMA_AUTHKEY, + SCHEMA_AUTHLAST, +}; + +static const char *create_stmts[] = { + [SCHEMA_META] = "CREATE TABLE IF NOT EXISTS Meta (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "key TEXT UNIQUE NOT NULL, " + "value TEXT NOT NULL" + ")", + [INSERT_META] = "INSERT OR IGNORE INTO Meta " + "(key, value) " + "VALUES " + "('revision', " SCHEMA_REVISION ")", + [SCHEMA_SUBSCRIBER] = "CREATE TABLE IF NOT EXISTS Subscriber (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "created TIMESTAMP NOT NULL, " + "updated TIMESTAMP NOT NULL, " + "imsi NUMERIC UNIQUE NOT NULL, " + "name TEXT, " + "extension TEXT UNIQUE, " + "authorized INTEGER NOT NULL DEFAULT 0, " + "tmsi TEXT UNIQUE, " + "lac INTEGER NOT NULL DEFAULT 0, " + "expire_lu TIMESTAMP DEFAULT NULL" + ")", + [SCHEMA_AUTH] = "CREATE TABLE IF NOT EXISTS AuthToken (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "subscriber_id INTEGER UNIQUE NOT NULL, " + "created TIMESTAMP NOT NULL, " + "token TEXT UNIQUE NOT NULL" + ")", + [SCHEMA_EQUIPMENT] = "CREATE TABLE IF NOT EXISTS Equipment (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "created TIMESTAMP NOT NULL, " + "updated TIMESTAMP NOT NULL, " + "name TEXT, " + "classmark1 NUMERIC, " + "classmark2 BLOB, " + "classmark3 BLOB, " + "imei NUMERIC UNIQUE NOT NULL" + ")", + [SCHEMA_EQUIPMENT_WATCH] = "CREATE TABLE IF NOT EXISTS EquipmentWatch (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "created TIMESTAMP NOT NULL, " + "updated TIMESTAMP NOT NULL, " + "subscriber_id NUMERIC NOT NULL, " + "equipment_id NUMERIC NOT NULL, " + "UNIQUE (subscriber_id, equipment_id) " + ")", + [SCHEMA_SMS] = "CREATE TABLE IF NOT EXISTS SMS (" + /* metadata, not part of sms */ + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "created TIMESTAMP NOT NULL, " + "sent TIMESTAMP, " + "deliver_attempts INTEGER NOT NULL DEFAULT 0, " + /* data directly copied/derived from SMS */ + "valid_until TIMESTAMP, " + "reply_path_req INTEGER NOT NULL, " + "status_rep_req INTEGER NOT NULL, " + "protocol_id INTEGER NOT NULL, " + "data_coding_scheme INTEGER NOT NULL, " + "ud_hdr_ind INTEGER NOT NULL, " + "src_addr TEXT NOT NULL, " + "src_ton INTEGER NOT NULL, " + "src_npi INTEGER NOT NULL, " + "dest_addr TEXT NOT NULL, " + "dest_ton INTEGER NOT NULL, " + "dest_npi INTEGER NOT NULL, " + "user_data BLOB, " /* TP-UD */ + /* additional data, interpreted from SMS */ + "header BLOB, " /* UD Header */ + "text TEXT " /* decoded UD after UDH */ + ")", + [SCHEMA_VLR] = "CREATE TABLE IF NOT EXISTS VLR (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "created TIMESTAMP NOT NULL, " + "updated TIMESTAMP NOT NULL, " + "subscriber_id NUMERIC UNIQUE NOT NULL, " + "last_bts NUMERIC NOT NULL " + ")", + [SCHEMA_APDU] = "CREATE TABLE IF NOT EXISTS ApduBlobs (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "created TIMESTAMP NOT NULL, " + "apdu_id_flags INTEGER NOT NULL, " + "subscriber_id INTEGER NOT NULL, " + "apdu BLOB " + ")", + [SCHEMA_COUNTERS] = "CREATE TABLE IF NOT EXISTS Counters (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "timestamp TIMESTAMP NOT NULL, " + "value INTEGER NOT NULL, " + "name TEXT NOT NULL " + ")", + [SCHEMA_RATE] = "CREATE TABLE IF NOT EXISTS RateCounters (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "timestamp TIMESTAMP NOT NULL, " + "value INTEGER NOT NULL, " + "name TEXT NOT NULL, " + "idx INTEGER NOT NULL " + ")", + [SCHEMA_AUTHKEY] = "CREATE TABLE IF NOT EXISTS AuthKeys (" + "subscriber_id INTEGER PRIMARY KEY, " + "algorithm_id INTEGER NOT NULL, " + "a3a8_ki BLOB " + ")", + [SCHEMA_AUTHLAST] = "CREATE TABLE IF NOT EXISTS AuthLastTuples (" + "subscriber_id INTEGER PRIMARY KEY, " + "issued TIMESTAMP NOT NULL, " + "use_count INTEGER NOT NULL DEFAULT 0, " + "key_seq INTEGER NOT NULL, " + "rand BLOB NOT NULL, " + "sres BLOB NOT NULL, " + "kc BLOB NOT NULL " + ")", +}; + +void db_error_func(dbi_conn conn, void *data) +{ + const char *msg; + dbi_conn_error(conn, &msg); + LOGP(DDB, LOGL_ERROR, "DBI: %s\n", msg); + osmo_log_backtrace(DDB, LOGL_ERROR); +} + +static int update_db_revision_2(void) +{ + dbi_result result; + + result = dbi_conn_query(conn, + "ALTER TABLE Subscriber " + "ADD COLUMN expire_lu " + "TIMESTAMP DEFAULT NULL"); + if (!result) { + LOGP(DDB, LOGL_ERROR, + "Failed to alter table Subscriber (upgrade from rev 2).\n"); + return -EINVAL; + } + dbi_result_free(result); + + result = dbi_conn_query(conn, + "UPDATE Meta " + "SET value = '3' " + "WHERE key = 'revision'"); + if (!result) { + LOGP(DDB, LOGL_ERROR, + "Failed to update DB schema revision (upgrade from rev 2).\n"); + return -EINVAL; + } + dbi_result_free(result); + + return 0; +} + +/** + * Copied from the normal sms_from_result_v3 to avoid having + * to make sure that the real routine will remain backward + * compatible. + */ +static struct gsm_sms *sms_from_result_v3(dbi_result result) +{ + struct gsm_sms *sms = sms_alloc(); + long long unsigned int sender_id; + struct gsm_subscriber *sender; + const char *text, *daddr; + const unsigned char *user_data; + char buf[32]; + + if (!sms) + return NULL; + + sms->id = dbi_result_get_ulonglong(result, "id"); + + sender_id = dbi_result_get_ulonglong(result, "sender_id"); + snprintf(buf, sizeof(buf), "%llu", sender_id); + sender = db_get_subscriber(GSM_SUBSCRIBER_ID, buf); + OSMO_ASSERT(sender); + osmo_strlcpy(sms->src.addr, sender->extension, sizeof(sms->src.addr)); + subscr_direct_free(sender); + sender = NULL; + + sms->reply_path_req = dbi_result_get_ulonglong(result, "reply_path_req"); + sms->status_rep_req = dbi_result_get_ulonglong(result, "status_rep_req"); + sms->ud_hdr_ind = dbi_result_get_ulonglong(result, "ud_hdr_ind"); + sms->protocol_id = dbi_result_get_ulonglong(result, "protocol_id"); + sms->data_coding_scheme = dbi_result_get_ulonglong(result, + "data_coding_scheme"); + + daddr = dbi_result_get_string(result, "dest_addr"); + if (daddr) + osmo_strlcpy(sms->dst.addr, daddr, sizeof(sms->dst.addr)); + + sms->user_data_len = dbi_result_get_field_length(result, "user_data"); + user_data = dbi_result_get_binary(result, "user_data"); + if (sms->user_data_len > sizeof(sms->user_data)) + sms->user_data_len = (uint8_t) sizeof(sms->user_data); + memcpy(sms->user_data, user_data, sms->user_data_len); + + text = dbi_result_get_string(result, "text"); + if (text) + osmo_strlcpy(sms->text, text, sizeof(sms->text)); + return sms; +} + +static int update_db_revision_3(void) +{ + dbi_result result; + struct gsm_sms *sms; + + LOGP(DDB, LOGL_NOTICE, "Going to migrate from revision 3\n"); + + result = dbi_conn_query(conn, "BEGIN EXCLUSIVE TRANSACTION"); + if (!result) { + LOGP(DDB, LOGL_ERROR, + "Failed to begin transaction (upgrade from rev 3)\n"); + return -EINVAL; + } + dbi_result_free(result); + + /* Rename old SMS table to be able create a new one */ + result = dbi_conn_query(conn, "ALTER TABLE SMS RENAME TO SMS_3"); + if (!result) { + LOGP(DDB, LOGL_ERROR, + "Failed to rename the old SMS table (upgrade from rev 3).\n"); + goto rollback; + } + dbi_result_free(result); + + /* Create new SMS table with all the bells and whistles! */ + result = dbi_conn_query(conn, create_stmts[SCHEMA_SMS]); + if (!result) { + LOGP(DDB, LOGL_ERROR, + "Failed to create a new SMS table (upgrade from rev 3).\n"); + goto rollback; + } + dbi_result_free(result); + + /* Cycle through old messages and convert them to the new format */ + result = dbi_conn_query(conn, "SELECT * FROM SMS_3"); + if (!result) { + LOGP(DDB, LOGL_ERROR, + "Failed fetch messages from the old SMS table (upgrade from rev 3).\n"); + goto rollback; + } + while (dbi_result_next_row(result)) { + sms = sms_from_result_v3(result); + if (db_sms_store(sms) != 0) { + LOGP(DDB, LOGL_ERROR, "Failed to store message to the new SMS table(upgrade from rev 3).\n"); + sms_free(sms); + dbi_result_free(result); + goto rollback; + } + sms_free(sms); + } + dbi_result_free(result); + + /* Remove the temporary table */ + result = dbi_conn_query(conn, "DROP TABLE SMS_3"); + if (!result) { + LOGP(DDB, LOGL_ERROR, + "Failed to drop the old SMS table (upgrade from rev 3).\n"); + goto rollback; + } + dbi_result_free(result); + + /* We're done. Bump DB Meta revision to 4 */ + result = dbi_conn_query(conn, + "UPDATE Meta " + "SET value = '4' " + "WHERE key = 'revision'"); + if (!result) { + LOGP(DDB, LOGL_ERROR, + "Failed to update DB schema revision (upgrade from rev 3).\n"); + goto rollback; + } + dbi_result_free(result); + + result = dbi_conn_query(conn, "COMMIT TRANSACTION"); + if (!result) { + LOGP(DDB, LOGL_ERROR, + "Failed to commit the transaction (upgrade from rev 3)\n"); + return -EINVAL; + } else { + dbi_result_free(result); + } + + /* Shrink DB file size by actually wiping out SMS_3 table data */ + result = dbi_conn_query(conn, "VACUUM"); + if (!result) + LOGP(DDB, LOGL_ERROR, + "VACUUM failed. Ignoring it (upgrade from rev 3).\n"); + else + dbi_result_free(result); + + return 0; + +rollback: + result = dbi_conn_query(conn, "ROLLBACK TRANSACTION"); + if (!result) + LOGP(DDB, LOGL_ERROR, + "Rollback failed (upgrade from rev 3).\n"); + else + dbi_result_free(result); + return -EINVAL; +} + +static int check_db_revision(void) +{ + dbi_result result; + const char *rev_s; + int db_rev = 0; + + /* Make a query */ + result = dbi_conn_query(conn, + "SELECT value FROM Meta " + "WHERE key = 'revision'"); + + if (!result) + return -EINVAL; + + if (!dbi_result_next_row(result)) { + dbi_result_free(result); + return -EINVAL; + } + + /* Fetch the DB schema revision */ + rev_s = dbi_result_get_string(result, "value"); + if (!rev_s) { + dbi_result_free(result); + return -EINVAL; + } + + if (!strcmp(rev_s, SCHEMA_REVISION)) { + /* Everything is fine */ + dbi_result_free(result); + return 0; + } + + db_rev = atoi(rev_s); + dbi_result_free(result); + + /* Incremental migration waterfall */ + switch (db_rev) { + case 2: + if (update_db_revision_2()) + goto error; + case 3: + if (update_db_revision_3()) + goto error; + + /* The end of waterfall */ + break; + default: + LOGP(DDB, LOGL_FATAL, + "Invalid database schema revision '%d'.\n", db_rev); + return -EINVAL; + } + + return 0; + +error: + LOGP(DDB, LOGL_FATAL, "Failed to update database " + "from schema revision '%d'.\n", db_rev); + return -EINVAL; +} + +static int db_configure(void) +{ + dbi_result result; + + result = dbi_conn_query(conn, + "PRAGMA synchronous = FULL"); + if (!result) + return -EINVAL; + + dbi_result_free(result); + return 0; +} + +int db_init(const char *name) +{ + dbi_initialize(NULL); + + conn = dbi_conn_new("sqlite3"); + if (conn == NULL) { + LOGP(DDB, LOGL_FATAL, "Failed to create connection.\n"); + return 1; + } + + dbi_conn_error_handler( conn, db_error_func, NULL ); + + /* MySQL + dbi_conn_set_option(conn, "host", "localhost"); + dbi_conn_set_option(conn, "username", "your_name"); + dbi_conn_set_option(conn, "password", "your_password"); + dbi_conn_set_option(conn, "dbname", "your_dbname"); + dbi_conn_set_option(conn, "encoding", "UTF-8"); + */ + + /* SqLite 3 */ + db_basename = strdup(name); + db_dirname = strdup(name); + dbi_conn_set_option(conn, "sqlite3_dbdir", dirname(db_dirname)); + dbi_conn_set_option(conn, "dbname", basename(db_basename)); + + if (dbi_conn_connect(conn) < 0) + goto out_err; + + return 0; + +out_err: + free(db_dirname); + free(db_basename); + db_dirname = db_basename = NULL; + return -1; +} + + +int db_prepare(void) +{ + dbi_result result; + int i; + + for (i = 0; i < ARRAY_SIZE(create_stmts); i++) { + result = dbi_conn_query(conn, create_stmts[i]); + if (!result) { + LOGP(DDB, LOGL_ERROR, + "Failed to create some table.\n"); + return 1; + } + dbi_result_free(result); + } + + if (check_db_revision() < 0) { + LOGP(DDB, LOGL_FATAL, "Database schema revision invalid, " + "please update your database schema\n"); + return -1; + } + + db_configure(); + + return 0; +} + +int db_fini(void) +{ + dbi_conn_close(conn); + dbi_shutdown(); + + free(db_dirname); + free(db_basename); + return 0; +} + +struct gsm_subscriber *db_create_subscriber(const char *imsi, uint64_t smin, + uint64_t smax, bool alloc_exten) +{ + dbi_result result; + struct gsm_subscriber *subscr; + + /* Is this subscriber known in the db? */ + subscr = db_get_subscriber(GSM_SUBSCRIBER_IMSI, imsi); + if (subscr) { + subscr_put(subscr); + return NULL; + } + + subscr = subscr_alloc(); + if (!subscr) + return NULL; + subscr->flags |= GSM_SUBSCRIBER_FIRST_CONTACT; + result = dbi_conn_queryf(conn, + "INSERT INTO Subscriber " + "(imsi, created, updated) " + "VALUES " + "(%s, datetime('now'), datetime('now')) ", + imsi + ); + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to create Subscriber by IMSI.\n"); + subscr_put(subscr); + return NULL; + } + subscr->id = dbi_conn_sequence_last(conn, NULL); + osmo_strlcpy(subscr->imsi, imsi, sizeof(subscr->imsi)); + dbi_result_free(result); + LOGP(DDB, LOGL_INFO, "New Subscriber: ID %llu, IMSI %s\n", subscr->id, subscr->imsi); + if (alloc_exten) + db_subscriber_alloc_exten(subscr, smin, smax); + return subscr; +} + +osmo_static_assert(sizeof(unsigned char) == sizeof(struct gsm48_classmark1), classmark1_size); + +static int get_equipment_by_subscr(struct gsm_subscriber *subscr) +{ + dbi_result result; + const char *string; + unsigned char cm1; + const unsigned char *cm2, *cm3; + struct gsm_equipment *equip = &subscr->equipment; + + result = dbi_conn_queryf(conn, + "SELECT Equipment.* " + "FROM Equipment JOIN EquipmentWatch ON " + "EquipmentWatch.equipment_id=Equipment.id " + "WHERE EquipmentWatch.subscriber_id = %llu " + "ORDER BY EquipmentWatch.updated DESC", subscr->id); + if (!result) + return -EIO; + + if (!dbi_result_next_row(result)) { + dbi_result_free(result); + return -ENOENT; + } + + equip->id = dbi_result_get_ulonglong(result, "id"); + + string = dbi_result_get_string(result, "imei"); + if (string) + osmo_strlcpy(equip->imei, string, sizeof(equip->imei)); + + string = dbi_result_get_string(result, "classmark1"); + if (string) { + cm1 = atoi(string) & 0xff; + memcpy(&equip->classmark1, &cm1, sizeof(equip->classmark1)); + } + + equip->classmark2_len = dbi_result_get_field_length(result, "classmark2"); + cm2 = dbi_result_get_binary(result, "classmark2"); + if (equip->classmark2_len > sizeof(equip->classmark2)) + equip->classmark2_len = sizeof(equip->classmark2); + if (cm2) + memcpy(equip->classmark2, cm2, equip->classmark2_len); + + equip->classmark3_len = dbi_result_get_field_length(result, "classmark3"); + cm3 = dbi_result_get_binary(result, "classmark3"); + if (equip->classmark3_len > sizeof(equip->classmark3)) + equip->classmark3_len = sizeof(equip->classmark3); + if (cm3) + memcpy(equip->classmark3, cm3, equip->classmark3_len); + + dbi_result_free(result); + + return 0; +} + +int db_get_authinfo_for_subscr(struct gsm_auth_info *ainfo, + struct gsm_subscriber *subscr) +{ + dbi_result result; + const unsigned char *a3a8_ki; + + result = dbi_conn_queryf(conn, + "SELECT * FROM AuthKeys WHERE subscriber_id=%llu", + subscr->id); + if (!result) + return -EIO; + + if (!dbi_result_next_row(result)) { + dbi_result_free(result); + return -ENOENT; + } + + ainfo->auth_algo = dbi_result_get_ulonglong(result, "algorithm_id"); + ainfo->a3a8_ki_len = dbi_result_get_field_length(result, "a3a8_ki"); + a3a8_ki = dbi_result_get_binary(result, "a3a8_ki"); + if (ainfo->a3a8_ki_len > sizeof(ainfo->a3a8_ki)) + ainfo->a3a8_ki_len = sizeof(ainfo->a3a8_ki); + memcpy(ainfo->a3a8_ki, a3a8_ki, ainfo->a3a8_ki_len); + + dbi_result_free(result); + + return 0; +} + +int db_sync_authinfo_for_subscr(struct gsm_auth_info *ainfo, + struct gsm_subscriber *subscr) +{ + dbi_result result; + struct gsm_auth_info ainfo_old; + int rc, upd; + unsigned char *ki_str; + + /* Deletion ? */ + if (ainfo == NULL) { + result = dbi_conn_queryf(conn, + "DELETE FROM AuthKeys WHERE subscriber_id=%llu", + subscr->id); + + if (!result) + return -EIO; + + dbi_result_free(result); + + return 0; + } + + /* Check if already existing */ + rc = db_get_authinfo_for_subscr(&ainfo_old, subscr); + if (rc && rc != -ENOENT) + return rc; + upd = rc ? 0 : 1; + + /* Update / Insert */ + dbi_conn_quote_binary_copy(conn, + ainfo->a3a8_ki, ainfo->a3a8_ki_len, &ki_str); + + if (!upd) { + result = dbi_conn_queryf(conn, + "INSERT INTO AuthKeys " + "(subscriber_id, algorithm_id, a3a8_ki) " + "VALUES (%llu, %u, %s)", + subscr->id, ainfo->auth_algo, ki_str); + } else { + result = dbi_conn_queryf(conn, + "UPDATE AuthKeys " + "SET algorithm_id=%u, a3a8_ki=%s " + "WHERE subscriber_id=%llu", + ainfo->auth_algo, ki_str, subscr->id); + } + + free(ki_str); + + if (!result) + return -EIO; + + dbi_result_free(result); + + return 0; +} + +int db_get_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple, + struct gsm_subscriber *subscr) +{ + dbi_result result; + int len; + const unsigned char *blob; + + result = dbi_conn_queryf(conn, + "SELECT * FROM AuthLastTuples WHERE subscriber_id=%llu", + subscr->id); + if (!result) + return -EIO; + + if (!dbi_result_next_row(result)) { + dbi_result_free(result); + return -ENOENT; + } + + memset(atuple, 0, sizeof(*atuple)); + + atuple->use_count = dbi_result_get_ulonglong(result, "use_count"); + atuple->key_seq = dbi_result_get_ulonglong(result, "key_seq"); + + len = dbi_result_get_field_length(result, "rand"); + if (len != sizeof(atuple->vec.rand)) + goto err_size; + + blob = dbi_result_get_binary(result, "rand"); + memcpy(atuple->vec.rand, blob, len); + + len = dbi_result_get_field_length(result, "sres"); + if (len != sizeof(atuple->vec.sres)) + goto err_size; + + blob = dbi_result_get_binary(result, "sres"); + memcpy(atuple->vec.sres, blob, len); + + len = dbi_result_get_field_length(result, "kc"); + if (len != sizeof(atuple->vec.kc)) + goto err_size; + + blob = dbi_result_get_binary(result, "kc"); + memcpy(atuple->vec.kc, blob, len); + + dbi_result_free(result); + + return 0; + +err_size: + dbi_result_free(result); + return -EIO; +} + +int db_sync_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple, + struct gsm_subscriber *subscr) +{ + dbi_result result; + int rc, upd; + struct gsm_auth_tuple atuple_old; + unsigned char *rand_str, *sres_str, *kc_str; + + /* Deletion ? */ + if (atuple == NULL) { + result = dbi_conn_queryf(conn, + "DELETE FROM AuthLastTuples WHERE subscriber_id=%llu", + subscr->id); + + if (!result) + return -EIO; + + dbi_result_free(result); + + return 0; + } + + /* Check if already existing */ + rc = db_get_lastauthtuple_for_subscr(&atuple_old, subscr); + if (rc && rc != -ENOENT) + return rc; + upd = rc ? 0 : 1; + + /* Update / Insert */ + dbi_conn_quote_binary_copy(conn, + atuple->vec.rand, sizeof(atuple->vec.rand), &rand_str); + dbi_conn_quote_binary_copy(conn, + atuple->vec.sres, sizeof(atuple->vec.sres), &sres_str); + dbi_conn_quote_binary_copy(conn, + atuple->vec.kc, sizeof(atuple->vec.kc), &kc_str); + + if (!upd) { + result = dbi_conn_queryf(conn, + "INSERT INTO AuthLastTuples " + "(subscriber_id, issued, use_count, " + "key_seq, rand, sres, kc) " + "VALUES (%llu, datetime('now'), %u, " + "%u, %s, %s, %s ) ", + subscr->id, atuple->use_count, atuple->key_seq, + rand_str, sres_str, kc_str); + } else { + char *issued = atuple->key_seq == atuple_old.key_seq ? + "issued" : "datetime('now')"; + result = dbi_conn_queryf(conn, + "UPDATE AuthLastTuples " + "SET issued=%s, use_count=%u, " + "key_seq=%u, rand=%s, sres=%s, kc=%s " + "WHERE subscriber_id = %llu", + issued, atuple->use_count, atuple->key_seq, + rand_str, sres_str, kc_str, subscr->id); + } + + free(rand_str); + free(sres_str); + free(kc_str); + + if (!result) + return -EIO; + + dbi_result_free(result); + + return 0; +} + +static void db_set_from_query(struct gsm_subscriber *subscr, dbi_conn result) +{ + const char *string; + string = dbi_result_get_string(result, "imsi"); + if (string) + osmo_strlcpy(subscr->imsi, string, sizeof(subscr->imsi)); + + string = dbi_result_get_string(result, "tmsi"); + if (string) + subscr->tmsi = tmsi_from_string(string); + + string = dbi_result_get_string(result, "name"); + if (string) + osmo_strlcpy(subscr->name, string, sizeof(subscr->name)); + + string = dbi_result_get_string(result, "extension"); + if (string) + osmo_strlcpy(subscr->extension, string, sizeof(subscr->extension)); + + subscr->lac = dbi_result_get_ulonglong(result, "lac"); + + if (!dbi_result_field_is_null(result, "expire_lu")) + subscr->expire_lu = dbi_result_get_datetime(result, "expire_lu"); + else + subscr->expire_lu = GSM_SUBSCRIBER_NO_EXPIRATION; + + subscr->authorized = dbi_result_get_ulonglong(result, "authorized"); + +} + +#define BASE_QUERY "SELECT * FROM Subscriber " +struct gsm_subscriber *db_get_subscriber(enum gsm_subscriber_field field, + const char *id) +{ + dbi_result result; + char *quoted; + struct gsm_subscriber *subscr; + + switch (field) { + case GSM_SUBSCRIBER_IMSI: + dbi_conn_quote_string_copy(conn, id, "ed); + result = dbi_conn_queryf(conn, + BASE_QUERY + "WHERE imsi = %s ", + quoted + ); + free(quoted); + break; + case GSM_SUBSCRIBER_TMSI: + dbi_conn_quote_string_copy(conn, id, "ed); + result = dbi_conn_queryf(conn, + BASE_QUERY + "WHERE tmsi = %s ", + quoted + ); + free(quoted); + break; + case GSM_SUBSCRIBER_EXTENSION: + dbi_conn_quote_string_copy(conn, id, "ed); + result = dbi_conn_queryf(conn, + BASE_QUERY + "WHERE extension = %s ", + quoted + ); + free(quoted); + break; + case GSM_SUBSCRIBER_ID: + dbi_conn_quote_string_copy(conn, id, "ed); + result = dbi_conn_queryf(conn, + BASE_QUERY + "WHERE id = %s ", quoted); + free(quoted); + break; + default: + LOGP(DDB, LOGL_NOTICE, "Unknown query selector for Subscriber.\n"); + return NULL; + } + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber.\n"); + return NULL; + } + if (!dbi_result_next_row(result)) { + DEBUGP(DDB, "Failed to find the Subscriber. '%u' '%s'\n", + field, id); + dbi_result_free(result); + return NULL; + } + + subscr = subscr_alloc(); + subscr->id = dbi_result_get_ulonglong(result, "id"); + + db_set_from_query(subscr, result); + DEBUGP(DDB, "Found Subscriber: ID %llu, IMSI %s, NAME '%s', TMSI %x, EXTEN '%s', LAC %hu, AUTH %u\n", + subscr->id, subscr->imsi, subscr->name, subscr->tmsi, subscr->extension, + subscr->lac, subscr->authorized); + dbi_result_free(result); + + get_equipment_by_subscr(subscr); + + return subscr; +} + +int db_subscriber_update(struct gsm_subscriber *subscr) +{ + char buf[32]; + dbi_result result; + + /* Copy the id to a string as queryf with %llu is failing */ + sprintf(buf, "%llu", subscr->id); + result = dbi_conn_queryf(conn, + BASE_QUERY + "WHERE id = %s", buf); + + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber: %llu\n", subscr->id); + return -EIO; + } + if (!dbi_result_next_row(result)) { + DEBUGP(DDB, "Failed to find the Subscriber. %llu\n", + subscr->id); + dbi_result_free(result); + return -EIO; + } + + db_set_from_query(subscr, result); + dbi_result_free(result); + get_equipment_by_subscr(subscr); + + return 0; +} + +int db_sync_subscriber(struct gsm_subscriber *subscriber) +{ + dbi_result result; + char tmsi[14]; + char *q_tmsi, *q_name, *q_extension; + + dbi_conn_quote_string_copy(conn, + subscriber->name, &q_name); + if (subscriber->extension[0] != '\0') + dbi_conn_quote_string_copy(conn, + subscriber->extension, &q_extension); + else + q_extension = strdup("NULL"); + + if (subscriber->tmsi != GSM_RESERVED_TMSI) { + sprintf(tmsi, "%u", subscriber->tmsi); + dbi_conn_quote_string_copy(conn, + tmsi, + &q_tmsi); + } else + q_tmsi = strdup("NULL"); + + if (subscriber->expire_lu == GSM_SUBSCRIBER_NO_EXPIRATION) { + result = dbi_conn_queryf(conn, + "UPDATE Subscriber " + "SET updated = datetime('now'), " + "name = %s, " + "extension = %s, " + "authorized = %i, " + "tmsi = %s, " + "lac = %i, " + "expire_lu = NULL " + "WHERE imsi = %s ", + q_name, + q_extension, + subscriber->authorized, + q_tmsi, + subscriber->lac, + subscriber->imsi); + } else { + result = dbi_conn_queryf(conn, + "UPDATE Subscriber " + "SET updated = datetime('now'), " + "name = %s, " + "extension = %s, " + "authorized = %i, " + "tmsi = %s, " + "lac = %i, " + "expire_lu = datetime(%i, 'unixepoch') " + "WHERE imsi = %s ", + q_name, + q_extension, + subscriber->authorized, + q_tmsi, + subscriber->lac, + (int) subscriber->expire_lu, + subscriber->imsi); + } + + free(q_tmsi); + free(q_name); + free(q_extension); + + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to update Subscriber (by IMSI).\n"); + return 1; + } + + dbi_result_free(result); + + return 0; +} + +int db_subscriber_delete(struct gsm_subscriber *subscr) +{ + dbi_result result; + + result = dbi_conn_queryf(conn, + "DELETE FROM AuthKeys WHERE subscriber_id=%llu", + subscr->id); + if (!result) { + LOGP(DDB, LOGL_ERROR, + "Failed to delete Authkeys for %llu\n", subscr->id); + return -1; + } + dbi_result_free(result); + + result = dbi_conn_queryf(conn, + "DELETE FROM AuthLastTuples WHERE subscriber_id=%llu", + subscr->id); + if (!result) { + LOGP(DDB, LOGL_ERROR, + "Failed to delete AuthLastTuples for %llu\n", subscr->id); + return -1; + } + dbi_result_free(result); + + result = dbi_conn_queryf(conn, + "DELETE FROM AuthToken WHERE subscriber_id=%llu", + subscr->id); + if (!result) { + LOGP(DDB, LOGL_ERROR, + "Failed to delete AuthToken for %llu\n", subscr->id); + return -1; + } + dbi_result_free(result); + + result = dbi_conn_queryf(conn, + "DELETE FROM EquipmentWatch WHERE subscriber_id=%llu", + subscr->id); + if (!result) { + LOGP(DDB, LOGL_ERROR, + "Failed to delete EquipmentWatch for %llu\n", subscr->id); + return -1; + } + dbi_result_free(result); + + if (subscr->extension[0] != '\0') { + result = dbi_conn_queryf(conn, + "DELETE FROM SMS WHERE src_addr=%s OR dest_addr=%s", + subscr->extension, subscr->extension); + if (!result) { + LOGP(DDB, LOGL_ERROR, + "Failed to delete SMS for %llu\n", subscr->id); + return -1; + } + dbi_result_free(result); + } + + result = dbi_conn_queryf(conn, + "DELETE FROM VLR WHERE subscriber_id=%llu", + subscr->id); + if (!result) { + LOGP(DDB, LOGL_ERROR, + "Failed to delete VLR for %llu\n", subscr->id); + return -1; + } + dbi_result_free(result); + + result = dbi_conn_queryf(conn, + "DELETE FROM ApduBlobs WHERE subscriber_id=%llu", + subscr->id); + if (!result) { + LOGP(DDB, LOGL_ERROR, + "Failed to delete ApduBlobs for %llu\n", subscr->id); + return -1; + } + dbi_result_free(result); + + result = dbi_conn_queryf(conn, + "DELETE FROM Subscriber WHERE id=%llu", + subscr->id); + if (!result) { + LOGP(DDB, LOGL_ERROR, + "Failed to delete Subscriber for %llu\n", subscr->id); + return -1; + } + dbi_result_free(result); + + return 0; +} + +/** + * List all the authorized and non-expired subscribers. The callback will + * be called one by one. The subscr argument is not fully initialize and + * subscr_get/subscr_put must not be called. The passed in pointer will be + * deleted after the callback by the database call. + */ +int db_subscriber_list_active(void (*cb)(struct gsm_subscriber*,void*), void *closure) +{ + dbi_result result; + + result = dbi_conn_query(conn, + "SELECT * from Subscriber WHERE LAC != 0 AND authorized = 1"); + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to list active subscribers\n"); + return -1; + } + + while (dbi_result_next_row(result)) { + struct gsm_subscriber *subscr; + + subscr = subscr_alloc(); + subscr->id = dbi_result_get_ulonglong(result, "id"); + db_set_from_query(subscr, result); + cb(subscr, closure); + OSMO_ASSERT(subscr->use_count == 1); + llist_del(&subscr->entry); + talloc_free(subscr); + } + + dbi_result_free(result); + return 0; +} + +int db_sync_equipment(struct gsm_equipment *equip) +{ + dbi_result result; + unsigned char *cm2, *cm3; + char *q_imei; + uint8_t classmark1; + + memcpy(&classmark1, &equip->classmark1, sizeof(classmark1)); + DEBUGP(DDB, "Sync Equipment IMEI=%s, classmark1=%02x", + equip->imei, classmark1); + if (equip->classmark2_len) + DEBUGPC(DDB, ", classmark2=%s", + osmo_hexdump(equip->classmark2, equip->classmark2_len)); + if (equip->classmark3_len) + DEBUGPC(DDB, ", classmark3=%s", + osmo_hexdump(equip->classmark3, equip->classmark3_len)); + DEBUGPC(DDB, "\n"); + + dbi_conn_quote_binary_copy(conn, equip->classmark2, + equip->classmark2_len, &cm2); + dbi_conn_quote_binary_copy(conn, equip->classmark3, + equip->classmark3_len, &cm3); + dbi_conn_quote_string_copy(conn, equip->imei, &q_imei); + + result = dbi_conn_queryf(conn, + "UPDATE Equipment SET " + "updated = datetime('now'), " + "classmark1 = %u, " + "classmark2 = %s, " + "classmark3 = %s " + "WHERE imei = %s ", + classmark1, cm2, cm3, q_imei); + + free(cm2); + free(cm3); + free(q_imei); + + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to update Equipment\n"); + return -EIO; + } + + dbi_result_free(result); + return 0; +} + +int db_subscriber_expire(void *priv, void (*callback)(void *priv, long long unsigned int id)) +{ + dbi_result result; + + result = dbi_conn_query(conn, + "SELECT id " + "FROM Subscriber " + "WHERE lac != 0 AND " + "( expire_lu is NOT NULL " + "AND expire_lu < datetime('now') ) " + "LIMIT 1"); + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to get expired subscribers\n"); + return -EIO; + } + + while (dbi_result_next_row(result)) + callback(priv, dbi_result_get_ulonglong(result, "id")); + + dbi_result_free(result); + return 0; +} + +int db_subscriber_alloc_tmsi(struct gsm_subscriber *subscriber) +{ + dbi_result result = NULL; + char tmsi[14]; + char *tmsi_quoted; + + for (;;) { + if (RAND_bytes((uint8_t *) &subscriber->tmsi, sizeof(subscriber->tmsi)) != 1) { + LOGP(DDB, LOGL_ERROR, "RAND_bytes failed\n"); + return 1; + } + if (subscriber->tmsi == GSM_RESERVED_TMSI) + continue; + + sprintf(tmsi, "%u", subscriber->tmsi); + dbi_conn_quote_string_copy(conn, tmsi, &tmsi_quoted); + result = dbi_conn_queryf(conn, + "SELECT * FROM Subscriber " + "WHERE tmsi = %s ", + tmsi_quoted); + + free(tmsi_quoted); + + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber " + "while allocating new TMSI.\n"); + return 1; + } + if (dbi_result_get_numrows(result)) { + dbi_result_free(result); + continue; + } + if (!dbi_result_next_row(result)) { + dbi_result_free(result); + DEBUGP(DDB, "Allocated TMSI %u for IMSI %s.\n", + subscriber->tmsi, subscriber->imsi); + return db_sync_subscriber(subscriber); + } + dbi_result_free(result); + } + return 0; +} + +int db_subscriber_alloc_exten(struct gsm_subscriber *subscriber, uint64_t smin, + uint64_t smax) +{ + dbi_result result = NULL; + uint64_t try; + + for (;;) { + try = (rand() % (smax - smin + 1) + smin); + result = dbi_conn_queryf(conn, + "SELECT * FROM Subscriber " + "WHERE extension = %"PRIu64, + try + ); + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber " + "while allocating new extension.\n"); + return 1; + } + if (dbi_result_get_numrows(result)){ + dbi_result_free(result); + continue; + } + if (!dbi_result_next_row(result)) { + dbi_result_free(result); + break; + } + dbi_result_free(result); + } + sprintf(subscriber->extension, "%"PRIu64, try); + DEBUGP(DDB, "Allocated extension %"PRIu64 " for IMSI %s.\n", try, subscriber->imsi); + return db_sync_subscriber(subscriber); +} +/* + * try to allocate a new unique token for this subscriber and return it + * via a parameter. if the subscriber already has a token, return + * an error. + */ + +int db_subscriber_alloc_token(struct gsm_subscriber *subscriber, uint32_t *token) +{ + dbi_result result; + uint32_t try; + + for (;;) { + if (RAND_bytes((uint8_t *) &try, sizeof(try)) != 1) { + LOGP(DDB, LOGL_ERROR, "RAND_bytes failed\n"); + return 1; + } + if (!try) /* 0 is an invalid token */ + continue; + result = dbi_conn_queryf(conn, + "SELECT * FROM AuthToken " + "WHERE subscriber_id = %llu OR token = \"%08X\" ", + subscriber->id, try); + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to query AuthToken " + "while allocating new token.\n"); + return 1; + } + if (dbi_result_get_numrows(result)) { + dbi_result_free(result); + continue; + } + if (!dbi_result_next_row(result)) { + dbi_result_free(result); + break; + } + dbi_result_free(result); + } + result = dbi_conn_queryf(conn, + "INSERT INTO AuthToken " + "(subscriber_id, created, token) " + "VALUES " + "(%llu, datetime('now'), \"%08X\") ", + subscriber->id, try); + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to create token %08X for " + "IMSI %s.\n", try, subscriber->imsi); + return 1; + } + dbi_result_free(result); + *token = try; + DEBUGP(DDB, "Allocated token %08X for IMSI %s.\n", try, subscriber->imsi); + + return 0; +} + +int db_subscriber_assoc_imei(struct gsm_subscriber *subscriber, char imei[GSM23003_IMEISV_NUM_DIGITS]) +{ + unsigned long long equipment_id, watch_id; + dbi_result result; + + osmo_strlcpy(subscriber->equipment.imei, imei, sizeof(subscriber->equipment.imei)); + + result = dbi_conn_queryf(conn, + "INSERT OR IGNORE INTO Equipment " + "(imei, created, updated) " + "VALUES " + "(%s, datetime('now'), datetime('now')) ", + imei); + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to create Equipment by IMEI.\n"); + return 1; + } + + equipment_id = 0; + if (dbi_result_get_numrows_affected(result)) { + equipment_id = dbi_conn_sequence_last(conn, NULL); + } + dbi_result_free(result); + + if (equipment_id) + DEBUGP(DDB, "New Equipment: ID %llu, IMEI %s\n", equipment_id, imei); + else { + result = dbi_conn_queryf(conn, + "SELECT id FROM Equipment " + "WHERE imei = %s ", + imei + ); + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to query Equipment by IMEI.\n"); + return 1; + } + if (!dbi_result_next_row(result)) { + LOGP(DDB, LOGL_ERROR, "Failed to find the Equipment.\n"); + dbi_result_free(result); + return 1; + } + equipment_id = dbi_result_get_ulonglong(result, "id"); + dbi_result_free(result); + } + + result = dbi_conn_queryf(conn, + "INSERT OR IGNORE INTO EquipmentWatch " + "(subscriber_id, equipment_id, created, updated) " + "VALUES " + "(%llu, %llu, datetime('now'), datetime('now')) ", + subscriber->id, equipment_id); + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to create EquipmentWatch.\n"); + return 1; + } + + watch_id = 0; + if (dbi_result_get_numrows_affected(result)) + watch_id = dbi_conn_sequence_last(conn, NULL); + + dbi_result_free(result); + if (watch_id) + DEBUGP(DDB, "New EquipmentWatch: ID %llu, IMSI %s, IMEI %s\n", + equipment_id, subscriber->imsi, imei); + else { + result = dbi_conn_queryf(conn, + "UPDATE EquipmentWatch " + "SET updated = datetime('now') " + "WHERE subscriber_id = %llu AND equipment_id = %llu ", + subscriber->id, equipment_id); + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to update EquipmentWatch.\n"); + return 1; + } + dbi_result_free(result); + DEBUGP(DDB, "Updated EquipmentWatch: ID %llu, IMSI %s, IMEI %s\n", + equipment_id, subscriber->imsi, imei); + } + + return 0; +} + +/* store an [unsent] SMS to the database */ +int db_sms_store(struct gsm_sms *sms) +{ + dbi_result result; + char *q_text, *q_daddr, *q_saddr; + unsigned char *q_udata; + char *validity_timestamp = "2222-2-2"; + + /* FIXME: generate validity timestamp based on validity_minutes */ + + dbi_conn_quote_string_copy(conn, (char *)sms->text, &q_text); + dbi_conn_quote_string_copy(conn, (char *)sms->dst.addr, &q_daddr); + dbi_conn_quote_string_copy(conn, (char *)sms->src.addr, &q_saddr); + dbi_conn_quote_binary_copy(conn, sms->user_data, sms->user_data_len, + &q_udata); + + /* FIXME: correct validity period */ + result = dbi_conn_queryf(conn, + "INSERT INTO SMS " + "(created, valid_until, " + "reply_path_req, status_rep_req, protocol_id, " + "data_coding_scheme, ud_hdr_ind, " + "user_data, text, " + "dest_addr, dest_ton, dest_npi, " + "src_addr, src_ton, src_npi) VALUES " + "(datetime('now'), %u, " + "%u, %u, %u, " + "%u, %u, " + "%s, %s, " + "%s, %u, %u, " + "%s, %u, %u)", + validity_timestamp, + sms->reply_path_req, sms->status_rep_req, sms->protocol_id, + sms->data_coding_scheme, sms->ud_hdr_ind, + q_udata, q_text, + q_daddr, sms->dst.ton, sms->dst.npi, + q_saddr, sms->src.ton, sms->src.npi); + free(q_text); + free(q_udata); + free(q_daddr); + free(q_saddr); + + if (!result) + return -EIO; + + dbi_result_free(result); + return 0; +} + +static struct gsm_sms *sms_from_result(struct gsm_network *net, dbi_result result) +{ + struct gsm_sms *sms = sms_alloc(); + const char *text, *daddr, *saddr; + const unsigned char *user_data; + + if (!sms) + return NULL; + + sms->id = dbi_result_get_ulonglong(result, "id"); + + /* FIXME: validity */ + /* FIXME: those should all be get_uchar, but sqlite3 is braindead */ + sms->reply_path_req = dbi_result_get_ulonglong(result, "reply_path_req"); + sms->status_rep_req = dbi_result_get_ulonglong(result, "status_rep_req"); + sms->ud_hdr_ind = dbi_result_get_ulonglong(result, "ud_hdr_ind"); + sms->protocol_id = dbi_result_get_ulonglong(result, "protocol_id"); + sms->data_coding_scheme = dbi_result_get_ulonglong(result, + "data_coding_scheme"); + /* sms->msg_ref is temporary and not stored in DB */ + + sms->dst.npi = dbi_result_get_ulonglong(result, "dest_npi"); + sms->dst.ton = dbi_result_get_ulonglong(result, "dest_ton"); + daddr = dbi_result_get_string(result, "dest_addr"); + if (daddr) + osmo_strlcpy(sms->dst.addr, daddr, sizeof(sms->dst.addr)); + sms->receiver = subscr_get_by_extension(net->subscr_group, sms->dst.addr); + + sms->src.npi = dbi_result_get_ulonglong(result, "src_npi"); + sms->src.ton = dbi_result_get_ulonglong(result, "src_ton"); + saddr = dbi_result_get_string(result, "src_addr"); + if (saddr) + osmo_strlcpy(sms->src.addr, saddr, sizeof(sms->src.addr)); + + sms->user_data_len = dbi_result_get_field_length(result, "user_data"); + user_data = dbi_result_get_binary(result, "user_data"); + if (sms->user_data_len > sizeof(sms->user_data)) + sms->user_data_len = (uint8_t) sizeof(sms->user_data); + memcpy(sms->user_data, user_data, sms->user_data_len); + + text = dbi_result_get_string(result, "text"); + if (text) + osmo_strlcpy(sms->text, text, sizeof(sms->text)); + return sms; +} + +struct gsm_sms *db_sms_get(struct gsm_network *net, unsigned long long id) +{ + dbi_result result; + struct gsm_sms *sms; + + result = dbi_conn_queryf(conn, + "SELECT * FROM SMS WHERE SMS.id = %llu", id); + if (!result) + return NULL; + + if (!dbi_result_next_row(result)) { + dbi_result_free(result); + return NULL; + } + + sms = sms_from_result(net, result); + + dbi_result_free(result); + + return sms; +} + +/* retrieve the next unsent SMS with ID >= min_id */ +struct gsm_sms *db_sms_get_unsent(struct gsm_network *net, unsigned long long min_id) +{ + dbi_result result; + struct gsm_sms *sms; + + result = dbi_conn_queryf(conn, + "SELECT SMS.* " + "FROM SMS JOIN Subscriber ON " + "SMS.dest_addr = Subscriber.extension " + "WHERE SMS.id >= %llu AND SMS.sent IS NULL " + "AND Subscriber.lac > 0 " + "ORDER BY SMS.id LIMIT 1", + min_id); + if (!result) + return NULL; + + if (!dbi_result_next_row(result)) { + dbi_result_free(result); + return NULL; + } + + sms = sms_from_result(net, result); + + dbi_result_free(result); + + return sms; +} + +struct gsm_sms *db_sms_get_unsent_by_subscr(struct gsm_network *net, + unsigned long long min_subscr_id, + unsigned int failed) +{ + dbi_result result; + struct gsm_sms *sms; + + result = dbi_conn_queryf(conn, + "SELECT SMS.* " + "FROM SMS JOIN Subscriber ON " + "SMS.dest_addr = Subscriber.extension " + "WHERE Subscriber.id >= %llu AND SMS.sent IS NULL " + "AND Subscriber.lac > 0 AND SMS.deliver_attempts < %u " + "ORDER BY Subscriber.id, SMS.id LIMIT 1", + min_subscr_id, failed); + if (!result) + return NULL; + + if (!dbi_result_next_row(result)) { + dbi_result_free(result); + return NULL; + } + + sms = sms_from_result(net, result); + + dbi_result_free(result); + + return sms; +} + +/* retrieve the next unsent SMS for a given subscriber */ +struct gsm_sms *db_sms_get_unsent_for_subscr(struct gsm_subscriber *subscr) +{ + dbi_result result; + struct gsm_sms *sms; + + result = dbi_conn_queryf(conn, + "SELECT SMS.* " + "FROM SMS JOIN Subscriber ON " + "SMS.dest_addr = Subscriber.extension " + "WHERE Subscriber.id = %llu AND SMS.sent IS NULL " + "AND Subscriber.lac > 0 " + "ORDER BY SMS.id LIMIT 1", + subscr->id); + if (!result) + return NULL; + + if (!dbi_result_next_row(result)) { + dbi_result_free(result); + return NULL; + } + + sms = sms_from_result(subscr->group->net, result); + + dbi_result_free(result); + + return sms; +} + +/* mark a given SMS as delivered */ +int db_sms_mark_delivered(struct gsm_sms *sms) +{ + dbi_result result; + + result = dbi_conn_queryf(conn, + "UPDATE SMS " + "SET sent = datetime('now') " + "WHERE id = %llu", sms->id); + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to mark SMS %llu as sent.\n", sms->id); + return 1; + } + + dbi_result_free(result); + return 0; +} + +/* increase the number of attempted deliveries */ +int db_sms_inc_deliver_attempts(struct gsm_sms *sms) +{ + dbi_result result; + + result = dbi_conn_queryf(conn, + "UPDATE SMS " + "SET deliver_attempts = deliver_attempts + 1 " + "WHERE id = %llu", sms->id); + if (!result) { + LOGP(DDB, LOGL_ERROR, "Failed to inc deliver attempts for " + "SMS %llu.\n", sms->id); + return 1; + } + + dbi_result_free(result); + return 0; +} + +int db_apdu_blob_store(struct gsm_subscriber *subscr, + uint8_t apdu_id_flags, uint8_t len, + uint8_t *apdu) +{ + dbi_result result; + unsigned char *q_apdu; + + dbi_conn_quote_binary_copy(conn, apdu, len, &q_apdu); + + result = dbi_conn_queryf(conn, + "INSERT INTO ApduBlobs " + "(created,subscriber_id,apdu_id_flags,apdu) VALUES " + "(datetime('now'),%llu,%u,%s)", + subscr->id, apdu_id_flags, q_apdu); + + free(q_apdu); + + if (!result) + return -EIO; + + dbi_result_free(result); + return 0; +} + +int db_store_counter(struct osmo_counter *ctr) +{ + dbi_result result; + char *q_name; + + dbi_conn_quote_string_copy(conn, ctr->name, &q_name); + + result = dbi_conn_queryf(conn, + "INSERT INTO Counters " + "(timestamp,name,value) VALUES " + "(datetime('now'),%s,%lu)", q_name, ctr->value); + + free(q_name); + + if (!result) + return -EIO; + + dbi_result_free(result); + return 0; +} + +static int db_store_rate_ctr(struct rate_ctr_group *ctrg, unsigned int num, + char *q_prefix) +{ + dbi_result result; + char *q_name; + + dbi_conn_quote_string_copy(conn, ctrg->desc->ctr_desc[num].name, + &q_name); + + result = dbi_conn_queryf(conn, + "Insert INTO RateCounters " + "(timestamp,name,idx,value) VALUES " + "(datetime('now'),%s.%s,%u,%"PRIu64")", + q_prefix, q_name, ctrg->idx, ctrg->ctr[num].current); + + free(q_name); + + if (!result) + return -EIO; + + dbi_result_free(result); + return 0; +} + +int db_store_rate_ctr_group(struct rate_ctr_group *ctrg) +{ + unsigned int i; + char *q_prefix; + + dbi_conn_quote_string_copy(conn, ctrg->desc->group_name_prefix, &q_prefix); + + for (i = 0; i < ctrg->desc->num_ctr; i++) + db_store_rate_ctr(ctrg, i, q_prefix); + + free(q_prefix); + + return 0; +} diff --git a/src/libmsc/gsm_04_08.c b/src/libmsc/gsm_04_08.c new file mode 100644 index 000000000..89108e466 --- /dev/null +++ b/src/libmsc/gsm_04_08.c @@ -0,0 +1,4047 @@ +/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface + * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */ + +/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org> + * (C) 2008-2012 by Holger Hans Peter Freyther <zecke@selfish.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 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <errno.h> +#include <time.h> +#include <netinet/in.h> +#include <regex.h> +#include <sys/types.h> + +#include "bscconfig.h" + +#include <openbsc/auth.h> +#include <openbsc/db.h> +#include <openbsc/debug.h> +#include <openbsc/gsm_data.h> +#include <openbsc/gsm_subscriber.h> +#include <openbsc/gsm_04_11.h> +#include <openbsc/gsm_04_08.h> +#include <openbsc/gsm_04_80.h> +#include <openbsc/abis_rsl.h> +#include <openbsc/chan_alloc.h> +#include <openbsc/paging.h> +#include <openbsc/signal.h> +#include <osmocom/abis/trau_frame.h> +#include <openbsc/trau_mux.h> +#include <openbsc/rtp_proxy.h> +#include <openbsc/transaction.h> +#include <openbsc/ussd.h> +#include <openbsc/silent_call.h> +#include <openbsc/bsc_api.h> +#include <openbsc/osmo_msc.h> +#include <openbsc/handover.h> +#include <openbsc/mncc_int.h> +#include <osmocom/abis/e1_input.h> +#include <osmocom/core/bitvec.h> + +#include <osmocom/gsm/gsm48.h> +#include <osmocom/gsm/gsm0480.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/gsm/tlv.h> + +#include <assert.h> + +void *tall_locop_ctx; +void *tall_authciphop_ctx; + +static int tch_rtp_signal(struct gsm_lchan *lchan, int signal); + +static int gsm0408_loc_upd_acc(struct gsm_subscriber_connection *conn); +static int gsm48_tx_simple(struct gsm_subscriber_connection *conn, + uint8_t pdisc, uint8_t msg_type); +static void schedule_reject(struct gsm_subscriber_connection *conn); +static void release_anchor(struct gsm_subscriber_connection *conn); + +struct gsm_lai { + uint16_t mcc; + uint16_t mnc; + uint16_t lac; +}; + +static int apply_codec_restrictions(struct gsm_bts *bts, + struct gsm_mncc_bearer_cap *bcap) +{ + int i, j; + + /* remove unsupported speech versions from list */ + for (i = 0, j = 0; bcap->speech_ver[i] >= 0; i++) { + if (bcap->speech_ver[i] == GSM48_BCAP_SV_FR) + bcap->speech_ver[j++] = GSM48_BCAP_SV_FR; + if (bcap->speech_ver[i] == GSM48_BCAP_SV_EFR && bts->codec.efr) + bcap->speech_ver[j++] = GSM48_BCAP_SV_EFR; + if (bcap->speech_ver[i] == GSM48_BCAP_SV_AMR_F && bts->codec.amr) + bcap->speech_ver[j++] = GSM48_BCAP_SV_AMR_F; + if (bcap->speech_ver[i] == GSM48_BCAP_SV_HR && bts->codec.hr) + bcap->speech_ver[j++] = GSM48_BCAP_SV_HR; + if (bcap->speech_ver[i] == GSM48_BCAP_SV_AMR_H && bts->codec.amr) + bcap->speech_ver[j++] = GSM48_BCAP_SV_AMR_H; + } + bcap->speech_ver[j] = -1; + + return 0; +} + +static uint32_t new_callref = 0x80000001; + +void cc_tx_to_mncc(struct gsm_network *net, struct msgb *msg) +{ + net->mncc_recv(net, msg); +} + +static int gsm48_conn_sendmsg(struct msgb *msg, struct gsm_subscriber_connection *conn, + struct gsm_trans *trans) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msg->data; + + /* if we get passed a transaction reference, do some common + * work that the caller no longer has to do */ + if (trans) { + gh->proto_discr = trans->protocol | (trans->transaction_id << 4); + msg->lchan = trans->conn->lchan; + } + + if (msg->lchan) { + struct e1inp_sign_link *sign_link = + msg->lchan->ts->trx->rsl_link; + + msg->dst = sign_link; + if (gsm48_hdr_pdisc(gh) == GSM48_PDISC_CC) + DEBUGP(DCC, "(bts %d trx %d ts %d ti %02x) " + "Sending '%s' to MS.\n", + sign_link->trx->bts->nr, + sign_link->trx->nr, msg->lchan->ts->nr, + gh->proto_discr & 0xf0, + gsm48_cc_msg_name(gh->msg_type)); + else + DEBUGP(DCC, "(bts %d trx %d ts %d pd %02x) " + "Sending 0x%02x to MS.\n", + sign_link->trx->bts->nr, + sign_link->trx->nr, msg->lchan->ts->nr, + gh->proto_discr, gh->msg_type); + } + + return gsm0808_submit_dtap(conn, msg, 0, 0); +} + +int gsm48_cc_tx_notify_ss(struct gsm_trans *trans, const char *message) +{ + struct gsm48_hdr *gh; + struct msgb *ss_notify; + + ss_notify = gsm0480_create_notifySS(message); + if (!ss_notify) + return -1; + + gsm0480_wrap_invoke(ss_notify, GSM0480_OP_CODE_NOTIFY_SS, 0); + uint8_t *data = msgb_push(ss_notify, 1); + data[0] = ss_notify->len - 1; + gh = (struct gsm48_hdr *) msgb_push(ss_notify, sizeof(*gh)); + gh->msg_type = GSM48_MT_CC_FACILITY; + return gsm48_conn_sendmsg(ss_notify, trans->conn, trans); +} + +void release_security_operation(struct gsm_subscriber_connection *conn) +{ + if (!conn->sec_operation) + return; + + talloc_free(conn->sec_operation); + conn->sec_operation = NULL; + msc_release_connection(conn); +} + +void allocate_security_operation(struct gsm_subscriber_connection *conn) +{ + conn->sec_operation = talloc_zero(tall_authciphop_ctx, + struct gsm_security_operation); +} + +int gsm48_secure_channel(struct gsm_subscriber_connection *conn, int key_seq, + gsm_cbfn *cb, void *cb_data) +{ + struct gsm_network *net = conn->network; + struct gsm_subscriber *subscr = conn->subscr; + struct gsm_security_operation *op; + struct gsm_auth_tuple atuple; + int status = -1, rc; + + /* Check if we _can_ enable encryption. Cases where we can't: + * - Encryption disabled in config + * - Channel already secured (nothing to do) + * - Subscriber equipment doesn't support configured encryption + */ + if (!net->a5_encryption) { + status = GSM_SECURITY_NOAVAIL; + } else if (conn->lchan->encr.alg_id > RSL_ENC_ALG_A5(0)) { + DEBUGP(DMM, "Requesting to secure an already secure channel"); + status = GSM_SECURITY_ALREADY; + } else if (!ms_cm2_a5n_support(subscr->equipment.classmark2, + net->a5_encryption)) { + DEBUGP(DMM, "Subscriber equipment doesn't support requested encryption"); + status = GSM_SECURITY_NOAVAIL; + } + + /* If not done yet, try to get info for this user */ + if (status < 0) { + rc = auth_get_tuple_for_subscr(&atuple, subscr, key_seq); + if (rc <= 0) + status = GSM_SECURITY_NOAVAIL; + } + + /* Are we done yet ? */ + if (status >= 0) + return cb ? + cb(GSM_HOOK_RR_SECURITY, status, NULL, conn, cb_data) : + 0; + + /* Start an operation (can't have more than one pending !!!) */ + if (conn->sec_operation) + return -EBUSY; + + allocate_security_operation(conn); + op = conn->sec_operation; + op->cb = cb; + op->cb_data = cb_data; + memcpy(&op->atuple, &atuple, sizeof(struct gsm_auth_tuple)); + + /* FIXME: Should start a timer for completion ... */ + + /* Then do whatever is needed ... */ + if (rc == AUTH_DO_AUTH_THEN_CIPH) { + /* Start authentication */ + return gsm48_tx_mm_auth_req(conn, op->atuple.vec.rand, NULL, + op->atuple.key_seq); + } else if (rc == AUTH_DO_CIPH) { + /* Start ciphering directly */ + return gsm0808_cipher_mode(conn, net->a5_encryption, + op->atuple.vec.kc, 8, 0); + } + + return -EINVAL; /* not reached */ +} + +static bool subscr_regexp_check(const struct gsm_network *net, const char *imsi) +{ + if (!net->authorized_reg_str) + return false; + + if (regexec(&net->authorized_regexp, imsi, 0, NULL, 0) != REG_NOMATCH) + return true; + + return false; +} + +static int authorize_subscriber(struct gsm_loc_updating_operation *loc, + struct gsm_subscriber *subscriber) +{ + if (!subscriber) + return 0; + + /* + * Do not send accept yet as more information should arrive. Some + * phones will not send us the information and we will have to check + * what we want to do with that. + */ + if (loc && (loc->waiting_for_imsi || loc->waiting_for_imei)) + return 0; + + switch (subscriber->group->net->auth_policy) { + case GSM_AUTH_POLICY_CLOSED: + return subscriber->authorized; + case GSM_AUTH_POLICY_REGEXP: + if (subscriber->authorized) + return 1; + if (subscr_regexp_check(subscriber->group->net, + subscriber->imsi)) + subscriber->authorized = 1; + return subscriber->authorized; + case GSM_AUTH_POLICY_TOKEN: + if (subscriber->authorized) + return subscriber->authorized; + return (subscriber->flags & GSM_SUBSCRIBER_FIRST_CONTACT); + case GSM_AUTH_POLICY_ACCEPT_ALL: + return 1; + default: + return 0; + } +} + +static void _release_loc_updating_req(struct gsm_subscriber_connection *conn, int release) +{ + if (!conn->loc_operation) + return; + + /* No need to keep the connection up */ + release_anchor(conn); + + osmo_timer_del(&conn->loc_operation->updating_timer); + talloc_free(conn->loc_operation); + conn->loc_operation = NULL; + if (release) + msc_release_connection(conn); +} + +static void loc_updating_failure(struct gsm_subscriber_connection *conn, int release) +{ + if (!conn->loc_operation) + return; + LOGP(DMM, LOGL_ERROR, "Location Updating failed for %s\n", + subscr_name(conn->subscr)); + rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_FAILED]); + _release_loc_updating_req(conn, release); +} + +static void loc_updating_success(struct gsm_subscriber_connection *conn, int release) +{ + if (!conn->loc_operation) + return; + LOGP(DMM, LOGL_INFO, "Location Updating completed for %s\n", + subscr_name(conn->subscr)); + rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_COMPLETED]); + _release_loc_updating_req(conn, release); +} + +static void allocate_loc_updating_req(struct gsm_subscriber_connection *conn) +{ + if (conn->loc_operation) + LOGP(DMM, LOGL_ERROR, "Connection already had operation.\n"); + loc_updating_failure(conn, 0); + + conn->loc_operation = talloc_zero(tall_locop_ctx, + struct gsm_loc_updating_operation); +} + +static int finish_lu(struct gsm_subscriber_connection *conn) +{ + int rc = 0; + int avoid_tmsi = conn->network->avoid_tmsi; + + /* We're all good */ + if (avoid_tmsi) { + conn->subscr->tmsi = GSM_RESERVED_TMSI; + db_sync_subscriber(conn->subscr); + } else { + db_subscriber_alloc_tmsi(conn->subscr); + } + + rc = gsm0408_loc_upd_acc(conn); + if (conn->network->send_mm_info) { + /* send MM INFO with network name */ + rc = gsm48_tx_mm_info(conn); + } + + /* call subscr_update after putting the loc_upd_acc + * in the transmit queue, since S_SUBSCR_ATTACHED might + * trigger further action like SMS delivery */ + subscr_update(conn->subscr, conn->bts, + GSM_SUBSCRIBER_UPDATE_ATTACHED); + + /* + * The gsm0408_loc_upd_acc sends a MI with the TMSI. The + * MS needs to respond with a TMSI REALLOCATION COMPLETE + * (even if the TMSI is the same). + * If avoid_tmsi == true, we don't send a TMSI, we don't + * expect a reply and Location Updating is done. + */ + if (avoid_tmsi) + loc_updating_success(conn, 1); + + return rc; +} + +static int _gsm0408_authorize_sec_cb(unsigned int hooknum, unsigned int event, + struct msgb *msg, void *data, void *param) +{ + struct gsm_subscriber_connection *conn = data; + int rc = 0; + + switch (event) { + case GSM_SECURITY_AUTH_FAILED: + loc_updating_failure(conn, 1); + break; + + case GSM_SECURITY_ALREADY: + LOGP(DMM, LOGL_ERROR, "We don't expect LOCATION " + "UPDATING after CM SERVICE REQUEST\n"); + /* fall through */ + + case GSM_SECURITY_NOAVAIL: + case GSM_SECURITY_SUCCEEDED: + rc = finish_lu(conn); + break; + + default: + rc = -EINVAL; + }; + + return rc; +} + +static int gsm0408_authorize(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + if (!conn->loc_operation) + return 0; + + if (authorize_subscriber(conn->loc_operation, conn->subscr)) + return gsm48_secure_channel(conn, + conn->loc_operation->key_seq, + _gsm0408_authorize_sec_cb, NULL); + return 0; +} + +void gsm0408_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause) +{ + struct gsm_trans *trans, *temp; + + /* avoid someone issuing a clear */ + conn->in_release = 1; + + /* + * Cancel any outstanding location updating request + * operation taking place on the subscriber connection. + */ + loc_updating_failure(conn, 0); + + /* We might need to cancel the paging response or such. */ + if (conn->sec_operation && conn->sec_operation->cb) { + conn->sec_operation->cb(GSM_HOOK_RR_SECURITY, GSM_SECURITY_AUTH_FAILED, + NULL, conn, conn->sec_operation->cb_data); + } + + release_security_operation(conn); + release_anchor(conn); + + /* + * Free all transactions that are associated with the released + * connection. The transaction code will inform the CC or SMS + * facilities that will send the release indications. As part of + * the CC REL_IND the remote leg might be released and this will + * trigger the call to trans_free. This is something the llist + * macro can not handle and we will need to re-iterate the list. + * + * TODO: Move the trans_list into the subscriber connection and + * create a pending list for MT transactions. These exist before + * we have a subscriber connection. + */ +restart: + llist_for_each_entry_safe(trans, temp, &conn->network->trans_list, entry) { + if (trans->conn == conn) { + trans_free(trans); + goto restart; + } + } +} + +void gsm0408_clear_all_trans(struct gsm_network *net, int protocol) +{ + struct gsm_trans *trans, *temp; + + LOGP(DCC, LOGL_NOTICE, "Clearing all currently active transactions!!!\n"); + + llist_for_each_entry_safe(trans, temp, &net->trans_list, entry) { + if (trans->protocol == protocol) { + trans->callref = 0; + trans_free(trans); + } + } +} + +/* Chapter 9.2.14 : Send LOCATION UPDATING REJECT */ +int gsm0408_loc_upd_rej(struct gsm_subscriber_connection *conn, uint8_t cause) +{ + struct gsm_bts *bts = conn->bts; + struct msgb *msg; + + msg = gsm48_create_loc_upd_rej(cause); + if (!msg) { + LOGP(DMM, LOGL_ERROR, "Failed to create msg for LOCATION UPDATING REJECT.\n"); + return -1; + } + + msg->lchan = conn->lchan; + + LOGP(DMM, LOGL_INFO, "Subscriber %s: LOCATION UPDATING REJECT " + "LAC=%u BTS=%u\n", subscr_name(conn->subscr), + bts->location_area_code, bts->nr); + + return gsm48_conn_sendmsg(msg, conn, NULL); +} + +/* Chapter 9.2.13 : Send LOCATION UPDATE ACCEPT */ +static int gsm0408_loc_upd_acc(struct gsm_subscriber_connection *conn) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 LOC UPD ACC"); + struct gsm48_hdr *gh; + struct gsm48_loc_area_id *lai; + uint8_t *mid; + + msg->lchan = conn->lchan; + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_MM; + gh->msg_type = GSM48_MT_MM_LOC_UPD_ACCEPT; + + lai = (struct gsm48_loc_area_id *) msgb_put(msg, sizeof(*lai)); + gsm48_generate_lai(lai, conn->network->country_code, + conn->network->network_code, + conn->bts->location_area_code); + + if (conn->subscr->tmsi == GSM_RESERVED_TMSI) { + uint8_t mi[10]; + int len; + len = gsm48_generate_mid_from_imsi(mi, conn->subscr->imsi); + mid = msgb_put(msg, len); + memcpy(mid, mi, len); + } else { + mid = msgb_put(msg, GSM48_MID_TMSI_LEN); + gsm48_generate_mid_from_tmsi(mid, conn->subscr->tmsi); + } + + DEBUGP(DMM, "-> LOCATION UPDATE ACCEPT\n"); + + return gsm48_conn_sendmsg(msg, conn, NULL); +} + +/* Transmit Chapter 9.2.10 Identity Request */ +static int mm_tx_identity_req(struct gsm_subscriber_connection *conn, uint8_t id_type) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 ID REQ"); + struct gsm48_hdr *gh; + + msg->lchan = conn->lchan; + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); + gh->proto_discr = GSM48_PDISC_MM; + gh->msg_type = GSM48_MT_MM_ID_REQ; + gh->data[0] = id_type; + + return gsm48_conn_sendmsg(msg, conn, NULL); +} + +static struct gsm_subscriber *subscr_create(const struct gsm_network *net, + const char *imsi) +{ + if (!net->auto_create_subscr) + return NULL; + + if (!subscr_regexp_check(net, imsi)) + return NULL; + + return subscr_create_subscriber(net->subscr_group, imsi); +} + +/* Parse Chapter 9.2.11 Identity Response */ +static int mm_rx_id_resp(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm_network *net = conn->network; + uint8_t mi_type = gh->data[1] & GSM_MI_TYPE_MASK; + char mi_string[GSM48_MI_SIZE]; + + gsm48_mi_to_string(mi_string, sizeof(mi_string), &gh->data[1], gh->data[0]); + DEBUGP(DMM, "IDENTITY RESPONSE: MI(%s)=%s\n", + gsm48_mi_type_name(mi_type), mi_string); + + osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_IDENTITY, gh->data); + + switch (mi_type) { + case GSM_MI_TYPE_IMSI: + /* look up subscriber based on IMSI, create if not found */ + if (!conn->subscr) { + conn->subscr = subscr_get_by_imsi(net->subscr_group, + mi_string); + if (!conn->subscr) + conn->subscr = subscr_create(net, mi_string); + } + if (!conn->subscr && conn->loc_operation) { + gsm0408_loc_upd_rej(conn, net->reject_cause); + loc_updating_failure(conn, 1); + return 0; + } + if (conn->loc_operation) + conn->loc_operation->waiting_for_imsi = 0; + break; + case GSM_MI_TYPE_IMEI: + case GSM_MI_TYPE_IMEISV: + /* update subscribe <-> IMEI mapping */ + if (conn->subscr) { + db_subscriber_assoc_imei(conn->subscr, mi_string); + db_sync_equipment(&conn->subscr->equipment); + } + if (conn->loc_operation) + conn->loc_operation->waiting_for_imei = 0; + break; + } + + /* Check if we can let the mobile station enter */ + return gsm0408_authorize(conn, msg); +} + + +static void loc_upd_rej_cb(void *data) +{ + struct gsm_subscriber_connection *conn = data; + + LOGP(DMM, LOGL_DEBUG, "Location Updating Request procedure timedout.\n"); + gsm0408_loc_upd_rej(conn, conn->network->reject_cause); + loc_updating_failure(conn, 1); +} + +static void schedule_reject(struct gsm_subscriber_connection *conn) +{ + osmo_timer_setup(&conn->loc_operation->updating_timer, loc_upd_rej_cb, + conn); + osmo_timer_schedule(&conn->loc_operation->updating_timer, 5, 0); +} + +static const struct value_string lupd_names[] = { + { GSM48_LUPD_NORMAL, "NORMAL" }, + { GSM48_LUPD_PERIODIC, "PERIODIC" }, + { GSM48_LUPD_IMSI_ATT, "IMSI ATTACH" }, + { 0, NULL } +}; + +/* Chapter 9.2.15: Receive Location Updating Request */ +static int mm_rx_loc_upd_req(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_loc_upd_req *lu; + struct gsm_subscriber *subscr = NULL; + uint8_t mi_type; + char mi_string[GSM48_MI_SIZE]; + + lu = (struct gsm48_loc_upd_req *) gh->data; + + mi_type = lu->mi[0] & GSM_MI_TYPE_MASK; + + gsm48_mi_to_string(mi_string, sizeof(mi_string), lu->mi, lu->mi_len); + + DEBUGPC(DMM, "MI(%s)=%s type=%s ", gsm48_mi_type_name(mi_type), + mi_string, get_value_string(lupd_names, lu->type)); + + osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_IDENTITY, &lu->mi_len); + + switch (lu->type) { + case GSM48_LUPD_NORMAL: + rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_NORMAL]); + break; + case GSM48_LUPD_IMSI_ATT: + rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_ATTACH]); + break; + case GSM48_LUPD_PERIODIC: + rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_PERIODIC]); + break; + } + + /* + * Pseudo Spoof detection: Just drop a second/concurrent + * location updating request. + */ + if (conn->loc_operation) { + DEBUGPC(DMM, "ignoring request due an existing one: %p.\n", + conn->loc_operation); + gsm0408_loc_upd_rej(conn, GSM48_REJECT_PROTOCOL_ERROR); + return 0; + } + + allocate_loc_updating_req(conn); + + conn->loc_operation->key_seq = lu->key_seq; + + switch (mi_type) { + case GSM_MI_TYPE_IMSI: + DEBUGPC(DMM, "\n"); + /* we always want the IMEI, too */ + mm_tx_identity_req(conn, GSM_MI_TYPE_IMEI); + conn->loc_operation->waiting_for_imei = 1; + + /* look up subscriber based on IMSI, create if not found */ + subscr = subscr_get_by_imsi(conn->network->subscr_group, mi_string); + if (!subscr) + subscr = subscr_create(conn->network, mi_string); + if (!subscr) { + gsm0408_loc_upd_rej(conn, conn->network->reject_cause); + loc_updating_failure(conn, 0); /* FIXME: set release == true? */ + return 0; + } + break; + case GSM_MI_TYPE_TMSI: + DEBUGPC(DMM, "\n"); + /* look up the subscriber based on TMSI, request IMSI if it fails */ + subscr = subscr_get_by_tmsi(conn->network->subscr_group, + tmsi_from_string(mi_string)); + if (!subscr) { + /* send IDENTITY REQUEST message to get IMSI */ + mm_tx_identity_req(conn, GSM_MI_TYPE_IMSI); + conn->loc_operation->waiting_for_imsi = 1; + } + /* we always want the IMEI, too */ + mm_tx_identity_req(conn, GSM_MI_TYPE_IMEI); + conn->loc_operation->waiting_for_imei = 1; + break; + case GSM_MI_TYPE_IMEI: + case GSM_MI_TYPE_IMEISV: + /* no sim card... FIXME: what to do ? */ + DEBUGPC(DMM, "unimplemented mobile identity type\n"); + break; + default: + DEBUGPC(DMM, "unknown mobile identity type\n"); + break; + } + + /* schedule the reject timer */ + schedule_reject(conn); + + if (!subscr) { + DEBUGPC(DRR, "<- Can't find any subscriber for this ID\n"); + /* FIXME: request id? close channel? */ + return -EINVAL; + } + + conn->subscr = subscr; + conn->subscr->equipment.classmark1 = lu->classmark1; + + /* check if we can let the subscriber into our network immediately + * or if we need to wait for identity responses. */ + return gsm0408_authorize(conn, msg); +} + +/* Turn int into semi-octet representation: 98 => 0x89 */ +static uint8_t bcdify(uint8_t value) +{ + uint8_t ret; + + ret = value / 10; + ret |= (value % 10) << 4; + + return ret; +} + + +/* Section 9.2.15a */ +int gsm48_tx_mm_info(struct gsm_subscriber_connection *conn) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 MM INF"); + struct gsm48_hdr *gh; + struct gsm_network *net = conn->network; + uint8_t *ptr8; + int name_len, name_pad; + + time_t cur_t; + struct tm* gmt_time; + struct tm* local_time; + int tzunits; + int dst = 0; + + msg->lchan = conn->lchan; + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_MM; + gh->msg_type = GSM48_MT_MM_INFO; + + if (net->name_long) { +#if 0 + name_len = strlen(net->name_long); + /* 10.5.3.5a */ + ptr8 = msgb_put(msg, 3); + ptr8[0] = GSM48_IE_NAME_LONG; + ptr8[1] = name_len*2 +1; + ptr8[2] = 0x90; /* UCS2, no spare bits, no CI */ + + ptr16 = (uint16_t *) msgb_put(msg, name_len*2); + for (i = 0; i < name_len; i++) + ptr16[i] = htons(net->name_long[i]); + + /* FIXME: Use Cell Broadcast, not UCS-2, since + * UCS-2 is only supported by later revisions of the spec */ +#endif + name_len = (strlen(net->name_long)*7)/8; + name_pad = (8 - strlen(net->name_long)*7)%8; + if (name_pad > 0) + name_len++; + /* 10.5.3.5a */ + ptr8 = msgb_put(msg, 3); + ptr8[0] = GSM48_IE_NAME_LONG; + ptr8[1] = name_len +1; + ptr8[2] = 0x80 | name_pad; /* Cell Broadcast DCS, no CI */ + + ptr8 = msgb_put(msg, name_len); + gsm_7bit_encode_n(ptr8, name_len, net->name_long, NULL); + + } + + if (net->name_short) { +#if 0 + name_len = strlen(net->name_short); + /* 10.5.3.5a */ + ptr8 = (uint8_t *) msgb_put(msg, 3); + ptr8[0] = GSM48_IE_NAME_SHORT; + ptr8[1] = name_len*2 + 1; + ptr8[2] = 0x90; /* UCS2, no spare bits, no CI */ + + ptr16 = (uint16_t *) msgb_put(msg, name_len*2); + for (i = 0; i < name_len; i++) + ptr16[i] = htons(net->name_short[i]); +#endif + name_len = (strlen(net->name_short)*7)/8; + name_pad = (8 - strlen(net->name_short)*7)%8; + if (name_pad > 0) + name_len++; + /* 10.5.3.5a */ + ptr8 = (uint8_t *) msgb_put(msg, 3); + ptr8[0] = GSM48_IE_NAME_SHORT; + ptr8[1] = name_len +1; + ptr8[2] = 0x80 | name_pad; /* Cell Broadcast DCS, no CI */ + + ptr8 = msgb_put(msg, name_len); + gsm_7bit_encode_n(ptr8, name_len, net->name_short, NULL); + + } + + /* Section 10.5.3.9 */ + cur_t = time(NULL); + gmt_time = gmtime(&cur_t); + + ptr8 = msgb_put(msg, 8); + ptr8[0] = GSM48_IE_NET_TIME_TZ; + ptr8[1] = bcdify(gmt_time->tm_year % 100); + ptr8[2] = bcdify(gmt_time->tm_mon + 1); + ptr8[3] = bcdify(gmt_time->tm_mday); + ptr8[4] = bcdify(gmt_time->tm_hour); + ptr8[5] = bcdify(gmt_time->tm_min); + ptr8[6] = bcdify(gmt_time->tm_sec); + + if (net->tz.override) { + /* Convert tz.hr and tz.mn to units */ + if (net->tz.hr < 0) { + tzunits = ((net->tz.hr/-1)*4); + tzunits = tzunits + (net->tz.mn/15); + ptr8[7] = bcdify(tzunits); + /* Set negative time */ + ptr8[7] |= 0x08; + } + else { + tzunits = net->tz.hr*4; + tzunits = tzunits + (net->tz.mn/15); + ptr8[7] = bcdify(tzunits); + } + /* Convert DST value */ + if (net->tz.dst >= 0 && net->tz.dst <= 2) + dst = net->tz.dst; + } + else { + /* Need to get GSM offset and convert into 15 min units */ + /* This probably breaks if gmtoff returns a value not evenly divisible by 15? */ + local_time = localtime(&cur_t); +#ifdef HAVE_TM_GMTOFF_IN_TM + tzunits = (local_time->tm_gmtoff/60)/15; +#else +#warning find a portable way to obtain the timezone offset + tzunits = 0; +#endif + if (tzunits < 0) { + tzunits = tzunits/-1; + ptr8[7] = bcdify(tzunits); + /* Flip it to negative */ + ptr8[7] |= 0x08; + } + else + ptr8[7] = bcdify(tzunits); + + /* Does not support DST +2 */ + if (local_time->tm_isdst) + dst = 1; + } + + if (dst) { + ptr8 = msgb_put(msg, 3); + ptr8[0] = GSM48_IE_NET_DST; + ptr8[1] = 1; + ptr8[2] = dst; + } + + DEBUGP(DMM, "-> MM INFO\n"); + + return gsm48_conn_sendmsg(msg, conn, NULL); +} + +/*! Send an Authentication Request to MS on the given subscriber connection + * according to 3GPP/ETSI TS 24.008, Section 9.2.2. + * \param[in] conn Subscriber connection to send on. + * \param[in] rand Random challenge token to send, must be 16 bytes long. + * \param[in] autn r99: In case of UMTS mutual authentication, AUTN token to + * send; must be 16 bytes long, or pass NULL for plain GSM auth. + * \param[in] key_seq auth tuple's sequence number. + */ +int gsm48_tx_mm_auth_req(struct gsm_subscriber_connection *conn, uint8_t *rand, + uint8_t *autn, int key_seq) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 AUTH REQ"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + struct gsm48_auth_req *ar = (struct gsm48_auth_req *) msgb_put(msg, sizeof(*ar)); + + DEBUGP(DMM, "-> AUTH REQ (rand = %s)\n", osmo_hexdump(rand, 16)); + if (autn) + DEBUGP(DMM, " AUTH REQ (autn = %s)\n", osmo_hexdump(autn, 16)); + + msg->lchan = conn->lchan; + gh->proto_discr = GSM48_PDISC_MM; + gh->msg_type = GSM48_MT_MM_AUTH_REQ; + + ar->key_seq = key_seq; + + /* 16 bytes RAND parameters */ + osmo_static_assert(sizeof(ar->rand) == 16, sizeof_auth_req_r99_rand); + if (rand) + memcpy(ar->rand, rand, 16); + + + /* 16 bytes AUTN */ + if (autn) + msgb_tlv_put(msg, GSM48_IE_AUTN, 16, autn); + + return gsm48_conn_sendmsg(msg, conn, NULL); +} + +/* Section 9.2.1 */ +int gsm48_tx_mm_auth_rej(struct gsm_subscriber_connection *conn) +{ + DEBUGP(DMM, "-> AUTH REJECT\n"); + return gsm48_tx_simple(conn, GSM48_PDISC_MM, GSM48_MT_MM_AUTH_REJ); +} + +/* + * At the 30C3 phones miss their periodic update + * interval a lot and then remain unreachable. In case + * we still know the TMSI we can just attach it again. + */ +static void implit_attach(struct gsm_subscriber_connection *conn) +{ + if (conn->subscr->lac != GSM_LAC_RESERVED_DETACHED) + return; + + subscr_update(conn->subscr, conn->bts, + GSM_SUBSCRIBER_UPDATE_ATTACHED); +} + + +static int _gsm48_rx_mm_serv_req_sec_cb( + unsigned int hooknum, unsigned int event, + struct msgb *msg, void *data, void *param) +{ + struct gsm_subscriber_connection *conn = data; + int rc = 0; + + /* auth failed or succeeded, the timer was stopped */ + conn->expire_timer_stopped = 1; + + switch (event) { + case GSM_SECURITY_AUTH_FAILED: + /* Nothing to do */ + break; + + case GSM_SECURITY_NOAVAIL: + case GSM_SECURITY_ALREADY: + rc = gsm48_tx_mm_serv_ack(conn); + implit_attach(conn); + break; + + case GSM_SECURITY_SUCCEEDED: + /* nothing to do. CIPHER MODE COMMAND is + * implicit CM SERV ACK */ + implit_attach(conn); + break; + + default: + rc = -EINVAL; + }; + + return rc; +} + +/* + * Handle CM Service Requests + * a) Verify that the packet is long enough to contain the information + * we require otherwsie reject with INCORRECT_MESSAGE + * b) Try to parse the TMSI. If we do not have one reject + * c) Check that we know the subscriber with the TMSI otherwise reject + * with a HLR cause + * d) Set the subscriber on the gsm_lchan and accept + */ +static int gsm48_rx_mm_serv_req(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + uint8_t mi_type; + char mi_string[GSM48_MI_SIZE]; + + struct gsm_network *network = conn->network; + struct gsm_subscriber *subscr; + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_service_request *req = + (struct gsm48_service_request *)gh->data; + /* unfortunately in Phase1 the classmark2 length is variable */ + uint8_t classmark2_len = gh->data[1]; + uint8_t *classmark2 = gh->data+2; + uint8_t mi_len = *(classmark2 + classmark2_len); + uint8_t *mi = (classmark2 + classmark2_len + 1); + + DEBUGP(DMM, "<- CM SERVICE REQUEST "); + if (msg->data_len < sizeof(struct gsm48_service_request*)) { + DEBUGPC(DMM, "wrong sized message\n"); + return gsm48_tx_mm_serv_rej(conn, + GSM48_REJECT_INCORRECT_MESSAGE); + } + + if (msg->data_len < req->mi_len + 6) { + DEBUGPC(DMM, "does not fit in packet\n"); + return gsm48_tx_mm_serv_rej(conn, + GSM48_REJECT_INCORRECT_MESSAGE); + } + + gsm48_mi_to_string(mi_string, sizeof(mi_string), mi, mi_len); + mi_type = mi[0] & GSM_MI_TYPE_MASK; + + if (mi_type == GSM_MI_TYPE_IMSI) { + DEBUGPC(DMM, "serv_type=0x%02x MI(%s)=%s\n", + req->cm_service_type, gsm48_mi_type_name(mi_type), + mi_string); + subscr = subscr_get_by_imsi(network->subscr_group, + mi_string); + } else if (mi_type == GSM_MI_TYPE_TMSI) { + DEBUGPC(DMM, "serv_type=0x%02x MI(%s)=%s\n", + req->cm_service_type, gsm48_mi_type_name(mi_type), + mi_string); + subscr = subscr_get_by_tmsi(network->subscr_group, + tmsi_from_string(mi_string)); + } else { + DEBUGPC(DMM, "mi_type is not expected: %d\n", mi_type); + return gsm48_tx_mm_serv_rej(conn, + GSM48_REJECT_INCORRECT_MESSAGE); + } + + osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_IDENTITY, (classmark2 + classmark2_len)); + + if (is_siemens_bts(conn->bts)) + send_siemens_mrpci(msg->lchan, classmark2-1); + + + /* FIXME: if we don't know the TMSI, inquire abit IMSI and allocate new TMSI */ + if (!subscr) + return gsm48_tx_mm_serv_rej(conn, + GSM48_REJECT_IMSI_UNKNOWN_IN_VLR); + + if (!conn->subscr) + conn->subscr = subscr; + else if (conn->subscr == subscr) + subscr_put(subscr); /* lchan already has a ref, don't need another one */ + else { + DEBUGP(DMM, "<- CM Channel already owned by someone else?\n"); + subscr_put(subscr); + } + + subscr->equipment.classmark2_len = classmark2_len; + memcpy(subscr->equipment.classmark2, classmark2, classmark2_len); + db_sync_equipment(&subscr->equipment); + + /* we will send a MM message soon */ + conn->expire_timer_stopped = 1; + + return gsm48_secure_channel(conn, req->cipher_key_seq, + _gsm48_rx_mm_serv_req_sec_cb, NULL); +} + +static int gsm48_rx_mm_imsi_detach_ind(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm_network *network = conn->network; + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_imsi_detach_ind *idi = + (struct gsm48_imsi_detach_ind *) gh->data; + uint8_t mi_type = idi->mi[0] & GSM_MI_TYPE_MASK; + char mi_string[GSM48_MI_SIZE]; + struct gsm_subscriber *subscr = NULL; + + gsm48_mi_to_string(mi_string, sizeof(mi_string), idi->mi, idi->mi_len); + DEBUGP(DMM, "IMSI DETACH INDICATION: MI(%s)=%s", + gsm48_mi_type_name(mi_type), mi_string); + + rate_ctr_inc(&network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_DETACH]); + + switch (mi_type) { + case GSM_MI_TYPE_TMSI: + DEBUGPC(DMM, "\n"); + subscr = subscr_get_by_tmsi(network->subscr_group, + tmsi_from_string(mi_string)); + break; + case GSM_MI_TYPE_IMSI: + DEBUGPC(DMM, "\n"); + subscr = subscr_get_by_imsi(network->subscr_group, + mi_string); + break; + case GSM_MI_TYPE_IMEI: + case GSM_MI_TYPE_IMEISV: + /* no sim card... FIXME: what to do ? */ + DEBUGPC(DMM, ": unimplemented mobile identity type\n"); + break; + default: + DEBUGPC(DMM, ": unknown mobile identity type\n"); + break; + } + + if (subscr) { + subscr_update(subscr, conn->bts, + GSM_SUBSCRIBER_UPDATE_DETACHED); + DEBUGP(DMM, "Subscriber: %s\n", subscr_name(subscr)); + + subscr->equipment.classmark1 = idi->classmark1; + db_sync_equipment(&subscr->equipment); + + subscr_put(subscr); + } else + DEBUGP(DMM, "Unknown Subscriber ?!?\n"); + + /* FIXME: iterate over all transactions and release them, + * imagine an IMSI DETACH happening during an active call! */ + + release_anchor(conn); + return 0; +} + +static int gsm48_rx_mm_status(struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + + DEBUGP(DMM, "MM STATUS (reject cause 0x%02x)\n", gh->data[0]); + + return 0; +} + +static int parse_gsm_auth_resp(uint8_t *res, uint8_t *res_len, + struct gsm_subscriber_connection *conn, + struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_auth_resp *ar = (struct gsm48_auth_resp*) gh->data; + + if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*ar)) { + LOGP(DMM, LOGL_ERROR, + "%s: MM AUTHENTICATION RESPONSE:" + " l3 length invalid: %u\n", + subscr_name(conn->subscr), msgb_l3len(msg)); + return -EINVAL; + } + + *res_len = sizeof(ar->sres); + memcpy(res, ar->sres, sizeof(ar->sres)); + return 0; +} + +static int parse_umts_auth_resp(uint8_t *res, uint8_t *res_len, + struct gsm_subscriber_connection *conn, + struct msgb *msg) +{ + struct gsm48_hdr *gh; + uint8_t *data; + uint8_t iei; + uint8_t ie_len; + unsigned int data_len; + + /* First parse the GSM part */ + if (parse_gsm_auth_resp(res, res_len, conn, msg)) + return -EINVAL; + OSMO_ASSERT(*res_len == 4); + + /* Then add the extended res part */ + gh = msgb_l3(msg); + data = gh->data + sizeof(struct gsm48_auth_resp); + data_len = msgb_l3len(msg) - (data - (uint8_t*)msgb_l3(msg)); + + if (data_len < 3) { + LOGP(DMM, LOGL_ERROR, + "%s: MM AUTHENTICATION RESPONSE:" + " l3 length invalid: %u\n", + subscr_name(conn->subscr), msgb_l3len(msg)); + return -EINVAL; + } + + iei = data[0]; + ie_len = data[1]; + if (iei != GSM48_IE_AUTH_RES_EXT) { + LOGP(DMM, LOGL_ERROR, + "%s: MM R99 AUTHENTICATION RESPONSE:" + " expected IEI 0x%02x, got 0x%02x\n", + subscr_name(conn->subscr), + GSM48_IE_AUTH_RES_EXT, iei); + return -EINVAL; + } + + if (ie_len > 12) { + LOGP(DMM, LOGL_ERROR, + "%s: MM R99 AUTHENTICATION RESPONSE:" + " extended Auth Resp IE 0x%02x is too large: %u bytes\n", + subscr_name(conn->subscr), GSM48_IE_AUTH_RES_EXT, ie_len); + return -EINVAL; + } + + *res_len += ie_len; + memcpy(res + 4, &data[2], ie_len); + return 0; +} + +/* Chapter 9.2.3: Authentication Response */ +static int gsm48_rx_mm_auth_resp(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm_network *net = conn->network; + uint8_t res[16]; + uint8_t res_len; + int rc; + bool is_r99; + + if (!conn->subscr) { + LOGP(DMM, LOGL_ERROR, + "MM AUTHENTICATION RESPONSE: invalid: no subscriber\n"); + gsm48_tx_mm_auth_rej(conn); + release_security_operation(conn); + return -EINVAL; + } + + if (msgb_l3len(msg) > + sizeof(struct gsm48_hdr) + sizeof(struct gsm48_auth_resp)) { + rc = parse_umts_auth_resp(res, &res_len, conn, msg); + is_r99 = true; + } else { + rc = parse_gsm_auth_resp(res, &res_len, conn, msg); + is_r99 = false; + } + + if (rc) { + gsm48_tx_mm_auth_rej(conn); + release_security_operation(conn); + return -EINVAL; + } + + DEBUGP(DMM, "%s: MM %s AUTHENTICATION RESPONSE (%s = %s)\n", + subscr_name(conn->subscr), + is_r99 ? "R99" : "GSM", is_r99 ? "res" : "sres", + osmo_hexdump_nospc(res, res_len)); + + /* Future: vlr_sub_rx_auth_resp(conn->vsub, is_r99, + * conn->via_ran == RAN_UTRAN_IU, + * res, res_len); + */ + + if (res_len != 4) { + LOGP(DMM, LOGL_ERROR, + "%s: MM AUTHENTICATION RESPONSE:" + " UMTS authentication not supported\n", + subscr_name(conn->subscr)); + } + + /* Safety check */ + if (!conn->sec_operation) { + DEBUGP(DMM, "No authentication/cipher operation in progress !!!\n"); + return -EIO; + } + + /* Validate SRES */ + if (memcmp(conn->sec_operation->atuple.vec.sres, res, 4)) { + int rc; + gsm_cbfn *cb = conn->sec_operation->cb; + + DEBUGPC(DMM, "Invalid (expected %s)\n", + osmo_hexdump(conn->sec_operation->atuple.vec.sres, 4)); + + if (cb) + cb(GSM_HOOK_RR_SECURITY, GSM_SECURITY_AUTH_FAILED, + NULL, conn, conn->sec_operation->cb_data); + + rc = gsm48_tx_mm_auth_rej(conn); + release_security_operation(conn); + return rc; + } + + DEBUGPC(DMM, "OK\n"); + + /* Start ciphering */ + return gsm0808_cipher_mode(conn, net->a5_encryption, + conn->sec_operation->atuple.vec.kc, 8, 0); +} + +static int gsm48_rx_mm_auth_fail(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + uint8_t cause; + uint8_t auts_tag; + uint8_t auts_len; + uint8_t *auts; + int rc; + + if (!conn->sec_operation) { + DEBUGP(DMM, "%s: MM R99 AUTHENTICATION FAILURE:" + " No authentication/cipher operation in progress\n", + subscr_name(conn->subscr)); + return -EINVAL; + } + + if (!conn->subscr) { + LOGP(DMM, LOGL_ERROR, + "MM R99 AUTHENTICATION FAILURE: invalid: no subscriber\n"); + gsm48_tx_mm_auth_rej(conn); + release_security_operation(conn); + return -EINVAL; + } + + if (msgb_l3len(msg) < sizeof(*gh) + 1) { + LOGP(DMM, LOGL_ERROR, + "%s: MM R99 AUTHENTICATION FAILURE:" + " l3 length invalid: %u\n", + subscr_name(conn->subscr), msgb_l3len(msg)); + gsm48_tx_mm_auth_rej(conn); + release_security_operation(conn); + return -EINVAL; + } + + cause = gh->data[0]; + + if (cause != GSM48_REJECT_SYNCH_FAILURE) { + LOGP(DMM, LOGL_INFO, + "%s: MM R99 AUTHENTICATION FAILURE: cause 0x%0x\n", + subscr_name(conn->subscr), cause); + rc = gsm48_tx_mm_auth_rej(conn); + release_security_operation(conn); + return rc; + } + + /* This is a Synch Failure procedure, which should pass an AUTS to + * resynchronize the sequence nr with the HLR. Expecting exactly one + * TLV with 14 bytes of AUTS. */ + + if (msgb_l3len(msg) < sizeof(*gh) + 1 + 2) { + LOGP(DMM, LOGL_INFO, + "%s: MM R99 AUTHENTICATION FAILURE:" + " invalid Synch Failure: missing AUTS IE\n", + subscr_name(conn->subscr)); + gsm48_tx_mm_auth_rej(conn); + release_security_operation(conn); + return -EINVAL; + } + + auts_tag = gh->data[1]; + auts_len = gh->data[2]; + auts = &gh->data[3]; + + if (auts_tag != GSM48_IE_AUTS + || auts_len != 14) { + LOGP(DMM, LOGL_INFO, + "%s: MM R99 AUTHENTICATION FAILURE:" + " invalid Synch Failure:" + " expected AUTS IE 0x%02x of 14 bytes," + " got IE 0x%02x of %u bytes\n", + subscr_name(conn->subscr), + GSM48_IE_AUTS, auts_tag, auts_len); + gsm48_tx_mm_auth_rej(conn); + release_security_operation(conn); + return -EINVAL; + } + + if (msgb_l3len(msg) < sizeof(*gh) + 1 + 2 + auts_len) { + LOGP(DMM, LOGL_INFO, + "%s: MM R99 AUTHENTICATION FAILURE:" + " invalid Synch Failure msg: message truncated (%u)\n", + subscr_name(conn->subscr), msgb_l3len(msg)); + gsm48_tx_mm_auth_rej(conn); + release_security_operation(conn); + return -EINVAL; + } + + /* We have an AUTS IE with exactly 14 bytes of AUTS and the msgb is + * large enough. */ + + DEBUGP(DMM, "%s: MM R99 AUTHENTICATION SYNCH (AUTS = %s)\n", + subscr_name(conn->subscr), osmo_hexdump_nospc(auts, 14)); + + /* Future: vlr_sub_rx_auth_fail(conn->vsub, auts); */ + + LOGP(DMM, LOGL_ERROR, "%s: MM R99 AUTHENTICATION not supported\n", + subscr_name(conn->subscr)); + rc = gsm48_tx_mm_auth_rej(conn); + release_security_operation(conn); + return rc; +} + +/* Receive a GSM 04.08 Mobility Management (MM) message */ +static int gsm0408_rcv_mm(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + int rc = 0; + + switch (gsm48_hdr_msg_type(gh)) { + case GSM48_MT_MM_LOC_UPD_REQUEST: + DEBUGP(DMM, "LOCATION UPDATING REQUEST: "); + rc = mm_rx_loc_upd_req(conn, msg); + break; + case GSM48_MT_MM_ID_RESP: + rc = mm_rx_id_resp(conn, msg); + break; + case GSM48_MT_MM_CM_SERV_REQ: + rc = gsm48_rx_mm_serv_req(conn, msg); + break; + case GSM48_MT_MM_STATUS: + rc = gsm48_rx_mm_status(msg); + break; + case GSM48_MT_MM_TMSI_REALL_COMPL: + DEBUGP(DMM, "TMSI Reallocation Completed. Subscriber: %s\n", + subscr_name(conn->subscr)); + loc_updating_success(conn, 1); + break; + case GSM48_MT_MM_IMSI_DETACH_IND: + rc = gsm48_rx_mm_imsi_detach_ind(conn, msg); + break; + case GSM48_MT_MM_CM_REEST_REQ: + DEBUGP(DMM, "CM REESTABLISH REQUEST: Not implemented\n"); + break; + case GSM48_MT_MM_AUTH_RESP: + rc = gsm48_rx_mm_auth_resp(conn, msg); + break; + case GSM48_MT_MM_AUTH_FAIL: + rc = gsm48_rx_mm_auth_fail(conn, msg); + break; + default: + LOGP(DMM, LOGL_NOTICE, "Unknown GSM 04.08 MM msg type 0x%02x\n", + gh->msg_type); + break; + } + + return rc; +} + +/* Receive a PAGING RESPONSE message from the MS */ +static int gsm48_rx_rr_pag_resp(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_pag_resp *resp; + uint8_t *classmark2_lv = gh->data + 1; + uint8_t mi_type; + char mi_string[GSM48_MI_SIZE]; + struct gsm_subscriber *subscr = NULL; + struct bsc_subscr *bsub; + uint32_t tmsi; + int rc = 0; + + resp = (struct gsm48_pag_resp *) &gh->data[0]; + gsm48_paging_extract_mi(resp, msgb_l3len(msg) - sizeof(*gh), + mi_string, &mi_type); + DEBUGP(DRR, "PAGING RESPONSE: MI(%s)=%s\n", + gsm48_mi_type_name(mi_type), mi_string); + + switch (mi_type) { + case GSM_MI_TYPE_TMSI: + tmsi = tmsi_from_string(mi_string); + subscr = subscr_get_by_tmsi(conn->network->subscr_group, tmsi); + break; + case GSM_MI_TYPE_IMSI: + subscr = subscr_get_by_imsi(conn->network->subscr_group, + mi_string); + break; + } + + if (!subscr) { + DEBUGP(DRR, "<- Can't find any subscriber for this ID\n"); + /* FIXME: request id? close channel? */ + return -EINVAL; + } + + if (!conn->subscr) { + conn->subscr = subscr; + } else if (conn->subscr != subscr) { + LOGP(DRR, LOGL_ERROR, "<- Channel already owned by someone else?\n"); + subscr_put(subscr); + return -EINVAL; + } else { + DEBUGP(DRR, "<- Channel already owned by us\n"); + subscr_put(subscr); + subscr = conn->subscr; + } + + log_set_context(LOG_CTX_VLR_SUBSCR, subscr); + DEBUGP(DRR, "<- Channel was requested by %s\n", + subscr->name && strlen(subscr->name) ? subscr->name : subscr->imsi); + + subscr->equipment.classmark2_len = *classmark2_lv; + memcpy(subscr->equipment.classmark2, classmark2_lv+1, *classmark2_lv); + db_sync_equipment(&subscr->equipment); + + /* TODO MSC split -- creating a BSC subscriber directly from MSC data + * structures in RAM. At some point the MSC will send a message to the + * BSC instead. */ + bsub = bsc_subscr_find_or_create_by_imsi(conn->network->bsc_subscribers, + subscr->imsi); + bsub->tmsi = subscr->tmsi; + bsub->lac = subscr->lac; + + /* We received a paging */ + conn->expire_timer_stopped = 1; + + rc = gsm48_handle_paging_resp(conn, msg, bsub); + return rc; +} + +static int gsm48_rx_rr_app_info(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + uint8_t apdu_id_flags; + uint8_t apdu_len; + uint8_t *apdu_data; + + apdu_id_flags = gh->data[0]; + apdu_len = gh->data[1]; + apdu_data = gh->data+2; + + DEBUGP(DRR, "RX APPLICATION INFO id/flags=0x%02x apdu_len=%u apdu=%s\n", + apdu_id_flags, apdu_len, osmo_hexdump(apdu_data, apdu_len)); + + return db_apdu_blob_store(conn->subscr, apdu_id_flags, apdu_len, apdu_data); +} + +/* Receive a GSM 04.08 Radio Resource (RR) message */ +static int gsm0408_rcv_rr(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + int rc = 0; + + switch (gh->msg_type) { + case GSM48_MT_RR_PAG_RESP: + rc = gsm48_rx_rr_pag_resp(conn, msg); + break; + case GSM48_MT_RR_APP_INFO: + rc = gsm48_rx_rr_app_info(conn, msg); + break; + default: + LOGP(DRR, LOGL_NOTICE, "MSC: Unimplemented %s GSM 04.08 RR " + "message\n", gsm48_rr_msg_name(gh->msg_type)); + break; + } + + return rc; +} + +int gsm48_send_rr_app_info(struct gsm_subscriber_connection *conn, uint8_t apdu_id, + uint8_t apdu_len, const uint8_t *apdu) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 APP INF"); + struct gsm48_hdr *gh; + + msg->lchan = conn->lchan; + + DEBUGP(DRR, "TX APPLICATION INFO id=0x%02x, len=%u\n", + apdu_id, apdu_len); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 2 + apdu_len); + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_APP_INFO; + gh->data[0] = apdu_id; + gh->data[1] = apdu_len; + memcpy(gh->data+2, apdu, apdu_len); + + return gsm48_conn_sendmsg(msg, conn, NULL); +} + +/* FIXME: this count_statistics is a state machine behaviour. we should convert + * the complete call control into a state machine. Afterwards we can move this + * code into state transitions. + */ +static void count_statistics(struct gsm_trans *trans, int new_state) +{ + int old_state = trans->cc.state; + struct rate_ctr_group *msc = trans->net->msc_ctrs; + + if (old_state == new_state) + return; + + /* state incoming */ + switch (new_state) { + case GSM_CSTATE_ACTIVE: + osmo_counter_inc(trans->net->active_calls); + rate_ctr_inc(&msc->ctr[MSC_CTR_CALL_ACTIVE]); + break; + } + + /* state outgoing */ + switch (old_state) { + case GSM_CSTATE_ACTIVE: + osmo_counter_dec(trans->net->active_calls); + if (new_state == GSM_CSTATE_DISCONNECT_REQ || + new_state == GSM_CSTATE_DISCONNECT_IND) + rate_ctr_inc(&msc->ctr[MSC_CTR_CALL_COMPLETE]); + else + rate_ctr_inc(&msc->ctr[MSC_CTR_CALL_INCOMPLETE]); + break; + } +} + +/* Call Control */ + +/* The entire call control code is written in accordance with Figure 7.10c + * for 'very early assignment', i.e. we allocate a TCH/F during IMMEDIATE + * ASSIGN, then first use that TCH/F for signalling and later MODE MODIFY + * it for voice */ + +static void new_cc_state(struct gsm_trans *trans, int state) +{ + if (state > 31 || state < 0) + return; + + DEBUGP(DCC, "new state %s -> %s\n", + gsm48_cc_state_name(trans->cc.state), + gsm48_cc_state_name(state)); + + count_statistics(trans, state); + trans->cc.state = state; +} + +static int gsm48_cc_tx_status(struct gsm_trans *trans, void *arg) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC STATUS"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + uint8_t *cause, *call_state; + + gh->msg_type = GSM48_MT_CC_STATUS; + + cause = msgb_put(msg, 3); + cause[0] = 2; + cause[1] = GSM48_CAUSE_CS_GSM | GSM48_CAUSE_LOC_USER; + cause[2] = 0x80 | 30; /* response to status inquiry */ + + call_state = msgb_put(msg, 1); + call_state[0] = 0xc0 | 0x00; + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_tx_simple(struct gsm_subscriber_connection *conn, + uint8_t pdisc, uint8_t msg_type) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 TX SIMPLE"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + msg->lchan = conn->lchan; + + gh->proto_discr = pdisc; + gh->msg_type = msg_type; + + return gsm48_conn_sendmsg(msg, conn, NULL); +} + +static void gsm48_stop_cc_timer(struct gsm_trans *trans) +{ + if (osmo_timer_pending(&trans->cc.timer)) { + DEBUGP(DCC, "stopping pending timer T%x\n", trans->cc.Tcurrent); + osmo_timer_del(&trans->cc.timer); + trans->cc.Tcurrent = 0; + } +} + +static int mncc_recvmsg(struct gsm_network *net, struct gsm_trans *trans, + int msg_type, struct gsm_mncc *mncc) +{ + struct msgb *msg; + unsigned char *data; + + if (trans) + if (trans->conn && trans->conn->lchan) + DEBUGP(DCC, "(bts %d trx %d ts %d ti %x sub %s) " + "Sending '%s' to MNCC.\n", + trans->conn->lchan->ts->trx->bts->nr, + trans->conn->lchan->ts->trx->nr, + trans->conn->lchan->ts->nr, trans->transaction_id, + (trans->subscr)?(trans->subscr->extension):"-", + get_mncc_name(msg_type)); + else + DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) " + "Sending '%s' to MNCC.\n", + (trans->subscr)?(trans->subscr->extension):"-", + get_mncc_name(msg_type)); + else + DEBUGP(DCC, "(bts - trx - ts - ti -- sub -) " + "Sending '%s' to MNCC.\n", get_mncc_name(msg_type)); + + mncc->msg_type = msg_type; + + msg = msgb_alloc(sizeof(struct gsm_mncc), "MNCC"); + if (!msg) + return -ENOMEM; + + data = msgb_put(msg, sizeof(struct gsm_mncc)); + memcpy(data, mncc, sizeof(struct gsm_mncc)); + + cc_tx_to_mncc(net, msg); + + return 0; +} + +int mncc_release_ind(struct gsm_network *net, struct gsm_trans *trans, + uint32_t callref, int location, int value) +{ + struct gsm_mncc rel; + + memset(&rel, 0, sizeof(rel)); + rel.callref = callref; + mncc_set_cause(&rel, location, value); + if (trans && trans->cc.state == GSM_CSTATE_RELEASE_REQ) + return mncc_recvmsg(net, trans, MNCC_REL_CNF, &rel); + return mncc_recvmsg(net, trans, MNCC_REL_IND, &rel); +} + +/* Call Control Specific transaction release. + * gets called by trans_free, DO NOT CALL YOURSELF! */ +void _gsm48_cc_trans_free(struct gsm_trans *trans) +{ + gsm48_stop_cc_timer(trans); + + /* send release to L4, if callref still exists */ + if (trans->callref) { + /* Ressource unavailable */ + mncc_release_ind(trans->net, trans, trans->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_RESOURCE_UNAVAIL); + } + if (trans->cc.state != GSM_CSTATE_NULL) + new_cc_state(trans, GSM_CSTATE_NULL); + if (trans->conn) + trau_mux_unmap(&trans->conn->lchan->ts->e1_link, trans->callref); +} + +static int gsm48_cc_tx_setup(struct gsm_trans *trans, void *arg); + +/* call-back from paging the B-end of the connection */ +static int setup_trig_pag_evt(unsigned int hooknum, unsigned int event, + struct msgb *msg, void *_conn, void *_transt) +{ + struct gsm_subscriber_connection *conn = _conn; + struct gsm_trans *transt = _transt; + + OSMO_ASSERT(!transt->conn); + + /* check all tranactions (without lchan) for subscriber */ + switch (event) { + case GSM_PAGING_SUCCEEDED: + DEBUGP(DCC, "Paging subscr %s succeeded!\n", transt->subscr->extension); + OSMO_ASSERT(conn); + /* Assign lchan */ + transt->conn = conn; + /* send SETUP request to called party */ + gsm48_cc_tx_setup(transt, &transt->cc.msg); + break; + case GSM_PAGING_EXPIRED: + case GSM_PAGING_BUSY: + DEBUGP(DCC, "Paging subscr %s expired!\n", + transt->subscr->extension); + /* Temporarily out of order */ + mncc_release_ind(transt->net, transt, + transt->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_DEST_OOO); + transt->callref = 0; + transt->paging_request = NULL; + trans_free(transt); + break; + default: + LOGP(DCC, LOGL_ERROR, "Unknown paging event %d\n", event); + break; + } + + transt->paging_request = NULL; + return 0; +} + +static int tch_recv_mncc(struct gsm_network *net, uint32_t callref, int enable); + +/* handle audio path for handover */ +static int switch_for_handover(struct gsm_lchan *old_lchan, + struct gsm_lchan *new_lchan) +{ + struct rtp_socket *old_rs, *new_rs, *other_rs; + + /* Ask the new socket to send to the already known port. */ + if (new_lchan->conn->mncc_rtp_bridge) { + LOGP(DHO, LOGL_DEBUG, "Forwarding RTP\n"); + rsl_ipacc_mdcx(new_lchan, + old_lchan->abis_ip.connect_ip, + old_lchan->abis_ip.connect_port, 0); + return 0; + } + + if (ipacc_rtp_direct) { + LOGP(DHO, LOGL_ERROR, "unable to handover in direct RTP mode\n"); + return 0; + } + + /* RTP Proxy mode */ + new_rs = new_lchan->abis_ip.rtp_socket; + old_rs = old_lchan->abis_ip.rtp_socket; + + if (!new_rs) { + LOGP(DHO, LOGL_ERROR, "no RTP socket for new_lchan\n"); + return -EIO; + } + + rsl_ipacc_mdcx_to_rtpsock(new_lchan); + + if (!old_rs) { + LOGP(DHO, LOGL_ERROR, "no RTP socket for old_lchan\n"); + return -EIO; + } + + /* copy rx_action and reference to other sock */ + new_rs->rx_action = old_rs->rx_action; + new_rs->tx_action = old_rs->tx_action; + new_rs->transmit = old_rs->transmit; + + switch (old_lchan->abis_ip.rtp_socket->rx_action) { + case RTP_PROXY: + other_rs = old_rs->proxy.other_sock; + rtp_socket_proxy(new_rs, other_rs); + /* delete reference to other end socket to prevent + * rtp_socket_free() from removing the inverse reference */ + old_rs->proxy.other_sock = NULL; + break; + case RTP_RECV_UPSTREAM: + new_rs->receive = old_rs->receive; + break; + case RTP_NONE: + break; + } + + return 0; +} + +static void maybe_switch_for_handover(struct gsm_lchan *lchan) +{ + struct gsm_lchan *old_lchan; + old_lchan = bsc_handover_pending(lchan); + if (old_lchan) + switch_for_handover(old_lchan, lchan); +} + +/* some other part of the code sends us a signal */ +static int handle_abisip_signal(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_lchan *lchan = signal_data; + int rc; + struct gsm_network *net; + struct gsm_trans *trans; + + if (subsys != SS_ABISIP) + return 0; + + /* RTP bridge handling */ + if (lchan->conn && lchan->conn->mncc_rtp_bridge) + return tch_rtp_signal(lchan, signal); + + /* in case we use direct BTS-to-BTS RTP */ + if (ipacc_rtp_direct) + return 0; + + switch (signal) { + case S_ABISIP_CRCX_ACK: + /* in case we don't use direct BTS-to-BTS RTP */ + /* the BTS has successfully bound a TCH to a local ip/port, + * which means we can connect our UDP socket to it */ + if (lchan->abis_ip.rtp_socket) { + rtp_socket_free(lchan->abis_ip.rtp_socket); + lchan->abis_ip.rtp_socket = NULL; + } + + lchan->abis_ip.rtp_socket = rtp_socket_create(); + if (!lchan->abis_ip.rtp_socket) + return -EIO; + + rc = rtp_socket_connect(lchan->abis_ip.rtp_socket, + lchan->abis_ip.bound_ip, + lchan->abis_ip.bound_port); + if (rc < 0) + return -EIO; + + /* check if any transactions on this lchan still have + * a tch_recv_mncc request pending */ + net = lchan->ts->trx->bts->network; + llist_for_each_entry(trans, &net->trans_list, entry) { + if (trans->conn && trans->conn->lchan == lchan && trans->tch_recv) { + DEBUGP(DCC, "pending tch_recv_mncc request\n"); + tch_recv_mncc(net, trans->callref, 1); + } + } + + /* + * TODO: this appears to be too early? Why not until after + * the handover detect or the handover complete? + * + * Do we have a handover pending for this new lchan? In that + * case re-route the audio from the old channel to the new one. + */ + maybe_switch_for_handover(lchan); + break; + case S_ABISIP_DLCX_IND: + /* the BTS tells us a RTP stream has been disconnected */ + if (lchan->abis_ip.rtp_socket) { + rtp_socket_free(lchan->abis_ip.rtp_socket); + lchan->abis_ip.rtp_socket = NULL; + } + + break; + } + + return 0; +} + +/* map two ipaccess RTP streams onto each other */ +static int tch_map(struct gsm_lchan *lchan, struct gsm_lchan *remote_lchan) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + struct gsm_bts *remote_bts = remote_lchan->ts->trx->bts; + enum gsm_chan_t lt = lchan->type, rt = remote_lchan->type; + enum gsm48_chan_mode lm = lchan->tch_mode, rm = remote_lchan->tch_mode; + int rc; + + DEBUGP(DCC, "Setting up TCH map between (bts=%u,trx=%u,ts=%u,%s) and " + "(bts=%u,trx=%u,ts=%u,%s)\n", + bts->nr, lchan->ts->trx->nr, lchan->ts->nr, + get_value_string(gsm_chan_t_names, lt), + remote_bts->nr, remote_lchan->ts->trx->nr, remote_lchan->ts->nr, + get_value_string(gsm_chan_t_names, rt)); + + if (bts->type != remote_bts->type) { + LOGP(DCC, LOGL_ERROR, "Cannot switch calls between different BTS types yet\n"); + return -EINVAL; + } + + if (lt != rt) { + LOGP(DCC, LOGL_ERROR, "Cannot patch through call with different" + " channel types: local = %s, remote = %s\n", + get_value_string(gsm_chan_t_names, lt), + get_value_string(gsm_chan_t_names, rt)); + return -EBADSLT; + } + + if (lm != rm) { + LOGP(DCC, LOGL_ERROR, "Cannot patch through call with different" + " channel modes: local = %s, remote = %s\n", + get_value_string(gsm48_chan_mode_names, lm), + get_value_string(gsm48_chan_mode_names, rm)); + return -EMEDIUMTYPE; + } + + // todo: map between different bts types + switch (bts->type) { + case GSM_BTS_TYPE_NANOBTS: + case GSM_BTS_TYPE_OSMOBTS: + if (!ipacc_rtp_direct) { + if (!lchan->abis_ip.rtp_socket) { + LOGP(DHO, LOGL_ERROR, "no RTP socket for " + "lchan\n"); + return -EIO; + } + if (!remote_lchan->abis_ip.rtp_socket) { + LOGP(DHO, LOGL_ERROR, "no RTP socket for " + "remote_lchan\n"); + return -EIO; + } + + /* connect the TCH's to our RTP proxy */ + rc = rsl_ipacc_mdcx_to_rtpsock(lchan); + if (rc < 0) + return rc; + rc = rsl_ipacc_mdcx_to_rtpsock(remote_lchan); + if (rc < 0) + return rc; + /* connect them with each other */ + rtp_socket_proxy(lchan->abis_ip.rtp_socket, + remote_lchan->abis_ip.rtp_socket); + } else { + /* directly connect TCH RTP streams to each other */ + rc = rsl_ipacc_mdcx(lchan, remote_lchan->abis_ip.bound_ip, + remote_lchan->abis_ip.bound_port, + remote_lchan->abis_ip.rtp_payload2); + if (rc < 0) + return rc; + rc = rsl_ipacc_mdcx(remote_lchan, lchan->abis_ip.bound_ip, + lchan->abis_ip.bound_port, + lchan->abis_ip.rtp_payload2); + } + break; + case GSM_BTS_TYPE_BS11: + case GSM_BTS_TYPE_RBS2000: + case GSM_BTS_TYPE_NOKIA_SITE: + trau_mux_map_lchan(lchan, remote_lchan); + break; + default: + LOGP(DCC, LOGL_ERROR, "Unknown BTS type %u\n", bts->type); + return -EINVAL; + } + + return 0; +} + +/* bridge channels of two transactions */ +static int tch_bridge(struct gsm_network *net, struct gsm_mncc_bridge *bridge) +{ + struct gsm_trans *trans1 = trans_find_by_callref(net, bridge->callref[0]); + struct gsm_trans *trans2 = trans_find_by_callref(net, bridge->callref[1]); + + if (!trans1 || !trans2) + return -EIO; + + if (!trans1->conn || !trans2->conn) + return -EIO; + + /* Which subscriber do we want to track trans1 or trans2? */ + log_set_context(LOG_CTX_VLR_SUBSCR, trans1->subscr); + + /* through-connect channel */ + return tch_map(trans1->conn->lchan, trans2->conn->lchan); +} + +/* enable receive of channels to MNCC upqueue */ +static int tch_recv_mncc(struct gsm_network *net, uint32_t callref, int enable) +{ + struct gsm_trans *trans; + struct gsm_lchan *lchan; + struct gsm_bts *bts; + int rc; + + /* Find callref */ + trans = trans_find_by_callref(net, callref); + if (!trans) + return -EIO; + if (!trans->conn) + return 0; + + log_set_context(LOG_CTX_VLR_SUBSCR, trans->subscr); + lchan = trans->conn->lchan; + bts = lchan->ts->trx->bts; + + /* store receive state */ + trans->tch_recv = enable; + + switch (bts->type) { + case GSM_BTS_TYPE_NANOBTS: + case GSM_BTS_TYPE_OSMOBTS: + if (ipacc_rtp_direct) { + LOGP(DCC, LOGL_ERROR, "Error: RTP proxy is disabled\n"); + return -EINVAL; + } + /* In case, we don't have a RTP socket to the BTS yet, the BTS + * will not be connected to our RTP proxy and the socket will + * not be assigned to the application interface. This method + * will be called again, once the audio socket is created and + * connected. */ + if (!lchan->abis_ip.rtp_socket) { + DEBUGP(DCC, "queue tch_recv_mncc request (%d)\n", enable); + return 0; + } + if (enable) { + /* connect the TCH's to our RTP proxy */ + rc = rsl_ipacc_mdcx_to_rtpsock(lchan); + if (rc < 0) + return rc; + /* assign socket to application interface */ + rtp_socket_upstream(lchan->abis_ip.rtp_socket, + net, callref); + } else + rtp_socket_upstream(lchan->abis_ip.rtp_socket, + net, 0); + break; + case GSM_BTS_TYPE_BS11: + case GSM_BTS_TYPE_RBS2000: + case GSM_BTS_TYPE_NOKIA_SITE: + /* In case we don't have a TCH with correct mode, the TRAU muxer + * will not be asigned to the application interface. This is + * performed by switch_trau_mux() after successful handover or + * assignment. */ + if (lchan->tch_mode == GSM48_CMODE_SIGN) { + DEBUGP(DCC, "queue tch_recv_mncc request (%d)\n", enable); + return 0; + } + if (enable) + return trau_recv_lchan(lchan, callref); + return trau_mux_unmap(NULL, callref); + break; + default: + LOGP(DCC, LOGL_ERROR, "Unknown BTS type %u\n", bts->type); + return -EINVAL; + } + + return 0; +} + +static int gsm48_cc_rx_status_enq(struct gsm_trans *trans, struct msgb *msg) +{ + DEBUGP(DCC, "-> STATUS ENQ\n"); + return gsm48_cc_tx_status(trans, msg); +} + +static int gsm48_cc_tx_release(struct gsm_trans *trans, void *arg); +static int gsm48_cc_tx_disconnect(struct gsm_trans *trans, void *arg); + +static void gsm48_cc_timeout(void *arg) +{ + struct gsm_trans *trans = arg; + int disconnect = 0, release = 0; + int mo_cause = GSM48_CC_CAUSE_RECOVERY_TIMER; + int mo_location = GSM48_CAUSE_LOC_USER; + int l4_cause = GSM48_CC_CAUSE_NORMAL_UNSPEC; + int l4_location = GSM48_CAUSE_LOC_PRN_S_LU; + struct gsm_mncc mo_rel, l4_rel; + + memset(&mo_rel, 0, sizeof(struct gsm_mncc)); + mo_rel.callref = trans->callref; + memset(&l4_rel, 0, sizeof(struct gsm_mncc)); + l4_rel.callref = trans->callref; + + switch(trans->cc.Tcurrent) { + case 0x303: + release = 1; + l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND; + break; + case 0x310: + disconnect = 1; + l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND; + break; + case 0x313: + disconnect = 1; + /* unknown, did not find it in the specs */ + break; + case 0x301: + disconnect = 1; + l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND; + break; + case 0x308: + if (!trans->cc.T308_second) { + /* restart T308 a second time */ + gsm48_cc_tx_release(trans, &trans->cc.msg); + trans->cc.T308_second = 1; + break; /* stay in release state */ + } + trans_free(trans); + return; +// release = 1; +// l4_cause = 14; +// break; + case 0x306: + release = 1; + mo_cause = trans->cc.msg.cause.value; + mo_location = trans->cc.msg.cause.location; + break; + case 0x323: + disconnect = 1; + break; + default: + release = 1; + } + + if (release && trans->callref) { + /* process release towards layer 4 */ + mncc_release_ind(trans->net, trans, trans->callref, + l4_location, l4_cause); + trans->callref = 0; + } + + if (disconnect && trans->callref) { + /* process disconnect towards layer 4 */ + mncc_set_cause(&l4_rel, l4_location, l4_cause); + mncc_recvmsg(trans->net, trans, MNCC_DISC_IND, &l4_rel); + } + + /* process disconnect towards mobile station */ + if (disconnect || release) { + mncc_set_cause(&mo_rel, mo_location, mo_cause); + mo_rel.cause.diag[0] = ((trans->cc.Tcurrent & 0xf00) >> 8) + '0'; + mo_rel.cause.diag[1] = ((trans->cc.Tcurrent & 0x0f0) >> 4) + '0'; + mo_rel.cause.diag[2] = (trans->cc.Tcurrent & 0x00f) + '0'; + mo_rel.cause.diag_len = 3; + + if (disconnect) + gsm48_cc_tx_disconnect(trans, &mo_rel); + if (release) + gsm48_cc_tx_release(trans, &mo_rel); + } + +} + +/* disconnect both calls from the bridge */ +static inline void disconnect_bridge(struct gsm_network *net, + struct gsm_mncc_bridge *bridge, int err) +{ + struct gsm_trans *trans0 = trans_find_by_callref(net, bridge->callref[0]); + struct gsm_trans *trans1 = trans_find_by_callref(net, bridge->callref[1]); + struct gsm_mncc mx_rel; + if (!trans0 || !trans1) + return; + + DEBUGP(DCC, "Failed to bridge TCH for calls %x <-> %x :: %s \n", + trans0->callref, trans1->callref, strerror(err)); + + memset(&mx_rel, 0, sizeof(struct gsm_mncc)); + mncc_set_cause(&mx_rel, GSM48_CAUSE_LOC_INN_NET, + GSM48_CC_CAUSE_CHAN_UNACCEPT); + + mx_rel.callref = trans0->callref; + gsm48_cc_tx_disconnect(trans0, &mx_rel); + + mx_rel.callref = trans1->callref; + gsm48_cc_tx_disconnect(trans1, &mx_rel); +} + +static void gsm48_start_cc_timer(struct gsm_trans *trans, int current, + int sec, int micro) +{ + DEBUGP(DCC, "starting timer T%x with %d seconds\n", current, sec); + osmo_timer_setup(&trans->cc.timer, gsm48_cc_timeout, trans); + osmo_timer_schedule(&trans->cc.timer, sec, micro); + trans->cc.Tcurrent = current; +} + +static int gsm48_cc_rx_setup(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + uint8_t msg_type = gsm48_hdr_msg_type(gh); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc setup; + + memset(&setup, 0, sizeof(struct gsm_mncc)); + setup.callref = trans->callref; + setup.lchan_type = trans->conn->lchan->type; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + /* emergency setup is identified by msg_type */ + if (msg_type == GSM48_MT_CC_EMERG_SETUP) + setup.emergency = 1; + + /* use subscriber as calling party number */ + setup.fields |= MNCC_F_CALLING; + osmo_strlcpy(setup.calling.number, trans->subscr->extension, + sizeof(setup.calling.number)); + osmo_strlcpy(setup.imsi, trans->subscr->imsi, sizeof(setup.imsi)); + + /* bearer capability */ + if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) { + setup.fields |= MNCC_F_BEARER_CAP; + gsm48_decode_bearer_cap(&setup.bearer_cap, + TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1); + apply_codec_restrictions(trans->conn->bts, &setup.bearer_cap); + } + /* facility */ + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + setup.fields |= MNCC_F_FACILITY; + gsm48_decode_facility(&setup.facility, + TLVP_VAL(&tp, GSM48_IE_FACILITY)-1); + } + /* called party bcd number */ + if (TLVP_PRESENT(&tp, GSM48_IE_CALLED_BCD)) { + setup.fields |= MNCC_F_CALLED; + gsm48_decode_called(&setup.called, + TLVP_VAL(&tp, GSM48_IE_CALLED_BCD)-1); + } + /* user-user */ + if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) { + setup.fields |= MNCC_F_USERUSER; + gsm48_decode_useruser(&setup.useruser, + TLVP_VAL(&tp, GSM48_IE_USER_USER)-1); + } + /* ss-version */ + if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) { + setup.fields |= MNCC_F_SSVERSION; + gsm48_decode_ssversion(&setup.ssversion, + TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1); + } + /* CLIR suppression */ + if (TLVP_PRESENT(&tp, GSM48_IE_CLIR_SUPP)) + setup.clir.sup = 1; + /* CLIR invocation */ + if (TLVP_PRESENT(&tp, GSM48_IE_CLIR_INVOC)) + setup.clir.inv = 1; + /* cc cap */ + if (TLVP_PRESENT(&tp, GSM48_IE_CC_CAP)) { + setup.fields |= MNCC_F_CCCAP; + gsm48_decode_cccap(&setup.cccap, + TLVP_VAL(&tp, GSM48_IE_CC_CAP)-1); + } + + new_cc_state(trans, GSM_CSTATE_INITIATED); + + LOGP(DCC, LOGL_INFO, "Subscriber %s (%s) sends SETUP to %s\n", + subscr_name(trans->subscr), trans->subscr->extension, + setup.called.number); + + rate_ctr_inc(&trans->net->msc_ctrs->ctr[MSC_CTR_CALL_MO_SETUP]); + + /* indicate setup to MNCC */ + mncc_recvmsg(trans->net, trans, MNCC_SETUP_IND, &setup); + + /* MNCC code will modify the channel asynchronously, we should + * ipaccess-bind only after the modification has been made to the + * lchan->tch_mode */ + return 0; +} + +static int gsm48_cc_tx_setup(struct gsm_trans *trans, void *arg) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC STUP"); + struct gsm48_hdr *gh; + struct gsm_mncc *setup = arg; + int rc, trans_id; + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + /* transaction id must not be assigned */ + if (trans->transaction_id != 0xff) { /* unasssigned */ + DEBUGP(DCC, "TX Setup with assigned transaction. " + "This is not allowed!\n"); + /* Temporarily out of order */ + rc = mncc_release_ind(trans->net, trans, trans->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_RESOURCE_UNAVAIL); + trans->callref = 0; + trans_free(trans); + return rc; + } + + /* Get free transaction_id */ + trans_id = trans_assign_trans_id(trans->net, trans->subscr, + GSM48_PDISC_CC, 0); + if (trans_id < 0) { + /* no free transaction ID */ + rc = mncc_release_ind(trans->net, trans, trans->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_RESOURCE_UNAVAIL); + trans->callref = 0; + trans_free(trans); + return rc; + } + trans->transaction_id = trans_id; + + gh->msg_type = GSM48_MT_CC_SETUP; + + gsm48_start_cc_timer(trans, 0x303, GSM48_T303); + + /* bearer capability */ + if (setup->fields & MNCC_F_BEARER_CAP) + gsm48_encode_bearer_cap(msg, 0, &setup->bearer_cap); + /* facility */ + if (setup->fields & MNCC_F_FACILITY) + gsm48_encode_facility(msg, 0, &setup->facility); + /* progress */ + if (setup->fields & MNCC_F_PROGRESS) + gsm48_encode_progress(msg, 0, &setup->progress); + /* calling party BCD number */ + if (setup->fields & MNCC_F_CALLING) + gsm48_encode_calling(msg, &setup->calling); + /* called party BCD number */ + if (setup->fields & MNCC_F_CALLED) + gsm48_encode_called(msg, &setup->called); + /* user-user */ + if (setup->fields & MNCC_F_USERUSER) + gsm48_encode_useruser(msg, 0, &setup->useruser); + /* redirecting party BCD number */ + if (setup->fields & MNCC_F_REDIRECTING) + gsm48_encode_redirecting(msg, &setup->redirecting); + /* signal */ + if (setup->fields & MNCC_F_SIGNAL) + gsm48_encode_signal(msg, setup->signal); + + new_cc_state(trans, GSM_CSTATE_CALL_PRESENT); + + rate_ctr_inc(&trans->net->msc_ctrs->ctr[MSC_CTR_CALL_MT_SETUP]); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_call_conf(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc call_conf; + + gsm48_stop_cc_timer(trans); + gsm48_start_cc_timer(trans, 0x310, GSM48_T310); + + memset(&call_conf, 0, sizeof(struct gsm_mncc)); + call_conf.callref = trans->callref; + call_conf.lchan_type = trans->conn->lchan->type; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); +#if 0 + /* repeat */ + if (TLVP_PRESENT(&tp, GSM48_IE_REPEAT_CIR)) + call_conf.repeat = 1; + if (TLVP_PRESENT(&tp, GSM48_IE_REPEAT_SEQ)) + call_conf.repeat = 2; +#endif + /* bearer capability */ + if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) { + call_conf.fields |= MNCC_F_BEARER_CAP; + gsm48_decode_bearer_cap(&call_conf.bearer_cap, + TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1); + apply_codec_restrictions(trans->conn->bts, &call_conf.bearer_cap); + } + /* cause */ + if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) { + call_conf.fields |= MNCC_F_CAUSE; + gsm48_decode_cause(&call_conf.cause, + TLVP_VAL(&tp, GSM48_IE_CAUSE)-1); + } + /* cc cap */ + if (TLVP_PRESENT(&tp, GSM48_IE_CC_CAP)) { + call_conf.fields |= MNCC_F_CCCAP; + gsm48_decode_cccap(&call_conf.cccap, + TLVP_VAL(&tp, GSM48_IE_CC_CAP)-1); + } + + /* IMSI of called subscriber */ + osmo_strlcpy(call_conf.imsi, trans->subscr->imsi, + sizeof(call_conf.imsi)); + + new_cc_state(trans, GSM_CSTATE_MO_TERM_CALL_CONF); + + return mncc_recvmsg(trans->net, trans, MNCC_CALL_CONF_IND, + &call_conf); +} + +static int gsm48_cc_tx_call_proc(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *proceeding = arg; + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC PROC"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_CALL_PROC; + + new_cc_state(trans, GSM_CSTATE_MO_CALL_PROC); + + /* bearer capability */ + if (proceeding->fields & MNCC_F_BEARER_CAP) + gsm48_encode_bearer_cap(msg, 0, &proceeding->bearer_cap); + /* facility */ + if (proceeding->fields & MNCC_F_FACILITY) + gsm48_encode_facility(msg, 0, &proceeding->facility); + /* progress */ + if (proceeding->fields & MNCC_F_PROGRESS) + gsm48_encode_progress(msg, 0, &proceeding->progress); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_alerting(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc alerting; + + gsm48_stop_cc_timer(trans); + gsm48_start_cc_timer(trans, 0x301, GSM48_T301); + + memset(&alerting, 0, sizeof(struct gsm_mncc)); + alerting.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + /* facility */ + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + alerting.fields |= MNCC_F_FACILITY; + gsm48_decode_facility(&alerting.facility, + TLVP_VAL(&tp, GSM48_IE_FACILITY)-1); + } + + /* progress */ + if (TLVP_PRESENT(&tp, GSM48_IE_PROGR_IND)) { + alerting.fields |= MNCC_F_PROGRESS; + gsm48_decode_progress(&alerting.progress, + TLVP_VAL(&tp, GSM48_IE_PROGR_IND)-1); + } + /* ss-version */ + if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) { + alerting.fields |= MNCC_F_SSVERSION; + gsm48_decode_ssversion(&alerting.ssversion, + TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1); + } + + new_cc_state(trans, GSM_CSTATE_CALL_RECEIVED); + + return mncc_recvmsg(trans->net, trans, MNCC_ALERT_IND, + &alerting); +} + +static int gsm48_cc_tx_alerting(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *alerting = arg; + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC ALERT"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_ALERTING; + + /* facility */ + if (alerting->fields & MNCC_F_FACILITY) + gsm48_encode_facility(msg, 0, &alerting->facility); + /* progress */ + if (alerting->fields & MNCC_F_PROGRESS) + gsm48_encode_progress(msg, 0, &alerting->progress); + /* user-user */ + if (alerting->fields & MNCC_F_USERUSER) + gsm48_encode_useruser(msg, 0, &alerting->useruser); + + new_cc_state(trans, GSM_CSTATE_CALL_DELIVERED); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_tx_progress(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *progress = arg; + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC PROGRESS"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_PROGRESS; + + /* progress */ + gsm48_encode_progress(msg, 1, &progress->progress); + /* user-user */ + if (progress->fields & MNCC_F_USERUSER) + gsm48_encode_useruser(msg, 0, &progress->useruser); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_tx_connect(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *connect = arg; + struct msgb *msg = gsm48_msgb_alloc_name("GSN 04.08 CC CON"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_CONNECT; + + gsm48_stop_cc_timer(trans); + gsm48_start_cc_timer(trans, 0x313, GSM48_T313); + + /* facility */ + if (connect->fields & MNCC_F_FACILITY) + gsm48_encode_facility(msg, 0, &connect->facility); + /* progress */ + if (connect->fields & MNCC_F_PROGRESS) + gsm48_encode_progress(msg, 0, &connect->progress); + /* connected number */ + if (connect->fields & MNCC_F_CONNECTED) + gsm48_encode_connected(msg, &connect->connected); + /* user-user */ + if (connect->fields & MNCC_F_USERUSER) + gsm48_encode_useruser(msg, 0, &connect->useruser); + + new_cc_state(trans, GSM_CSTATE_CONNECT_IND); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_connect(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc connect; + + gsm48_stop_cc_timer(trans); + + memset(&connect, 0, sizeof(struct gsm_mncc)); + connect.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + /* use subscriber as connected party number */ + connect.fields |= MNCC_F_CONNECTED; + osmo_strlcpy(connect.connected.number, trans->subscr->extension, + sizeof(connect.connected.number)); + osmo_strlcpy(connect.imsi, trans->subscr->imsi, sizeof(connect.imsi)); + + /* facility */ + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + connect.fields |= MNCC_F_FACILITY; + gsm48_decode_facility(&connect.facility, + TLVP_VAL(&tp, GSM48_IE_FACILITY)-1); + } + /* user-user */ + if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) { + connect.fields |= MNCC_F_USERUSER; + gsm48_decode_useruser(&connect.useruser, + TLVP_VAL(&tp, GSM48_IE_USER_USER)-1); + } + /* ss-version */ + if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) { + connect.fields |= MNCC_F_SSVERSION; + gsm48_decode_ssversion(&connect.ssversion, + TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1); + } + + new_cc_state(trans, GSM_CSTATE_CONNECT_REQUEST); + rate_ctr_inc(&trans->net->msc_ctrs->ctr[MSC_CTR_CALL_MT_CONNECT]); + + return mncc_recvmsg(trans->net, trans, MNCC_SETUP_CNF, &connect); +} + + +static int gsm48_cc_rx_connect_ack(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm_mncc connect_ack; + + gsm48_stop_cc_timer(trans); + + new_cc_state(trans, GSM_CSTATE_ACTIVE); + rate_ctr_inc(&trans->net->msc_ctrs->ctr[MSC_CTR_CALL_MO_CONNECT_ACK]); + + memset(&connect_ack, 0, sizeof(struct gsm_mncc)); + connect_ack.callref = trans->callref; + + return mncc_recvmsg(trans->net, trans, MNCC_SETUP_COMPL_IND, + &connect_ack); +} + +static int gsm48_cc_tx_connect_ack(struct gsm_trans *trans, void *arg) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC CON ACK"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_CONNECT_ACK; + + new_cc_state(trans, GSM_CSTATE_ACTIVE); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_disconnect(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc disc; + + gsm48_stop_cc_timer(trans); + + new_cc_state(trans, GSM_CSTATE_DISCONNECT_REQ); + + memset(&disc, 0, sizeof(struct gsm_mncc)); + disc.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_CAUSE, 0); + /* cause */ + if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) { + disc.fields |= MNCC_F_CAUSE; + gsm48_decode_cause(&disc.cause, + TLVP_VAL(&tp, GSM48_IE_CAUSE)-1); + } + /* facility */ + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + disc.fields |= MNCC_F_FACILITY; + gsm48_decode_facility(&disc.facility, + TLVP_VAL(&tp, GSM48_IE_FACILITY)-1); + } + /* user-user */ + if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) { + disc.fields |= MNCC_F_USERUSER; + gsm48_decode_useruser(&disc.useruser, + TLVP_VAL(&tp, GSM48_IE_USER_USER)-1); + } + /* ss-version */ + if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) { + disc.fields |= MNCC_F_SSVERSION; + gsm48_decode_ssversion(&disc.ssversion, + TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1); + } + + return mncc_recvmsg(trans->net, trans, MNCC_DISC_IND, &disc); + +} + +static struct gsm_mncc_cause default_cause = { + .location = GSM48_CAUSE_LOC_PRN_S_LU, + .coding = 0, + .rec = 0, + .rec_val = 0, + .value = GSM48_CC_CAUSE_NORMAL_UNSPEC, + .diag_len = 0, + .diag = { 0 }, +}; + +static int gsm48_cc_tx_disconnect(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *disc = arg; + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC DISC"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_DISCONNECT; + + gsm48_stop_cc_timer(trans); + gsm48_start_cc_timer(trans, 0x306, GSM48_T306); + + /* cause */ + if (disc->fields & MNCC_F_CAUSE) + gsm48_encode_cause(msg, 1, &disc->cause); + else + gsm48_encode_cause(msg, 1, &default_cause); + + /* facility */ + if (disc->fields & MNCC_F_FACILITY) + gsm48_encode_facility(msg, 0, &disc->facility); + /* progress */ + if (disc->fields & MNCC_F_PROGRESS) + gsm48_encode_progress(msg, 0, &disc->progress); + /* user-user */ + if (disc->fields & MNCC_F_USERUSER) + gsm48_encode_useruser(msg, 0, &disc->useruser); + + /* store disconnect cause for T306 expiry */ + memcpy(&trans->cc.msg, disc, sizeof(struct gsm_mncc)); + + new_cc_state(trans, GSM_CSTATE_DISCONNECT_IND); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_release(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc rel; + int rc; + + gsm48_stop_cc_timer(trans); + + memset(&rel, 0, sizeof(struct gsm_mncc)); + rel.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + /* cause */ + if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) { + rel.fields |= MNCC_F_CAUSE; + gsm48_decode_cause(&rel.cause, + TLVP_VAL(&tp, GSM48_IE_CAUSE)-1); + } + /* facility */ + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + rel.fields |= MNCC_F_FACILITY; + gsm48_decode_facility(&rel.facility, + TLVP_VAL(&tp, GSM48_IE_FACILITY)-1); + } + /* user-user */ + if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) { + rel.fields |= MNCC_F_USERUSER; + gsm48_decode_useruser(&rel.useruser, + TLVP_VAL(&tp, GSM48_IE_USER_USER)-1); + } + /* ss-version */ + if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) { + rel.fields |= MNCC_F_SSVERSION; + gsm48_decode_ssversion(&rel.ssversion, + TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1); + } + + if (trans->cc.state == GSM_CSTATE_RELEASE_REQ) { + /* release collision 5.4.5 */ + rc = mncc_recvmsg(trans->net, trans, MNCC_REL_CNF, &rel); + } else { + rc = gsm48_tx_simple(trans->conn, + GSM48_PDISC_CC | (trans->transaction_id << 4), + GSM48_MT_CC_RELEASE_COMPL); + rc = mncc_recvmsg(trans->net, trans, MNCC_REL_IND, &rel); + } + + new_cc_state(trans, GSM_CSTATE_NULL); + + trans->callref = 0; + trans_free(trans); + + return rc; +} + +static int gsm48_cc_tx_release(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *rel = arg; + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC REL"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_RELEASE; + + gsm48_stop_cc_timer(trans); + gsm48_start_cc_timer(trans, 0x308, GSM48_T308); + + /* cause */ + if (rel->fields & MNCC_F_CAUSE) + gsm48_encode_cause(msg, 0, &rel->cause); + /* facility */ + if (rel->fields & MNCC_F_FACILITY) + gsm48_encode_facility(msg, 0, &rel->facility); + /* user-user */ + if (rel->fields & MNCC_F_USERUSER) + gsm48_encode_useruser(msg, 0, &rel->useruser); + + trans->cc.T308_second = 0; + memcpy(&trans->cc.msg, rel, sizeof(struct gsm_mncc)); + + if (trans->cc.state != GSM_CSTATE_RELEASE_REQ) + new_cc_state(trans, GSM_CSTATE_RELEASE_REQ); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_release_compl(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc rel; + int rc = 0; + + gsm48_stop_cc_timer(trans); + + memset(&rel, 0, sizeof(struct gsm_mncc)); + rel.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + /* cause */ + if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) { + rel.fields |= MNCC_F_CAUSE; + gsm48_decode_cause(&rel.cause, + TLVP_VAL(&tp, GSM48_IE_CAUSE)-1); + } + /* facility */ + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + rel.fields |= MNCC_F_FACILITY; + gsm48_decode_facility(&rel.facility, + TLVP_VAL(&tp, GSM48_IE_FACILITY)-1); + } + /* user-user */ + if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) { + rel.fields |= MNCC_F_USERUSER; + gsm48_decode_useruser(&rel.useruser, + TLVP_VAL(&tp, GSM48_IE_USER_USER)-1); + } + /* ss-version */ + if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) { + rel.fields |= MNCC_F_SSVERSION; + gsm48_decode_ssversion(&rel.ssversion, + TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1); + } + + if (trans->callref) { + switch (trans->cc.state) { + case GSM_CSTATE_CALL_PRESENT: + rc = mncc_recvmsg(trans->net, trans, + MNCC_REJ_IND, &rel); + break; + case GSM_CSTATE_RELEASE_REQ: + rc = mncc_recvmsg(trans->net, trans, + MNCC_REL_CNF, &rel); + break; + default: + rc = mncc_recvmsg(trans->net, trans, + MNCC_REL_IND, &rel); + } + } + + trans->callref = 0; + trans_free(trans); + + return rc; +} + +static int gsm48_cc_tx_release_compl(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *rel = arg; + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC REL COMPL"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + int ret; + + gh->msg_type = GSM48_MT_CC_RELEASE_COMPL; + + trans->callref = 0; + + gsm48_stop_cc_timer(trans); + + /* cause */ + if (rel->fields & MNCC_F_CAUSE) + gsm48_encode_cause(msg, 0, &rel->cause); + /* facility */ + if (rel->fields & MNCC_F_FACILITY) + gsm48_encode_facility(msg, 0, &rel->facility); + /* user-user */ + if (rel->fields & MNCC_F_USERUSER) + gsm48_encode_useruser(msg, 0, &rel->useruser); + + ret = gsm48_conn_sendmsg(msg, trans->conn, trans); + + trans_free(trans); + + return ret; +} + +static int gsm48_cc_rx_facility(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc fac; + + memset(&fac, 0, sizeof(struct gsm_mncc)); + fac.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_FACILITY, 0); + /* facility */ + if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) { + fac.fields |= MNCC_F_FACILITY; + gsm48_decode_facility(&fac.facility, + TLVP_VAL(&tp, GSM48_IE_FACILITY)-1); + } + /* ss-version */ + if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) { + fac.fields |= MNCC_F_SSVERSION; + gsm48_decode_ssversion(&fac.ssversion, + TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1); + } + + return mncc_recvmsg(trans->net, trans, MNCC_FACILITY_IND, &fac); +} + +static int gsm48_cc_tx_facility(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *fac = arg; + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC FAC"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_FACILITY; + + /* facility */ + gsm48_encode_facility(msg, 1, &fac->facility); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_hold(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm_mncc hold; + + memset(&hold, 0, sizeof(struct gsm_mncc)); + hold.callref = trans->callref; + return mncc_recvmsg(trans->net, trans, MNCC_HOLD_IND, &hold); +} + +static int gsm48_cc_tx_hold_ack(struct gsm_trans *trans, void *arg) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC HLD ACK"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_HOLD_ACK; + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_tx_hold_rej(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *hold_rej = arg; + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC HLD REJ"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_HOLD_REJ; + + /* cause */ + if (hold_rej->fields & MNCC_F_CAUSE) + gsm48_encode_cause(msg, 1, &hold_rej->cause); + else + gsm48_encode_cause(msg, 1, &default_cause); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_retrieve(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm_mncc retrieve; + + memset(&retrieve, 0, sizeof(struct gsm_mncc)); + retrieve.callref = trans->callref; + return mncc_recvmsg(trans->net, trans, MNCC_RETRIEVE_IND, + &retrieve); +} + +static int gsm48_cc_tx_retrieve_ack(struct gsm_trans *trans, void *arg) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC RETR ACK"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_RETR_ACK; + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_tx_retrieve_rej(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *retrieve_rej = arg; + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC RETR REJ"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_RETR_REJ; + + /* cause */ + if (retrieve_rej->fields & MNCC_F_CAUSE) + gsm48_encode_cause(msg, 1, &retrieve_rej->cause); + else + gsm48_encode_cause(msg, 1, &default_cause); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_start_dtmf(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc dtmf; + + memset(&dtmf, 0, sizeof(struct gsm_mncc)); + dtmf.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + /* keypad facility */ + if (TLVP_PRESENT(&tp, GSM48_IE_KPD_FACILITY)) { + dtmf.fields |= MNCC_F_KEYPAD; + gsm48_decode_keypad(&dtmf.keypad, + TLVP_VAL(&tp, GSM48_IE_KPD_FACILITY)-1); + } + + return mncc_recvmsg(trans->net, trans, MNCC_START_DTMF_IND, &dtmf); +} + +static int gsm48_cc_tx_start_dtmf_ack(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *dtmf = arg; + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 DTMF ACK"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_START_DTMF_ACK; + + /* keypad */ + if (dtmf->fields & MNCC_F_KEYPAD) + gsm48_encode_keypad(msg, dtmf->keypad); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_tx_start_dtmf_rej(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *dtmf = arg; + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 DTMF REJ"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_START_DTMF_REJ; + + /* cause */ + if (dtmf->fields & MNCC_F_CAUSE) + gsm48_encode_cause(msg, 1, &dtmf->cause); + else + gsm48_encode_cause(msg, 1, &default_cause); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_tx_stop_dtmf_ack(struct gsm_trans *trans, void *arg) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 DTMF STP ACK"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_STOP_DTMF_ACK; + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_stop_dtmf(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm_mncc dtmf; + + memset(&dtmf, 0, sizeof(struct gsm_mncc)); + dtmf.callref = trans->callref; + + return mncc_recvmsg(trans->net, trans, MNCC_STOP_DTMF_IND, &dtmf); +} + +static int gsm48_cc_rx_modify(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc modify; + + memset(&modify, 0, sizeof(struct gsm_mncc)); + modify.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_BEARER_CAP, 0); + /* bearer capability */ + if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) { + modify.fields |= MNCC_F_BEARER_CAP; + gsm48_decode_bearer_cap(&modify.bearer_cap, + TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1); + apply_codec_restrictions(trans->conn->bts, &modify.bearer_cap); + } + + new_cc_state(trans, GSM_CSTATE_MO_ORIG_MODIFY); + + return mncc_recvmsg(trans->net, trans, MNCC_MODIFY_IND, &modify); +} + +static int gsm48_cc_tx_modify(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *modify = arg; + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC MOD"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_MODIFY; + + gsm48_start_cc_timer(trans, 0x323, GSM48_T323); + + /* bearer capability */ + gsm48_encode_bearer_cap(msg, 1, &modify->bearer_cap); + + new_cc_state(trans, GSM_CSTATE_MO_TERM_MODIFY); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_modify_complete(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc modify; + + gsm48_stop_cc_timer(trans); + + memset(&modify, 0, sizeof(struct gsm_mncc)); + modify.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_BEARER_CAP, 0); + /* bearer capability */ + if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) { + modify.fields |= MNCC_F_BEARER_CAP; + gsm48_decode_bearer_cap(&modify.bearer_cap, + TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1); + apply_codec_restrictions(trans->conn->bts, &modify.bearer_cap); + } + + new_cc_state(trans, GSM_CSTATE_ACTIVE); + + return mncc_recvmsg(trans->net, trans, MNCC_MODIFY_CNF, &modify); +} + +static int gsm48_cc_tx_modify_complete(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *modify = arg; + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC MOD COMPL"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_MODIFY_COMPL; + + /* bearer capability */ + gsm48_encode_bearer_cap(msg, 1, &modify->bearer_cap); + + new_cc_state(trans, GSM_CSTATE_ACTIVE); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_modify_reject(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc modify; + + gsm48_stop_cc_timer(trans); + + memset(&modify, 0, sizeof(struct gsm_mncc)); + modify.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_BEARER_CAP, GSM48_IE_CAUSE); + /* bearer capability */ + if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) { + modify.fields |= GSM48_IE_BEARER_CAP; + gsm48_decode_bearer_cap(&modify.bearer_cap, + TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1); + apply_codec_restrictions(trans->conn->bts, &modify.bearer_cap); + } + /* cause */ + if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) { + modify.fields |= MNCC_F_CAUSE; + gsm48_decode_cause(&modify.cause, + TLVP_VAL(&tp, GSM48_IE_CAUSE)-1); + } + + new_cc_state(trans, GSM_CSTATE_ACTIVE); + + return mncc_recvmsg(trans->net, trans, MNCC_MODIFY_REJ, &modify); +} + +static int gsm48_cc_tx_modify_reject(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *modify = arg; + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC MOD REJ"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_MODIFY_REJECT; + + /* bearer capability */ + gsm48_encode_bearer_cap(msg, 1, &modify->bearer_cap); + /* cause */ + gsm48_encode_cause(msg, 1, &modify->cause); + + new_cc_state(trans, GSM_CSTATE_ACTIVE); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_tx_notify(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *notify = arg; + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC NOT"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_NOTIFY; + + /* notify */ + gsm48_encode_notify(msg, notify->notify); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_notify(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); +// struct tlv_parsed tp; + struct gsm_mncc notify; + + memset(¬ify, 0, sizeof(struct gsm_mncc)); + notify.callref = trans->callref; +// tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len); + if (payload_len >= 1) + gsm48_decode_notify(¬ify.notify, gh->data); + + return mncc_recvmsg(trans->net, trans, MNCC_NOTIFY_IND, ¬ify); +} + +static int gsm48_cc_tx_userinfo(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *user = arg; + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 USR INFO"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->msg_type = GSM48_MT_CC_USER_INFO; + + /* user-user */ + if (user->fields & MNCC_F_USERUSER) + gsm48_encode_useruser(msg, 1, &user->useruser); + /* more data */ + if (user->more) + gsm48_encode_more(msg); + + return gsm48_conn_sendmsg(msg, trans->conn, trans); +} + +static int gsm48_cc_rx_userinfo(struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + struct tlv_parsed tp; + struct gsm_mncc user; + + memset(&user, 0, sizeof(struct gsm_mncc)); + user.callref = trans->callref; + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_USER_USER, 0); + /* user-user */ + if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) { + user.fields |= MNCC_F_USERUSER; + gsm48_decode_useruser(&user.useruser, + TLVP_VAL(&tp, GSM48_IE_USER_USER)-1); + } + /* more data */ + if (TLVP_PRESENT(&tp, GSM48_IE_MORE_DATA)) + user.more = 1; + + return mncc_recvmsg(trans->net, trans, MNCC_USERINFO_IND, &user); +} + +static int _gsm48_lchan_modify(struct gsm_trans *trans, void *arg) +{ + struct gsm_mncc *mode = arg; + struct gsm_lchan *lchan = trans->conn->lchan; + + /* + * We were forced to make an assignment a lot earlier and + * we should avoid sending another assignment that might + * even lead to a different kind of lchan (TCH/F vs. TCH/H). + * In case of rtp-bridge it is too late to change things + * here. + */ + if (trans->conn->mncc_rtp_bridge && lchan->tch_mode != GSM48_CMODE_SIGN) + return 0; + + return gsm0808_assign_req(trans->conn, mode->lchan_mode, + trans->conn->lchan->type != GSM_LCHAN_TCH_H); +} + +static void mncc_recv_rtp(struct gsm_network *net, uint32_t callref, + int cmd, uint32_t addr, uint16_t port, uint32_t payload_type, + uint32_t payload_msg_type) +{ + uint8_t data[sizeof(struct gsm_mncc)]; + struct gsm_mncc_rtp *rtp; + + memset(&data, 0, sizeof(data)); + rtp = (struct gsm_mncc_rtp *) &data[0]; + + rtp->callref = callref; + rtp->msg_type = cmd; + rtp->ip = addr; + rtp->port = port; + rtp->payload_type = payload_type; + rtp->payload_msg_type = payload_msg_type; + mncc_recvmsg(net, NULL, cmd, (struct gsm_mncc *)data); +} + +static void mncc_recv_rtp_sock(struct gsm_network *net, struct gsm_trans *trans, int cmd) +{ + struct gsm_lchan *lchan; + int msg_type; + + lchan = trans->conn->lchan; + switch (lchan->abis_ip.rtp_payload) { + case RTP_PT_GSM_FULL: + msg_type = GSM_TCHF_FRAME; + break; + case RTP_PT_GSM_EFR: + msg_type = GSM_TCHF_FRAME_EFR; + break; + case RTP_PT_GSM_HALF: + msg_type = GSM_TCHH_FRAME; + break; + case RTP_PT_AMR: + msg_type = GSM_TCH_FRAME_AMR; + break; + default: + LOGP(DMNCC, LOGL_ERROR, "%s unknown payload type %d\n", + gsm_lchan_name(lchan), lchan->abis_ip.rtp_payload); + msg_type = 0; + break; + } + + return mncc_recv_rtp(net, trans->callref, cmd, + lchan->abis_ip.bound_ip, + lchan->abis_ip.bound_port, + lchan->abis_ip.rtp_payload, + msg_type); +} + +static void mncc_recv_rtp_err(struct gsm_network *net, uint32_t callref, int cmd) +{ + return mncc_recv_rtp(net, callref, cmd, 0, 0, 0, 0); +} + +static int tch_rtp_create(struct gsm_network *net, uint32_t callref) +{ + struct gsm_bts *bts; + struct gsm_lchan *lchan; + struct gsm_trans *trans; + enum gsm48_chan_mode m; + + /* Find callref */ + trans = trans_find_by_callref(net, callref); + if (!trans) { + LOGP(DMNCC, LOGL_ERROR, "RTP create for non-existing trans\n"); + mncc_recv_rtp_err(net, callref, MNCC_RTP_CREATE); + return -EIO; + } + log_set_context(LOG_CTX_VLR_SUBSCR, trans->subscr); + if (!trans->conn) { + LOGP(DMNCC, LOGL_NOTICE, "RTP create for trans without conn\n"); + mncc_recv_rtp_err(net, callref, MNCC_RTP_CREATE); + return 0; + } + + lchan = trans->conn->lchan; + bts = lchan->ts->trx->bts; + if (!is_ipaccess_bts(bts)) { + /* + * I want this to be straight forward and have no audio flow + * through the nitb/osmo-mss system. This currently means that + * this will not work with BS11/Nokia type BTS. We would need + * to have a trau<->rtp bridge for these but still preferable + * in another process. + */ + LOGP(DMNCC, LOGL_ERROR, "RTP create only works with IP systems\n"); + mncc_recv_rtp_err(net, callref, MNCC_RTP_CREATE); + return -EINVAL; + } + + trans->conn->mncc_rtp_bridge = 1; + /* + * *sigh* we need to pick a codec now. Pick the most generic one + * right now and hope we could fix that later on. This is very + * similiar to the routine above. + * Fallback to the internal MNCC mode to select a route. + */ + if (lchan->tch_mode == GSM48_CMODE_SIGN) { + trans->conn->mncc_rtp_create_pending = 1; + m = mncc_codec_for_mode(lchan->type); + LOGP(DMNCC, LOGL_DEBUG, "RTP create: codec=%s, chan_type=%s\n", + get_value_string(gsm48_chan_mode_names, m), + get_value_string(gsm_chan_t_names, lchan->type)); + return gsm0808_assign_req(trans->conn, m, + lchan->type != GSM_LCHAN_TCH_H); + } + + mncc_recv_rtp_sock(trans->net, trans, MNCC_RTP_CREATE); + return 0; +} + +static int tch_rtp_connect(struct gsm_network *net, void *arg) +{ + struct gsm_lchan *lchan; + struct gsm_trans *trans; + struct gsm_mncc_rtp *rtp = arg; + + /* Find callref */ + trans = trans_find_by_callref(net, rtp->callref); + if (!trans) { + LOGP(DMNCC, LOGL_ERROR, "RTP connect for non-existing trans\n"); + mncc_recv_rtp_err(net, rtp->callref, MNCC_RTP_CONNECT); + return -EIO; + } + log_set_context(LOG_CTX_VLR_SUBSCR, trans->subscr); + if (!trans->conn) { + LOGP(DMNCC, LOGL_ERROR, "RTP connect for trans without conn\n"); + mncc_recv_rtp_err(net, rtp->callref, MNCC_RTP_CONNECT); + return 0; + } + + lchan = trans->conn->lchan; + LOGP(DMNCC, LOGL_DEBUG, "RTP connect: codec=%s, chan_type=%s\n", + get_value_string(gsm48_chan_mode_names, + mncc_codec_for_mode(lchan->type)), + get_value_string(gsm_chan_t_names, lchan->type)); + + /* TODO: Check if payload_msg_type is compatible with what we have */ + if (rtp->payload_type != lchan->abis_ip.rtp_payload) { + LOGP(DMNCC, LOGL_ERROR, "RTP connect with different RTP payload\n"); + mncc_recv_rtp_err(net, rtp->callref, MNCC_RTP_CONNECT); + } + + /* + * FIXME: payload2 can't be sent with MDCX as the osmo-bts code + * complains about both rtp and rtp payload2 being present in the + * same package! + */ + trans->conn->mncc_rtp_connect_pending = 1; + return rsl_ipacc_mdcx(lchan, rtp->ip, rtp->port, 0); +} + +static int tch_rtp_signal(struct gsm_lchan *lchan, int signal) +{ + struct gsm_network *net; + struct gsm_trans *tmp, *trans = NULL; + + net = lchan->ts->trx->bts->network; + llist_for_each_entry(tmp, &net->trans_list, entry) { + if (!tmp->conn) + continue; + if (tmp->conn->lchan != lchan && tmp->conn->ho_lchan != lchan) + continue; + trans = tmp; + break; + } + + if (!trans) { + LOGP(DMNCC, LOGL_ERROR, "%s IPA abis signal but no transaction.\n", + gsm_lchan_name(lchan)); + return 0; + } + + switch (signal) { + case S_ABISIP_CRCX_ACK: + if (lchan->conn->mncc_rtp_create_pending) { + lchan->conn->mncc_rtp_create_pending = 0; + LOGP(DMNCC, LOGL_NOTICE, "%s sending pending RTP create ind.\n", + gsm_lchan_name(lchan)); + mncc_recv_rtp_sock(net, trans, MNCC_RTP_CREATE); + } + /* + * TODO: this appears to be too early? Why not until after + * the handover detect or the handover complete? + */ + maybe_switch_for_handover(lchan); + break; + case S_ABISIP_MDCX_ACK: + if (lchan->conn->mncc_rtp_connect_pending) { + lchan->conn->mncc_rtp_connect_pending = 0; + LOGP(DMNCC, LOGL_NOTICE, "%s sending pending RTP connect ind.\n", + gsm_lchan_name(lchan)); + mncc_recv_rtp_sock(net, trans, MNCC_RTP_CONNECT); + } + break; + } + + return 0; +} + + +static struct downstate { + uint32_t states; + int type; + int (*rout) (struct gsm_trans *trans, void *arg); +} downstatelist[] = { + /* mobile originating call establishment */ + {SBIT(GSM_CSTATE_INITIATED), /* 5.2.1.2 */ + MNCC_CALL_PROC_REQ, gsm48_cc_tx_call_proc}, + {SBIT(GSM_CSTATE_INITIATED) | SBIT(GSM_CSTATE_MO_CALL_PROC), /* 5.2.1.2 | 5.2.1.5 */ + MNCC_ALERT_REQ, gsm48_cc_tx_alerting}, + {SBIT(GSM_CSTATE_INITIATED) | SBIT(GSM_CSTATE_MO_CALL_PROC) | SBIT(GSM_CSTATE_CALL_DELIVERED), /* 5.2.1.2 | 5.2.1.6 | 5.2.1.6 */ + MNCC_SETUP_RSP, gsm48_cc_tx_connect}, + {SBIT(GSM_CSTATE_MO_CALL_PROC), /* 5.2.1.4.2 */ + MNCC_PROGRESS_REQ, gsm48_cc_tx_progress}, + /* mobile terminating call establishment */ + {SBIT(GSM_CSTATE_NULL), /* 5.2.2.1 */ + MNCC_SETUP_REQ, gsm48_cc_tx_setup}, + {SBIT(GSM_CSTATE_CONNECT_REQUEST), + MNCC_SETUP_COMPL_REQ, gsm48_cc_tx_connect_ack}, + /* signalling during call */ + {SBIT(GSM_CSTATE_ACTIVE), + MNCC_NOTIFY_REQ, gsm48_cc_tx_notify}, + {ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ), + MNCC_FACILITY_REQ, gsm48_cc_tx_facility}, + {ALL_STATES, + MNCC_START_DTMF_RSP, gsm48_cc_tx_start_dtmf_ack}, + {ALL_STATES, + MNCC_START_DTMF_REJ, gsm48_cc_tx_start_dtmf_rej}, + {ALL_STATES, + MNCC_STOP_DTMF_RSP, gsm48_cc_tx_stop_dtmf_ack}, + {SBIT(GSM_CSTATE_ACTIVE), + MNCC_HOLD_CNF, gsm48_cc_tx_hold_ack}, + {SBIT(GSM_CSTATE_ACTIVE), + MNCC_HOLD_REJ, gsm48_cc_tx_hold_rej}, + {SBIT(GSM_CSTATE_ACTIVE), + MNCC_RETRIEVE_CNF, gsm48_cc_tx_retrieve_ack}, + {SBIT(GSM_CSTATE_ACTIVE), + MNCC_RETRIEVE_REJ, gsm48_cc_tx_retrieve_rej}, + {SBIT(GSM_CSTATE_ACTIVE), + MNCC_MODIFY_REQ, gsm48_cc_tx_modify}, + {SBIT(GSM_CSTATE_MO_ORIG_MODIFY), + MNCC_MODIFY_RSP, gsm48_cc_tx_modify_complete}, + {SBIT(GSM_CSTATE_MO_ORIG_MODIFY), + MNCC_MODIFY_REJ, gsm48_cc_tx_modify_reject}, + {SBIT(GSM_CSTATE_ACTIVE), + MNCC_USERINFO_REQ, gsm48_cc_tx_userinfo}, + /* clearing */ + {SBIT(GSM_CSTATE_INITIATED), + MNCC_REJ_REQ, gsm48_cc_tx_release_compl}, + {ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_DISCONNECT_IND) - SBIT(GSM_CSTATE_RELEASE_REQ) - SBIT(GSM_CSTATE_DISCONNECT_REQ), /* 5.4.4 */ + MNCC_DISC_REQ, gsm48_cc_tx_disconnect}, + {ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ), /* 5.4.3.2 */ + MNCC_REL_REQ, gsm48_cc_tx_release}, + /* special */ + {ALL_STATES, + MNCC_LCHAN_MODIFY, _gsm48_lchan_modify}, +}; + +#define DOWNSLLEN \ + (sizeof(downstatelist) / sizeof(struct downstate)) + + +int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg) +{ + int i, rc = 0; + struct gsm_trans *trans = NULL, *transt; + struct gsm_subscriber_connection *conn = NULL; + struct gsm_bts *bts = NULL; + struct gsm_mncc *data = arg, rel; + + DEBUGP(DMNCC, "receive message %s\n", get_mncc_name(msg_type)); + + /* handle special messages */ + switch(msg_type) { + case MNCC_BRIDGE: + rc = tch_bridge(net, arg); + if (rc < 0) + disconnect_bridge(net, arg, -rc); + return rc; + case MNCC_FRAME_DROP: + return tch_recv_mncc(net, data->callref, 0); + case MNCC_FRAME_RECV: + return tch_recv_mncc(net, data->callref, 1); + case MNCC_RTP_CREATE: + return tch_rtp_create(net, data->callref); + case MNCC_RTP_CONNECT: + return tch_rtp_connect(net, arg); + case MNCC_RTP_FREE: + /* unused right now */ + return -EIO; + case GSM_TCHF_FRAME: + case GSM_TCHF_FRAME_EFR: + case GSM_TCHH_FRAME: + case GSM_TCH_FRAME_AMR: + /* Find callref */ + trans = trans_find_by_callref(net, data->callref); + if (!trans) { + LOGP(DMNCC, LOGL_ERROR, "TCH frame for non-existing trans\n"); + return -EIO; + } + log_set_context(LOG_CTX_VLR_SUBSCR, trans->subscr); + if (!trans->conn) { + LOGP(DMNCC, LOGL_NOTICE, "TCH frame for trans without conn\n"); + return 0; + } + if (!trans->conn->lchan) { + LOGP(DMNCC, LOGL_NOTICE, "TCH frame for trans without lchan\n"); + return 0; + } + if (trans->conn->lchan->type != GSM_LCHAN_TCH_F + && trans->conn->lchan->type != GSM_LCHAN_TCH_H) { + /* This should be LOGL_ERROR or NOTICE, but + * unfortuantely it happens for a couple of frames at + * the beginning of every RTP connection */ + LOGP(DMNCC, LOGL_DEBUG, "TCH frame for lchan != TCH_F/TCH_H\n"); + return 0; + } + bts = trans->conn->lchan->ts->trx->bts; + switch (bts->type) { + case GSM_BTS_TYPE_NANOBTS: + case GSM_BTS_TYPE_OSMOBTS: + if (!trans->conn->lchan->abis_ip.rtp_socket) { + DEBUGP(DMNCC, "TCH frame to lchan without RTP connection\n"); + return 0; + } + return rtp_send_frame(trans->conn->lchan->abis_ip.rtp_socket, arg); + case GSM_BTS_TYPE_BS11: + case GSM_BTS_TYPE_RBS2000: + case GSM_BTS_TYPE_NOKIA_SITE: + return trau_send_frame(trans->conn->lchan, arg); + default: + LOGP(DCC, LOGL_ERROR, "Unknown BTS type %u\n", bts->type); + } + return -EINVAL; + } + + memset(&rel, 0, sizeof(struct gsm_mncc)); + rel.callref = data->callref; + + /* Find callref */ + trans = trans_find_by_callref(net, data->callref); + + /* Callref unknown */ + if (!trans) { + struct gsm_subscriber *subscr; + + if (msg_type != MNCC_SETUP_REQ) { + DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) " + "Received '%s' from MNCC with " + "unknown callref %d\n", data->called.number, + get_mncc_name(msg_type), data->callref); + /* Invalid call reference */ + return mncc_release_ind(net, NULL, data->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_INVAL_TRANS_ID); + } + if (!data->called.number[0] && !data->imsi[0]) { + DEBUGP(DCC, "(bts - trx - ts - ti) " + "Received '%s' from MNCC with " + "no number or IMSI\n", get_mncc_name(msg_type)); + /* Invalid number */ + return mncc_release_ind(net, NULL, data->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_INV_NR_FORMAT); + } + /* New transaction due to setup, find subscriber */ + if (data->called.number[0]) + subscr = subscr_get_by_extension(net->subscr_group, + data->called.number); + else + subscr = subscr_get_by_imsi(net->subscr_group, + data->imsi); + + /* update the subscriber we deal with */ + log_set_context(LOG_CTX_VLR_SUBSCR, subscr); + + /* If subscriber is not found */ + if (!subscr) { + DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) " + "Received '%s' from MNCC with " + "unknown subscriber %s\n", data->called.number, + get_mncc_name(msg_type), data->called.number); + /* Unknown subscriber */ + return mncc_release_ind(net, NULL, data->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_UNASSIGNED_NR); + } + /* If subscriber is not "attached" */ + if (!subscr->lac) { + DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) " + "Received '%s' from MNCC with " + "detached subscriber %s\n", data->called.number, + get_mncc_name(msg_type), data->called.number); + subscr_put(subscr); + /* Temporarily out of order */ + return mncc_release_ind(net, NULL, data->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_DEST_OOO); + } + /* Create transaction */ + trans = trans_alloc(net, subscr, GSM48_PDISC_CC, 0xff, data->callref); + if (!trans) { + DEBUGP(DCC, "No memory for trans.\n"); + subscr_put(subscr); + /* Ressource unavailable */ + mncc_release_ind(net, NULL, data->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_RESOURCE_UNAVAIL); + return -ENOMEM; + } + /* Find lchan */ + conn = connection_for_subscr(subscr); + + /* If subscriber has no lchan */ + if (!conn) { + /* find transaction with this subscriber already paging */ + llist_for_each_entry(transt, &net->trans_list, entry) { + /* Transaction of our lchan? */ + if (transt == trans || + transt->subscr != subscr) + continue; + DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) " + "Received '%s' from MNCC with " + "unallocated channel, paging already " + "started for lac %d.\n", + data->called.number, + get_mncc_name(msg_type), subscr->lac); + subscr_put(subscr); + trans_free(trans); + return 0; + } + /* store setup informations until paging was successfull */ + memcpy(&trans->cc.msg, data, sizeof(struct gsm_mncc)); + + /* Request a channel */ + trans->paging_request = subscr_request_channel(subscr, + RSL_CHANNEED_TCH_F, setup_trig_pag_evt, + trans); + if (!trans->paging_request) { + LOGP(DCC, LOGL_ERROR, "Failed to allocate paging token.\n"); + subscr_put(subscr); + trans_free(trans); + return 0; + } + subscr_put(subscr); + return 0; + } + /* Assign lchan */ + trans->conn = conn; + subscr_put(subscr); + } else { + /* update the subscriber we deal with */ + log_set_context(LOG_CTX_VLR_SUBSCR, trans->subscr); + } + + if (trans->conn) + conn = trans->conn; + + /* if paging did not respond yet */ + if (!conn) { + DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) " + "Received '%s' from MNCC in paging state\n", + (trans->subscr)?(trans->subscr->extension):"-", + get_mncc_name(msg_type)); + mncc_set_cause(&rel, GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_NORM_CALL_CLEAR); + if (msg_type == MNCC_REL_REQ) + rc = mncc_recvmsg(net, trans, MNCC_REL_CNF, &rel); + else + rc = mncc_recvmsg(net, trans, MNCC_REL_IND, &rel); + trans->callref = 0; + trans_free(trans); + return rc; + } + + DEBUGP(DCC, "(bts %d trx %d ts %d ti %02x sub %s) " + "Received '%s' from MNCC in state %d (%s)\n", + conn->bts->nr, conn->lchan->ts->trx->nr, conn->lchan->ts->nr, + trans->transaction_id, + (trans->conn->subscr)?(trans->conn->subscr->extension):"-", + get_mncc_name(msg_type), trans->cc.state, + gsm48_cc_state_name(trans->cc.state)); + + /* Find function for current state and message */ + for (i = 0; i < DOWNSLLEN; i++) + if ((msg_type == downstatelist[i].type) + && ((1 << trans->cc.state) & downstatelist[i].states)) + break; + if (i == DOWNSLLEN) { + DEBUGP(DCC, "Message unhandled at this state.\n"); + return 0; + } + + rc = downstatelist[i].rout(trans, arg); + + return rc; +} + + +static struct datastate { + uint32_t states; + int type; + int (*rout) (struct gsm_trans *trans, struct msgb *msg); +} datastatelist[] = { + /* mobile originating call establishment */ + {SBIT(GSM_CSTATE_NULL), /* 5.2.1.2 */ + GSM48_MT_CC_SETUP, gsm48_cc_rx_setup}, + {SBIT(GSM_CSTATE_NULL), /* 5.2.1.2 */ + GSM48_MT_CC_EMERG_SETUP, gsm48_cc_rx_setup}, + {SBIT(GSM_CSTATE_CONNECT_IND), /* 5.2.1.2 */ + GSM48_MT_CC_CONNECT_ACK, gsm48_cc_rx_connect_ack}, + /* mobile terminating call establishment */ + {SBIT(GSM_CSTATE_CALL_PRESENT), /* 5.2.2.3.2 */ + GSM48_MT_CC_CALL_CONF, gsm48_cc_rx_call_conf}, + {SBIT(GSM_CSTATE_CALL_PRESENT) | SBIT(GSM_CSTATE_MO_TERM_CALL_CONF), /* ???? | 5.2.2.3.2 */ + GSM48_MT_CC_ALERTING, gsm48_cc_rx_alerting}, + {SBIT(GSM_CSTATE_CALL_PRESENT) | SBIT(GSM_CSTATE_MO_TERM_CALL_CONF) | SBIT(GSM_CSTATE_CALL_RECEIVED), /* (5.2.2.6) | 5.2.2.6 | 5.2.2.6 */ + GSM48_MT_CC_CONNECT, gsm48_cc_rx_connect}, + /* signalling during call */ + {ALL_STATES - SBIT(GSM_CSTATE_NULL), + GSM48_MT_CC_FACILITY, gsm48_cc_rx_facility}, + {SBIT(GSM_CSTATE_ACTIVE), + GSM48_MT_CC_NOTIFY, gsm48_cc_rx_notify}, + {ALL_STATES, + GSM48_MT_CC_START_DTMF, gsm48_cc_rx_start_dtmf}, + {ALL_STATES, + GSM48_MT_CC_STOP_DTMF, gsm48_cc_rx_stop_dtmf}, + {ALL_STATES, + GSM48_MT_CC_STATUS_ENQ, gsm48_cc_rx_status_enq}, + {SBIT(GSM_CSTATE_ACTIVE), + GSM48_MT_CC_HOLD, gsm48_cc_rx_hold}, + {SBIT(GSM_CSTATE_ACTIVE), + GSM48_MT_CC_RETR, gsm48_cc_rx_retrieve}, + {SBIT(GSM_CSTATE_ACTIVE), + GSM48_MT_CC_MODIFY, gsm48_cc_rx_modify}, + {SBIT(GSM_CSTATE_MO_TERM_MODIFY), + GSM48_MT_CC_MODIFY_COMPL, gsm48_cc_rx_modify_complete}, + {SBIT(GSM_CSTATE_MO_TERM_MODIFY), + GSM48_MT_CC_MODIFY_REJECT, gsm48_cc_rx_modify_reject}, + {SBIT(GSM_CSTATE_ACTIVE), + GSM48_MT_CC_USER_INFO, gsm48_cc_rx_userinfo}, + /* clearing */ + {ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ), /* 5.4.3.2 */ + GSM48_MT_CC_DISCONNECT, gsm48_cc_rx_disconnect}, + {ALL_STATES - SBIT(GSM_CSTATE_NULL), /* 5.4.4.1.2.2 */ + GSM48_MT_CC_RELEASE, gsm48_cc_rx_release}, + {ALL_STATES, /* 5.4.3.4 */ + GSM48_MT_CC_RELEASE_COMPL, gsm48_cc_rx_release_compl}, +}; + +#define DATASLLEN \ + (sizeof(datastatelist) / sizeof(struct datastate)) + +static int gsm0408_rcv_cc(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + uint8_t msg_type = gsm48_hdr_msg_type(gh); + uint8_t transaction_id = gsm48_hdr_trans_id_flip_ti(gh); + struct gsm_trans *trans = NULL; + int i, rc = 0; + + if (msg_type & 0x80) { + DEBUGP(DCC, "MSG 0x%2x not defined for PD error\n", msg_type); + return -EINVAL; + } + + if (!conn->subscr) { + LOGP(DCC, LOGL_ERROR, "Invalid conn, no subscriber\n"); + return -EINVAL; + } + + /* Find transaction */ + trans = trans_find_by_id(conn, GSM48_PDISC_CC, transaction_id); + + DEBUGP(DCC, "(bts %d trx %d ts %d ti %x sub %s) " + "Received '%s' from MS in state %d (%s)\n", + conn->bts->nr, conn->lchan->ts->trx->nr, conn->lchan->ts->nr, + transaction_id, (conn->subscr)?(conn->subscr->extension):"-", + gsm48_cc_msg_name(msg_type), trans?(trans->cc.state):0, + gsm48_cc_state_name(trans?(trans->cc.state):0)); + + /* Create transaction */ + if (!trans) { + DEBUGP(DCC, "Unknown transaction ID %x, " + "creating new trans.\n", transaction_id); + /* Create transaction */ + trans = trans_alloc(conn->network, conn->subscr, + GSM48_PDISC_CC, + transaction_id, new_callref++); + if (!trans) { + DEBUGP(DCC, "No memory for trans.\n"); + rc = gsm48_tx_simple(conn, + GSM48_PDISC_CC | (transaction_id << 4), + GSM48_MT_CC_RELEASE_COMPL); + return -ENOMEM; + } + /* Assign transaction */ + trans->conn = conn; + } + + /* find function for current state and message */ + for (i = 0; i < DATASLLEN; i++) + if ((msg_type == datastatelist[i].type) + && ((1 << trans->cc.state) & datastatelist[i].states)) + break; + if (i == DATASLLEN) { + DEBUGP(DCC, "Message unhandled at this state.\n"); + return 0; + } + + assert(trans->subscr); + + rc = datastatelist[i].rout(trans, msg); + + return rc; +} + +/* Create a dummy to wait five seconds */ +static void release_anchor(struct gsm_subscriber_connection *conn) +{ + if (!conn->anch_operation) + return; + + osmo_timer_del(&conn->anch_operation->timeout); + talloc_free(conn->anch_operation); + conn->anch_operation = NULL; +} + +static void anchor_timeout(void *_data) +{ + struct gsm_subscriber_connection *con = _data; + + release_anchor(con); + msc_release_connection(con); +} + +int gsm0408_new_conn(struct gsm_subscriber_connection *conn) +{ + conn->anch_operation = talloc_zero(conn, struct gsm_anchor_operation); + if (!conn->anch_operation) + return -1; + + osmo_timer_setup(&conn->anch_operation->timeout, anchor_timeout, conn); + osmo_timer_schedule(&conn->anch_operation->timeout, 5, 0); + return 0; +} + +struct gsm_subscriber_connection *msc_subscr_con_allocate(struct gsm_network *network) +{ + struct gsm_subscriber_connection *conn; + + conn = talloc_zero(network, struct gsm_subscriber_connection); + if (!conn) + return NULL; + + conn->network = network; + llist_add_tail(&conn->entry, &network->subscr_conns); + return conn; +} + +void msc_subscr_con_free(struct gsm_subscriber_connection *conn) +{ + if (!conn) + return; + + if (conn->subscr) { + subscr_put(conn->subscr); + conn->subscr = NULL; + } + + llist_del(&conn->entry); + talloc_free(conn); +} + +/* Main entry point for GSM 04.08/44.008 Layer 3 data (e.g. from the BSC). */ +int gsm0408_dispatch(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + uint8_t pdisc = gsm48_hdr_pdisc(gh); + int rc = 0; + + OSMO_ASSERT(conn); + OSMO_ASSERT(msg); + + LOGP(DRLL, LOGL_DEBUG, "Dispatching 04.08 message, pdisc=%d\n", pdisc); +#if 0 + if (silent_call_reroute(conn, msg)) + return silent_call_rx(conn, msg); +#endif + + switch (pdisc) { + case GSM48_PDISC_CC: + release_anchor(conn); + rc = gsm0408_rcv_cc(conn, msg); + break; + case GSM48_PDISC_MM: + rc = gsm0408_rcv_mm(conn, msg); + break; + case GSM48_PDISC_RR: + rc = gsm0408_rcv_rr(conn, msg); + break; + case GSM48_PDISC_SMS: + release_anchor(conn); + rc = gsm0411_rcv_sms(conn, msg); + break; + case GSM48_PDISC_MM_GPRS: + case GSM48_PDISC_SM_GPRS: + LOGP(DRLL, LOGL_NOTICE, "Unimplemented " + "GSM 04.08 discriminator 0x%02x\n", pdisc); + rc = -ENOTSUP; + break; + case GSM48_PDISC_NC_SS: + release_anchor(conn); + rc = handle_rcv_ussd(conn, msg); + break; + default: + LOGP(DRLL, LOGL_NOTICE, "Unknown " + "GSM 04.08 discriminator 0x%02x\n", pdisc); + rc = -EINVAL; + break; + } + + return rc; +} + +/* + * This will be run by the linker when loading the DSO. We use it to + * do system initialization, e.g. registration of signal handlers. + */ +static __attribute__((constructor)) void on_dso_load_0408(void) +{ + osmo_signal_register_handler(SS_ABISIP, handle_abisip_signal, NULL); +} diff --git a/src/libmsc/gsm_04_11.c b/src/libmsc/gsm_04_11.c new file mode 100644 index 000000000..aa2030f80 --- /dev/null +++ b/src/libmsc/gsm_04_11.c @@ -0,0 +1,1070 @@ +/* Point-to-Point (PP) Short Message Service (SMS) + * Support on Mobile Radio Interface + * 3GPP TS 04.11 version 7.1.0 Release 1998 / ETSI TS 100 942 V7.1.0 */ + +/* (C) 2008 by Daniel Willmann <daniel@totalueberwachung.de> + * (C) 2009 by Harald Welte <laforge@gnumonks.org> + * (C) 2010-2012 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2010 by On-Waves + * (C) 2011 by Andreas Eversberg <jolly@eversberg.eu> + * + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <time.h> +#include <netinet/in.h> + +#include "bscconfig.h" + +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> + +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/gsm0411_utils.h> +#include <osmocom/gsm/protocol/gsm_04_11.h> + +#include <openbsc/debug.h> +#include <openbsc/gsm_data.h> +#include <openbsc/db.h> +#include <openbsc/gsm_subscriber.h> +#include <openbsc/gsm_04_08.h> +#include <openbsc/abis_rsl.h> +#include <openbsc/signal.h> +#include <openbsc/db.h> +#include <openbsc/transaction.h> +#include <openbsc/paging.h> +#include <openbsc/bsc_rll.h> +#include <openbsc/chan_alloc.h> +#include <openbsc/bsc_api.h> + +#ifdef BUILD_SMPP +#include "smpp_smsc.h" +#endif + +void *tall_gsms_ctx; +static uint32_t new_callref = 0x40000001; + + +struct gsm_sms *sms_alloc(void) +{ + return talloc_zero(tall_gsms_ctx, struct gsm_sms); +} + +void sms_free(struct gsm_sms *sms) +{ + /* drop references to subscriber structure */ + if (sms->receiver) + subscr_put(sms->receiver); +#ifdef BUILD_SMPP + if (sms->smpp.esme) + smpp_esme_put(sms->smpp.esme); +#endif + + talloc_free(sms); +} + +struct gsm_sms *sms_from_text(struct gsm_subscriber *receiver, + struct gsm_subscriber *sender, + int dcs, const char *text) +{ + struct gsm_sms *sms = sms_alloc(); + + if (!sms) + return NULL; + + sms->receiver = subscr_get(receiver); + osmo_strlcpy(sms->text, text, sizeof(sms->text)); + + osmo_strlcpy(sms->src.addr, sender->extension, sizeof(sms->src.addr)); + sms->reply_path_req = 0; + sms->status_rep_req = 0; + sms->ud_hdr_ind = 0; + sms->protocol_id = 0; /* implicit */ + sms->data_coding_scheme = dcs; + osmo_strlcpy(sms->dst.addr, receiver->extension, sizeof(sms->dst.addr)); + /* Generate user_data */ + sms->user_data_len = gsm_7bit_encode_n(sms->user_data, sizeof(sms->user_data), + sms->text, NULL); + + return sms; +} + + +static void send_signal(int sig_no, + struct gsm_trans *trans, + struct gsm_sms *sms, + int paging_result) +{ + struct sms_signal_data sig; + sig.trans = trans; + sig.sms = sms; + sig.paging_result = paging_result; + osmo_signal_dispatch(SS_SMS, sig_no, &sig); +} + +static int gsm411_sendmsg(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + DEBUGP(DLSMS, "GSM4.11 TX %s\n", osmo_hexdump(msg->data, msg->len)); + msg->l3h = msg->data; + return gsm0808_submit_dtap(conn, msg, UM_SAPI_SMS, 1); +} + +/* Prefix msg with a 04.08/04.11 CP header */ +static int gsm411_cp_sendmsg(struct msgb *msg, struct gsm_trans *trans, + uint8_t msg_type) +{ + struct gsm48_hdr *gh; + + gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh)); + /* Outgoing needs the highest bit set */ + gh->proto_discr = trans->protocol | (trans->transaction_id<<4); + gh->msg_type = msg_type; + + DEBUGP(DLSMS, "sending CP message (trans=%x)\n", trans->transaction_id); + + return gsm411_sendmsg(trans->conn, msg); +} + +/* mm_send: receive MMCCSMS sap message from SMC */ +static int gsm411_mm_send(struct gsm411_smc_inst *inst, int msg_type, + struct msgb *msg, int cp_msg_type) +{ + struct gsm_trans *trans = + container_of(inst, struct gsm_trans, sms.smc_inst); + int rc = 0; + + switch (msg_type) { + case GSM411_MMSMS_EST_REQ: + /* recycle msg */ + rc = gsm411_smc_recv(inst, GSM411_MMSMS_EST_CNF, msg, 0); + msgb_free(msg); /* upper layer does not free msg */ + break; + case GSM411_MMSMS_DATA_REQ: + rc = gsm411_cp_sendmsg(msg, trans, cp_msg_type); + break; + case GSM411_MMSMS_REL_REQ: + DEBUGP(DLSMS, "Got MMSMS_REL_REQ, destroying transaction.\n"); + msgb_free(msg); + trans_free(trans); + break; + default: + LOGP(DLSMS, LOGL_NOTICE, "Unhandled MMCCSMS msg 0x%x\n", msg_type); + msgb_free(msg); + rc = -EINVAL; + } + + return rc; +} + +/* mm_send: receive MNCCSMS sap message from SMR */ +int gsm411_mn_send(struct gsm411_smr_inst *inst, int msg_type, + struct msgb *msg) +{ + struct gsm_trans *trans = + container_of(inst, struct gsm_trans, sms.smr_inst); + + /* forward to SMC */ + return gsm411_smc_send(&trans->sms.smc_inst, msg_type, msg); +} + +static int gsm340_rx_sms_submit(struct msgb *msg, struct gsm_sms *gsms) +{ + if (db_sms_store(gsms) != 0) { + LOGP(DLSMS, LOGL_ERROR, "Failed to store SMS in Database\n"); + return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER; + } + /* dispatch a signal to tell higher level about it */ + send_signal(S_SMS_SUBMITTED, NULL, gsms, 0); + + return 0; +} + +/* generate a TPDU address field compliant with 03.40 sec. 9.1.2.5 */ +static int gsm340_gen_oa_sub(uint8_t *oa, unsigned int oa_len, + const struct gsm_sms_addr *src) +{ + /* network specific, private numbering plan */ + return gsm340_gen_oa(oa, oa_len, src->ton, src->npi, src->addr); +} + +/* generate a msgb containing an 03.40 9.2.2.1 SMS-DELIVER TPDU derived from + * struct gsm_sms, returns total size of TPDU */ +static int gsm340_gen_sms_deliver_tpdu(struct msgb *msg, struct gsm_sms *sms) +{ + uint8_t *smsp; + uint8_t oa[12]; /* max len per 03.40 */ + uint8_t oa_len = 0; + uint8_t octet_len; + unsigned int old_msg_len = msg->len; + + /* generate first octet with masked bits */ + smsp = msgb_put(msg, 1); + /* TP-MTI (message type indicator) */ + *smsp = GSM340_SMS_DELIVER_SC2MS; + /* TP-MMS (more messages to send) */ + if (0 /* FIXME */) + *smsp |= 0x04; + /* TP-SRI(deliver)/SRR(submit) */ + if (sms->status_rep_req) + *smsp |= 0x20; + /* TP-UDHI (indicating TP-UD contains a header) */ + if (sms->ud_hdr_ind) + *smsp |= 0x40; + + /* generate originator address */ + oa_len = gsm340_gen_oa_sub(oa, sizeof(oa), &sms->src); + smsp = msgb_put(msg, oa_len); + memcpy(smsp, oa, oa_len); + + /* generate TP-PID */ + smsp = msgb_put(msg, 1); + *smsp = sms->protocol_id; + + /* generate TP-DCS */ + smsp = msgb_put(msg, 1); + *smsp = sms->data_coding_scheme; + + /* generate TP-SCTS */ + smsp = msgb_put(msg, 7); + gsm340_gen_scts(smsp, time(NULL)); + + /* generate TP-UDL */ + smsp = msgb_put(msg, 1); + *smsp = sms->user_data_len; + + /* generate TP-UD */ + switch (gsm338_get_sms_alphabet(sms->data_coding_scheme)) { + case DCS_7BIT_DEFAULT: + octet_len = sms->user_data_len*7/8; + if (sms->user_data_len*7%8 != 0) + octet_len++; + /* Warning, user_data_len indicates the amount of septets + * (characters), we need amount of octets occupied */ + smsp = msgb_put(msg, octet_len); + memcpy(smsp, sms->user_data, octet_len); + break; + case DCS_UCS2: + case DCS_8BIT_DATA: + smsp = msgb_put(msg, sms->user_data_len); + memcpy(smsp, sms->user_data, sms->user_data_len); + break; + default: + LOGP(DLSMS, LOGL_NOTICE, "Unhandled Data Coding Scheme: 0x%02X\n", + sms->data_coding_scheme); + break; + } + + return msg->len - old_msg_len; +} + +int sms_route_mt_sms(struct gsm_subscriber_connection *conn, struct msgb *msg, + struct gsm_sms *gsms, uint8_t sms_mti, bool *deferred) +{ + int rc; + +#ifdef BUILD_SMPP + int smpp_first = smpp_route_smpp_first(gsms, conn); + + /* + * Route through SMPP first before going to the local database. In case + * of a unroutable message and no local subscriber, SMPP will be tried + * twice. In case of an unknown subscriber continue with the normal + * delivery of the SMS. + */ + if (smpp_first) { + rc = smpp_try_deliver(gsms, conn, deferred); + if (rc == GSM411_RP_CAUSE_MO_NUM_UNASSIGNED) + goto try_local; + if (rc < 0) { + LOGP(DLSMS, LOGL_ERROR, "%s: SMS delivery error: %d.", + subscr_name(conn->subscr), rc); + rc = GSM411_RP_CAUSE_MO_TEMP_FAIL; + /* rc will be logged by gsm411_send_rp_error() */ + rate_ctr_inc(&conn->bts->network->msc_ctrs->ctr[ + MSC_CTR_SMS_DELIVER_UNKNOWN_ERROR]); + } + return rc; + } + +try_local: +#endif + + /* determine gsms->receiver based on dialled number */ + gsms->receiver = subscr_get_by_extension(conn->network->subscr_group, + gsms->dst.addr); + if (!gsms->receiver) { +#ifdef BUILD_SMPP + /* Avoid a second look-up */ + if (smpp_first) { + rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_SMS_NO_RECEIVER]); + return GSM411_RP_CAUSE_MO_NUM_UNASSIGNED; + } + + rc = smpp_try_deliver(gsms, conn, deferred); + if (rc == GSM411_RP_CAUSE_MO_NUM_UNASSIGNED) { + rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_SMS_NO_RECEIVER]); + } else if (rc < 0) { + LOGP(DLSMS, LOGL_ERROR, "%s: SMS delivery error: %d.", + subscr_name(conn->subscr), rc); + rc = GSM411_RP_CAUSE_MO_TEMP_FAIL; + /* rc will be logged by gsm411_send_rp_error() */ + rate_ctr_inc(&conn->bts->network->msc_ctrs->ctr[ + MSC_CTR_SMS_DELIVER_UNKNOWN_ERROR]); + } +#else + rc = GSM411_RP_CAUSE_MO_NUM_UNASSIGNED; + rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_SMS_NO_RECEIVER]); +#endif + return rc; + } + + switch (sms_mti) { + case GSM340_SMS_SUBMIT_MS2SC: + /* MS is submitting a SMS */ + rc = gsm340_rx_sms_submit(msg, gsms); + break; + case GSM340_SMS_COMMAND_MS2SC: + case GSM340_SMS_DELIVER_REP_MS2SC: + LOGP(DLSMS, LOGL_NOTICE, "Unimplemented MTI 0x%02x\n", sms_mti); + rc = GSM411_RP_CAUSE_IE_NOTEXIST; + break; + default: + LOGP(DLSMS, LOGL_NOTICE, "Undefined MTI 0x%02x\n", sms_mti); + rc = GSM411_RP_CAUSE_IE_NOTEXIST; + break; + } + + if (!rc && !gsms->receiver) + rc = GSM411_RP_CAUSE_MO_NUM_UNASSIGNED; + + return rc; +} + + +/* process an incoming TPDU (called from RP-DATA) + * return value > 0: RP CAUSE for ERROR; < 0: silent error; 0 = success */ +static int gsm340_rx_tpdu(struct gsm_trans *trans, struct msgb *msg, + uint32_t gsm411_msg_ref, bool *deferred) +{ + struct gsm_subscriber_connection *conn = trans->conn; + uint8_t *smsp = msgb_sms(msg); + struct gsm_sms *gsms; + unsigned int sms_alphabet; + uint8_t sms_mti, sms_vpf; + uint8_t *sms_vp; + uint8_t da_len_bytes; + uint8_t address_lv[12]; /* according to 03.40 / 9.1.2.5 */ + int rc = 0; + + rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_SMS_SUBMITTED]); + + gsms = sms_alloc(); + if (!gsms) + return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER; + + /* invert those fields where 0 means active/present */ + sms_mti = *smsp & 0x03; + sms_vpf = (*smsp & 0x18) >> 3; + gsms->status_rep_req = (*smsp & 0x20); + gsms->ud_hdr_ind = (*smsp & 0x40); + /* + * Not evaluating MMS (More Messages to Send) because the + * lchan stays open anyway. + * Not evaluating RP (Reply Path) because we're not aware of its + * benefits. + */ + + smsp++; + gsms->msg_ref = *smsp++; + + gsms->gsm411.transaction_id = trans->transaction_id; + gsms->gsm411.msg_ref = gsm411_msg_ref; + + /* length in bytes of the destination address */ + da_len_bytes = 2 + *smsp/2 + *smsp%2; + if (da_len_bytes > 12) { + LOGP(DLSMS, LOGL_ERROR, "Destination Address > 12 bytes ?!?\n"); + rc = GSM411_RP_CAUSE_SEMANT_INC_MSG; + goto out; + } else if (da_len_bytes < 4) { + LOGP(DLSMS, LOGL_ERROR, "Destination Address < 4 bytes ?!?\n"); + rc = GSM411_RP_CAUSE_SEMANT_INC_MSG; + goto out; + } + memset(address_lv, 0, sizeof(address_lv)); + memcpy(address_lv, smsp, da_len_bytes); + /* mangle first byte to reflect length in bytes, not digits */ + address_lv[0] = da_len_bytes - 1; + + gsms->dst.ton = (address_lv[1] >> 4) & 7; + gsms->dst.npi = address_lv[1] & 0xF; + /* convert to real number */ + gsm48_decode_bcd_number(gsms->dst.addr, + sizeof(gsms->dst.addr), address_lv, 1); + smsp += da_len_bytes; + + gsms->protocol_id = *smsp++; + gsms->data_coding_scheme = *smsp++; + + sms_alphabet = gsm338_get_sms_alphabet(gsms->data_coding_scheme); + if (sms_alphabet == 0xffffffff) { + rc = GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER; + goto out; + } + + switch (sms_vpf) { + case GSM340_TP_VPF_RELATIVE: + sms_vp = smsp++; + break; + case GSM340_TP_VPF_ABSOLUTE: + case GSM340_TP_VPF_ENHANCED: + sms_vp = smsp; + /* the additional functionality indicator... */ + if (sms_vpf == GSM340_TP_VPF_ENHANCED && *smsp & (1<<7)) + smsp++; + smsp += 7; + break; + case GSM340_TP_VPF_NONE: + sms_vp = 0; + break; + default: + LOGP(DLSMS, LOGL_NOTICE, + "SMS Validity period not implemented: 0x%02x\n", sms_vpf); + rc = GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER; + goto out; + } + gsms->user_data_len = *smsp++; + if (gsms->user_data_len) { + memcpy(gsms->user_data, smsp, gsms->user_data_len); + + switch (sms_alphabet) { + case DCS_7BIT_DEFAULT: + gsm_7bit_decode_n(gsms->text, sizeof(gsms->text), smsp, + gsms->user_data_len); + break; + case DCS_8BIT_DATA: + case DCS_UCS2: + case DCS_NONE: + break; + } + } + + osmo_strlcpy(gsms->src.addr, conn->subscr->extension, + sizeof(gsms->src.addr)); + + LOGP(DLSMS, LOGL_INFO, "RX SMS: Sender: %s, MTI: 0x%02x, VPF: 0x%02x, " + "MR: 0x%02x PID: 0x%02x, DCS: 0x%02x, DA: %s, " + "UserDataLength: 0x%02x, UserData: \"%s\"\n", + subscr_name(conn->subscr), sms_mti, sms_vpf, gsms->msg_ref, + gsms->protocol_id, gsms->data_coding_scheme, gsms->dst.addr, + gsms->user_data_len, + sms_alphabet == DCS_7BIT_DEFAULT ? gsms->text : + osmo_hexdump(gsms->user_data, gsms->user_data_len)); + + gsms->validity_minutes = gsm340_validity_period(sms_vpf, sms_vp); + + /* FIXME: This looks very wrong */ + send_signal(0, NULL, gsms, 0); + + rc = sms_route_mt_sms(conn, msg, gsms, sms_mti, deferred); +out: + if (!deferred) + sms_free(gsms); + + return rc; +} + +/* Prefix msg with a RP-DATA header and send as SMR DATA */ +static int gsm411_rp_sendmsg(struct gsm411_smr_inst *inst, struct msgb *msg, + uint8_t rp_msg_type, uint8_t rp_msg_ref, + int rl_msg_type) +{ + struct gsm411_rp_hdr *rp; + uint8_t len = msg->len; + + /* GSM 04.11 RP-DATA header */ + rp = (struct gsm411_rp_hdr *)msgb_push(msg, sizeof(*rp)); + rp->len = len + 2; + rp->msg_type = rp_msg_type; + rp->msg_ref = rp_msg_ref; + + return gsm411_smr_send(inst, rl_msg_type, msg); +} + +int gsm411_send_rp_ack(struct gsm_trans *trans, uint8_t msg_ref) +{ + struct msgb *msg = gsm411_msgb_alloc(); + + DEBUGP(DLSMS, "TX: SMS RP ACK\n"); + + return gsm411_rp_sendmsg(&trans->sms.smr_inst, msg, GSM411_MT_RP_ACK_MT, + msg_ref, GSM411_SM_RL_REPORT_REQ); +} + +int gsm411_send_rp_error(struct gsm_trans *trans, uint8_t msg_ref, + uint8_t cause) +{ + struct msgb *msg = gsm411_msgb_alloc(); + + msgb_tv_put(msg, 1, cause); + + LOGP(DLSMS, LOGL_NOTICE, "TX: SMS RP ERROR, cause %d (%s)\n", cause, + get_value_string(gsm411_rp_cause_strs, cause)); + + return gsm411_rp_sendmsg(&trans->sms.smr_inst, msg, + GSM411_MT_RP_ERROR_MT, msg_ref, GSM411_SM_RL_REPORT_REQ); +} + +/* Receive a 04.11 TPDU inside RP-DATA / user data */ +static int gsm411_rx_rp_ud(struct msgb *msg, struct gsm_trans *trans, + struct gsm411_rp_hdr *rph, + uint8_t src_len, uint8_t *src, + uint8_t dst_len, uint8_t *dst, + uint8_t tpdu_len, uint8_t *tpdu) +{ + bool deferred = false; + int rc = 0; + + if (src_len && src) + LOGP(DLSMS, LOGL_ERROR, "RP-DATA (MO) with SRC ?!?\n"); + + if (!dst_len || !dst || !tpdu_len || !tpdu) { + LOGP(DLSMS, LOGL_ERROR, + "RP-DATA (MO) without DST or TPDU ?!?\n"); + gsm411_send_rp_error(trans, rph->msg_ref, + GSM411_RP_CAUSE_INV_MAND_INF); + return -EIO; + } + msg->l4h = tpdu; + + DEBUGP(DLSMS, "DST(%u,%s)\n", dst_len, osmo_hexdump(dst, dst_len)); + + rc = gsm340_rx_tpdu(trans, msg, rph->msg_ref, &deferred); + if (rc == 0 && !deferred) + return gsm411_send_rp_ack(trans, rph->msg_ref); + else if (rc > 0) + return gsm411_send_rp_error(trans, rph->msg_ref, rc); + else + return rc; +} + +/* Receive a 04.11 RP-DATA message in accordance with Section 7.3.1.2 */ +static int gsm411_rx_rp_data(struct msgb *msg, struct gsm_trans *trans, + struct gsm411_rp_hdr *rph) +{ + uint8_t src_len, dst_len, rpud_len; + uint8_t *src = NULL, *dst = NULL , *rp_ud = NULL; + + /* in the MO case, this should always be zero length */ + src_len = rph->data[0]; + if (src_len) + src = &rph->data[1]; + + dst_len = rph->data[1+src_len]; + if (dst_len) + dst = &rph->data[1+src_len+1]; + + rpud_len = rph->data[1+src_len+1+dst_len]; + if (rpud_len) + rp_ud = &rph->data[1+src_len+1+dst_len+1]; + + DEBUGP(DLSMS, "RX_RP-DATA: src_len=%u, dst_len=%u ud_len=%u\n", + src_len, dst_len, rpud_len); + return gsm411_rx_rp_ud(msg, trans, rph, src_len, src, dst_len, dst, + rpud_len, rp_ud); +} + +/* Receive a 04.11 RP-ACK message (response to RP-DATA from us) */ +static int gsm411_rx_rp_ack(struct msgb *msg, struct gsm_trans *trans, + struct gsm411_rp_hdr *rph) +{ + struct gsm_sms *sms = trans->sms.sms; + + /* Acnkowledgement to MT RP_DATA, i.e. the MS confirms it + * successfully received a SMS. We can now safely mark it as + * transmitted */ + + if (!sms) { + LOGP(DLSMS, LOGL_ERROR, "RX RP-ACK but no sms in transaction?!?\n"); + return gsm411_send_rp_error(trans, rph->msg_ref, + GSM411_RP_CAUSE_PROTOCOL_ERR); + } + + /* mark this SMS as sent in database */ + db_sms_mark_delivered(sms); + + send_signal(S_SMS_DELIVERED, trans, sms, 0); + + sms_free(sms); + trans->sms.sms = NULL; + + return 0; +} + +static int gsm411_rx_rp_error(struct msgb *msg, struct gsm_trans *trans, + struct gsm411_rp_hdr *rph) +{ + struct gsm_network *net = trans->conn->network; + struct gsm_sms *sms = trans->sms.sms; + uint8_t cause_len = rph->data[0]; + uint8_t cause = rph->data[1]; + + /* Error in response to MT RP_DATA, i.e. the MS did not + * successfully receive the SMS. We need to investigate + * the cause and take action depending on it */ + + LOGP(DLSMS, LOGL_NOTICE, "%s: RX SMS RP-ERROR, cause %d:%d (%s)\n", + subscr_name(trans->conn->subscr), cause_len, cause, + get_value_string(gsm411_rp_cause_strs, cause)); + + if (!sms) { + LOGP(DLSMS, LOGL_ERROR, + "RX RP-ERR, but no sms in transaction?!?\n"); + return -EINVAL; +#if 0 + return gsm411_send_rp_error(trans, rph->msg_ref, + GSM411_RP_CAUSE_PROTOCOL_ERR); +#endif + } + + if (cause == GSM411_RP_CAUSE_MT_MEM_EXCEEDED) { + /* MS has not enough memory to store the message. We need + * to store this in our database and wait for a SMMA message */ + /* FIXME */ + send_signal(S_SMS_MEM_EXCEEDED, trans, sms, 0); + rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_SMS_RP_ERR_MEM]); + } else { + send_signal(S_SMS_UNKNOWN_ERROR, trans, sms, 0); + rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_SMS_RP_ERR_OTHER]); + } + + sms_free(sms); + trans->sms.sms = NULL; + + return 0; +} + +static int gsm411_rx_rp_smma(struct msgb *msg, struct gsm_trans *trans, + struct gsm411_rp_hdr *rph) +{ + int rc; + + rc = gsm411_send_rp_ack(trans, rph->msg_ref); + + /* MS tells us that it has memory for more SMS, we need + * to check if we have any pending messages for it and then + * transfer those */ + send_signal(S_SMS_SMMA, trans, NULL, 0); + + return rc; +} + +/* receive RL DATA */ +static int gsm411_rx_rl_data(struct msgb *msg, struct gsm48_hdr *gh, + struct gsm_trans *trans) +{ + struct gsm411_rp_hdr *rp_data = (struct gsm411_rp_hdr*)&gh->data; + uint8_t msg_type = rp_data->msg_type & 0x07; + int rc = 0; + + switch (msg_type) { + case GSM411_MT_RP_DATA_MO: + DEBUGP(DLSMS, "RX SMS RP-DATA (MO)\n"); + rc = gsm411_rx_rp_data(msg, trans, rp_data); + break; + case GSM411_MT_RP_SMMA_MO: + DEBUGP(DLSMS, "RX SMS RP-SMMA\n"); + rc = gsm411_rx_rp_smma(msg, trans, rp_data); + break; + default: + LOGP(DLSMS, LOGL_NOTICE, "Invalid RP type 0x%02x\n", msg_type); + rc = -EINVAL; + break; + } + + return rc; +} + +/* receive RL REPORT */ +static int gsm411_rx_rl_report(struct msgb *msg, struct gsm48_hdr *gh, + struct gsm_trans *trans) +{ + struct gsm411_rp_hdr *rp_data = (struct gsm411_rp_hdr*)&gh->data; + uint8_t msg_type = rp_data->msg_type & 0x07; + int rc = 0; + + switch (msg_type) { + case GSM411_MT_RP_ACK_MO: + DEBUGP(DLSMS, "RX SMS RP-ACK (MO)\n"); + rc = gsm411_rx_rp_ack(msg, trans, rp_data); + break; + case GSM411_MT_RP_ERROR_MO: + DEBUGP(DLSMS, "RX SMS RP-ERROR (MO)\n"); + rc = gsm411_rx_rp_error(msg, trans, rp_data); + break; + default: + LOGP(DLSMS, LOGL_NOTICE, "Invalid RP type 0x%02x\n", msg_type); + rc = -EINVAL; + break; + } + + return rc; +} + +/* receive SM-RL sap message from SMR + * NOTE: Message is freed by sender + */ +int gsm411_rl_recv(struct gsm411_smr_inst *inst, int msg_type, + struct msgb *msg) +{ + struct gsm_trans *trans = + container_of(inst, struct gsm_trans, sms.smr_inst); + struct gsm48_hdr *gh = msgb_l3(msg); + int rc = 0; + + switch (msg_type) { + case GSM411_SM_RL_DATA_IND: + rc = gsm411_rx_rl_data(msg, gh, trans); + break; + case GSM411_SM_RL_REPORT_IND: + if (gh) + rc = gsm411_rx_rl_report(msg, gh, trans); + break; + default: + LOGP(DLSMS, LOGL_NOTICE, "Unhandled SM-RL message 0x%x\n", msg_type); + rc = -EINVAL; + } + + return rc; +} + +/* receive MNCCSMS sap message from SMC + * NOTE: Message is freed by sender + */ +static int gsm411_mn_recv(struct gsm411_smc_inst *inst, int msg_type, + struct msgb *msg) +{ + struct gsm_trans *trans = + container_of(inst, struct gsm_trans, sms.smc_inst); + struct gsm48_hdr *gh = msgb_l3(msg); + int rc = 0; + + switch (msg_type) { + case GSM411_MNSMS_EST_IND: + case GSM411_MNSMS_DATA_IND: + DEBUGP(DLSMS, "MNSMS-DATA/EST-IND\n"); + rc = gsm411_smr_recv(&trans->sms.smr_inst, msg_type, msg); + break; + case GSM411_MNSMS_ERROR_IND: + if (gh) + DEBUGP(DLSMS, "MNSMS-ERROR-IND, cause %d (%s)\n", + gh->data[0], + get_value_string(gsm411_cp_cause_strs, + gh->data[0])); + else + DEBUGP(DLSMS, "MNSMS-ERROR-IND, no cause\n"); + rc = gsm411_smr_recv(&trans->sms.smr_inst, msg_type, msg); + break; + default: + LOGP(DLSMS, LOGL_NOTICE, "Unhandled MNCCSMS msg 0x%x\n", msg_type); + rc = -EINVAL; + } + + return rc; +} + +/* Entry point for incoming GSM48_PDISC_SMS from abis_rsl.c */ +int gsm0411_rcv_sms(struct gsm_subscriber_connection *conn, + struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + uint8_t msg_type = gh->msg_type; + uint8_t transaction_id = gsm48_hdr_trans_id_flip_ti(gh); + struct gsm_trans *trans; + int new_trans = 0; + int rc = 0; + + if (!conn->subscr) + return -EIO; + /* FIXME: send some error message */ + + DEBUGP(DLSMS, "receiving data (trans_id=%x)\n", transaction_id); + trans = trans_find_by_id(conn, GSM48_PDISC_SMS, transaction_id); + + /* + * A transaction we created but don't know about? + */ + if (!trans && (transaction_id & 0x8) == 0) { + LOGP(DLSMS, LOGL_ERROR, "trans_id=%x allocated by us but known " + "to us anymore. We are ignoring it, maybe a CP-ERROR " + "from a MS?\n", + transaction_id); + return -EINVAL; + } + + if (!trans) { + DEBUGP(DLSMS, " -> (new transaction)\n"); + trans = trans_alloc(conn->network, conn->subscr, + GSM48_PDISC_SMS, + transaction_id, new_callref++); + if (!trans) { + DEBUGP(DLSMS, " -> No memory for trans\n"); + /* FIXME: send some error message */ + return -ENOMEM; + } + gsm411_smc_init(&trans->sms.smc_inst, 0, 1, + gsm411_mn_recv, gsm411_mm_send); + gsm411_smr_init(&trans->sms.smr_inst, 0, 1, + gsm411_rl_recv, gsm411_mn_send); + + trans->conn = conn; + + new_trans = 1; + } + + /* 5.4: For MO, if a CP-DATA is received for a new + * transaction, equals reception of an implicit + * last CP-ACK for previous transaction */ + if (trans->sms.smc_inst.cp_state == GSM411_CPS_IDLE + && msg_type == GSM411_MT_CP_DATA) { + int i; + struct gsm_trans *ptrans; + + /* Scan through all remote initiated transactions */ + for (i=8; i<15; i++) { + if (i == transaction_id) + continue; + + ptrans = trans_find_by_id(conn, GSM48_PDISC_SMS, i); + if (!ptrans) + continue; + + DEBUGP(DLSMS, "Implicit CP-ACK for trans_id=%x\n", i); + + /* Finish it for good */ + trans_free(ptrans); + } + } + + gsm411_smc_recv(&trans->sms.smc_inst, + (new_trans) ? GSM411_MMSMS_EST_IND : GSM411_MMSMS_DATA_IND, + msg, msg_type); + + return rc; +} + +/* Take a SMS in gsm_sms structure and send it through an already + * existing lchan. We also assume that the caller ensured this lchan already + * has a SAPI3 RLL connection! */ +int gsm411_send_sms(struct gsm_subscriber_connection *conn, struct gsm_sms *sms) +{ + struct msgb *msg = gsm411_msgb_alloc(); + struct gsm_trans *trans; + uint8_t *data, *rp_ud_len; + uint8_t msg_ref = sms_next_rp_msg_ref(&conn->next_rp_ref); + int transaction_id; + int rc; + + transaction_id = + trans_assign_trans_id(conn->network, conn->subscr, + GSM48_PDISC_SMS, 0); + if (transaction_id == -1) { + LOGP(DLSMS, LOGL_ERROR, "No available transaction ids\n"); + send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, 0); + sms_free(sms); + msgb_free(msg); + return -EBUSY; + } + + DEBUGP(DLSMS, "%s()\n", __func__); + + /* FIXME: allocate transaction with message reference */ + trans = trans_alloc(conn->network, conn->subscr, + GSM48_PDISC_SMS, + transaction_id, new_callref++); + if (!trans) { + LOGP(DLSMS, LOGL_ERROR, "No memory for trans\n"); + send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, 0); + sms_free(sms); + msgb_free(msg); + /* FIXME: send some error message */ + return -ENOMEM; + } + gsm411_smc_init(&trans->sms.smc_inst, sms->id, 1, + gsm411_mn_recv, gsm411_mm_send); + gsm411_smr_init(&trans->sms.smr_inst, sms->id, 1, + gsm411_rl_recv, gsm411_mn_send); + trans->sms.sms = sms; + + trans->conn = conn; + + /* Hardcode SMSC Originating Address for now */ + data = (uint8_t *)msgb_put(msg, 8); + data[0] = 0x07; /* originator length == 7 */ + data[1] = 0x91; /* type of number: international, ISDN */ + data[2] = 0x44; /* 447785016005 */ + data[3] = 0x77; + data[4] = 0x58; + data[5] = 0x10; + data[6] = 0x06; + data[7] = 0x50; + + /* Hardcoded Destination Address */ + data = (uint8_t *)msgb_put(msg, 1); + data[0] = 0; /* destination length == 0 */ + + /* obtain a pointer for the rp_ud_len, so we can fill it later */ + rp_ud_len = (uint8_t *)msgb_put(msg, 1); + + /* generate the 03.40 SMS-DELIVER TPDU */ + rc = gsm340_gen_sms_deliver_tpdu(msg, sms); + if (rc < 0) { + send_signal(S_SMS_UNKNOWN_ERROR, trans, sms, 0); + sms_free(sms); + trans->sms.sms = NULL; + trans_free(trans); + msgb_free(msg); + return rc; + } + + *rp_ud_len = rc; + + DEBUGP(DLSMS, "TX: SMS DELIVER\n"); + + rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_SMS_DELIVERED]); + db_sms_inc_deliver_attempts(trans->sms.sms); + + return gsm411_rp_sendmsg(&trans->sms.smr_inst, msg, + GSM411_MT_RP_DATA_MT, msg_ref, GSM411_SM_RL_DATA_REQ); +} + +/* paging callback. Here we get called if paging a subscriber has + * succeeded or failed. */ +static int paging_cb_send_sms(unsigned int hooknum, unsigned int event, + struct msgb *msg, void *_conn, void *_sms) +{ + struct gsm_subscriber_connection *conn = _conn; + struct gsm_sms *sms = _sms; + int rc = 0; + + DEBUGP(DLSMS, "paging_cb_send_sms(hooknum=%u, event=%u, msg=%p," + "conn=%p, sms=%p/id: %llu)\n", hooknum, event, msg, conn, sms, sms->id); + + if (hooknum != GSM_HOOK_RR_PAGING) + return -EINVAL; + + switch (event) { + case GSM_PAGING_SUCCEEDED: + gsm411_send_sms(conn, sms); + break; + case GSM_PAGING_EXPIRED: + case GSM_PAGING_OOM: + case GSM_PAGING_BUSY: + send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, event); + sms_free(sms); + rc = -ETIMEDOUT; + break; + default: + LOGP(DLSMS, LOGL_ERROR, "Unhandled paging event: %d\n", event); + } + + return rc; +} + +/* high-level function to send a SMS to a given subscriber. The function + * will take care of paging the subscriber, establishing the RLL SAPI3 + * connection, etc. */ +int gsm411_send_sms_subscr(struct gsm_subscriber *subscr, + struct gsm_sms *sms) +{ + struct gsm_subscriber_connection *conn; + void *res; + + /* check if we already have an open lchan to the subscriber. + * if yes, send the SMS this way */ + conn = connection_for_subscr(subscr); + if (conn) { + LOGP(DLSMS, LOGL_DEBUG, "Sending SMS via already open connection %p to %s\n", + conn, subscr_name(subscr)); + return gsm411_send_sms(conn, sms); + } + + /* if not, we have to start paging */ + LOGP(DLSMS, LOGL_DEBUG, "Sending SMS: no connection open, start paging %s\n", + subscr_name(subscr)); + res = subscr_request_channel(subscr, RSL_CHANNEED_SDCCH, + paging_cb_send_sms, sms); + if (!res) { + send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, GSM_PAGING_BUSY); + sms_free(sms); + } + return 0; +} + +void _gsm411_sms_trans_free(struct gsm_trans *trans) +{ + /* cleanup SMS instance */ + gsm411_smr_clear(&trans->sms.smr_inst); + trans->sms.smr_inst.rl_recv = NULL; + trans->sms.smr_inst.mn_send = NULL; + + gsm411_smc_clear(&trans->sms.smc_inst); + trans->sms.smc_inst.mn_recv = NULL; + trans->sms.smc_inst.mm_send = NULL; + + if (trans->sms.sms) { + LOGP(DLSMS, LOGL_ERROR, "Transaction contains SMS.\n"); + send_signal(S_SMS_UNKNOWN_ERROR, trans, trans->sms.sms, 0); + sms_free(trans->sms.sms); + trans->sms.sms = NULL; + } +} + +void gsm411_sapi_n_reject(struct gsm_subscriber_connection *conn) +{ + struct gsm_network *net; + struct gsm_trans *trans, *tmp; + + net = conn->network; + + llist_for_each_entry_safe(trans, tmp, &net->trans_list, entry) { + struct gsm_sms *sms; + + if (trans->conn != conn) + continue; + if (trans->protocol != GSM48_PDISC_SMS) + continue; + + sms = trans->sms.sms; + if (!sms) { + LOGP(DLSMS, LOGL_ERROR, "SAPI Reject but no SMS.\n"); + continue; + } + + send_signal(S_SMS_UNKNOWN_ERROR, trans, sms, 0); + sms_free(sms); + trans->sms.sms = NULL; + trans_free(trans); + } +} + diff --git a/src/libmsc/gsm_04_80.c b/src/libmsc/gsm_04_80.c new file mode 100644 index 000000000..479d6fbd2 --- /dev/null +++ b/src/libmsc/gsm_04_80.c @@ -0,0 +1,155 @@ +/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface + * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */ + +/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org> + * (C) 2008, 2009, 2010 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009 by Mike Haben <michael.haben@btinternet.com> + * + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include <openbsc/debug.h> +#include <openbsc/gsm_data.h> +#include <openbsc/gsm_04_08.h> +#include <openbsc/gsm_04_80.h> +#include <openbsc/bsc_api.h> + +#include <osmocom/gsm/gsm0480.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/core/msgb.h> +#include <osmocom/gsm/tlv.h> + +static inline unsigned char *msgb_wrap_with_TL(struct msgb *msgb, uint8_t tag) +{ + uint8_t *data = msgb_push(msgb, 2); + + data[0] = tag; + data[1] = msgb->len - 2; + return data; +} + +static inline unsigned char *msgb_push_TLV1(struct msgb *msgb, uint8_t tag, + uint8_t value) +{ + uint8_t *data = msgb_push(msgb, 3); + + data[0] = tag; + data[1] = 1; + data[2] = value; + return data; +} + + +/* Send response to a mobile-originated ProcessUnstructuredSS-Request */ +int gsm0480_send_ussd_response(struct gsm_subscriber_connection *conn, + const struct msgb *in_msg, const char *response_text, + const struct ss_request *req) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 USSD RSP"); + struct gsm48_hdr *gh; + uint8_t *ptr8; + int response_len; + + /* First put the payload text into the message */ + ptr8 = msgb_put(msg, 0); + gsm_7bit_encode_n_ussd(ptr8, msgb_tailroom(msg), response_text, &response_len); + msgb_put(msg, response_len); + + /* Then wrap it as an Octet String */ + msgb_wrap_with_TL(msg, ASN1_OCTET_STRING_TAG); + + /* Pre-pend the DCS octet string */ + msgb_push_TLV1(msg, ASN1_OCTET_STRING_TAG, 0x0F); + + /* Then wrap these as a Sequence */ + msgb_wrap_with_TL(msg, GSM_0480_SEQUENCE_TAG); + + /* Pre-pend the operation code */ + msgb_push_TLV1(msg, GSM0480_OPERATION_CODE, + GSM0480_OP_CODE_PROCESS_USS_REQ); + + /* Wrap the operation code and IA5 string as a sequence */ + msgb_wrap_with_TL(msg, GSM_0480_SEQUENCE_TAG); + + /* Pre-pend the invoke ID */ + msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, req->invoke_id); + + /* Wrap this up as a Return Result component */ + msgb_wrap_with_TL(msg, GSM0480_CTYPE_RETURN_RESULT); + + /* Wrap the component in a Facility message */ + msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY); + + /* And finally pre-pend the L3 header */ + gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_NC_SS | req->transaction_id + | (1<<7); /* TI direction = 1 */ + gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE; + + return gsm0808_submit_dtap(conn, msg, 0, 0); +} + +int gsm0480_send_ussd_reject(struct gsm_subscriber_connection *conn, + const struct msgb *in_msg, + const struct ss_request *req) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 USSD REJ"); + struct gsm48_hdr *gh; + + /* First insert the problem code */ + msgb_push_TLV1(msg, GSM_0480_PROBLEM_CODE_TAG_GENERAL, + GSM_0480_GEN_PROB_CODE_UNRECOGNISED); + + /* Before it insert the invoke ID */ + msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, req->invoke_id); + + /* Wrap this up as a Reject component */ + msgb_wrap_with_TL(msg, GSM0480_CTYPE_REJECT); + + /* Wrap the component in a Facility message */ + msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY); + + /* And finally pre-pend the L3 header */ + gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_NC_SS; + gh->proto_discr |= req->transaction_id | (1<<7); /* TI direction = 1 */ + gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE; + + return gsm0808_submit_dtap(conn, msg, 0, 0); +} + +int msc_send_ussd_notify(struct gsm_subscriber_connection *conn, int level, const char *text) +{ + struct msgb *msg = gsm0480_create_ussd_notify(level, text); + if (!msg) + return -1; + return gsm0808_submit_dtap(conn, msg, 0, 0); +} + +int msc_send_ussd_release_complete(struct gsm_subscriber_connection *conn) +{ + struct msgb *msg = gsm0480_create_ussd_release_complete(); + if (!msg) + return -1; + return gsm0808_submit_dtap(conn, msg, 0, 0); +} diff --git a/src/libmsc/gsm_subscriber.c b/src/libmsc/gsm_subscriber.c new file mode 100644 index 000000000..1a03cf76e --- /dev/null +++ b/src/libmsc/gsm_subscriber.c @@ -0,0 +1,422 @@ +/* The concept of a subscriber for the MSC, roughly HLR/VLR functionality */ + +/* (C) 2008 by Harald Welte <laforge@gnumonks.org> + * (C) 2009,2013 by Holger Hans Peter Freyther <zecke@selfish.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 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 <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> +#include <time.h> +#include <stdbool.h> + +#include <osmocom/core/talloc.h> + +#include <osmocom/vty/vty.h> + +#include <openbsc/gsm_subscriber.h> +#include <openbsc/gsm_04_08.h> +#include <openbsc/debug.h> +#include <openbsc/paging.h> +#include <openbsc/signal.h> +#include <openbsc/db.h> +#include <openbsc/chan_alloc.h> + +void *tall_sub_req_ctx; + +extern struct llist_head *subscr_bsc_active_subscribers(void); + +int gsm48_secure_channel(struct gsm_subscriber_connection *conn, int key_seq, + gsm_cbfn *cb, void *cb_data); + + +/* + * Struct for pending channel requests. This is managed in the + * llist_head requests of each subscriber. The reference counting + * should work in such a way that a subscriber with a pending request + * remains in memory. + */ +struct subscr_request { + struct llist_head entry; + + /* the callback data */ + gsm_cbfn *cbfn; + void *param; +}; + +static struct gsm_subscriber *get_subscriber(struct gsm_subscriber_group *sgrp, + int type, const char *ident) +{ + struct gsm_subscriber *subscr = db_get_subscriber(type, ident); + if (subscr) + subscr->group = sgrp; + return subscr; +} + +/* + * We got the channel assigned and can now hand this channel + * over to one of our callbacks. + */ +static int subscr_paging_dispatch(unsigned int hooknum, unsigned int event, + struct msgb *msg, void *data, void *param) +{ + struct subscr_request *request, *tmp; + struct gsm_subscriber_connection *conn = data; + struct gsm_subscriber *subscr = param; + struct paging_signal_data sig_data; + struct bsc_subscr *bsub; + struct gsm_network *net; + + OSMO_ASSERT(subscr && subscr->is_paging); + net = subscr->group->net; + + /* + * Stop paging on all other BTS. E.g. if this is + * the first timeout on a BTS then the others will + * timeout soon as well. Let's just stop everything + * and forget we wanted to page. + */ + + /* TODO MSC split -- creating a BSC subscriber directly from MSC data + * structures in RAM. At some point the MSC will send a message to the + * BSC instead. */ + bsub = bsc_subscr_find_or_create_by_imsi(net->bsc_subscribers, + subscr->imsi); + bsub->tmsi = subscr->tmsi; + bsub->lac = subscr->lac; + paging_request_stop(&net->bts_list, NULL, bsub, NULL, NULL); + bsc_subscr_put(bsub); + + /* Inform parts of the system we don't know */ + sig_data.subscr = subscr; + sig_data.bts = conn ? conn->bts : NULL; + sig_data.conn = conn; + sig_data.paging_result = event; + osmo_signal_dispatch( + SS_PAGING, + event == GSM_PAGING_SUCCEEDED ? + S_PAGING_SUCCEEDED : S_PAGING_EXPIRED, + &sig_data + ); + + llist_for_each_entry_safe(request, tmp, &subscr->requests, entry) { + llist_del(&request->entry); + request->cbfn(hooknum, event, msg, data, request->param); + talloc_free(request); + } + + /* balanced with the moment we start paging */ + subscr->is_paging = 0; + subscr_put(subscr); + return 0; +} + +static int subscr_paging_sec_cb(unsigned int hooknum, unsigned int event, + struct msgb *msg, void *data, void *param) +{ + int rc; + + switch (event) { + case GSM_SECURITY_AUTH_FAILED: + /* Dispatch as paging failure */ + rc = subscr_paging_dispatch( + GSM_HOOK_RR_PAGING, GSM_PAGING_EXPIRED, + msg, data, param); + break; + + case GSM_SECURITY_NOAVAIL: + case GSM_SECURITY_SUCCEEDED: + /* Dispatch as paging failure */ + rc = subscr_paging_dispatch( + GSM_HOOK_RR_PAGING, GSM_PAGING_SUCCEEDED, + msg, data, param); + break; + + default: + rc = -EINVAL; + } + + return rc; +} + +static int subscr_paging_cb(unsigned int hooknum, unsigned int event, + struct msgb *msg, void *data, void *param) +{ + struct gsm_subscriber_connection *conn = data; + struct gsm48_hdr *gh; + struct gsm48_pag_resp *pr; + + /* Other cases mean problem, dispatch direclty */ + if (event != GSM_PAGING_SUCCEEDED) + return subscr_paging_dispatch(hooknum, event, msg, data, param); + + /* Get paging response */ + gh = msgb_l3(msg); + pr = (struct gsm48_pag_resp *)gh->data; + + /* We _really_ have a channel, secure it now ! */ + return gsm48_secure_channel(conn, pr->key_seq, subscr_paging_sec_cb, param); +} + +struct subscr_request *subscr_request_channel(struct gsm_subscriber *subscr, + int channel_type, gsm_cbfn *cbfn, void *param) +{ + int rc; + struct subscr_request *request; + struct bsc_subscr *bsub; + struct gsm_network *net = subscr->group->net; + + /* Start paging.. we know it is async so we can do it before */ + if (!subscr->is_paging) { + LOGP(DMM, LOGL_DEBUG, "Subscriber %s not paged yet.\n", + subscr_name(subscr)); + /* TODO MSC split -- creating a BSC subscriber directly from + * MSC data structures in RAM. At some point the MSC will send + * a message to the BSC instead. */ + bsub = bsc_subscr_find_or_create_by_imsi(net->bsc_subscribers, + subscr->imsi); + bsub->tmsi = subscr->tmsi; + bsub->lac = subscr->lac; + rc = paging_request(net, bsub, channel_type, subscr_paging_cb, + subscr); + bsc_subscr_put(bsub); + if (rc <= 0) { + LOGP(DMM, LOGL_ERROR, "Subscriber %s paging failed: %d\n", + subscr_name(subscr), rc); + return NULL; + } + /* reduced on the first paging callback */ + subscr_get(subscr); + subscr->is_paging = 1; + } + + /* TODO: Stop paging in case of memory allocation failure */ + request = talloc_zero(subscr, struct subscr_request); + if (!request) + return NULL; + + request->cbfn = cbfn; + request->param = param; + llist_add_tail(&request->entry, &subscr->requests); + return request; +} + +void subscr_remove_request(struct subscr_request *request) +{ + llist_del(&request->entry); + talloc_free(request); +} + +struct gsm_subscriber *subscr_create_subscriber(struct gsm_subscriber_group *sgrp, + const char *imsi) +{ + struct gsm_subscriber *subscr = db_create_subscriber(imsi, + sgrp->net->ext_min, + sgrp->net->ext_max, + sgrp->net->auto_assign_exten); + if (subscr) + subscr->group = sgrp; + return subscr; +} + +struct gsm_subscriber *subscr_get_by_tmsi(struct gsm_subscriber_group *sgrp, + uint32_t tmsi) +{ + char tmsi_string[14]; + struct gsm_subscriber *subscr; + + /* we might have a record in memory already */ + llist_for_each_entry(subscr, subscr_bsc_active_subscribers(), entry) { + if (tmsi == subscr->tmsi) + return subscr_get(subscr); + } + + sprintf(tmsi_string, "%u", tmsi); + return get_subscriber(sgrp, GSM_SUBSCRIBER_TMSI, tmsi_string); +} + +struct gsm_subscriber *subscr_get_by_imsi(struct gsm_subscriber_group *sgrp, + const char *imsi) +{ + struct gsm_subscriber *subscr; + + llist_for_each_entry(subscr, subscr_bsc_active_subscribers(), entry) { + if (strcmp(subscr->imsi, imsi) == 0) + return subscr_get(subscr); + } + + return get_subscriber(sgrp, GSM_SUBSCRIBER_IMSI, imsi); +} + +struct gsm_subscriber *subscr_get_by_extension(struct gsm_subscriber_group *sgrp, + const char *ext) +{ + struct gsm_subscriber *subscr; + + llist_for_each_entry(subscr, subscr_bsc_active_subscribers(), entry) { + if (strcmp(subscr->extension, ext) == 0) + return subscr_get(subscr); + } + + return get_subscriber(sgrp, GSM_SUBSCRIBER_EXTENSION, ext); +} + +struct gsm_subscriber *subscr_get_by_id(struct gsm_subscriber_group *sgrp, + unsigned long long id) +{ + struct gsm_subscriber *subscr; + char buf[32]; + sprintf(buf, "%llu", id); + + llist_for_each_entry(subscr, subscr_bsc_active_subscribers(), entry) { + if (subscr->id == id) + return subscr_get(subscr); + } + + return get_subscriber(sgrp, GSM_SUBSCRIBER_ID, buf); +} + +int subscr_update_expire_lu(struct gsm_subscriber *s, struct gsm_bts *bts) +{ + int rc; + + if (!s) { + LOGP(DMM, LOGL_ERROR, "LU Expiration but NULL subscriber\n"); + return -1; + } + if (!bts) { + LOGP(DMM, LOGL_ERROR, "%s: LU Expiration but NULL bts\n", + subscr_name(s)); + return -1; + } + + /* Table 10.5.33: The T3212 timeout value field is coded as the + * binary representation of the timeout value for + * periodic updating in decihours. Mark the subscriber as + * inactive if it missed two consecutive location updates. + * Timeout is twice the t3212 value plus one minute */ + + /* Is expiration handling enabled? */ + if (bts->si_common.chan_desc.t3212 == 0) + s->expire_lu = GSM_SUBSCRIBER_NO_EXPIRATION; + else + s->expire_lu = time(NULL) + + (bts->si_common.chan_desc.t3212 * 60 * 6 * 2) + 60; + + rc = db_sync_subscriber(s); + db_subscriber_update(s); + return rc; +} + +int subscr_update(struct gsm_subscriber *s, struct gsm_bts *bts, int reason) +{ + int rc; + + /* FIXME: Migrate pending requests from one BSC to another */ + switch (reason) { + case GSM_SUBSCRIBER_UPDATE_ATTACHED: + s->group = bts->network->subscr_group; + /* Indicate "attached to LAC" */ + s->lac = bts->location_area_code; + + LOGP(DMM, LOGL_INFO, "Subscriber %s ATTACHED LAC=%u\n", + subscr_name(s), s->lac); + + /* + * The below will set a new expire_lu but as a side-effect + * the new lac will be saved in the database. + */ + rc = subscr_update_expire_lu(s, bts); + osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_ATTACHED, s); + break; + case GSM_SUBSCRIBER_UPDATE_DETACHED: + /* Only detach if we are currently in this area */ + if (bts->location_area_code == s->lac) + s->lac = GSM_LAC_RESERVED_DETACHED; + LOGP(DMM, LOGL_INFO, "Subscriber %s DETACHED\n", subscr_name(s)); + rc = db_sync_subscriber(s); + db_subscriber_update(s); + osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_DETACHED, s); + break; + default: + fprintf(stderr, "subscr_update with unknown reason: %d\n", + reason); + rc = db_sync_subscriber(s); + db_subscriber_update(s); + break; + }; + + return rc; +} + +void subscr_update_from_db(struct gsm_subscriber *sub) +{ + db_subscriber_update(sub); +} + +static void subscr_expire_callback(void *data, long long unsigned int id) +{ + struct gsm_network *net = data; + struct gsm_subscriber *s = subscr_get_by_id(net->subscr_group, id); + struct gsm_subscriber_connection *conn = connection_for_subscr(s); + + /* + * The subscriber is active and the phone stopped the timer. As + * we don't want to periodically update the database for active + * subscribers we will just do it when the subscriber was selected + * for expiration. This way on the next around another subscriber + * will be selected. + */ + if (conn && conn->expire_timer_stopped) { + LOGP(DMM, LOGL_DEBUG, "Not expiring subscriber %s (ID %llu)\n", + subscr_name(s), id); + subscr_update_expire_lu(s, conn->bts); + subscr_put(s); + return; + } + + + LOGP(DMM, LOGL_NOTICE, "Expiring inactive subscriber %s (ID %llu)\n", + subscr_name(s), id); + s->lac = GSM_LAC_RESERVED_DETACHED; + db_sync_subscriber(s); + + subscr_put(s); +} + +void subscr_expire(struct gsm_subscriber_group *sgrp) +{ + db_subscriber_expire(sgrp->net, subscr_expire_callback); +} + +struct gsm_subscriber_connection *connection_for_subscr(struct gsm_subscriber *subscr) +{ + /* FIXME: replace this with a backpointer in gsm_subscriber? */ + struct gsm_network *net = subscr->group->net; + struct gsm_subscriber_connection *conn; + + llist_for_each_entry(conn, &net->subscr_conns, entry) { + if (conn->subscr == subscr) + return conn; + } + + return NULL; +} diff --git a/src/libmsc/meas_feed.c b/src/libmsc/meas_feed.c new file mode 100644 index 000000000..3ddcdc39c --- /dev/null +++ b/src/libmsc/meas_feed.c @@ -0,0 +1,167 @@ +/* UDP-Feed of measurement reports */ + +#include <unistd.h> + +#include <sys/socket.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/write_queue.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> + +#include <osmocom/vty/command.h> +#include <osmocom/vty/vty.h> + +#include <openbsc/meas_rep.h> +#include <openbsc/signal.h> +#include <openbsc/gsm_subscriber.h> +#include <openbsc/meas_feed.h> +#include <openbsc/vty.h> + +#include "meas_feed.h" + +struct meas_feed_state { + struct osmo_wqueue wqueue; + char scenario[31+1]; + char *dst_host; + uint16_t dst_port; +}; + + +static struct meas_feed_state g_mfs; + +static int process_meas_rep(struct gsm_meas_rep *mr) +{ + struct msgb *msg; + struct meas_feed_meas *mfm; + struct gsm_subscriber *subscr; + + /* ignore measurements as long as we don't know who it is */ + if (!mr->lchan || !mr->lchan->conn || !mr->lchan->conn->subscr) + return 0; + + subscr = mr->lchan->conn->subscr; + + msg = msgb_alloc(sizeof(struct meas_feed_meas), "Meas. Feed"); + if (!msg) + return 0; + + /* fill in the header */ + mfm = (struct meas_feed_meas *) msgb_put(msg, sizeof(*mfm)); + mfm->hdr.msg_type = MEAS_FEED_MEAS; + mfm->hdr.version = MEAS_FEED_VERSION; + + /* fill in MEAS_FEED_MEAS specific header */ + osmo_strlcpy(mfm->imsi, subscr->imsi, sizeof(mfm->imsi)); + osmo_strlcpy(mfm->name, subscr->name, sizeof(mfm->name)); + osmo_strlcpy(mfm->scenario, g_mfs.scenario, sizeof(mfm->scenario)); + + /* copy the entire measurement report */ + memcpy(&mfm->mr, mr, sizeof(mfm->mr)); + + /* copy channel information */ + /* we assume that the measurement report always belong to some timeslot */ + mfm->lchan_type = (uint8_t)mr->lchan->type; + mfm->pchan_type = (uint8_t)mr->lchan->ts->pchan; + mfm->bts_nr = mr->lchan->ts->trx->bts->nr; + mfm->trx_nr = mr->lchan->ts->trx->nr; + mfm->ts_nr = mr->lchan->ts->nr; + mfm->ss_nr = mr->lchan->nr; + + /* and send it to the socket */ + if (osmo_wqueue_enqueue(&g_mfs.wqueue, msg) != 0) + msgb_free(msg); + + return 0; +} + +static int meas_feed_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct lchan_signal_data *sdata = signal_data; + + if (subsys != SS_LCHAN) + return 0; + + if (signal == S_LCHAN_MEAS_REP) + process_meas_rep(sdata->mr); + + return 0; +} + +static int feed_write_cb(struct osmo_fd *ofd, struct msgb *msg) +{ + return write(ofd->fd, msgb_data(msg), msgb_length(msg)); +} + +static int feed_read_cb(struct osmo_fd *ofd) +{ + int rc; + char buf[256]; + + rc = read(ofd->fd, buf, sizeof(buf)); + ofd->fd &= ~BSC_FD_READ; + + return rc; +} + +int meas_feed_cfg_set(const char *dst_host, uint16_t dst_port) +{ + int rc; + int already_initialized = 0; + + if (g_mfs.wqueue.bfd.fd) + already_initialized = 1; + + + if (already_initialized && + !strcmp(dst_host, g_mfs.dst_host) && + dst_port == g_mfs.dst_port) + return 0; + + if (!already_initialized) { + osmo_wqueue_init(&g_mfs.wqueue, 10); + g_mfs.wqueue.write_cb = feed_write_cb; + g_mfs.wqueue.read_cb = feed_read_cb; + osmo_signal_register_handler(SS_LCHAN, meas_feed_sig_cb, NULL); + } + + if (already_initialized) { + osmo_wqueue_clear(&g_mfs.wqueue); + osmo_fd_unregister(&g_mfs.wqueue.bfd); + close(g_mfs.wqueue.bfd.fd); + /* don't set to zero, as that would mean 'not yet initialized' */ + g_mfs.wqueue.bfd.fd = -1; + } + rc = osmo_sock_init_ofd(&g_mfs.wqueue.bfd, AF_UNSPEC, SOCK_DGRAM, + IPPROTO_UDP, dst_host, dst_port, + OSMO_SOCK_F_CONNECT); + if (rc < 0) + return rc; + + g_mfs.wqueue.bfd.when &= ~BSC_FD_READ; + + if (g_mfs.dst_host) + talloc_free(g_mfs.dst_host); + g_mfs.dst_host = talloc_strdup(NULL, dst_host); + g_mfs.dst_port = dst_port; + + return 0; +} + +void meas_feed_cfg_get(char **host, uint16_t *port) +{ + *port = g_mfs.dst_port; + *host = g_mfs.dst_host; +} + +void meas_feed_scenario_set(const char *name) +{ + osmo_strlcpy(g_mfs.scenario, name, sizeof(g_mfs.scenario)); +} + +const char *meas_feed_scenario_get(void) +{ + return g_mfs.scenario; +} diff --git a/src/libmsc/meas_feed.h b/src/libmsc/meas_feed.h new file mode 100644 index 000000000..782a9616c --- /dev/null +++ b/src/libmsc/meas_feed.h @@ -0,0 +1,12 @@ +#ifndef _INT_MEAS_FEED_H +#define _INT_MEAS_FEED_H + +#include <stdint.h> + +int meas_feed_cfg_set(const char *dst_host, uint16_t dst_port); +void meas_feed_cfg_get(char **host, uint16_t *port); + +void meas_feed_scenario_set(const char *name); +const char *meas_feed_scenario_get(void); + +#endif /* _INT_MEAS_FEED_H */ diff --git a/src/libmsc/mncc.c b/src/libmsc/mncc.c new file mode 100644 index 000000000..8110eadca --- /dev/null +++ b/src/libmsc/mncc.c @@ -0,0 +1,107 @@ +/* mncc.c - utility routines for the MNCC API between the 04.08 + * message parsing and the actual Call Control logic */ + +/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org> + * (C) 2009 by Andreas Eversberg <Andreas.Eversberg@versatel.de> + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> + +#include <openbsc/gsm_04_08.h> +#include <openbsc/debug.h> +#include <openbsc/mncc.h> +#include <openbsc/gsm_data.h> +#include <openbsc/transaction.h> +#include <openbsc/rtp_proxy.h> + + +static const struct value_string mncc_names[] = { + { MNCC_SETUP_REQ, "MNCC_SETUP_REQ" }, + { MNCC_SETUP_IND, "MNCC_SETUP_IND" }, + { MNCC_SETUP_RSP, "MNCC_SETUP_RSP" }, + { MNCC_SETUP_CNF, "MNCC_SETUP_CNF" }, + { MNCC_SETUP_COMPL_REQ, "MNCC_SETUP_COMPL_REQ" }, + { MNCC_SETUP_COMPL_IND, "MNCC_SETUP_COMPL_IND" }, + { MNCC_CALL_CONF_IND, "MNCC_CALL_CONF_IND" }, + { MNCC_CALL_PROC_REQ, "MNCC_CALL_PROC_REQ" }, + { MNCC_PROGRESS_REQ, "MNCC_PROGRESS_REQ" }, + { MNCC_ALERT_REQ, "MNCC_ALERT_REQ" }, + { MNCC_ALERT_IND, "MNCC_ALERT_IND" }, + { MNCC_NOTIFY_REQ, "MNCC_NOTIFY_REQ" }, + { MNCC_NOTIFY_IND, "MNCC_NOTIFY_IND" }, + { MNCC_DISC_REQ, "MNCC_DISC_REQ" }, + { MNCC_DISC_IND, "MNCC_DISC_IND" }, + { MNCC_REL_REQ, "MNCC_REL_REQ" }, + { MNCC_REL_IND, "MNCC_REL_IND" }, + { MNCC_REL_CNF, "MNCC_REL_CNF" }, + { MNCC_FACILITY_REQ, "MNCC_FACILITY_REQ" }, + { MNCC_FACILITY_IND, "MNCC_FACILITY_IND" }, + { MNCC_START_DTMF_IND, "MNCC_START_DTMF_IND" }, + { MNCC_START_DTMF_RSP, "MNCC_START_DTMF_RSP" }, + { MNCC_START_DTMF_REJ, "MNCC_START_DTMF_REJ" }, + { MNCC_STOP_DTMF_IND, "MNCC_STOP_DTMF_IND" }, + { MNCC_STOP_DTMF_RSP, "MNCC_STOP_DTMF_RSP" }, + { MNCC_MODIFY_REQ, "MNCC_MODIFY_REQ" }, + { MNCC_MODIFY_IND, "MNCC_MODIFY_IND" }, + { MNCC_MODIFY_RSP, "MNCC_MODIFY_RSP" }, + { MNCC_MODIFY_CNF, "MNCC_MODIFY_CNF" }, + { MNCC_MODIFY_REJ, "MNCC_MODIFY_REJ" }, + { MNCC_HOLD_IND, "MNCC_HOLD_IND" }, + { MNCC_HOLD_CNF, "MNCC_HOLD_CNF" }, + { MNCC_HOLD_REJ, "MNCC_HOLD_REJ" }, + { MNCC_RETRIEVE_IND, "MNCC_RETRIEVE_IND" }, + { MNCC_RETRIEVE_CNF, "MNCC_RETRIEVE_CNF" }, + { MNCC_RETRIEVE_REJ, "MNCC_RETRIEVE_REJ" }, + { MNCC_USERINFO_REQ, "MNCC_USERINFO_REQ" }, + { MNCC_USERINFO_IND, "MNCC_USERINFO_IND" }, + { MNCC_REJ_REQ, "MNCC_REJ_REQ" }, + { MNCC_REJ_IND, "MNCC_REJ_IND" }, + { MNCC_BRIDGE, "MNCC_BRIDGE" }, + { MNCC_FRAME_RECV, "MNCC_FRAME_RECV" }, + { MNCC_FRAME_DROP, "MNCC_FRAME_DROP" }, + { MNCC_LCHAN_MODIFY, "MNCC_LCHAN_MODIFY" }, + { MNCC_RTP_CREATE, "MNCC_RTP_CREATE" }, + { MNCC_RTP_CONNECT, "MNCC_RTP_CONNECT" }, + { MNCC_RTP_FREE, "MNCC_RTP_FREE" }, + { GSM_TCHF_FRAME, "GSM_TCHF_FRAME" }, + { GSM_TCHF_FRAME_EFR, "GSM_TCHF_FRAME_EFR" }, + { GSM_TCHH_FRAME, "GSM_TCHH_FRAME" }, + { GSM_TCH_FRAME_AMR, "GSM_TCH_FRAME_AMR" }, + { GSM_BAD_FRAME, "GSM_BAD_FRAME" }, + { 0, NULL }, +}; + +const char *get_mncc_name(int value) +{ + return get_value_string(mncc_names, value); +} + +void mncc_set_cause(struct gsm_mncc *data, int loc, int val) +{ + data->fields |= MNCC_F_CAUSE; + data->cause.location = loc; + data->cause.value = val; +} + diff --git a/src/libmsc/mncc_builtin.c b/src/libmsc/mncc_builtin.c new file mode 100644 index 000000000..067cc92f8 --- /dev/null +++ b/src/libmsc/mncc_builtin.c @@ -0,0 +1,426 @@ +/* mncc_builtin.c - default, minimal built-in MNCC Application for + * standalone bsc_hack (network-in-the-box mode) */ + +/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org> + * (C) 2009 by Andreas Eversberg <Andreas.Eversberg@versatel.de> + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include <openbsc/gsm_04_08.h> +#include <openbsc/debug.h> +#include <openbsc/mncc.h> +#include <openbsc/mncc_int.h> +#include <osmocom/core/talloc.h> +#include <openbsc/gsm_data.h> +#include <openbsc/transaction.h> +#include <openbsc/rtp_proxy.h> + +void *tall_call_ctx; + +static LLIST_HEAD(call_list); + +static uint32_t new_callref = 0x00000001; + +struct mncc_int mncc_int = { + .def_codec = { GSM48_CMODE_SPEECH_V1, GSM48_CMODE_SPEECH_V1 }, +}; + +static void free_call(struct gsm_call *call) +{ + llist_del(&call->entry); + DEBUGP(DMNCC, "(call %x) Call removed.\n", call->callref); + talloc_free(call); +} + + +static struct gsm_call *get_call_ref(uint32_t callref) +{ + struct gsm_call *callt; + + llist_for_each_entry(callt, &call_list, entry) { + if (callt->callref == callref) + return callt; + } + return NULL; +} + +uint8_t mncc_codec_for_mode(int lchan_type) +{ + /* FIXME: check codec capabilities of the phone */ + + if (lchan_type != GSM_LCHAN_TCH_H) + return mncc_int.def_codec[0]; + else + return mncc_int.def_codec[1]; +} + +static uint8_t determine_lchan_mode(struct gsm_mncc *setup) +{ + return mncc_codec_for_mode(setup->lchan_type); +} + +/* on incoming call, look up database and send setup to remote subscr. */ +static int mncc_setup_ind(struct gsm_call *call, int msg_type, + struct gsm_mncc *setup) +{ + struct gsm_mncc mncc; + struct gsm_call *remote; + + memset(&mncc, 0, sizeof(struct gsm_mncc)); + mncc.callref = call->callref; + + /* already have remote call */ + if (call->remote_ref) + return 0; + + /* transfer mode 1 would be packet mode, which was never specified */ + if (setup->bearer_cap.mode != 0) { + LOGP(DMNCC, LOGL_NOTICE, "(call %x) We don't support " + "packet mode\n", call->callref); + mncc_set_cause(&mncc, GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_BEARER_CA_UNAVAIL); + goto out_reject; + } + + /* we currently only do speech */ + if (setup->bearer_cap.transfer != GSM_MNCC_BCAP_SPEECH) { + LOGP(DMNCC, LOGL_NOTICE, "(call %x) We only support " + "voice calls\n", call->callref); + mncc_set_cause(&mncc, GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_BEARER_CA_UNAVAIL); + goto out_reject; + } + + /* create remote call */ + if (!(remote = talloc_zero(tall_call_ctx, struct gsm_call))) { + mncc_set_cause(&mncc, GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_RESOURCE_UNAVAIL); + goto out_reject; + } + llist_add_tail(&remote->entry, &call_list); + remote->net = call->net; + remote->callref = new_callref++; + DEBUGP(DMNCC, "(call %x) Creating new remote instance %x.\n", + call->callref, remote->callref); + + /* link remote call */ + call->remote_ref = remote->callref; + remote->remote_ref = call->callref; + + /* send call proceeding */ + memset(&mncc, 0, sizeof(struct gsm_mncc)); + mncc.callref = call->callref; + DEBUGP(DMNCC, "(call %x) Accepting call.\n", call->callref); + mncc_tx_to_cc(call->net, MNCC_CALL_PROC_REQ, &mncc); + + /* modify mode */ + memset(&mncc, 0, sizeof(struct gsm_mncc)); + mncc.callref = call->callref; + mncc.lchan_mode = determine_lchan_mode(setup); + DEBUGP(DMNCC, "(call %x) Modify channel mode: %s\n", call->callref, + get_value_string(gsm48_chan_mode_names, mncc.lchan_mode)); + mncc_tx_to_cc(call->net, MNCC_LCHAN_MODIFY, &mncc); + + /* send setup to remote */ +// setup->fields |= MNCC_F_SIGNAL; +// setup->signal = GSM48_SIGNAL_DIALTONE; + setup->callref = remote->callref; + DEBUGP(DMNCC, "(call %x) Forwarding SETUP to remote.\n", call->callref); + return mncc_tx_to_cc(remote->net, MNCC_SETUP_REQ, setup); + +out_reject: + mncc_tx_to_cc(call->net, MNCC_REJ_REQ, &mncc); + free_call(call); + return 0; +} + +static int mncc_alert_ind(struct gsm_call *call, int msg_type, + struct gsm_mncc *alert) +{ + struct gsm_call *remote; + + /* send alerting to remote */ + if (!(remote = get_call_ref(call->remote_ref))) + return 0; + alert->callref = remote->callref; + DEBUGP(DMNCC, "(call %x) Forwarding ALERT to remote.\n", call->callref); + return mncc_tx_to_cc(remote->net, MNCC_ALERT_REQ, alert); +} + +static int mncc_notify_ind(struct gsm_call *call, int msg_type, + struct gsm_mncc *notify) +{ + struct gsm_call *remote; + + /* send notify to remote */ + if (!(remote = get_call_ref(call->remote_ref))) + return 0; + notify->callref = remote->callref; + DEBUGP(DMNCC, "(call %x) Forwarding NOTIF to remote.\n", call->callref); + return mncc_tx_to_cc(remote->net, MNCC_NOTIFY_REQ, notify); +} + +static int mncc_setup_cnf(struct gsm_call *call, int msg_type, + struct gsm_mncc *connect) +{ + struct gsm_mncc connect_ack, frame_recv; + struct gsm_network *net = call->net; + struct gsm_call *remote; + struct gsm_mncc_bridge bridge = { .msg_type = MNCC_BRIDGE }; + + /* acknowledge connect */ + memset(&connect_ack, 0, sizeof(struct gsm_mncc)); + connect_ack.callref = call->callref; + DEBUGP(DMNCC, "(call %x) Acknowledge SETUP.\n", call->callref); + mncc_tx_to_cc(call->net, MNCC_SETUP_COMPL_REQ, &connect_ack); + + /* send connect message to remote */ + if (!(remote = get_call_ref(call->remote_ref))) + return 0; + connect->callref = remote->callref; + DEBUGP(DMNCC, "(call %x) Sending CONNECT to remote.\n", call->callref); + mncc_tx_to_cc(remote->net, MNCC_SETUP_RSP, connect); + + /* bridge tch */ + bridge.callref[0] = call->callref; + bridge.callref[1] = call->remote_ref; + DEBUGP(DMNCC, "(call %x) Bridging with remote.\n", call->callref); + + /* in direct mode, we always have to bridge the channels */ + if (ipacc_rtp_direct) + return mncc_tx_to_cc(call->net, MNCC_BRIDGE, &bridge); + + /* proxy mode */ + if (!net->handover.active) { + /* in the no-handover case, we can bridge, i.e. use + * the old RTP proxy code */ + return mncc_tx_to_cc(call->net, MNCC_BRIDGE, &bridge); + } else { + /* in case of handover, we need to re-write the RTP + * SSRC, sequence and timestamp values and thus + * need to enable RTP receive for both directions */ + memset(&frame_recv, 0, sizeof(struct gsm_mncc)); + frame_recv.callref = call->callref; + mncc_tx_to_cc(call->net, MNCC_FRAME_RECV, &frame_recv); + frame_recv.callref = call->remote_ref; + return mncc_tx_to_cc(call->net, MNCC_FRAME_RECV, &frame_recv); + } +} + +static int mncc_disc_ind(struct gsm_call *call, int msg_type, + struct gsm_mncc *disc) +{ + struct gsm_call *remote; + + /* send release */ + DEBUGP(DMNCC, "(call %x) Releasing call with cause %d\n", + call->callref, disc->cause.value); + mncc_tx_to_cc(call->net, MNCC_REL_REQ, disc); + + /* send disc to remote */ + if (!(remote = get_call_ref(call->remote_ref))) { + return 0; + } + disc->callref = remote->callref; + DEBUGP(DMNCC, "(call %x) Disconnecting remote with cause %d\n", + remote->callref, disc->cause.value); + return mncc_tx_to_cc(remote->net, MNCC_DISC_REQ, disc); +} + +static int mncc_rel_ind(struct gsm_call *call, int msg_type, struct gsm_mncc *rel) +{ + struct gsm_call *remote; + + /* send release to remote */ + if (!(remote = get_call_ref(call->remote_ref))) { + free_call(call); + return 0; + } + + rel->callref = remote->callref; + DEBUGP(DMNCC, "(call %x) Releasing remote with cause %d\n", + call->callref, rel->cause.value); + + /* + * Release this side of the call right now. Otherwise we end up + * in this method for the other call and will also try to release + * it and then we will end up with a double free and a crash + */ + free_call(call); + mncc_tx_to_cc(remote->net, MNCC_REL_REQ, rel); + + return 0; +} + +static int mncc_rel_cnf(struct gsm_call *call, int msg_type, struct gsm_mncc *rel) +{ + free_call(call); + return 0; +} + +/* receiving a (speech) traffic frame from the BSC code */ +static int mncc_rcv_data(struct gsm_call *call, int msg_type, + struct gsm_data_frame *dfr) +{ + struct gsm_trans *remote_trans; + + remote_trans = trans_find_by_callref(call->net, call->remote_ref); + + /* this shouldn't really happen */ + if (!remote_trans || !remote_trans->conn) { + LOGP(DMNCC, LOGL_ERROR, "No transaction or transaction without lchan?!?\n"); + return -EIO; + } + + /* RTP socket of remote end has meanwhile died */ + if (!remote_trans->conn->lchan->abis_ip.rtp_socket) + return -EIO; + + return rtp_send_frame(remote_trans->conn->lchan->abis_ip.rtp_socket, dfr); +} + + +/* Internal MNCC handler input function (from CC -> MNCC -> here) */ +int int_mncc_recv(struct gsm_network *net, struct msgb *msg) +{ + void *arg = msgb_data(msg); + struct gsm_mncc *data = arg; + int msg_type = data->msg_type; + int callref; + struct gsm_call *call = NULL, *callt; + int rc = 0; + + /* Special messages */ + switch(msg_type) { + } + + /* find callref */ + callref = data->callref; + llist_for_each_entry(callt, &call_list, entry) { + if (callt->callref == callref) { + call = callt; + break; + } + } + + /* create callref, if setup is received */ + if (!call) { + if (msg_type != MNCC_SETUP_IND) + goto out_free; /* drop */ + /* create call */ + if (!(call = talloc_zero(tall_call_ctx, struct gsm_call))) { + struct gsm_mncc rel; + + memset(&rel, 0, sizeof(struct gsm_mncc)); + rel.callref = callref; + mncc_set_cause(&rel, GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_RESOURCE_UNAVAIL); + mncc_tx_to_cc(net, MNCC_REL_REQ, &rel); + goto out_free; + } + llist_add_tail(&call->entry, &call_list); + call->net = net; + call->callref = callref; + DEBUGP(DMNCC, "(call %x) Call created.\n", call->callref); + } + + if (mncc_is_data_frame(msg_type)) { + rc = mncc_rcv_data(call, msg_type, arg); + goto out_free; + } + + DEBUGP(DMNCC, "(call %x) Received message %s\n", call->callref, + get_mncc_name(msg_type)); + + switch(msg_type) { + case MNCC_SETUP_IND: + rc = mncc_setup_ind(call, msg_type, arg); + break; + case MNCC_SETUP_CNF: + rc = mncc_setup_cnf(call, msg_type, arg); + break; + case MNCC_SETUP_COMPL_IND: + break; + case MNCC_CALL_CONF_IND: + /* we now need to MODIFY the channel */ + data->lchan_mode = determine_lchan_mode(data); + mncc_tx_to_cc(call->net, MNCC_LCHAN_MODIFY, data); + break; + case MNCC_ALERT_IND: + rc = mncc_alert_ind(call, msg_type, arg); + break; + case MNCC_NOTIFY_IND: + rc = mncc_notify_ind(call, msg_type, arg); + break; + case MNCC_DISC_IND: + rc = mncc_disc_ind(call, msg_type, arg); + break; + case MNCC_REL_IND: + case MNCC_REJ_IND: + rc = mncc_rel_ind(call, msg_type, arg); + break; + case MNCC_REL_CNF: + rc = mncc_rel_cnf(call, msg_type, arg); + break; + case MNCC_FACILITY_IND: + break; + case MNCC_START_DTMF_IND: + rc = mncc_tx_to_cc(net, MNCC_START_DTMF_REJ, data); + break; + case MNCC_STOP_DTMF_IND: + rc = mncc_tx_to_cc(net, MNCC_STOP_DTMF_RSP, data); + break; + case MNCC_MODIFY_IND: + mncc_set_cause(data, GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_SERV_OPT_UNIMPL); + DEBUGP(DMNCC, "(call %x) Rejecting MODIFY with cause %d\n", + call->callref, data->cause.value); + rc = mncc_tx_to_cc(net, MNCC_MODIFY_REJ, data); + break; + case MNCC_MODIFY_CNF: + break; + case MNCC_HOLD_IND: + mncc_set_cause(data, GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_SERV_OPT_UNIMPL); + DEBUGP(DMNCC, "(call %x) Rejecting HOLD with cause %d\n", + call->callref, data->cause.value); + rc = mncc_tx_to_cc(net, MNCC_HOLD_REJ, data); + break; + case MNCC_RETRIEVE_IND: + mncc_set_cause(data, GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_SERV_OPT_UNIMPL); + DEBUGP(DMNCC, "(call %x) Rejecting RETRIEVE with cause %d\n", + call->callref, data->cause.value); + rc = mncc_tx_to_cc(net, MNCC_RETRIEVE_REJ, data); + break; + default: + LOGP(DMNCC, LOGL_NOTICE, "(call %x) Message unhandled\n", callref); + break; + } + +out_free: + msgb_free(msg); + + return rc; +} diff --git a/src/libmsc/mncc_sock.c b/src/libmsc/mncc_sock.c new file mode 100644 index 000000000..0efe3a156 --- /dev/null +++ b/src/libmsc/mncc_sock.c @@ -0,0 +1,318 @@ +/* mncc_sock.c: Tie the MNCC interface to a unix domain socket */ + +/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org> + * (C) 2009 by Andreas Eversberg <Andreas.Eversberg@versatel.de> + * (C) 2012 by Holger Hans Peter Freyther + * 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 <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/socket.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> + +#include <openbsc/debug.h> +#include <openbsc/mncc.h> +#include <openbsc/gsm_data.h> + +struct mncc_sock_state { + struct gsm_network *net; + struct osmo_fd listen_bfd; /* fd for listen socket */ + struct osmo_fd conn_bfd; /* fd for connection to lcr */ +}; + +/* input from CC code into mncc_sock */ +int mncc_sock_from_cc(struct gsm_network *net, struct msgb *msg) +{ + struct gsm_mncc *mncc_in = (struct gsm_mncc *) msgb_data(msg); + int msg_type = mncc_in->msg_type; + + /* Check if we currently have a MNCC handler connected */ + if (net->mncc_state->conn_bfd.fd < 0) { + LOGP(DMNCC, LOGL_ERROR, "mncc_sock receives %s for external CC app " + "but socket is gone\n", get_mncc_name(msg_type)); + if (!mncc_is_data_frame(msg_type)) { + /* release the request */ + struct gsm_mncc mncc_out; + memset(&mncc_out, 0, sizeof(mncc_out)); + mncc_out.callref = mncc_in->callref; + mncc_set_cause(&mncc_out, GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_TEMP_FAILURE); + mncc_tx_to_cc(net, MNCC_REL_REQ, &mncc_out); + } + /* free the original message */ + msgb_free(msg); + return -1; + } + + /* FIXME: check for some maximum queue depth? */ + + /* Actually enqueue the message and mark socket write need */ + msgb_enqueue(&net->upqueue, msg); + net->mncc_state->conn_bfd.when |= BSC_FD_WRITE; + return 0; +} + +static void mncc_sock_close(struct mncc_sock_state *state) +{ + struct osmo_fd *bfd = &state->conn_bfd; + + LOGP(DMNCC, LOGL_NOTICE, "MNCC Socket has LOST connection\n"); + + close(bfd->fd); + bfd->fd = -1; + osmo_fd_unregister(bfd); + + /* re-enable the generation of ACCEPT for new connections */ + state->listen_bfd.when |= BSC_FD_READ; + + /* release all exisitng calls */ + gsm0408_clear_all_trans(state->net, GSM48_PDISC_CC); + + /* flush the queue */ + while (!llist_empty(&state->net->upqueue)) { + struct msgb *msg = msgb_dequeue(&state->net->upqueue); + msgb_free(msg); + } +} + +static int mncc_sock_read(struct osmo_fd *bfd) +{ + struct mncc_sock_state *state = (struct mncc_sock_state *)bfd->data; + struct gsm_mncc *mncc_prim; + struct msgb *msg; + int rc; + + msg = msgb_alloc(sizeof(*mncc_prim)+256, "mncc_sock_rx"); + if (!msg) + return -ENOMEM; + + mncc_prim = (struct gsm_mncc *) msg->tail; + + rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0); + if (rc == 0) + goto close; + + if (rc < 0) { + if (errno == EAGAIN) + return 0; + goto close; + } + + rc = mncc_tx_to_cc(state->net, mncc_prim->msg_type, mncc_prim); + + /* as we always synchronously process the message in mncc_send() and + * its callbacks, we can free the message here. */ + msgb_free(msg); + + return rc; + +close: + msgb_free(msg); + mncc_sock_close(state); + return -1; +} + +static int mncc_sock_write(struct osmo_fd *bfd) +{ + struct mncc_sock_state *state = bfd->data; + struct gsm_network *net = state->net; + int rc; + + while (!llist_empty(&net->upqueue)) { + struct msgb *msg, *msg2; + struct gsm_mncc *mncc_prim; + + /* peek at the beginning of the queue */ + msg = llist_entry(net->upqueue.next, struct msgb, list); + mncc_prim = (struct gsm_mncc *)msg->data; + + bfd->when &= ~BSC_FD_WRITE; + + /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */ + if (!msgb_length(msg)) { + LOGP(DMNCC, LOGL_ERROR, "message type (%d) with ZERO " + "bytes!\n", mncc_prim->msg_type); + goto dontsend; + } + + /* try to send it over the socket */ + rc = write(bfd->fd, msgb_data(msg), msgb_length(msg)); + if (rc == 0) + goto close; + if (rc < 0) { + if (errno == EAGAIN) { + bfd->when |= BSC_FD_WRITE; + break; + } + goto close; + } + +dontsend: + /* _after_ we send it, we can deueue */ + msg2 = msgb_dequeue(&net->upqueue); + assert(msg == msg2); + msgb_free(msg); + } + return 0; + +close: + mncc_sock_close(state); + + return -1; +} + +static int mncc_sock_cb(struct osmo_fd *bfd, unsigned int flags) +{ + int rc = 0; + + if (flags & BSC_FD_READ) + rc = mncc_sock_read(bfd); + if (rc < 0) + return rc; + + if (flags & BSC_FD_WRITE) + rc = mncc_sock_write(bfd); + + return rc; +} + +/** + * Send a version indication to the remote. + */ +static void queue_hello(struct mncc_sock_state *mncc) +{ + struct gsm_mncc_hello *hello; + struct msgb *msg; + + msg = msgb_alloc(512, "mncc hello"); + if (!msg) { + LOGP(DMNCC, LOGL_ERROR, "Failed to allocate hello.\n"); + mncc_sock_close(mncc); + return; + } + + hello = (struct gsm_mncc_hello *) msgb_put(msg, sizeof(*hello)); + hello->msg_type = MNCC_SOCKET_HELLO; + hello->version = MNCC_SOCK_VERSION; + hello->mncc_size = sizeof(struct gsm_mncc); + hello->data_frame_size = sizeof(struct gsm_data_frame); + hello->called_offset = offsetof(struct gsm_mncc, called); + hello->signal_offset = offsetof(struct gsm_mncc, signal); + hello->emergency_offset = offsetof(struct gsm_mncc, emergency); + hello->lchan_type_offset = offsetof(struct gsm_mncc, lchan_type); + + msgb_enqueue(&mncc->net->upqueue, msg); + mncc->conn_bfd.when |= BSC_FD_WRITE; +} + +/* accept a new connection */ +static int mncc_sock_accept(struct osmo_fd *bfd, unsigned int flags) +{ + struct mncc_sock_state *state = (struct mncc_sock_state *)bfd->data; + struct osmo_fd *conn_bfd = &state->conn_bfd; + struct sockaddr_un un_addr; + socklen_t len; + int rc; + + len = sizeof(un_addr); + rc = accept(bfd->fd, (struct sockaddr *) &un_addr, &len); + if (rc < 0) { + LOGP(DMNCC, LOGL_ERROR, "Failed to accept a new connection\n"); + return -1; + } + + if (conn_bfd->fd >= 0) { + LOGP(DMNCC, LOGL_NOTICE, "MNCC app connects but we already have " + "another active connection ?!?\n"); + /* We already have one MNCC app connected, this is all we support */ + state->listen_bfd.when &= ~BSC_FD_READ; + close(rc); + return 0; + } + + conn_bfd->fd = rc; + conn_bfd->when = BSC_FD_READ; + conn_bfd->cb = mncc_sock_cb; + conn_bfd->data = state; + + if (osmo_fd_register(conn_bfd) != 0) { + LOGP(DMNCC, LOGL_ERROR, "Failed to register new connection fd\n"); + close(conn_bfd->fd); + conn_bfd->fd = -1; + return -1; + } + + LOGP(DMNCC, LOGL_NOTICE, "MNCC Socket has connection with external " + "call control application\n"); + + queue_hello(state); + return 0; +} + + +int mncc_sock_init(struct gsm_network *net, const char *sock_path) +{ + struct mncc_sock_state *state; + struct osmo_fd *bfd; + int rc; + + state = talloc_zero(tall_bsc_ctx, struct mncc_sock_state); + if (!state) + return -ENOMEM; + + state->net = net; + state->conn_bfd.fd = -1; + + bfd = &state->listen_bfd; + + bfd->fd = osmo_sock_unix_init(SOCK_SEQPACKET, 0, sock_path, + OSMO_SOCK_F_BIND); + if (bfd->fd < 0) { + LOGP(DMNCC, LOGL_ERROR, "Could not create unix socket: %s: %s\n", + sock_path, strerror(errno)); + talloc_free(state); + return -1; + } + + bfd->when = BSC_FD_READ; + bfd->cb = mncc_sock_accept; + bfd->data = state; + + rc = osmo_fd_register(bfd); + if (rc < 0) { + LOGP(DMNCC, LOGL_ERROR, "Could not register listen fd: %d\n", rc); + close(bfd->fd); + talloc_free(state); + return rc; + } + + net->mncc_state = state; + + LOGP(DMNCC, LOGL_NOTICE, "MNCC socket at %s\n", sock_path); + return 0; +} diff --git a/src/libmsc/osmo_msc.c b/src/libmsc/osmo_msc.c new file mode 100644 index 000000000..2389980d3 --- /dev/null +++ b/src/libmsc/osmo_msc.c @@ -0,0 +1,177 @@ +/* main MSC management code... */ + +/* + * (C) 2010,2013 by Holger Hans Peter Freyther <zecke@selfish.org> + * (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 <openbsc/bsc_api.h> +#include <openbsc/debug.h> +#include <openbsc/transaction.h> +#include <openbsc/db.h> + +#include <openbsc/gsm_04_11.h> + +static void msc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci) +{ + int sapi = dlci & 0x7; + + if (sapi == UM_SAPI_SMS) + gsm411_sapi_n_reject(conn); +} + +static int msc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause) +{ + gsm0408_clear_request(conn, cause); + return 1; +} + +static int msc_compl_l3(struct gsm_subscriber_connection *conn, struct msgb *msg, + uint16_t chosen_channel) +{ + gsm0408_new_conn(conn); + gsm0408_dispatch(conn, msg); + + /* + * If this is a silent call we want the channel to remain open as long as + * possible and this is why we accept this connection regardless of any + * pending transaction or ongoing operation. + */ + if (conn->silent_call) + return BSC_API_CONN_POL_ACCEPT; + if (conn->loc_operation || conn->sec_operation || conn->anch_operation) + return BSC_API_CONN_POL_ACCEPT; + if (trans_has_conn(conn)) + return BSC_API_CONN_POL_ACCEPT; + + LOGP(DRR, LOGL_INFO, "MSC Complete L3: Rejecting connection.\n"); + return BSC_API_CONN_POL_REJECT; +} + +static void msc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg) +{ + gsm0408_dispatch(conn, msg); +} + +static void msc_assign_compl(struct gsm_subscriber_connection *conn, + uint8_t rr_cause, uint8_t chosen_channel, + uint8_t encr_alg_id, uint8_t speec) +{ + LOGP(DRR, LOGL_DEBUG, "MSC assign complete (do nothing).\n"); +} + +static void msc_assign_fail(struct gsm_subscriber_connection *conn, + uint8_t cause, uint8_t *rr_cause) +{ + LOGP(DRR, LOGL_DEBUG, "MSC assign failure (do nothing).\n"); +} + +static void msc_classmark_chg(struct gsm_subscriber_connection *conn, + const uint8_t *cm2, uint8_t cm2_len, + const uint8_t *cm3, uint8_t cm3_len) +{ + struct gsm_subscriber *subscr = conn->subscr; + + if (subscr) { + subscr->equipment.classmark2_len = cm2_len; + memcpy(subscr->equipment.classmark2, cm2, cm2_len); + if (cm3) { + subscr->equipment.classmark3_len = cm3_len; + memcpy(subscr->equipment.classmark3, cm3, cm3_len); + } + db_sync_equipment(&subscr->equipment); + } +} + +static void msc_ciph_m_compl(struct gsm_subscriber_connection *conn, + struct msgb *msg, uint8_t alg_id) +{ + gsm_cbfn *cb; + + DEBUGP(DRR, "CIPHERING MODE COMPLETE\n"); + + /* Safety check */ + if (!conn->sec_operation) { + DEBUGP(DRR, "No authentication/cipher operation in progress !!!\n"); + return; + } + + /* FIXME: check for MI (if any) */ + + /* Call back whatever was in progress (if anything) ... */ + cb = conn->sec_operation->cb; + if (cb) { + cb(GSM_HOOK_RR_SECURITY, GSM_SECURITY_SUCCEEDED, + NULL, conn, conn->sec_operation->cb_data); + + } + + /* Complete the operation */ + release_security_operation(conn); +} + + + +static struct bsc_api msc_handler = { + .sapi_n_reject = msc_sapi_n_reject, + .compl_l3 = msc_compl_l3, + .dtap = msc_dtap, + .clear_request = msc_clear_request, + .assign_compl = msc_assign_compl, + .assign_fail = msc_assign_fail, + .classmark_chg = msc_classmark_chg, + .cipher_mode_compl = msc_ciph_m_compl, +}; + +struct bsc_api *msc_bsc_api() { + return &msc_handler; +} + +/* lchan release handling */ +void msc_release_connection(struct gsm_subscriber_connection *conn) +{ + /* skip when we are in release, e.g. due an error */ + if (conn->in_release) + return; + + /* skip releasing of silent calls as they have no transaction */ + if (conn->silent_call) + return; + + /* check if there is a pending operation */ + if (conn->loc_operation || conn->sec_operation || conn->anch_operation) + return; + + if (trans_has_conn(conn)) + return; + + /* no more connections, asking to release the channel */ + + /* + * We had stopped the LU expire timer T3212. Now we are about + * to send the MS back to the idle state and this should lead + * to restarting the timer. Set the new expiration time. + */ + if (conn->expire_timer_stopped) + subscr_update_expire_lu(conn->subscr, conn->bts); + + conn->in_release = 1; + gsm0808_clear(conn); + msc_subscr_con_free(conn); +} diff --git a/src/libmsc/rrlp.c b/src/libmsc/rrlp.c new file mode 100644 index 000000000..e695daac7 --- /dev/null +++ b/src/libmsc/rrlp.c @@ -0,0 +1,104 @@ +/* Radio Resource LCS (Location) Protocol, GMS TS 04.31 */ + +/* (C) 2009 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 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 <openbsc/gsm_04_08.h> +#include <openbsc/signal.h> +#include <openbsc/gsm_subscriber.h> +#include <openbsc/chan_alloc.h> + +/* RRLP msPositionReq, nsBased, + * Accuracy=60, Method=gps, ResponseTime=2, oneSet */ +static const uint8_t ms_based_pos_req[] = { 0x40, 0x01, 0x78, 0xa8 }; + +/* RRLP msPositionReq, msBasedPref, + Accuracy=60, Method=gpsOrEOTD, ResponseTime=5, multipleSets */ +static const uint8_t ms_pref_pos_req[] = { 0x40, 0x02, 0x79, 0x50 }; + +/* RRLP msPositionReq, msAssistedPref, + Accuracy=60, Method=gpsOrEOTD, ResponseTime=5, multipleSets */ +static const uint8_t ass_pref_pos_req[] = { 0x40, 0x03, 0x79, 0x50 }; + +static int send_rrlp_req(struct gsm_subscriber_connection *conn) +{ + struct gsm_network *net = conn->network; + const uint8_t *req; + + switch (net->rrlp.mode) { + case RRLP_MODE_MS_BASED: + req = ms_based_pos_req; + break; + case RRLP_MODE_MS_PREF: + req = ms_pref_pos_req; + break; + case RRLP_MODE_ASS_PREF: + req = ass_pref_pos_req; + break; + case RRLP_MODE_NONE: + default: + return 0; + } + + return gsm48_send_rr_app_info(conn, 0x00, + sizeof(ms_based_pos_req), req); +} + +static int subscr_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_subscriber *subscr; + struct gsm_subscriber_connection *conn; + + switch (signal) { + case S_SUBSCR_ATTACHED: + /* A subscriber has attached. */ + subscr = signal_data; + conn = connection_for_subscr(subscr); + if (!conn) + break; + send_rrlp_req(conn); + break; + } + return 0; +} + +static int paging_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct paging_signal_data *psig_data = signal_data; + + switch (signal) { + case S_PAGING_SUCCEEDED: + /* A subscriber has attached. */ + send_rrlp_req(psig_data->conn); + break; + case S_PAGING_EXPIRED: + break; + } + return 0; +} + +void on_dso_load_rrlp(void) +{ + osmo_signal_register_handler(SS_SUBSCR, subscr_sig_cb, NULL); + osmo_signal_register_handler(SS_PAGING, paging_sig_cb, NULL); +} diff --git a/src/libmsc/silent_call.c b/src/libmsc/silent_call.c new file mode 100644 index 000000000..590d01bbf --- /dev/null +++ b/src/libmsc/silent_call.c @@ -0,0 +1,152 @@ +/* GSM silent call feature */ + +/* + * (C) 2009 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 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 <stdlib.h> +#include <unistd.h> +#include <errno.h> + +#include <osmocom/core/msgb.h> +#include <openbsc/signal.h> +#include <openbsc/debug.h> +#include <openbsc/paging.h> +#include <openbsc/gsm_data.h> +#include <openbsc/gsm_subscriber.h> +#include <openbsc/abis_rsl.h> +#include <openbsc/chan_alloc.h> +#include <openbsc/osmo_msc.h> + +/* paging of the requested subscriber has completed */ +static int paging_cb_silent(unsigned int hooknum, unsigned int event, + struct msgb *msg, void *_conn, void *_data) +{ + struct gsm_subscriber_connection *conn = _conn; + struct scall_signal_data sigdata; + int rc = 0; + + if (hooknum != GSM_HOOK_RR_PAGING) + return -EINVAL; + + DEBUGP(DLSMS, "paging_cb_silent: "); + + sigdata.conn = conn; + sigdata.data = _data; + + switch (event) { + case GSM_PAGING_SUCCEEDED: + DEBUGPC(DLSMS, "success, using Timeslot %u on ARFCN %u\n", + conn->lchan->ts->nr, conn->lchan->ts->trx->arfcn); + conn->silent_call = 1; + /* increment lchan reference count */ + osmo_signal_dispatch(SS_SCALL, S_SCALL_SUCCESS, &sigdata); + break; + case GSM_PAGING_EXPIRED: + case GSM_PAGING_BUSY: + case GSM_PAGING_OOM: + DEBUGP(DLSMS, "expired\n"); + osmo_signal_dispatch(SS_SCALL, S_SCALL_EXPIRED, &sigdata); + break; + default: + rc = -EINVAL; + break; + } + + return rc; +} + +#if 0 +/* receive a layer 3 message from a silent call */ +int silent_call_rx(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + /* FIXME: do something like sending it through a UDP port */ + LOGP(DLSMS, LOGL_NOTICE, "Discarding L3 message from a silent call.\n"); + return 0; +} +#endif + +struct msg_match { + uint8_t pdisc; + uint8_t msg_type; +}; + +/* list of messages that are handled inside OpenBSC, even in a silent call */ +static const struct msg_match silent_call_accept[] = { + { GSM48_PDISC_MM, GSM48_MT_MM_LOC_UPD_REQUEST }, + { GSM48_PDISC_MM, GSM48_MT_MM_CM_SERV_REQ }, +}; + +#if 0 +/* decide if we need to reroute a message as part of a silent call */ +int silent_call_reroute(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + uint8_t pdisc = gsm48_hdr_pdisc(gh); + uint8_t msg_type = gsm48_hdr_msg_type(gh); + int i; + + /* if we're not part of a silent call, never reroute */ + if (!conn->silent_call) + return 0; + + /* check if we are a special message that is handled in openbsc */ + for (i = 0; i < ARRAY_SIZE(silent_call_accept); i++) { + if (silent_call_accept[i].pdisc == pdisc && + silent_call_accept[i].msg_type == msg_type) + return 0; + } + + /* otherwise, reroute */ + LOGP(DLSMS, LOGL_INFO, "Rerouting L3 message from a silent call.\n"); + return 1; +} +#endif + + +/* initiate a silent call with a given subscriber */ +int gsm_silent_call_start(struct gsm_subscriber *subscr, void *data, int type) +{ + struct subscr_request *req; + + req = subscr_request_channel(subscr, type, paging_cb_silent, data); + return req != NULL; +} + +/* end a silent call with a given subscriber */ +int gsm_silent_call_stop(struct gsm_subscriber *subscr) +{ + struct gsm_subscriber_connection *conn; + + conn = connection_for_subscr(subscr); + if (!conn) + return -EINVAL; + + /* did we actually establish a silent call for this guy? */ + if (!conn->silent_call) + return -EINVAL; + + DEBUGPC(DLSMS, "Stopping silent call using Timeslot %u on ARFCN %u\n", + conn->lchan->ts->nr, conn->lchan->ts->trx->arfcn); + + conn->silent_call = 0; + msc_release_connection(conn); + + return 0; +} diff --git a/src/libmsc/smpp_openbsc.c b/src/libmsc/smpp_openbsc.c new file mode 100644 index 000000000..f94968a3f --- /dev/null +++ b/src/libmsc/smpp_openbsc.c @@ -0,0 +1,758 @@ +/* OpenBSC SMPP 3.4 interface, SMSC-side implementation */ + +/* (C) 2012-2013 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 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 <stdio.h> +#include <unistd.h> +#include <string.h> +#include <stdint.h> +#include <errno.h> + +#include <smpp34.h> +#include <smpp34_structs.h> +#include <smpp34_params.h> + +#include <osmocom/core/utils.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/talloc.h> +#include <osmocom/gsm/protocol/gsm_04_11.h> +#include <osmocom/gsm/protocol/smpp34_osmocom.h> + +#include <openbsc/gsm_subscriber.h> +#include <openbsc/debug.h> +#include <openbsc/db.h> +#include <openbsc/gsm_04_11.h> +#include <openbsc/gsm_data.h> +#include <openbsc/signal.h> +#include <openbsc/transaction.h> +#include <openbsc/gsm_subscriber.h> +#include <openbsc/chan_alloc.h> + +#include "smpp_smsc.h" + +/*! \brief find gsm_subscriber for a given SMPP NPI/TON/Address */ +static struct gsm_subscriber *subscr_by_dst(struct gsm_network *net, + uint8_t npi, uint8_t ton, const char *addr) +{ + struct gsm_subscriber *subscr = NULL; + + switch (npi) { + case NPI_Land_Mobile_E212: + subscr = subscr_get_by_imsi(net->subscr_group, addr); + break; + case NPI_ISDN_E163_E164: + case NPI_Private: + subscr = subscr_get_by_extension(net->subscr_group, addr); + break; + default: + LOGP(DSMPP, LOGL_NOTICE, "Unsupported NPI: %u\n", npi); + break; + } + + /* tag the context in case we know it */ + log_set_context(LOG_CTX_VLR_SUBSCR, subscr); + return subscr; +} + +/*! \brief find a TLV with given tag in list of libsmpp34 TLVs */ +static struct tlv_t *find_tlv(struct tlv_t *head, uint16_t tag) +{ + struct tlv_t *t; + + for (t = head; t != NULL; t = t->next) { + if (t->tag == tag) + return t; + } + return NULL; +} + +/*! \brief convert from submit_sm_t to gsm_sms */ +static int submit_to_sms(struct gsm_sms **psms, struct gsm_network *net, + const struct submit_sm_t *submit) +{ + struct gsm_subscriber *dest; + struct gsm_sms *sms; + struct tlv_t *t; + const uint8_t *sms_msg; + unsigned int sms_msg_len; + int mode; + + dest = subscr_by_dst(net, submit->dest_addr_npi, + submit->dest_addr_ton, + (const char *)submit->destination_addr); + if (!dest) { + LOGP(DLSMS, LOGL_NOTICE, "SMPP SUBMIT-SM for unknown subscriber: " + "%s (NPI=%u)\n", submit->destination_addr, + submit->dest_addr_npi); + return ESME_RINVDSTADR; + } + + t = find_tlv(submit->tlv, TLVID_message_payload); + if (t) { + if (submit->sm_length) { + /* ERROR: we cannot have both! */ + LOGP(DLSMS, LOGL_ERROR, "SMPP Cannot have payload in " + "TLV _and_ in the header\n"); + subscr_put(dest); + return ESME_ROPTPARNOTALLWD; + } + sms_msg = t->value.octet; + sms_msg_len = t->length; + } else if (submit->sm_length > 0 && submit->sm_length < 255) { + sms_msg = submit->short_message; + sms_msg_len = submit->sm_length; + } else { + LOGP(DLSMS, LOGL_ERROR, + "SMPP neither message payload nor valid sm_length.\n"); + subscr_put(dest); + return ESME_RINVPARLEN; + } + + sms = sms_alloc(); + sms->source = SMS_SOURCE_SMPP; + sms->smpp.sequence_nr = submit->sequence_number; + + /* fill in the destination address */ + sms->receiver = dest; + sms->dst.ton = submit->dest_addr_ton; + sms->dst.npi = submit->dest_addr_npi; + osmo_strlcpy(sms->dst.addr, dest->extension, sizeof(sms->dst.addr)); + + /* fill in the source address */ + sms->src.ton = submit->source_addr_ton; + sms->src.npi = submit->source_addr_npi; + osmo_strlcpy(sms->src.addr, (char *)submit->source_addr, + sizeof(sms->src.addr)); + + if (submit->esm_class & 0x40) + sms->ud_hdr_ind = 1; + + if (submit->esm_class & 0x80) { + sms->reply_path_req = 1; +#warning Implement reply path + } + + if (submit->data_coding == 0x00 || /* SMSC default */ + submit->data_coding == 0x01) { /* GSM default alphabet */ + sms->data_coding_scheme = GSM338_DCS_1111_7BIT; + mode = MODE_7BIT; + } else if ((submit->data_coding & 0xFC) == 0xF0) { /* 03.38 DCS default */ + /* pass DCS 1:1 through from SMPP to GSM */ + sms->data_coding_scheme = submit->data_coding; + mode = MODE_7BIT; + } else if (submit->data_coding == 0x02 || + submit->data_coding == 0x04) { + /* 8-bit binary */ + sms->data_coding_scheme = GSM338_DCS_1111_8BIT_DATA; + mode = MODE_8BIT; + } else if ((submit->data_coding & 0xFC) == 0xF4) { /* 03.38 DCS 8bit */ + /* pass DCS 1:1 through from SMPP to GSM */ + sms->data_coding_scheme = submit->data_coding; + mode = MODE_8BIT; + } else if (submit->data_coding == 0x08) { + /* UCS-2 */ + sms->data_coding_scheme = (2 << 2); + mode = MODE_8BIT; + } else { + sms_free(sms); + LOGP(DLSMS, LOGL_ERROR, "SMPP Unknown Data Coding 0x%02x\n", + submit->data_coding); + return ESME_RUNKNOWNERR; + } + + if (mode == MODE_7BIT) { + uint8_t ud_len = 0, padbits = 0; + sms->data_coding_scheme = GSM338_DCS_1111_7BIT; + if (sms->ud_hdr_ind) { + ud_len = *sms_msg + 1; + printf("copying %u bytes user data...\n", ud_len); + memcpy(sms->user_data, sms_msg, + OSMO_MIN(ud_len, sizeof(sms->user_data))); + sms_msg += ud_len; + sms_msg_len -= ud_len; + padbits = 7 - (ud_len % 7); + } + gsm_septets2octets(sms->user_data+ud_len, sms_msg, + sms_msg_len, padbits); + sms->user_data_len = (ud_len*8 + padbits)/7 + sms_msg_len;/* SEPTETS */ + /* FIXME: sms->text */ + } else { + memcpy(sms->user_data, sms_msg, sms_msg_len); + sms->user_data_len = sms_msg_len; + } + + *psms = sms; + return ESME_ROK; +} + +/*! \brief handle incoming libsmpp34 ssubmit_sm_t from remote ESME */ +int handle_smpp_submit(struct osmo_esme *esme, struct submit_sm_t *submit, + struct submit_sm_resp_t *submit_r) +{ + struct gsm_sms *sms; + struct gsm_network *net = esme->smsc->priv; + struct sms_signal_data sig; + int rc = -1; + + rc = submit_to_sms(&sms, net, submit); + if (rc != ESME_ROK) { + submit_r->command_status = rc; + return 0; + } + smpp_esme_get(esme); + sms->smpp.esme = esme; + sms->protocol_id = submit->protocol_id; + + switch (submit->esm_class & 3) { + case 0: /* default */ + case 1: /* datagram */ + case 3: /* store-and-forward */ + rc = db_sms_store(sms); + sms_free(sms); + sms = NULL; + if (rc < 0) { + LOGP(DLSMS, LOGL_ERROR, "SMPP SUBMIT-SM: Unable to " + "store SMS in database\n"); + submit_r->command_status = ESME_RSYSERR; + return 0; + } + strcpy((char *)submit_r->message_id, "msg_id_not_implemented"); + LOGP(DLSMS, LOGL_INFO, "SMPP SUBMIT-SM: Stored in DB\n"); + + memset(&sig, 0, sizeof(sig)); + osmo_signal_dispatch(SS_SMS, S_SMS_SUBMITTED, &sig); + rc = 0; + break; + case 2: /* forward (i.e. transaction) mode */ + LOGP(DLSMS, LOGL_DEBUG, "SMPP SUBMIT-SM: Forwarding in " + "real time (Transaction/Forward mode)\n"); + sms->smpp.transaction_mode = 1; + gsm411_send_sms_subscr(sms->receiver, sms); + rc = 1; /* don't send any response yet */ + break; + } + return rc; +} + +static void alert_all_esme(struct smsc *smsc, struct gsm_subscriber *subscr, + uint8_t smpp_avail_status) +{ + struct osmo_esme *esme; + + llist_for_each_entry(esme, &smsc->esme_list, list) { + /* we currently send an alert notification to each ESME that is + * connected, and do not require a (non-existant) delivery + * pending flag to be set before, FIXME: make this VTY + * configurable */ + if (esme->acl && esme->acl->deliver_src_imsi) { + smpp_tx_alert(esme, TON_Subscriber_Number, + NPI_Land_Mobile_E212, + subscr->imsi, smpp_avail_status); + } else { + smpp_tx_alert(esme, TON_Network_Specific, + NPI_ISDN_E163_E164, + subscr->extension, smpp_avail_status); + } + } +} + + +/*! \brief signal handler for status of attempted SMS deliveries */ +static int smpp_sms_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct sms_signal_data *sig_sms = signal_data; + struct gsm_sms *sms = sig_sms->sms; + struct smsc *smsc = handler_data; + int rc = 0; + + if (!sms) + return 0; + + if (sms->source != SMS_SOURCE_SMPP) + return 0; + + switch (signal) { + case S_SMS_MEM_EXCEEDED: + /* fall-through: There is no ESME_Rxxx result code to + * indicate a MEMORY EXCEEDED in transaction mode back + * to the ESME */ + case S_SMS_UNKNOWN_ERROR: + if (sms->smpp.transaction_mode) { + /* Send back the SUBMIT-SM response with apropriate error */ + LOGP(DLSMS, LOGL_INFO, "SMPP SUBMIT-SM: Error\n"); + rc = smpp_tx_submit_r(sms->smpp.esme, + sms->smpp.sequence_nr, + ESME_RDELIVERYFAILURE, + sms->smpp.msg_id); + } + break; + case S_SMS_DELIVERED: + /* SMS layer tells us the delivery has been completed */ + if (sms->smpp.transaction_mode) { + /* Send back the SUBMIT-SM response */ + LOGP(DLSMS, LOGL_INFO, "SMPP SUBMIT-SM: Success\n"); + rc = smpp_tx_submit_r(sms->smpp.esme, + sms->smpp.sequence_nr, + ESME_ROK, sms->smpp.msg_id); + } + break; + case S_SMS_SMMA: + if (!sig_sms->trans || !sig_sms->trans->subscr) { + /* SMMA without a subscriber? strange... */ + LOGP(DLSMS, LOGL_NOTICE, "SMMA without subscriber?\n"); + break; + } + + /* There's no real 1:1 match for SMMA in SMPP. However, + * an ALERT NOTIFICATION seems to be the most logical + * choice */ + alert_all_esme(smsc, sig_sms->trans->subscr, 0); + break; + } + + return rc; +} + +/*! \brief signal handler for subscriber related signals */ +static int smpp_subscr_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_subscriber *subscr = signal_data; + struct smsc *smsc = handler_data; + uint8_t smpp_avail_status; + + /* determine the smpp_avail_status depending on attach/detach */ + switch (signal) { + case S_SUBSCR_ATTACHED: + smpp_avail_status = 0; + break; + case S_SUBSCR_DETACHED: + smpp_avail_status = 2; + break; + default: + return 0; + } + + alert_all_esme(smsc, subscr, smpp_avail_status); + + return 0; +} + +/* GSM 03.38 6.2.1 Character expanding (no decode!) */ +static int gsm_7bit_expand(char *text, const uint8_t *user_data, uint8_t septet_l, uint8_t ud_hdr_ind) +{ + int i = 0; + int shift = 0; + uint8_t c; + + /* skip the user data header */ + if (ud_hdr_ind) { + /* get user data header length + 1 (for the 'user data header length'-field) */ + shift = ((user_data[0] + 1) * 8) / 7; + if ((((user_data[0] + 1) * 8) % 7) != 0) + shift++; + septet_l = septet_l - shift; + } + + for (i = 0; i < septet_l; i++) { + c = + ((user_data[((i + shift) * 7 + 7) >> 3] << + (7 - (((i + shift) * 7 + 7) & 7))) | + (user_data[((i + shift) * 7) >> 3] >> + (((i + shift) * 7) & 7))) & 0x7f; + + *(text++) = c; + } + + *text = '\0'; + + return i; +} + + +/* FIXME: libsmpp34 helpers, they should be part of libsmpp34! */ +void append_tlv(tlv_t **req_tlv, uint16_t tag, + const uint8_t *data, uint16_t len) +{ + tlv_t tlv; + + memset(&tlv, 0, sizeof(tlv)); + tlv.tag = tag; + tlv.length = len; + memcpy(tlv.value.octet, data, tlv.length); + build_tlv(req_tlv, &tlv); +} +void append_tlv_u8(tlv_t **req_tlv, uint16_t tag, uint8_t val) +{ + tlv_t tlv; + + memset(&tlv, 0, sizeof(tlv)); + tlv.tag = tag; + tlv.length = 1; + tlv.value.val08 = val; + build_tlv(req_tlv, &tlv); +} +void append_tlv_u16(tlv_t **req_tlv, uint16_t tag, uint16_t val) +{ + tlv_t tlv; + + memset(&tlv, 0, sizeof(tlv)); + tlv.tag = tag; + tlv.length = 2; + tlv.value.val16 = htons(val); + build_tlv(req_tlv, &tlv); +} + +/* Append the Osmocom vendor-specific additional TLVs to a SMPP msg */ +static void append_osmo_tlvs(tlv_t **req_tlv, const struct gsm_lchan *lchan) +{ + int idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep), + lchan->meas_rep_idx, 1); + const struct gsm_meas_rep *mr = &lchan->meas_rep[idx]; + const struct gsm_meas_rep_unidir *ul_meas = &mr->ul; + const struct gsm_meas_rep_unidir *dl_meas = &mr->dl; + + /* Osmocom vendor-specific SMPP34 extensions */ + append_tlv_u16(req_tlv, TLVID_osmo_arfcn, lchan->ts->trx->arfcn); + if (mr->flags & MEAS_REP_F_MS_L1) { + uint8_t ms_dbm; + append_tlv_u8(req_tlv, TLVID_osmo_ta, mr->ms_l1.ta); + ms_dbm = ms_pwr_dbm(lchan->ts->trx->bts->band, mr->ms_l1.pwr); + append_tlv_u8(req_tlv, TLVID_osmo_ms_l1_txpwr, ms_dbm); + } else if (mr->flags & MEAS_REP_F_MS_TO) /* Save Timing Offset field = MS Timing Offset + 63 */ + append_tlv_u8(req_tlv, TLVID_osmo_ta, mr->ms_timing_offset + 63); + + append_tlv_u16(req_tlv, TLVID_osmo_rxlev_ul, + rxlev2dbm(ul_meas->full.rx_lev)); + append_tlv_u8(req_tlv, TLVID_osmo_rxqual_ul, ul_meas->full.rx_qual); + + if (mr->flags & MEAS_REP_F_DL_VALID) { + append_tlv_u16(req_tlv, TLVID_osmo_rxlev_dl, + rxlev2dbm(dl_meas->full.rx_lev)); + append_tlv_u8(req_tlv, TLVID_osmo_rxqual_dl, + dl_meas->full.rx_qual); + } + + if (lchan->conn && lchan->conn->subscr) { + struct gsm_subscriber *subscr = lchan->conn->subscr; + size_t imei_len = strlen(subscr->equipment.imei); + if (imei_len) + append_tlv(req_tlv, TLVID_osmo_imei, + (uint8_t *)subscr->equipment.imei, imei_len+1); + } +} + +struct { + uint32_t smpp_status_code; + uint8_t gsm411_cause; +} smpp_to_gsm411_err_array[] = { + + /* Seems like most phones don't care about the failure cause, + * although some will display a different notification for + * GSM411_RP_CAUSE_MO_NUM_UNASSIGNED + * Some provoke a display of "Try again later" + * while others a more definitive "Message sending failed" + */ + + { ESME_RSYSERR, GSM411_RP_CAUSE_MO_DEST_OUT_OF_ORDER }, + { ESME_RINVDSTADR, GSM411_RP_CAUSE_MO_NUM_UNASSIGNED }, + { ESME_RMSGQFUL, GSM411_RP_CAUSE_MO_CONGESTION }, + { ESME_RINVSRCADR, GSM411_RP_CAUSE_MO_SMS_REJECTED }, + { ESME_RINVMSGID, GSM411_RP_CAUSE_INV_TRANS_REF } +}; + +static int smpp_to_gsm411_err(uint32_t smpp_status_code, int *gsm411_cause) +{ + int i; + for (i = 0; i < ARRAY_SIZE(smpp_to_gsm411_err_array); i++) { + if (smpp_to_gsm411_err_array[i].smpp_status_code != smpp_status_code) + continue; + *gsm411_cause = smpp_to_gsm411_err_array[i].gsm411_cause; + return 0; + } + return -1; +} + +static void smpp_cmd_free(struct osmo_smpp_cmd *cmd) +{ + osmo_timer_del(&cmd->response_timer); + llist_del(&cmd->list); + subscr_put(cmd->subscr); + sms_free(cmd->sms); + talloc_free(cmd); +} + +void smpp_cmd_flush_pending(struct osmo_esme *esme) +{ + struct osmo_smpp_cmd *cmd, *next; + + llist_for_each_entry_safe(cmd, next, &esme->smpp_cmd_list, list) + smpp_cmd_free(cmd); +} + +void smpp_cmd_ack(struct osmo_smpp_cmd *cmd) +{ + struct gsm_subscriber_connection *conn; + struct gsm_trans *trans; + + conn = connection_for_subscr(cmd->subscr); + if (!conn) { + LOGP(DSMPP, LOGL_ERROR, "No connection to subscriber anymore\n"); + return; + } + + trans = trans_find_by_id(conn, GSM48_PDISC_SMS, + cmd->sms->gsm411.transaction_id); + if (!trans) { + LOGP(DSMPP, LOGL_ERROR, "GSM transaction %u is gone\n", + cmd->sms->gsm411.transaction_id); + return; + } + + gsm411_send_rp_ack(trans, cmd->sms->gsm411.msg_ref); + smpp_cmd_free(cmd); +} + +void smpp_cmd_err(struct osmo_smpp_cmd *cmd, uint32_t status) +{ + struct gsm_subscriber_connection *conn; + struct gsm_trans *trans; + int gsm411_cause; + + conn = connection_for_subscr(cmd->subscr); + if (!conn) { + LOGP(DSMPP, LOGL_ERROR, "No connection to subscriber anymore\n"); + return; + } + + trans = trans_find_by_id(conn, GSM48_PDISC_SMS, + cmd->sms->gsm411.transaction_id); + if (!trans) { + LOGP(DSMPP, LOGL_ERROR, "GSM transaction %u is gone\n", + cmd->sms->gsm411.transaction_id); + return; + } + + if (smpp_to_gsm411_err(status, &gsm411_cause) < 0) + gsm411_cause = GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER; + + gsm411_send_rp_error(trans, cmd->sms->gsm411.msg_ref, gsm411_cause); + + smpp_cmd_free(cmd); +} + +static void smpp_deliver_sm_cb(void *data) +{ + smpp_cmd_err(data, ESME_RSYSERR); +} + +static int smpp_cmd_enqueue(struct osmo_esme *esme, + struct gsm_subscriber *subscr, struct gsm_sms *sms, + uint32_t sequence_number, bool *deferred) +{ + struct osmo_smpp_cmd *cmd; + + cmd = talloc_zero(esme, struct osmo_smpp_cmd); + if (!cmd) + return -1; + + cmd->sequence_nr = sequence_number; + cmd->sms = sms; + cmd->subscr = subscr_get(subscr); + + /* FIXME: No predefined value for this response_timer as specified by + * SMPP 3.4 specs, section 7.2. Make this configurable? Don't forget + * lchan keeps busy until we get a reply to this SMPP command. Too high + * value may exhaust resources. + */ + osmo_timer_setup(&cmd->response_timer, smpp_deliver_sm_cb, cmd); + osmo_timer_schedule(&cmd->response_timer, 5, 0); + llist_add_tail(&cmd->list, &esme->smpp_cmd_list); + *deferred = true; + + return 0; +} + +struct osmo_smpp_cmd *smpp_cmd_find_by_seqnum(struct osmo_esme *esme, + uint32_t sequence_nr) +{ + struct osmo_smpp_cmd *cmd; + + llist_for_each_entry(cmd, &esme->smpp_cmd_list, list) { + if (cmd->sequence_nr == sequence_nr) + return cmd; + } + return NULL; +} + +static int deliver_to_esme(struct osmo_esme *esme, struct gsm_sms *sms, + struct gsm_subscriber_connection *conn, + bool *deferred) +{ + struct deliver_sm_t deliver; + int mode, ret; + uint8_t dcs; + + memset(&deliver, 0, sizeof(deliver)); + deliver.command_length = 0; + deliver.command_id = DELIVER_SM; + deliver.command_status = ESME_ROK; + + strcpy((char *)deliver.service_type, "CMT"); + if (esme->acl && esme->acl->deliver_src_imsi) { + deliver.source_addr_ton = TON_Subscriber_Number; + deliver.source_addr_npi = NPI_Land_Mobile_E212; + snprintf((char *)deliver.source_addr, + sizeof(deliver.source_addr), "%s", + conn->subscr->imsi); + } else { + deliver.source_addr_ton = TON_Network_Specific; + deliver.source_addr_npi = NPI_ISDN_E163_E164; + snprintf((char *)deliver.source_addr, + sizeof(deliver.source_addr), "%s", + conn->subscr->extension); + } + + deliver.dest_addr_ton = sms->dst.ton; + deliver.dest_addr_npi = sms->dst.npi; + memcpy(deliver.destination_addr, sms->dst.addr, + sizeof(deliver.destination_addr)); + + deliver.esm_class = 1; /* datagram mode */ + if (sms->ud_hdr_ind) + deliver.esm_class |= 0x40; + if (sms->reply_path_req) + deliver.esm_class |= 0x80; + + deliver.protocol_id = sms->protocol_id; + deliver.priority_flag = 0; + deliver.registered_delivery = 0; + + /* Figure out SMPP DCS from TP-DCS */ + dcs = sms->data_coding_scheme; + if (smpp_determine_scheme(dcs, &deliver.data_coding, &mode) == -1) + return -1; + + /* Transparently pass on DCS via SMPP if requested */ + if (esme->acl && esme->acl->dcs_transparent) + deliver.data_coding = dcs; + + if (mode == MODE_7BIT) { + uint8_t *dst = deliver.short_message; + + /* SMPP has this strange notion of putting 7bit SMS in + * an octet-aligned mode */ + if (sms->ud_hdr_ind) { + /* length (bytes) of UDH inside UD */ + uint8_t udh_len = sms->user_data[0] + 1; + + /* copy over the UDH */ + memcpy(dst, sms->user_data, udh_len); + dst += udh_len; + deliver.sm_length = udh_len; + } + /* add decoded text */ + deliver.sm_length += gsm_7bit_expand((char *)dst, sms->user_data, sms->user_data_len, sms->ud_hdr_ind); + } else { + deliver.sm_length = sms->user_data_len; + memcpy(deliver.short_message, sms->user_data, deliver.sm_length); + deliver.sm_length = sms->user_data_len; + memcpy(deliver.short_message, sms->user_data, deliver.sm_length); + } + + if (esme->acl && esme->acl->osmocom_ext && conn->lchan) + append_osmo_tlvs(&deliver.tlv, conn->lchan); + + ret = smpp_tx_deliver(esme, &deliver); + if (ret < 0) + return ret; + + return smpp_cmd_enqueue(esme, conn->subscr, sms, + deliver.sequence_number, deferred); +} + +static struct smsc *g_smsc; + +int smpp_route_smpp_first(struct gsm_sms *sms, struct gsm_subscriber_connection *conn) +{ + return g_smsc->smpp_first; +} + +int smpp_try_deliver(struct gsm_sms *sms, + struct gsm_subscriber_connection *conn, bool *deferred) +{ + struct osmo_esme *esme; + struct osmo_smpp_addr dst; + + memset(&dst, 0, sizeof(dst)); + dst.ton = sms->dst.ton; + dst.npi = sms->dst.npi; + memcpy(dst.addr, sms->dst.addr, sizeof(dst.addr)); + + esme = smpp_route(g_smsc, &dst); + if (!esme) + return GSM411_RP_CAUSE_MO_NUM_UNASSIGNED; + + return deliver_to_esme(esme, sms, conn, deferred); +} + +struct smsc *smsc_from_vty(struct vty *v) +{ + /* FIXME: this is ugly */ + return g_smsc; +} + +/*! \brief Allocate the OpenBSC SMPP interface struct and init VTY. */ +int smpp_openbsc_alloc_init(void *ctx) +{ + g_smsc = smpp_smsc_alloc_init(ctx); + if (!g_smsc) { + LOGP(DSMPP, LOGL_FATAL, "Cannot allocate smsc struct\n"); + return -1; + } + return smpp_vty_init(); +} + +/*! \brief Launch the OpenBSC SMPP interface with the parameters set from VTY. + */ +int smpp_openbsc_start(struct gsm_network *net) +{ + int rc; + g_smsc->priv = net; + + /* If a VTY configuration has taken place, the values have been stored + * in the smsc struct. Otherwise, use the defaults (NULL -> any, 0 -> + * default SMPP port, see smpp_smsc_bind()). */ + rc = smpp_smsc_start(g_smsc, g_smsc->bind_addr, g_smsc->listen_port); + if (rc < 0) + return rc; + + rc = osmo_signal_register_handler(SS_SMS, smpp_sms_cb, g_smsc); + if (rc < 0) + return rc; + rc = osmo_signal_register_handler(SS_SUBSCR, smpp_subscr_cb, g_smsc); + if (rc < 0) + return rc; + + return 0; +} + diff --git a/src/libmsc/smpp_smsc.c b/src/libmsc/smpp_smsc.c new file mode 100644 index 000000000..48a119261 --- /dev/null +++ b/src/libmsc/smpp_smsc.c @@ -0,0 +1,1030 @@ +/* SMPP 3.4 interface, SMSC-side implementation */ +/* (C) 2012 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 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 <stdio.h> +#include <unistd.h> +#include <string.h> +#include <stdint.h> +#include <errno.h> +#include <limits.h> + +#include <sys/socket.h> +#include <netinet/in.h> + +#include <smpp34.h> +#include <smpp34_structs.h> +#include <smpp34_params.h> + +#include <osmocom/core/utils.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/write_queue.h> +#include <osmocom/core/talloc.h> + +#include "smpp_smsc.h" + +#include <openbsc/debug.h> +#include <openbsc/gsm_data.h> + +/*! \brief Ugly wrapper. libsmpp34 should do this itself! */ +#define SMPP34_UNPACK(rc, type, str, data, len) \ + memset(str, 0, sizeof(*str)); \ + rc = smpp34_unpack(type, str, data, len) + +enum emse_bind { + ESME_BIND_RX = 0x01, + ESME_BIND_TX = 0x02, +}; + +const struct value_string smpp_status_strs[] = { + { ESME_ROK, "No Error" }, + { ESME_RINVMSGLEN, "Message Length is invalid" }, + { ESME_RINVCMDLEN, "Command Length is invalid" }, + { ESME_RINVCMDID, "Invalid Command ID" }, + { ESME_RINVBNDSTS, "Incorrect BIND Status for given command" }, + { ESME_RALYBND, "ESME Already in Bound State" }, + { ESME_RINVPRTFLG, "Invalid Priority Flag" }, + { ESME_RINVREGDLVFLG, "Invalid Registered Delivery Flag" }, + { ESME_RSYSERR, "System Error" }, + { ESME_RINVSRCADR, "Invalid Source Address" }, + { ESME_RINVDSTADR, "Invalid Destination Address" }, + { ESME_RINVMSGID, "Message ID is invalid" }, + { ESME_RBINDFAIL, "Bind failed" }, + { ESME_RINVPASWD, "Invalid Password" }, + { ESME_RINVSYSID, "Invalid System ID" }, + { ESME_RCANCELFAIL, "Cancel SM Failed" }, + { ESME_RREPLACEFAIL, "Replace SM Failed" }, + { ESME_RMSGQFUL, "Message Queue Full" }, + { ESME_RINVSERTYP, "Invalid Service Type" }, + { ESME_RINVNUMDESTS, "Invalid number of destinations" }, + { ESME_RINVDLNAME, "Invalid Distribution List name" }, + { ESME_RINVDESTFLAG, "Destination flag is invalid" }, + { ESME_RINVSUBREP, "Invalid submit with replace request" }, + { ESME_RINVESMCLASS, "Invalid esm_class field data" }, + { ESME_RCNTSUBDL, "Cannot Submit to Distribution List" }, + { ESME_RSUBMITFAIL, "submit_sm or submit_multi failed" }, + { ESME_RINVSRCTON, "Invalid Source address TON" }, + { ESME_RINVSRCNPI, "Invalid Sourec address NPI" }, + { ESME_RINVDSTTON, "Invalid Destination address TON" }, + { ESME_RINVDSTNPI, "Invalid Desetination address NPI" }, + { ESME_RINVSYSTYP, "Invalid system_type field" }, + { ESME_RINVREPFLAG, "Invalid replace_if_present field" }, + { ESME_RINVNUMMSGS, "Invalid number of messages" }, + { ESME_RTHROTTLED, "Throttling error (ESME has exceeded message limits)" }, + { ESME_RINVSCHED, "Invalid Scheduled Delivery Time" }, + { ESME_RINVEXPIRY, "Invalid message validity period (Expiry time)" }, + { ESME_RINVDFTMSGID, "Predefined Message Invalid or Not Found" }, + { ESME_RX_T_APPN, "ESME Receiver Temporary App Error Code" }, + { ESME_RX_P_APPN, "ESME Receiver Permanent App Error Code" }, + { ESME_RX_R_APPN, "ESME Receiver Reject Message Error Code" }, + { ESME_RQUERYFAIL, "query_sm request failed" }, + { ESME_RINVOPTPARSTREAM,"Error in the optional part of the PDU Body" }, + { ESME_ROPTPARNOTALLWD, "Optional Parameter not allowed" }, + { ESME_RINVPARLEN, "Invalid Parameter Length" }, + { ESME_RMISSINGOPTPARAM,"Expected Optional Parameter missing" }, + { ESME_RINVOPTPARAMVAL, "Invalid Optional Parameter Value" }, + { ESME_RDELIVERYFAILURE,"Delivery Failure (used for data_sm_resp)" }, + { ESME_RUNKNOWNERR, "Unknown Error" }, + { 0, NULL } +}; + +/*! \brief compare if two SMPP addresses are equal */ +int smpp_addr_eq(const struct osmo_smpp_addr *a, + const struct osmo_smpp_addr *b) +{ + if (a->ton == b->ton && + a->npi == b->npi && + !strcmp(a->addr, b->addr)) + return 1; + + return 0; +} + + +struct osmo_smpp_acl *smpp_acl_by_system_id(struct smsc *smsc, + const char *sys_id) +{ + struct osmo_smpp_acl *acl; + + llist_for_each_entry(acl, &smsc->acl_list, list) { + if (!strcmp(acl->system_id, sys_id)) + return acl; + } + + return NULL; +} + +struct osmo_smpp_acl *smpp_acl_alloc(struct smsc *smsc, const char *sys_id) +{ + struct osmo_smpp_acl *acl; + + if (strlen(sys_id) > SMPP_SYS_ID_LEN) + return NULL; + + if (smpp_acl_by_system_id(smsc, sys_id)) + return NULL; + + acl = talloc_zero(smsc, struct osmo_smpp_acl); + if (!acl) + return NULL; + + acl->smsc = smsc; + strcpy(acl->system_id, sys_id); + INIT_LLIST_HEAD(&acl->route_list); + + llist_add_tail(&acl->list, &smsc->acl_list); + + return acl; +} + +void smpp_acl_delete(struct osmo_smpp_acl *acl) +{ + struct osmo_smpp_route *r, *r2; + + llist_del(&acl->list); + + /* kill any active ESMEs */ + if (acl->esme) { + struct osmo_esme *esme = acl->esme; + osmo_fd_unregister(&esme->wqueue.bfd); + close(esme->wqueue.bfd.fd); + esme->wqueue.bfd.fd = -1; + esme->acl = NULL; + smpp_esme_put(esme); + } + + /* delete all routes for this ACL */ + llist_for_each_entry_safe(r, r2, &acl->route_list, list) { + llist_del(&r->list); + llist_del(&r->global_list); + talloc_free(r); + } + + talloc_free(acl); +} + +static struct osmo_smpp_route *route_alloc(struct osmo_smpp_acl *acl) +{ + struct osmo_smpp_route *r; + + r = talloc_zero(acl, struct osmo_smpp_route); + if (!r) + return NULL; + + llist_add_tail(&r->list, &acl->route_list); + llist_add_tail(&r->global_list, &acl->smsc->route_list); + + return r; +} + +int smpp_route_pfx_add(struct osmo_smpp_acl *acl, + const struct osmo_smpp_addr *pfx) +{ + struct osmo_smpp_route *r; + + llist_for_each_entry(r, &acl->route_list, list) { + if (r->type == SMPP_ROUTE_PREFIX && + smpp_addr_eq(&r->u.prefix, pfx)) + return -EEXIST; + } + + r = route_alloc(acl); + if (!r) + return -ENOMEM; + r->type = SMPP_ROUTE_PREFIX; + r->acl = acl; + memcpy(&r->u.prefix, pfx, sizeof(r->u.prefix)); + + return 0; +} + +int smpp_route_pfx_del(struct osmo_smpp_acl *acl, + const struct osmo_smpp_addr *pfx) +{ + struct osmo_smpp_route *r, *r2; + + llist_for_each_entry_safe(r, r2, &acl->route_list, list) { + if (r->type == SMPP_ROUTE_PREFIX && + smpp_addr_eq(&r->u.prefix, pfx)) { + llist_del(&r->list); + talloc_free(r); + return 0; + } + } + + return -ENODEV; +} + + +/*! \brief increaes the use/reference count */ +void smpp_esme_get(struct osmo_esme *esme) +{ + esme->use++; +} + +static void esme_destroy(struct osmo_esme *esme) +{ + osmo_wqueue_clear(&esme->wqueue); + if (esme->wqueue.bfd.fd >= 0) { + osmo_fd_unregister(&esme->wqueue.bfd); + close(esme->wqueue.bfd.fd); + } + smpp_cmd_flush_pending(esme); + llist_del(&esme->list); + talloc_free(esme); +} + +static uint32_t esme_inc_seq_nr(struct osmo_esme *esme) +{ + esme->own_seq_nr++; + if (esme->own_seq_nr > 0x7fffffff) + esme->own_seq_nr = 1; + + return esme->own_seq_nr; +} + +/*! \brief decrease the use/reference count, free if it is 0 */ +void smpp_esme_put(struct osmo_esme *esme) +{ + esme->use--; + if (esme->use <= 0) + esme_destroy(esme); +} + +/*! \brief try to find a SMPP route (ESME) for given destination */ +struct osmo_esme * +smpp_route(const struct smsc *smsc, const struct osmo_smpp_addr *dest) +{ + struct osmo_smpp_route *r; + struct osmo_smpp_acl *acl = NULL; + + DEBUGP(DSMPP, "Looking up route for (%u/%u/%s)\n", + dest->ton, dest->npi, dest->addr); + + /* search for a specific route */ + llist_for_each_entry(r, &smsc->route_list, global_list) { + switch (r->type) { + case SMPP_ROUTE_PREFIX: + DEBUGP(DSMPP, "Checking prefix route (%u/%u/%s)->%s\n", + r->u.prefix.ton, r->u.prefix.npi, r->u.prefix.addr, + r->acl->system_id); + if (r->u.prefix.ton == dest->ton && + r->u.prefix.npi == dest->npi && + !strncmp(r->u.prefix.addr, dest->addr, + strlen(r->u.prefix.addr))) { + DEBUGP(DSMPP, "Found prefix route ACL\n"); + acl = r->acl; + } + break; + default: + break; + } + + if (acl) + break; + } + + if (!acl) { + /* check for default route */ + if (smsc->def_route) { + DEBUGP(DSMPP, "Using existing default route\n"); + acl = smsc->def_route; + } + } + + if (acl && acl->esme) { + struct osmo_esme *esme; + DEBUGP(DSMPP, "ACL even has ESME, we can route to it!\n"); + esme = acl->esme; + if (esme->bind_flags & ESME_BIND_RX) + return esme; + else + LOGP(DSMPP, LOGL_NOTICE, "[%s] is matching route, " + "but not bound for Rx, discarding MO SMS\n", + esme->system_id); + } + + return NULL; +} + + +/*! \brief initialize the libsmpp34 data structure for a response */ +#define INIT_RESP(type, resp, req) { \ + memset((resp), 0, sizeof(*(resp))); \ + (resp)->command_length = 0; \ + (resp)->command_id = type; \ + (resp)->command_status = ESME_ROK; \ + (resp)->sequence_number = (req)->sequence_number; \ +} + +/*! \brief pack a libsmpp34 data strcutrure and send it to the ESME */ +#define PACK_AND_SEND(esme, ptr) pack_and_send(esme, (ptr)->command_id, ptr) +static int pack_and_send(struct osmo_esme *esme, uint32_t type, void *ptr) +{ + struct msgb *msg = msgb_alloc(4096, "SMPP_Tx"); + int rc, rlen; + if (!msg) + return -ENOMEM; + + rc = smpp34_pack(type, msg->tail, msgb_tailroom(msg), &rlen, ptr); + if (rc != 0) { + LOGP(DSMPP, LOGL_ERROR, "[%s] Error during smpp34_pack(): %s\n", + esme->system_id, smpp34_strerror); + msgb_free(msg); + return -EINVAL; + } + msgb_put(msg, rlen); + + if (osmo_wqueue_enqueue(&esme->wqueue, msg) != 0) { + LOGP(DSMPP, LOGL_ERROR, "[%s] Write queue full. Dropping message\n", + esme->system_id); + msgb_free(msg); + return -EAGAIN; + } + return 0; +} + +/*! \brief transmit a generic NACK to a remote ESME */ +static int smpp_tx_gen_nack(struct osmo_esme *esme, uint32_t seq, uint32_t status) +{ + struct generic_nack_t nack; + char buf[SMALL_BUFF]; + + nack.command_length = 0; + nack.command_id = GENERIC_NACK; + nack.sequence_number = seq; + nack.command_status = status; + + LOGP(DSMPP, LOGL_ERROR, "[%s] Tx GENERIC NACK: %s\n", + esme->system_id, str_command_status(status, buf)); + + return PACK_AND_SEND(esme, &nack); +} + +/*! \brief retrieve SMPP command ID from a msgb */ +static inline uint32_t smpp_msgb_cmdid(struct msgb *msg) +{ + uint8_t *tmp = msgb_data(msg) + 4; + return ntohl(*(uint32_t *)tmp); +} + +/*! \brief retrieve SMPP sequence number from a msgb */ +static inline uint32_t smpp_msgb_seq(struct msgb *msg) +{ + uint8_t *tmp = msgb_data(msg); + return ntohl(*(uint32_t *)tmp); +} + +/*! \brief handle an incoming SMPP generic NACK */ +static int smpp_handle_gen_nack(struct osmo_esme *esme, struct msgb *msg) +{ + struct generic_nack_t nack; + char buf[SMALL_BUFF]; + int rc; + + SMPP34_UNPACK(rc, GENERIC_NACK, &nack, msgb_data(msg), + msgb_length(msg)); + if (rc < 0) { + LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n", + esme->system_id, smpp34_strerror); + return rc; + } + + LOGP(DSMPP, LOGL_ERROR, "[%s] Rx GENERIC NACK: %s\n", + esme->system_id, str_command_status(nack.command_status, buf)); + + return 0; +} + +static int _process_bind(struct osmo_esme *esme, uint8_t if_version, + uint32_t bind_flags, const char *sys_id, + const char *passwd) +{ + struct osmo_smpp_acl *acl; + + if (if_version != SMPP_VERSION) + return ESME_RSYSERR; + + if (esme->bind_flags) + return ESME_RALYBND; + + esme->smpp_version = if_version; + snprintf(esme->system_id, sizeof(esme->system_id), "%s", sys_id); + + acl = smpp_acl_by_system_id(esme->smsc, esme->system_id); + if (!esme->smsc->accept_all) { + if (!acl) { + /* This system is unknown */ + return ESME_RINVSYSID; + } else { + if (strlen(acl->passwd) && + strcmp(acl->passwd, passwd)) { + return ESME_RINVPASWD; + } + } + } + if (acl) { + esme->acl = acl; + acl->esme = esme; + } + + esme->bind_flags = bind_flags; + + return ESME_ROK; +} + + +/*! \brief handle an incoming SMPP BIND RECEIVER */ +static int smpp_handle_bind_rx(struct osmo_esme *esme, struct msgb *msg) +{ + struct bind_receiver_t bind; + struct bind_receiver_resp_t bind_r; + int rc; + + SMPP34_UNPACK(rc, BIND_RECEIVER, &bind, msgb_data(msg), + msgb_length(msg)); + if (rc < 0) { + LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n", + esme->system_id, smpp34_strerror); + return rc; + } + + INIT_RESP(BIND_TRANSMITTER_RESP, &bind_r, &bind); + + LOGP(DSMPP, LOGL_INFO, "[%s] Rx BIND Rx from (Version %02x)\n", + bind.system_id, bind.interface_version); + + rc = _process_bind(esme, bind.interface_version, ESME_BIND_RX, + (const char *)bind.system_id, (const char *)bind.password); + bind_r.command_status = rc; + + return PACK_AND_SEND(esme, &bind_r); +} + +/*! \brief handle an incoming SMPP BIND TRANSMITTER */ +static int smpp_handle_bind_tx(struct osmo_esme *esme, struct msgb *msg) +{ + struct bind_transmitter_t bind; + struct bind_transmitter_resp_t bind_r; + struct tlv_t tlv; + int rc; + + SMPP34_UNPACK(rc, BIND_TRANSMITTER, &bind, msgb_data(msg), + msgb_length(msg)); + if (rc < 0) { + LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n", + esme->system_id, smpp34_strerror); + return rc; + } + + INIT_RESP(BIND_TRANSMITTER_RESP, &bind_r, &bind); + + LOGP(DSMPP, LOGL_INFO, "[%s] Rx BIND Tx (Version %02x)\n", + bind.system_id, bind.interface_version); + + rc = _process_bind(esme, bind.interface_version, ESME_BIND_TX, + (const char *)bind.system_id, (const char *)bind.password); + bind_r.command_status = rc; + + /* build response */ + snprintf((char *)bind_r.system_id, sizeof(bind_r.system_id), "%s", + esme->smsc->system_id); + + /* add interface version TLV */ + tlv.tag = TLVID_sc_interface_version; + tlv.length = sizeof(uint8_t); + tlv.value.val16 = esme->smpp_version; + build_tlv(&bind_r.tlv, &tlv); + + return PACK_AND_SEND(esme, &bind_r); +} + +/*! \brief handle an incoming SMPP BIND TRANSCEIVER */ +static int smpp_handle_bind_trx(struct osmo_esme *esme, struct msgb *msg) +{ + struct bind_transceiver_t bind; + struct bind_transceiver_resp_t bind_r; + int rc; + + SMPP34_UNPACK(rc, BIND_TRANSCEIVER, &bind, msgb_data(msg), + msgb_length(msg)); + if (rc < 0) { + LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n", + esme->system_id, smpp34_strerror); + return rc; + } + + INIT_RESP(BIND_TRANSCEIVER_RESP, &bind_r, &bind); + + LOGP(DSMPP, LOGL_INFO, "[%s] Rx BIND Trx (Version %02x)\n", + bind.system_id, bind.interface_version); + + rc = _process_bind(esme, bind.interface_version, ESME_BIND_RX|ESME_BIND_TX, + (const char *)bind.system_id, (const char *)bind.password); + bind_r.command_status = rc; + + return PACK_AND_SEND(esme, &bind_r); +} + +/*! \brief handle an incoming SMPP UNBIND */ +static int smpp_handle_unbind(struct osmo_esme *esme, struct msgb *msg) +{ + struct unbind_t unbind; + struct unbind_resp_t unbind_r; + int rc; + + SMPP34_UNPACK(rc, UNBIND, &unbind, msgb_data(msg), + msgb_length(msg)); + if (rc < 0) { + LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n", + esme->system_id, smpp34_strerror); + return rc; + } + + INIT_RESP(UNBIND_RESP, &unbind_r, &unbind); + + LOGP(DSMPP, LOGL_INFO, "[%s] Rx UNBIND\n", esme->system_id); + + if (esme->bind_flags == 0) { + unbind_r.command_status = ESME_RINVBNDSTS; + goto err; + } + + esme->bind_flags = 0; +err: + return PACK_AND_SEND(esme, &unbind_r); +} + +/*! \brief handle an incoming SMPP ENQUIRE LINK */ +static int smpp_handle_enq_link(struct osmo_esme *esme, struct msgb *msg) +{ + struct enquire_link_t enq; + struct enquire_link_resp_t enq_r; + int rc; + + SMPP34_UNPACK(rc, ENQUIRE_LINK, &enq, msgb_data(msg), + msgb_length(msg)); + if (rc < 0) { + LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n", + esme->system_id, smpp34_strerror); + return rc; + } + + LOGP(DSMPP, LOGL_DEBUG, "[%s] Rx Enquire Link\n", esme->system_id); + + INIT_RESP(ENQUIRE_LINK_RESP, &enq_r, &enq); + + LOGP(DSMPP, LOGL_DEBUG, "[%s] Tx Enquire Link Response\n", esme->system_id); + + return PACK_AND_SEND(esme, &enq_r); +} + +/*! \brief send a SUBMIT-SM RESPONSE to a remote ESME */ +int smpp_tx_submit_r(struct osmo_esme *esme, uint32_t sequence_nr, + uint32_t command_status, char *msg_id) +{ + struct submit_sm_resp_t submit_r; + + memset(&submit_r, 0, sizeof(submit_r)); + submit_r.command_length = 0; + submit_r.command_id = SUBMIT_SM_RESP; + submit_r.command_status = command_status; + submit_r.sequence_number= sequence_nr; + snprintf((char *) submit_r.message_id, sizeof(submit_r.message_id), "%s", msg_id); + + return PACK_AND_SEND(esme, &submit_r); +} + +static const struct value_string smpp_avail_strs[] = { + { 0, "Available" }, + { 1, "Denied" }, + { 2, "Unavailable" }, + { 0, NULL } +}; + +/*! \brief send an ALERT_NOTIFICATION to a remote ESME */ +int smpp_tx_alert(struct osmo_esme *esme, uint8_t ton, uint8_t npi, + const char *addr, uint8_t avail_status) +{ + struct alert_notification_t alert; + struct tlv_t tlv; + + memset(&alert, 0, sizeof(alert)); + alert.command_length = 0; + alert.command_id = ALERT_NOTIFICATION; + alert.command_status = ESME_ROK; + alert.sequence_number = esme_inc_seq_nr(esme); + alert.source_addr_ton = ton; + alert.source_addr_npi = npi; + snprintf((char *)alert.source_addr, sizeof(alert.source_addr), "%s", addr); + + tlv.tag = TLVID_ms_availability_status; + tlv.length = sizeof(uint8_t); + tlv.value.val08 = avail_status; + build_tlv(&alert.tlv, &tlv); + + LOGP(DSMPP, LOGL_DEBUG, "[%s] Tx ALERT_NOTIFICATION (%s/%u/%u): %s\n", + esme->system_id, alert.source_addr, alert.source_addr_ton, + alert.source_addr_npi, + get_value_string(smpp_avail_strs, avail_status)); + + return PACK_AND_SEND(esme, &alert); +} + +/* \brief send a DELIVER-SM message to given ESME */ +int smpp_tx_deliver(struct osmo_esme *esme, struct deliver_sm_t *deliver) +{ + deliver->sequence_number = esme_inc_seq_nr(esme); + + return PACK_AND_SEND(esme, deliver); +} + +/*! \brief handle an incoming SMPP DELIVER-SM RESPONSE */ +static int smpp_handle_deliver_resp(struct osmo_esme *esme, struct msgb *msg) +{ + struct deliver_sm_resp_t deliver_r; + struct osmo_smpp_cmd *cmd; + int rc; + + memset(&deliver_r, 0, sizeof(deliver_r)); + SMPP34_UNPACK(rc, DELIVER_SM_RESP, &deliver_r, msgb_data(msg), + msgb_length(msg)); + if (rc < 0) { + LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n", + esme->system_id, smpp34_strerror); + return rc; + } + + cmd = smpp_cmd_find_by_seqnum(esme, deliver_r.sequence_number); + if (!cmd) { + LOGP(DSMPP, LOGL_ERROR, "[%s] Rx DELIVER-SM RESP !? (%s)\n", + esme->system_id, get_value_string(smpp_status_strs, + deliver_r.command_status)); + return -1; + } + + if (deliver_r.command_status == ESME_ROK) + smpp_cmd_ack(cmd); + else + smpp_cmd_err(cmd, deliver_r.command_status); + + LOGP(DSMPP, LOGL_INFO, "[%s] Rx DELIVER-SM RESP (%s)\n", + esme->system_id, get_value_string(smpp_status_strs, + deliver_r.command_status)); + + return 0; +} + +/*! \brief handle an incoming SMPP SUBMIT-SM */ +static int smpp_handle_submit(struct osmo_esme *esme, struct msgb *msg) +{ + struct submit_sm_t submit; + struct submit_sm_resp_t submit_r; + int rc; + + memset(&submit, 0, sizeof(submit)); + SMPP34_UNPACK(rc, SUBMIT_SM, &submit, msgb_data(msg), + msgb_length(msg)); + if (rc < 0) { + LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n", + esme->system_id, smpp34_strerror); + return rc; + } + + INIT_RESP(SUBMIT_SM_RESP, &submit_r, &submit); + + if (!(esme->bind_flags & ESME_BIND_TX)) { + submit_r.command_status = ESME_RINVBNDSTS; + return PACK_AND_SEND(esme, &submit_r); + } + + LOGP(DSMPP, LOGL_INFO, "[%s] Rx SUBMIT-SM (%s/%u/%u)\n", + esme->system_id, submit.destination_addr, + submit.dest_addr_ton, submit.dest_addr_npi); + + INIT_RESP(SUBMIT_SM_RESP, &submit_r, &submit); + + rc = handle_smpp_submit(esme, &submit, &submit_r); + if (rc == 0) + return PACK_AND_SEND(esme, &submit_r); + + return rc; +} + +/*! \brief one complete SMPP PDU from the ESME has been received */ +static int smpp_pdu_rx(struct osmo_esme *esme, struct msgb *msg __uses) +{ + uint32_t cmd_id = smpp_msgb_cmdid(msg); + int rc = 0; + + LOGP(DSMPP, LOGL_DEBUG, "[%s] smpp_pdu_rx(%s)\n", esme->system_id, + osmo_hexdump(msgb_data(msg), msgb_length(msg))); + + switch (cmd_id) { + case GENERIC_NACK: + rc = smpp_handle_gen_nack(esme, msg); + break; + case BIND_RECEIVER: + rc = smpp_handle_bind_rx(esme, msg); + break; + case BIND_TRANSMITTER: + rc = smpp_handle_bind_tx(esme, msg); + break; + case BIND_TRANSCEIVER: + rc = smpp_handle_bind_trx(esme, msg); + break; + case UNBIND: + rc = smpp_handle_unbind(esme, msg); + break; + case ENQUIRE_LINK: + rc = smpp_handle_enq_link(esme, msg); + break; + case SUBMIT_SM: + rc = smpp_handle_submit(esme, msg); + break; + case DELIVER_SM_RESP: + rc = smpp_handle_deliver_resp(esme, msg); + break; + case DELIVER_SM: + break; + case DATA_SM: + break; + case CANCEL_SM: + case QUERY_SM: + case REPLACE_SM: + case SUBMIT_MULTI: + LOGP(DSMPP, LOGL_NOTICE, "[%s] Unimplemented PDU Command " + "0x%08x\n", esme->system_id, cmd_id); + break; + default: + LOGP(DSMPP, LOGL_ERROR, "[%s] Unknown PDU Command 0x%08x\n", + esme->system_id, cmd_id); + rc = smpp_tx_gen_nack(esme, smpp_msgb_seq(msg), ESME_RINVCMDID); + break; + } + + return rc; +} + +/* This macro should be called after a call to read() in the read_cb of an + * osmo_fd to properly check for errors. + * rc is the return value of read, err_label is the label to jump to in case of + * an error. The code there should handle closing the connection. + * FIXME: This code should go in libosmocore utils.h so it can be used by other + * projects as well. + * */ +#define OSMO_FD_CHECK_READ(rc, err_label) \ + if (rc < 0) { \ + /* EINTR is a non-fatal error, just try again */ \ + if (errno == EINTR) \ + return 0; \ + goto err_label; \ + } else if (rc == 0) { \ + goto err_label; \ + } + +/* !\brief call-back when per-ESME TCP socket has some data to be read */ +static int esme_link_read_cb(struct osmo_fd *ofd) +{ + struct osmo_esme *esme = ofd->data; + uint32_t len; + uint8_t *lenptr = (uint8_t *) &len; + uint8_t *cur; + struct msgb *msg; + ssize_t rdlen, rc; + + switch (esme->read_state) { + case READ_ST_IN_LEN: + rdlen = sizeof(uint32_t) - esme->read_idx; + rc = read(ofd->fd, lenptr + esme->read_idx, rdlen); + if (rc < 0) + LOGP(DSMPP, LOGL_ERROR, "[%s] read returned %zd (%s)\n", + esme->system_id, rc, strerror(errno)); + OSMO_FD_CHECK_READ(rc, dead_socket); + + esme->read_idx += rc; + + if (esme->read_idx >= sizeof(uint32_t)) { + esme->read_len = ntohl(len); + if (esme->read_len < 8 || esme->read_len > UINT16_MAX) { + LOGP(DSMPP, LOGL_ERROR, "[%s] length invalid %u\n", + esme->system_id, esme->read_len); + goto dead_socket; + } + + msg = msgb_alloc(esme->read_len, "SMPP Rx"); + if (!msg) + return -ENOMEM; + esme->read_msg = msg; + cur = msgb_put(msg, sizeof(uint32_t)); + memcpy(cur, lenptr, sizeof(uint32_t)); + esme->read_state = READ_ST_IN_MSG; + esme->read_idx = sizeof(uint32_t); + } + break; + case READ_ST_IN_MSG: + msg = esme->read_msg; + rdlen = esme->read_len - esme->read_idx; + rc = read(ofd->fd, msg->tail, OSMO_MIN(rdlen, msgb_tailroom(msg))); + if (rc < 0) + LOGP(DSMPP, LOGL_ERROR, "[%s] read returned %zd (%s)\n", + esme->system_id, rc, strerror(errno)); + OSMO_FD_CHECK_READ(rc, dead_socket); + + esme->read_idx += rc; + msgb_put(msg, rc); + + if (esme->read_idx >= esme->read_len) { + rc = smpp_pdu_rx(esme, esme->read_msg); + msgb_free(esme->read_msg); + esme->read_msg = NULL; + esme->read_idx = 0; + esme->read_len = 0; + esme->read_state = READ_ST_IN_LEN; + } + break; + } + + return 0; +dead_socket: + msgb_free(esme->read_msg); + osmo_fd_unregister(&esme->wqueue.bfd); + close(esme->wqueue.bfd.fd); + esme->wqueue.bfd.fd = -1; + smpp_esme_put(esme); + + return 0; +} + +/* call-back of write queue once it wishes to write a message to the socket */ +static int esme_link_write_cb(struct osmo_fd *ofd, struct msgb *msg) +{ + struct osmo_esme *esme = ofd->data; + int rc; + + rc = write(ofd->fd, msgb_data(msg), msgb_length(msg)); + if (rc == 0) { + osmo_fd_unregister(&esme->wqueue.bfd); + close(esme->wqueue.bfd.fd); + esme->wqueue.bfd.fd = -1; + smpp_esme_put(esme); + } else if (rc < msgb_length(msg)) { + LOGP(DSMPP, LOGL_ERROR, "[%s] Short write\n", esme->system_id); + return -1; + } + + return 0; +} + +/* callback for already-accepted new TCP socket */ +static int link_accept_cb(struct smsc *smsc, int fd, + struct sockaddr_storage *s, socklen_t s_len) +{ + struct osmo_esme *esme = talloc_zero(smsc, struct osmo_esme); + if (!esme) { + close(fd); + return -ENOMEM; + } + + INIT_LLIST_HEAD(&esme->smpp_cmd_list); + smpp_esme_get(esme); + esme->own_seq_nr = rand(); + esme_inc_seq_nr(esme); + esme->smsc = smsc; + osmo_wqueue_init(&esme->wqueue, 10); + esme->wqueue.bfd.fd = fd; + esme->wqueue.bfd.data = esme; + esme->wqueue.bfd.when = BSC_FD_READ; + + if (osmo_fd_register(&esme->wqueue.bfd) != 0) { + close(fd); + talloc_free(esme); + return -EIO; + } + + esme->wqueue.read_cb = esme_link_read_cb; + esme->wqueue.write_cb = esme_link_write_cb; + + esme->sa_len = OSMO_MIN(sizeof(esme->sa), s_len); + memcpy(&esme->sa, s, esme->sa_len); + + llist_add_tail(&esme->list, &smsc->esme_list); + + return 0; +} + +/* callback of listening TCP socket */ +static int smsc_fd_cb(struct osmo_fd *ofd, unsigned int what) +{ + int rc; + struct sockaddr_storage sa; + socklen_t sa_len = sizeof(sa); + + rc = accept(ofd->fd, (struct sockaddr *)&sa, &sa_len); + if (rc < 0) { + LOGP(DSMPP, LOGL_ERROR, "Accept returns %d (%s)\n", + rc, strerror(errno)); + return rc; + } + return link_accept_cb(ofd->data, rc, &sa, sa_len); +} + +/*! \brief allocate and initialize an smsc struct from talloc context ctx. */ +struct smsc *smpp_smsc_alloc_init(void *ctx) +{ + struct smsc *smsc = talloc_zero(ctx, struct smsc); + + INIT_LLIST_HEAD(&smsc->esme_list); + INIT_LLIST_HEAD(&smsc->acl_list); + INIT_LLIST_HEAD(&smsc->route_list); + + smsc->listen_ofd.data = smsc; + smsc->listen_ofd.cb = smsc_fd_cb; + + return smsc; +} + +/*! \brief Set the SMPP address and port without binding. */ +int smpp_smsc_conf(struct smsc *smsc, const char *bind_addr, uint16_t port) +{ + talloc_free((void*)smsc->bind_addr); + smsc->bind_addr = NULL; + if (bind_addr) { + smsc->bind_addr = talloc_strdup(smsc, bind_addr); + if (!smsc->bind_addr) + return -ENOMEM; + } + smsc->listen_port = port; + return 0; +} + +/*! \brief Bind to given address and port and accept connections. + * \param[in] bind_addr Local IP address, may be NULL for any. + * \param[in] port TCP port number, may be 0 for default SMPP (2775). + */ +int smpp_smsc_start(struct smsc *smsc, const char *bind_addr, uint16_t port) +{ + int rc; + + /* default port for SMPP */ + if (!port) + port = 2775; + + smpp_smsc_stop(smsc); + + LOGP(DSMPP, LOGL_NOTICE, "SMPP at %s %d\n", + bind_addr? bind_addr : "0.0.0.0", port); + + rc = osmo_sock_init_ofd(&smsc->listen_ofd, AF_UNSPEC, SOCK_STREAM, + IPPROTO_TCP, bind_addr, port, + OSMO_SOCK_F_BIND); + if (rc < 0) + return rc; + + /* store new address and port */ + rc = smpp_smsc_conf(smsc, bind_addr, port); + if (rc) + smpp_smsc_stop(smsc); + return rc; +} + +/*! \brief Change a running connection to a different address/port, and upon + * error switch back to the running configuration. */ +int smpp_smsc_restart(struct smsc *smsc, const char *bind_addr, uint16_t port) +{ + int rc; + + rc = smpp_smsc_start(smsc, bind_addr, port); + if (rc) + /* if there is an error, try to re-bind to the old port */ + return smpp_smsc_start(smsc, smsc->bind_addr, smsc->listen_port); + return 0; +} + +/*! /brief Close SMPP connection. */ +void smpp_smsc_stop(struct smsc *smsc) +{ + if (smsc->listen_ofd.fd > 0) { + close(smsc->listen_ofd.fd); + smsc->listen_ofd.fd = 0; + osmo_fd_unregister(&smsc->listen_ofd); + } +} diff --git a/src/libmsc/smpp_smsc.h b/src/libmsc/smpp_smsc.h new file mode 100644 index 000000000..d8e82e421 --- /dev/null +++ b/src/libmsc/smpp_smsc.h @@ -0,0 +1,166 @@ +#ifndef _SMPP_SMSC_H +#define _SMPP_SMSC_H + +#include <sys/socket.h> +#include <netinet/in.h> + +#include <osmocom/core/utils.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/write_queue.h> +#include <osmocom/core/timer.h> + +#include <smpp34.h> +#include <smpp34_structs.h> +#include <smpp34_params.h> + +#define SMPP_SYS_ID_LEN 16 +#define SMPP_PASSWD_LEN 16 + +#define MODE_7BIT 7 +#define MODE_8BIT 8 + +enum esme_read_state { + READ_ST_IN_LEN = 0, + READ_ST_IN_MSG = 1, +}; + +struct osmo_smpp_acl; + +struct osmo_smpp_addr { + uint8_t ton; + uint8_t npi; + char addr[21+1]; +}; + +struct osmo_esme { + struct llist_head list; + struct smsc *smsc; + struct osmo_smpp_acl *acl; + int use; + + struct llist_head smpp_cmd_list; + + uint32_t own_seq_nr; + + struct osmo_wqueue wqueue; + struct sockaddr_storage sa; + socklen_t sa_len; + + enum esme_read_state read_state; + uint32_t read_len; + uint32_t read_idx; + struct msgb *read_msg; + + uint8_t smpp_version; + char system_id[SMPP_SYS_ID_LEN+1]; + + uint8_t bind_flags; +}; + +struct osmo_smpp_acl { + struct llist_head list; + struct smsc *smsc; + struct osmo_esme *esme; + char *description; + char system_id[SMPP_SYS_ID_LEN+1]; + char passwd[SMPP_PASSWD_LEN+1]; + int default_route; + int deliver_src_imsi; + int osmocom_ext; + int dcs_transparent; + struct llist_head route_list; +}; + +enum osmo_smpp_rtype { + SMPP_ROUTE_NONE, + SMPP_ROUTE_PREFIX, +}; + +struct osmo_smpp_route { + struct llist_head list; /*!< in acl.route_list */ + struct llist_head global_list; /*!< in smsc->route_list */ + struct osmo_smpp_acl *acl; + enum osmo_smpp_rtype type; + union { + struct osmo_smpp_addr prefix; + } u; +}; + +struct osmo_smpp_cmd { + struct llist_head list; + struct gsm_subscriber *subscr; + struct gsm_sms *sms; + uint32_t sequence_nr; + struct osmo_timer_list response_timer; +}; + +struct osmo_smpp_cmd *smpp_cmd_find_by_seqnum(struct osmo_esme *esme, + uint32_t sequence_number); +void smpp_cmd_ack(struct osmo_smpp_cmd *cmd); +void smpp_cmd_err(struct osmo_smpp_cmd *cmd, uint32_t status); +void smpp_cmd_flush_pending(struct osmo_esme *esme); + +struct smsc { + struct osmo_fd listen_ofd; + struct llist_head esme_list; + struct llist_head acl_list; + struct llist_head route_list; + const char *bind_addr; + uint16_t listen_port; + char system_id[SMPP_SYS_ID_LEN+1]; + int accept_all; + int smpp_first; + struct osmo_smpp_acl *def_route; + void *priv; +}; + +int smpp_addr_eq(const struct osmo_smpp_addr *a, + const struct osmo_smpp_addr *b); + +struct smsc *smpp_smsc_alloc_init(void *ctx); +int smpp_smsc_conf(struct smsc *smsc, const char *bind_addr, uint16_t port); +int smpp_smsc_start(struct smsc *smsc, const char *bind_addr, uint16_t port); +int smpp_smsc_restart(struct smsc *smsc, const char *bind_addr, uint16_t port); +void smpp_smsc_stop(struct smsc *smsc); + +void smpp_esme_get(struct osmo_esme *esme); +void smpp_esme_put(struct osmo_esme *esme); + +struct osmo_esme * +smpp_route(const struct smsc *smsc, const struct osmo_smpp_addr *dest); + +struct osmo_smpp_acl *smpp_acl_alloc(struct smsc *smsc, const char *sys_id); +struct osmo_smpp_acl *smpp_acl_by_system_id(struct smsc *smsc, + const char *sys_id); +void smpp_acl_delete(struct osmo_smpp_acl *acl); + +int smpp_tx_submit_r(struct osmo_esme *esme, uint32_t sequence_nr, + uint32_t command_status, char *msg_id); + +int smpp_tx_alert(struct osmo_esme *esme, uint8_t ton, uint8_t npi, + const char *addr, uint8_t avail_status); + +int smpp_tx_deliver(struct osmo_esme *esme, struct deliver_sm_t *deliver); + +int handle_smpp_submit(struct osmo_esme *esme, struct submit_sm_t *submit, + struct submit_sm_resp_t *submit_r); + +int smpp_route_pfx_add(struct osmo_smpp_acl *acl, + const struct osmo_smpp_addr *pfx); +int smpp_route_pfx_del(struct osmo_smpp_acl *acl, + const struct osmo_smpp_addr *pfx); + +int smpp_vty_init(void); + +int smpp_determine_scheme(uint8_t dcs, uint8_t *data_coding, int *mode); + + + +struct gsm_sms; +struct gsm_subscriber_connection; + +int smpp_route_smpp_first(struct gsm_sms *sms, + struct gsm_subscriber_connection *conn); +int smpp_try_deliver(struct gsm_sms *sms, + struct gsm_subscriber_connection *conn, bool *deferred); +#endif diff --git a/src/libmsc/smpp_utils.c b/src/libmsc/smpp_utils.c new file mode 100644 index 000000000..d0850d8c1 --- /dev/null +++ b/src/libmsc/smpp_utils.c @@ -0,0 +1,62 @@ + +/* (C) 2012-2013 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 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 "smpp_smsc.h" +#include <openbsc/debug.h> + + +int smpp_determine_scheme(uint8_t dcs, uint8_t *data_coding, int *mode) +{ + if ((dcs & 0xF0) == 0xF0) { + if (dcs & 0x04) { + /* bit 2 == 1: 8bit data */ + *data_coding = 0x02; + *mode = MODE_8BIT; + } else { + /* bit 2 == 0: default alphabet */ + *data_coding = 0x01; + *mode = MODE_7BIT; + } + } else if ((dcs & 0xE0) == 0) { + switch (dcs & 0xC) { + case 0: + *data_coding = 0x01; + *mode = MODE_7BIT; + break; + case 4: + *data_coding = 0x02; + *mode = MODE_8BIT; + break; + case 8: + *data_coding = 0x08; /* UCS-2 */ + *mode = MODE_8BIT; + break; + default: + goto unknown_mo; + } + } else { +unknown_mo: + LOGP(DLSMS, LOGL_ERROR, "SMPP MO Unknown Data Coding 0x%02x\n", dcs); + return -1; + } + + return 0; + +} diff --git a/src/libmsc/smpp_vty.c b/src/libmsc/smpp_vty.c new file mode 100644 index 000000000..13467f182 --- /dev/null +++ b/src/libmsc/smpp_vty.c @@ -0,0 +1,612 @@ +/* SMPP vty interface */ + +/* (C) 2012 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 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 <ctype.h> +#include <string.h> +#include <errno.h> +#include <netdb.h> +#include <sys/socket.h> + +#include <osmocom/vty/command.h> +#include <osmocom/vty/buffer.h> +#include <osmocom/vty/vty.h> + +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/talloc.h> + +#include <openbsc/vty.h> + +#include "smpp_smsc.h" + +struct smsc *smsc_from_vty(struct vty *v); + +static struct cmd_node smpp_node = { + SMPP_NODE, + "%s(config-smpp)# ", + 1, +}; + +static struct cmd_node esme_node = { + SMPP_ESME_NODE, + "%s(config-smpp-esme)# ", + 1, +}; + +DEFUN(cfg_smpp, cfg_smpp_cmd, + "smpp", "Configure SMPP SMS Interface") +{ + vty->node = SMPP_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_smpp_first, cfg_smpp_first_cmd, + "smpp-first", + "Try SMPP routes before the subscriber DB\n") +{ + struct smsc *smsc = smsc_from_vty(vty); + smsc->smpp_first = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_smpp_first, cfg_no_smpp_first_cmd, + "no smpp-first", + NO_STR "Try SMPP before routes before the subscriber DB\n") +{ + struct smsc *smsc = smsc_from_vty(vty); + smsc->smpp_first = 0; + return CMD_SUCCESS; +} + +static int smpp_local_tcp(struct vty *vty, + const char *bind_addr, uint16_t port) +{ + struct smsc *smsc = smsc_from_vty(vty); + int is_running = smsc->listen_ofd.fd > 0; + int same_bind_addr; + int rc; + + /* If it is not up yet, don't rebind, just set values. */ + if (!is_running) { + rc = smpp_smsc_conf(smsc, bind_addr, port); + if (rc < 0) { + vty_out(vty, "%% Cannot configure new address:port%s", + VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; + } + + rc = smpp_smsc_restart(smsc, bind_addr, port); + if (rc < 0) { + vty_out(vty, "%% Cannot bind to new port %s:%u nor to" + " old port %s:%u%s", + bind_addr? bind_addr : "0.0.0.0", + port, + smsc->bind_addr? smsc->bind_addr : "0.0.0.0", + smsc->listen_port, + VTY_NEWLINE); + return CMD_WARNING; + } + + same_bind_addr = (bind_addr == smsc->bind_addr) + || (bind_addr && smsc->bind_addr + && (strcmp(bind_addr, smsc->bind_addr) == 0)); + + if (!same_bind_addr || port != smsc->listen_port) { + vty_out(vty, "%% Cannot bind to new port %s:%u, staying on" + " old port %s:%u%s", + bind_addr? bind_addr : "0.0.0.0", + port, + smsc->bind_addr? smsc->bind_addr : "0.0.0.0", + smsc->listen_port, + VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_smpp_port, cfg_smpp_port_cmd, + "local-tcp-port <1-65535>", + "Set the local TCP port on which we listen for SMPP\n" + "TCP port number") +{ + struct smsc *smsc = smsc_from_vty(vty); + uint16_t port = atoi(argv[0]); + return smpp_local_tcp(vty, smsc->bind_addr, port); +} + +DEFUN(cfg_smpp_addr_port, cfg_smpp_addr_port_cmd, + "local-tcp-ip A.B.C.D <1-65535>", + "Set the local IP address and TCP port on which we listen for SMPP\n" + "Local IP address\n" + "TCP port number") +{ + const char *bind_addr = argv[0]; + uint16_t port = atoi(argv[1]); + return smpp_local_tcp(vty, bind_addr, port); +} + +DEFUN(cfg_smpp_sys_id, cfg_smpp_sys_id_cmd, + "system-id ID", + "Set the System ID of this SMSC\n" + "Alphanumeric SMSC System ID\n") +{ + struct smsc *smsc = smsc_from_vty(vty); + + if (strlen(argv[0])+1 > sizeof(smsc->system_id)) + return CMD_WARNING; + + strcpy(smsc->system_id, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_smpp_policy, cfg_smpp_policy_cmd, + "policy (accept-all|closed)", + "Set the authentication policy of this SMSC\n" + "Accept all SMPP connections independeint of system ID / passwd\n" + "Accept only SMPP connections from ESMEs explicitly configured") +{ + struct smsc *smsc = smsc_from_vty(vty); + + if (!strcmp(argv[0], "accept-all")) + smsc->accept_all = 1; + else + smsc->accept_all = 0; + + return CMD_SUCCESS; +} + + +static int config_write_smpp(struct vty *vty) +{ + struct smsc *smsc = smsc_from_vty(vty); + + vty_out(vty, "smpp%s", VTY_NEWLINE); + if (smsc->bind_addr) + vty_out(vty, " local-tcp-ip %s %u%s", smsc->bind_addr, + smsc->listen_port, VTY_NEWLINE); + else + vty_out(vty, " local-tcp-port %u%s", smsc->listen_port, + VTY_NEWLINE); + if (strlen(smsc->system_id) > 0) + vty_out(vty, " system-id %s%s", smsc->system_id, VTY_NEWLINE); + vty_out(vty, " policy %s%s", + smsc->accept_all ? "accept-all" : "closed", VTY_NEWLINE); + vty_out(vty, " %ssmpp-first%s", + smsc->smpp_first ? "" : "no ", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(cfg_esme, cfg_esme_cmd, + "esme NAME", + "Configure a particular ESME\n" + "Alphanumeric System ID of the ESME to be configured\n") +{ + struct smsc *smsc = smsc_from_vty(vty); + struct osmo_smpp_acl *acl; + const char *id = argv[0]; + + if (strlen(id) > 16) { + vty_out(vty, "%% System ID cannot be more than 16 " + "characters long%s", VTY_NEWLINE); + return CMD_WARNING; + } + acl = smpp_acl_by_system_id(smsc, id); + if (!acl) { + acl = smpp_acl_alloc(smsc, id); + if (!acl) + return CMD_WARNING; + } + + vty->index = acl; + vty->index_sub = &acl->description; + vty->node = SMPP_ESME_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_esme, cfg_no_esme_cmd, + "no esme NAME", + NO_STR "Remove ESME configuration\n" + "Alphanumeric System ID of the ESME to be removed\n") +{ + struct smsc *smsc = smsc_from_vty(vty); + struct osmo_smpp_acl *acl; + const char *id = argv[0]; + + acl = smpp_acl_by_system_id(smsc, id); + if (!acl) { + vty_out(vty, "%% ESME with system id '%s' unknown%s", + id, VTY_NEWLINE); + return CMD_WARNING; + } + + /* FIXME: close the connection, free data structure, etc. */ + + smpp_acl_delete(acl); + + return CMD_SUCCESS; +} + + +DEFUN(cfg_esme_passwd, cfg_esme_passwd_cmd, + "password PASSWORD", + "Set the password for this ESME\n" + "Alphanumeric password string\n") +{ + struct osmo_smpp_acl *acl = vty->index; + + if (strlen(argv[0])+1 > sizeof(acl->passwd)) + return CMD_WARNING; + + strcpy(acl->passwd, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_esme_no_passwd, cfg_esme_no_passwd_cmd, + "no password", + NO_STR "Remove the password for this ESME\n") +{ + struct osmo_smpp_acl *acl = vty->index; + + memset(acl->passwd, 0, sizeof(acl->passwd)); + + return CMD_SUCCESS; +} + +static int osmo_is_digits(const char *str) +{ + int i; + for (i = 0; i < strlen(str); i++) { + if (!isdigit(str[i])) + return 0; + } + return 1; +} + +static const struct value_string route_errstr[] = { + { -EEXIST, "Route already exists" }, + { -ENODEV, "Route does not exist" }, + { -ENOMEM, "No memory" }, + { -EINVAL, "Invalid" }, + { 0, NULL } +}; + +static const struct value_string smpp_ton_str_short[] = { + { TON_Unknown, "unknown" }, + { TON_International, "international" }, + { TON_National, "national" }, + { TON_Network_Specific, "network" }, + { TON_Subscriber_Number,"subscriber" }, + { TON_Alphanumeric, "alpha" }, + { TON_Abbreviated, "abbrev" }, + { 0, NULL } +}; + +static const struct value_string smpp_npi_str_short[] = { + { NPI_Unknown, "unknown" }, + { NPI_ISDN_E163_E164, "isdn" }, + { NPI_Data_X121, "x121" }, + { NPI_Telex_F69, "f69" }, + { NPI_Land_Mobile_E212, "e212" }, + { NPI_National, "national" }, + { NPI_Private, "private" }, + { NPI_ERMES, "ermes" }, + { NPI_Internet_IP, "ip" }, + { NPI_WAP_Client_Id, "wap" }, + { 0, NULL } +}; + + +#define SMPP_ROUTE_STR "Configure a route for MO-SMS to be sent to this ESME\n" +#define SMPP_ROUTE_P_STR SMPP_ROUTE_STR "Prefix-match route\n" +#define SMPP_PREFIX_STR "Destination number prefix\n" + +#define TON_CMD "(unknown|international|national|network|subscriber|alpha|abbrev)" +#define NPI_CMD "(unknown|isdn|x121|f69|e212|national|private|ermes|ip|wap)" +#define TON_STR "Unknown type-of-number\n" \ + "International type-of-number\n" \ + "National type-of-number\n" \ + "Network specific type-of-number\n" \ + "Subscriber type-of-number\n" \ + "Alphanumeric type-of-number\n" \ + "Abbreviated type-of-number\n" +#define NPI_STR "Unknown numbering plan\n" \ + "ISDN (E.164) numbering plan\n" \ + "X.121 numbering plan\n" \ + "F.69 numbering plan\n" \ + "E.212 numbering plan\n" \ + "National numbering plan\n" \ + "Private numbering plan\n" \ + "ERMES numbering plan\n" \ + "IP numbering plan\n" \ + "WAP numbeing plan\n" + +DEFUN(cfg_esme_route_pfx, cfg_esme_route_pfx_cmd, + "route prefix " TON_CMD " " NPI_CMD " PREFIX", + SMPP_ROUTE_P_STR TON_STR NPI_STR SMPP_PREFIX_STR) +{ + struct osmo_smpp_acl *acl = vty->index; + struct osmo_smpp_addr pfx; + int rc; + + /* check if DESTINATION is all-digits */ + if (!osmo_is_digits(argv[2])) { + vty_out(vty, "%% PREFIX has to be numeric%s", VTY_NEWLINE); + return CMD_WARNING; + } + + pfx.ton = get_string_value(smpp_ton_str_short, argv[0]); + pfx.npi = get_string_value(smpp_npi_str_short, argv[1]); + snprintf(pfx.addr, sizeof(pfx.addr), "%s", argv[2]); + + rc = smpp_route_pfx_add(acl, &pfx); + if (rc < 0) { + vty_out(vty, "%% error adding prefix route: %s%s", + get_value_string(route_errstr, rc), VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_esme_no_route_pfx, cfg_esme_no_route_pfx_cmd, + "no route prefix " TON_CMD " " NPI_CMD " PREFIX", + NO_STR SMPP_ROUTE_P_STR TON_STR NPI_STR SMPP_PREFIX_STR) +{ + struct osmo_smpp_acl *acl = vty->index; + struct osmo_smpp_addr pfx; + int rc; + + /* check if DESTINATION is all-digits */ + if (!osmo_is_digits(argv[2])) { + vty_out(vty, "%% PREFIX has to be numeric%s", VTY_NEWLINE); + return CMD_WARNING; + } + + pfx.ton = get_string_value(smpp_ton_str_short, argv[0]); + pfx.npi = get_string_value(smpp_npi_str_short, argv[1]); + snprintf(pfx.addr, sizeof(pfx.addr), "%s", argv[2]); + + rc = smpp_route_pfx_del(acl, &pfx); + if (rc < 0) { + vty_out(vty, "%% error removing prefix route: %s%s", + get_value_string(route_errstr, rc), VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; + +} + + +DEFUN(cfg_esme_defaultroute, cfg_esme_defaultroute_cmd, + "default-route", + "Set this ESME as default-route for all SMS to unknown destinations") +{ + struct osmo_smpp_acl *acl = vty->index; + + acl->default_route = 1; + + if (!acl->smsc->def_route) + acl->smsc->def_route = acl; + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_esme_defaultroute, cfg_esme_no_defaultroute_cmd, + "no default-route", NO_STR + "Remove this ESME as default-route for all SMS to unknown destinations") +{ + struct osmo_smpp_acl *acl = vty->index; + + acl->default_route = 0; + + /* remove currently active default route, if it was created by + * this ACL */ + if (acl->smsc->def_route && acl->smsc->def_route == acl) + acl->smsc->def_route = NULL; + + return CMD_SUCCESS; +} + +DEFUN(cfg_esme_del_src_imsi, cfg_esme_del_src_imsi_cmd, + "deliver-src-imsi", + "Enable the use of IMSI as source address in DELIVER") +{ + struct osmo_smpp_acl *acl = vty->index; + + acl->deliver_src_imsi = 1; + + return CMD_SUCCESS; +} + +DEFUN(cfg_esme_no_del_src_imsi, cfg_esme_no_del_src_imsi_cmd, + "no deliver-src-imsi", NO_STR + "Disable the use of IMSI as source address in DELIVER") +{ + struct osmo_smpp_acl *acl = vty->index; + + acl->deliver_src_imsi = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_esme_osmo_ext, cfg_esme_osmo_ext_cmd, + "osmocom-extensions", + "Enable the use of Osmocom SMPP Extensions for this ESME") +{ + struct osmo_smpp_acl *acl = vty->index; + + acl->osmocom_ext = 1; + + return CMD_SUCCESS; +} + +DEFUN(cfg_esme_no_osmo_ext, cfg_esme_no_osmo_ext_cmd, + "no osmocom-extensions", NO_STR + "Disable the use of Osmocom SMPP Extensions for this ESME") +{ + struct osmo_smpp_acl *acl = vty->index; + + acl->osmocom_ext = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_esme_dcs_transp, cfg_esme_dcs_transp_cmd, + "dcs-transparent", + "Enable the transparent pass-through of TP-DCS to SMPP DataCoding") +{ + struct osmo_smpp_acl *acl = vty->index; + + acl->dcs_transparent = 1; + + return CMD_SUCCESS; +} + +DEFUN(cfg_esme_no_dcs_transp, cfg_esme_no_dcs_transp_cmd, + "no dcs-transparent", NO_STR + "Disable the transparent pass-through of TP-DCS to SMPP DataCoding") +{ + struct osmo_smpp_acl *acl = vty->index; + + acl->dcs_transparent = 0; + + return CMD_SUCCESS; +} + + +static void dump_one_esme(struct vty *vty, struct osmo_esme *esme) +{ + char host[128], serv[128]; + + host[0] = 0; + serv[0] = 0; + getnameinfo((const struct sockaddr *) &esme->sa, esme->sa_len, + host, sizeof(host), serv, sizeof(serv), NI_NUMERICSERV); + + vty_out(vty, "ESME System ID: %s, Password: %s, SMPP Version %02x%s", + esme->system_id, esme->acl ? esme->acl->passwd : "", + esme->smpp_version, VTY_NEWLINE); + vty_out(vty, " Connected from: %s:%s%s", host, serv, VTY_NEWLINE); + if (esme->smsc->def_route == esme->acl) + vty_out(vty, " Is current default route%s", VTY_NEWLINE); +} + +DEFUN(show_esme, show_esme_cmd, + "show smpp esme", + SHOW_STR "SMPP Interface\n" "SMPP Extrenal SMS Entity\n") +{ + struct smsc *smsc = smsc_from_vty(vty); + struct osmo_esme *esme; + + llist_for_each_entry(esme, &smsc->esme_list, list) + dump_one_esme(vty, esme); + + return CMD_SUCCESS; +} + +static void write_esme_route_single(struct vty *vty, struct osmo_smpp_route *r) +{ + switch (r->type) { + case SMPP_ROUTE_PREFIX: + vty_out(vty, " route prefix %s ", + get_value_string(smpp_ton_str_short, r->u.prefix.ton)); + vty_out(vty, "%s %s%s", + get_value_string(smpp_npi_str_short, r->u.prefix.npi), + r->u.prefix.addr, VTY_NEWLINE); + break; + case SMPP_ROUTE_NONE: + break; + } +} + +static void config_write_esme_single(struct vty *vty, struct osmo_smpp_acl *acl) +{ + struct osmo_smpp_route *r; + + vty_out(vty, " esme %s%s", acl->system_id, VTY_NEWLINE); + if (strlen(acl->passwd)) + vty_out(vty, " password %s%s", acl->passwd, VTY_NEWLINE); + if (acl->default_route) + vty_out(vty, " default-route%s", VTY_NEWLINE); + if (acl->deliver_src_imsi) + vty_out(vty, " deliver-src-imsi%s", VTY_NEWLINE); + if (acl->osmocom_ext) + vty_out(vty, " osmocom-extensions%s", VTY_NEWLINE); + if (acl->dcs_transparent) + vty_out(vty, " dcs-transparent%s", VTY_NEWLINE); + + llist_for_each_entry(r, &acl->route_list, list) + write_esme_route_single(vty, r); +} + +static int config_write_esme(struct vty *v) +{ + struct smsc *smsc = smsc_from_vty(v); + struct osmo_smpp_acl *acl; + + llist_for_each_entry(acl, &smsc->acl_list, list) + config_write_esme_single(v, acl); + + return CMD_SUCCESS; +} + +int smpp_vty_init(void) +{ + install_node(&smpp_node, config_write_smpp); + vty_install_default(SMPP_NODE); + install_element(CONFIG_NODE, &cfg_smpp_cmd); + + install_element(SMPP_NODE, &cfg_smpp_first_cmd); + install_element(SMPP_NODE, &cfg_no_smpp_first_cmd); + install_element(SMPP_NODE, &cfg_smpp_port_cmd); + install_element(SMPP_NODE, &cfg_smpp_addr_port_cmd); + install_element(SMPP_NODE, &cfg_smpp_sys_id_cmd); + install_element(SMPP_NODE, &cfg_smpp_policy_cmd); + install_element(SMPP_NODE, &cfg_esme_cmd); + install_element(SMPP_NODE, &cfg_no_esme_cmd); + + install_node(&esme_node, config_write_esme); + vty_install_default(SMPP_ESME_NODE); + install_element(SMPP_ESME_NODE, &cfg_esme_passwd_cmd); + install_element(SMPP_ESME_NODE, &cfg_esme_no_passwd_cmd); + install_element(SMPP_ESME_NODE, &cfg_esme_route_pfx_cmd); + install_element(SMPP_ESME_NODE, &cfg_esme_no_route_pfx_cmd); + install_element(SMPP_ESME_NODE, &cfg_esme_defaultroute_cmd); + install_element(SMPP_ESME_NODE, &cfg_esme_no_defaultroute_cmd); + install_element(SMPP_ESME_NODE, &cfg_esme_del_src_imsi_cmd); + install_element(SMPP_ESME_NODE, &cfg_esme_no_del_src_imsi_cmd); + install_element(SMPP_ESME_NODE, &cfg_esme_osmo_ext_cmd); + install_element(SMPP_ESME_NODE, &cfg_esme_no_osmo_ext_cmd); + install_element(SMPP_ESME_NODE, &cfg_esme_dcs_transp_cmd); + install_element(SMPP_ESME_NODE, &cfg_esme_no_dcs_transp_cmd); + + install_element_ve(&show_esme_cmd); + + return 0; +} diff --git a/src/libmsc/sms_queue.c b/src/libmsc/sms_queue.c new file mode 100644 index 000000000..dc7f6e8c6 --- /dev/null +++ b/src/libmsc/sms_queue.c @@ -0,0 +1,544 @@ +/* SMS queue to continously attempt to deliver SMS */ +/* + * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.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 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/>. + * + */ + +/** + * The difficulty of such a queue is to send a lot of SMS without + * overloading the paging subsystem and the database and other users + * of the MSC. To make the best use we would need to know the number + * of pending paging requests, then throttle the number of SMS we + * want to send and such. + * We will start with a very simple SMS Queue and then try to speed + * things up by collecting data from other parts of the system. + */ + +#include <openbsc/sms_queue.h> +#include <openbsc/chan_alloc.h> +#include <openbsc/db.h> +#include <openbsc/debug.h> +#include <openbsc/gsm_data.h> +#include <openbsc/gsm_04_11.h> +#include <openbsc/gsm_subscriber.h> +#include <openbsc/signal.h> + +#include <osmocom/core/talloc.h> + +#include <osmocom/vty/vty.h> + +/* + * One pending SMS that we wait for. + */ +struct gsm_sms_pending { + struct llist_head entry; + + struct gsm_subscriber *subscr; + unsigned long long sms_id; + int failed_attempts; + int resend; +}; + +struct gsm_sms_queue { + struct osmo_timer_list resend_pending; + struct osmo_timer_list push_queue; + struct gsm_network *network; + int max_fail; + int max_pending; + int pending; + + struct llist_head pending_sms; + unsigned long long last_subscr_id; +}; + +static int sms_subscr_cb(unsigned int, unsigned int, void *, void *); +static int sms_sms_cb(unsigned int, unsigned int, void *, void *); + +static struct gsm_sms_pending *sms_find_pending(struct gsm_sms_queue *smsq, + struct gsm_sms *sms) +{ + struct gsm_sms_pending *pending; + + llist_for_each_entry(pending, &smsq->pending_sms, entry) { + if (pending->sms_id == sms->id) + return pending; + } + + return NULL; +} + +static int sms_is_in_pending(struct gsm_sms_queue *smsq, struct gsm_sms *sms) +{ + return sms_find_pending(smsq, sms) != NULL; +} + +static struct gsm_sms_pending *sms_subscriber_find_pending( + struct gsm_sms_queue *smsq, + struct gsm_subscriber *subscr) +{ + struct gsm_sms_pending *pending; + + llist_for_each_entry(pending, &smsq->pending_sms, entry) { + if (pending->subscr == subscr) + return pending; + } + + return NULL; +} + +static int sms_subscriber_is_pending(struct gsm_sms_queue *smsq, + struct gsm_subscriber *subscr) +{ + return sms_subscriber_find_pending(smsq, subscr) != NULL; +} + +static struct gsm_sms_pending *sms_pending_from(struct gsm_sms_queue *smsq, + struct gsm_sms *sms) +{ + struct gsm_sms_pending *pending; + + pending = talloc_zero(smsq, struct gsm_sms_pending); + if (!pending) + return NULL; + + pending->subscr = subscr_get(sms->receiver); + pending->sms_id = sms->id; + return pending; +} + +static void sms_pending_free(struct gsm_sms_pending *pending) +{ + subscr_put(pending->subscr); + llist_del(&pending->entry); + talloc_free(pending); +} + +static void sms_pending_resend(struct gsm_sms_pending *pending) +{ + struct gsm_sms_queue *smsq; + LOGP(DLSMS, LOGL_DEBUG, + "Scheduling resend of SMS %llu.\n", pending->sms_id); + + pending->resend = 1; + + smsq = pending->subscr->group->net->sms_queue; + if (osmo_timer_pending(&smsq->resend_pending)) + return; + + osmo_timer_schedule(&smsq->resend_pending, 1, 0); +} + +static void sms_pending_failed(struct gsm_sms_pending *pending, int paging_error) +{ + struct gsm_sms_queue *smsq; + + LOGP(DLSMS, LOGL_NOTICE, "Sending SMS %llu failed %d times.\n", + pending->sms_id, pending->failed_attempts); + + smsq = pending->subscr->group->net->sms_queue; + if (++pending->failed_attempts < smsq->max_fail) + return sms_pending_resend(pending); + + sms_pending_free(pending); + smsq->pending -= 1; + sms_queue_trigger(smsq); +} + +/* + * Resend all SMS that are scheduled for a resend. This is done to + * avoid an immediate failure. + */ +static void sms_resend_pending(void *_data) +{ + struct gsm_sms_pending *pending, *tmp; + struct gsm_sms_queue *smsq = _data; + + llist_for_each_entry_safe(pending, tmp, &smsq->pending_sms, entry) { + struct gsm_sms *sms; + if (!pending->resend) + continue; + + sms = db_sms_get(smsq->network, pending->sms_id); + + /* the sms is gone? Move to the next */ + if (!sms) { + sms_pending_free(pending); + smsq->pending -= 1; + sms_queue_trigger(smsq); + } else { + pending->resend = 0; + gsm411_send_sms_subscr(sms->receiver, sms); + } + } +} + +static struct gsm_sms *take_next_sms(struct gsm_sms_queue *smsq) +{ + struct gsm_sms *sms; + + sms = db_sms_get_unsent_by_subscr(smsq->network, smsq->last_subscr_id, 10); + if (sms) { + smsq->last_subscr_id = sms->receiver->id + 1; + return sms; + } + + /* need to wrap around */ + smsq->last_subscr_id = 0; + sms = db_sms_get_unsent_by_subscr(smsq->network, + smsq->last_subscr_id, 10); + if (sms) + smsq->last_subscr_id = sms->receiver->id + 1; + return sms; +} + +/** + * I will submit up to max_pending - pending SMS to the + * subsystem. + */ +static void sms_submit_pending(void *_data) +{ + struct gsm_sms_queue *smsq = _data; + int attempts = smsq->max_pending - smsq->pending; + int initialized = 0; + unsigned long long first_sub = 0; + int attempted = 0, rounds = 0; + + LOGP(DLSMS, LOGL_DEBUG, "Attempting to send %d SMS\n", attempts); + + do { + struct gsm_sms_pending *pending; + struct gsm_sms *sms; + + + sms = take_next_sms(smsq); + if (!sms) { + LOGP(DLSMS, LOGL_DEBUG, "Sending SMS done (%d attempted)\n", + attempted); + break; + } + + rounds += 1; + LOGP(DLSMS, LOGL_DEBUG, "Sending SMS round %d\n", rounds); + + /* + * This code needs to detect a loop. It assumes that no SMS + * will vanish during the time this is executed. We will remember + * the id of the first GSM subscriber we see and then will + * compare this. The Database code should make sure that we will + * see all other subscribers first before seeing this one again. + * + * It is always scary to have an infinite loop like this. + */ + if (!initialized) { + first_sub = sms->receiver->id; + initialized = 1; + } else if (first_sub == sms->receiver->id) { + LOGP(DLSMS, LOGL_DEBUG, "Sending SMS done (loop) (%d attempted)\n", + attempted); + sms_free(sms); + break; + } + + /* no need to send a pending sms */ + if (sms_is_in_pending(smsq, sms)) { + LOGP(DLSMS, LOGL_DEBUG, + "SMSqueue with pending sms: %llu. Skipping\n", sms->id); + sms_free(sms); + continue; + } + + /* no need to send a SMS with the same receiver */ + if (sms_subscriber_is_pending(smsq, sms->receiver)) { + LOGP(DLSMS, LOGL_DEBUG, + "SMSqueue with pending sub: %llu. Skipping\n", sms->receiver->id); + sms_free(sms); + continue; + } + + pending = sms_pending_from(smsq, sms); + if (!pending) { + LOGP(DLSMS, LOGL_ERROR, + "Failed to create pending SMS entry.\n"); + sms_free(sms); + continue; + } + + attempted += 1; + smsq->pending += 1; + llist_add_tail(&pending->entry, &smsq->pending_sms); + gsm411_send_sms_subscr(sms->receiver, sms); + } while (attempted < attempts && rounds < 1000); + + LOGP(DLSMS, LOGL_DEBUG, "SMSqueue added %d messages in %d rounds\n", attempted, rounds); +} + +/** + * Send the next SMS or trigger the queue + */ +static void sms_send_next(struct gsm_subscriber *subscr) +{ + struct gsm_sms_queue *smsq = subscr->group->net->sms_queue; + struct gsm_sms_pending *pending; + struct gsm_sms *sms; + + /* the subscriber should not be in the queue */ + OSMO_ASSERT(!sms_subscriber_is_pending(smsq, subscr)); + + /* check for more messages for this subscriber */ + sms = db_sms_get_unsent_for_subscr(subscr); + if (!sms) + goto no_pending_sms; + + /* No sms should be scheduled right now */ + OSMO_ASSERT(!sms_is_in_pending(smsq, sms)); + + /* Remember that we deliver this SMS and send it */ + pending = sms_pending_from(smsq, sms); + if (!pending) { + LOGP(DLSMS, LOGL_ERROR, + "Failed to create pending SMS entry.\n"); + sms_free(sms); + goto no_pending_sms; + } + + smsq->pending += 1; + llist_add_tail(&pending->entry, &smsq->pending_sms); + gsm411_send_sms_subscr(sms->receiver, sms); + return; + +no_pending_sms: + /* Try to send the SMS to avoid the queue being stuck */ + sms_submit_pending(subscr->group->net->sms_queue); +} + +/* + * Kick off the queue again. + */ +int sms_queue_trigger(struct gsm_sms_queue *smsq) +{ + LOGP(DLSMS, LOGL_DEBUG, "Triggering SMS queue\n"); + if (osmo_timer_pending(&smsq->push_queue)) + return 0; + + osmo_timer_schedule(&smsq->push_queue, 1, 0); + return 0; +} + +int sms_queue_start(struct gsm_network *network, int max_pending) +{ + struct gsm_sms_queue *sms = talloc_zero(network, struct gsm_sms_queue); + if (!sms) { + LOGP(DMSC, LOGL_ERROR, "Failed to create the SMS queue.\n"); + return -1; + } + + osmo_signal_register_handler(SS_SUBSCR, sms_subscr_cb, network); + osmo_signal_register_handler(SS_SMS, sms_sms_cb, network); + + network->sms_queue = sms; + INIT_LLIST_HEAD(&sms->pending_sms); + sms->max_fail = 1; + sms->network = network; + sms->max_pending = max_pending; + osmo_timer_setup(&sms->push_queue, sms_submit_pending, sms); + osmo_timer_setup(&sms->resend_pending, sms_resend_pending, sms); + + sms_submit_pending(sms); + + return 0; +} + +static int sub_ready_for_sm(struct gsm_network *net, struct gsm_subscriber *subscr) +{ + struct gsm_sms *sms; + struct gsm_sms_pending *pending; + struct gsm_subscriber_connection *conn; + + /* + * The code used to be very clever and tried to submit + * a SMS during the Location Updating Request. This has + * two issues: + * 1.) The Phone might not be ready yet, e.g. the C155 + * will not respond to the Submit when it is booting. + * 2.) The queue is already trying to submit SMS to the + * user and by not responding to the paging request + * we will set the LAC back to 0. We would have to + * stop the paging and move things over. + * + * We need to be careful in what we try here. + */ + + /* check if we have pending requests */ + pending = sms_subscriber_find_pending(net->sms_queue, subscr); + if (pending) { + LOGP(DMSC, LOGL_NOTICE, + "Pending paging while subscriber %llu attached.\n", + subscr->id); + return 0; + } + + conn = connection_for_subscr(subscr); + if (!conn) + return -1; + + /* Now try to deliver any pending SMS to this sub */ + sms = db_sms_get_unsent_for_subscr(subscr); + if (!sms) + return -1; + gsm411_send_sms(conn, sms); + return 0; +} + +static int sms_subscr_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_subscriber *subscr = signal_data; + + if (signal != S_SUBSCR_ATTACHED) + return 0; + + /* this is readyForSM */ + return sub_ready_for_sm(handler_data, subscr); +} + +static int sms_sms_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_network *network = handler_data; + struct sms_signal_data *sig_sms = signal_data; + struct gsm_sms_pending *pending; + struct gsm_subscriber *subscr; + + /* We got a new SMS and maybe should launch the queue again. */ + if (signal == S_SMS_SUBMITTED || signal == S_SMS_SMMA) { + /* TODO: For SMMA we might want to re-use the radio connection. */ + sms_queue_trigger(network->sms_queue); + return 0; + } + + if (!sig_sms->sms) + return -1; + + + /* + * Find the entry of our queue. The SMS subsystem will submit + * sms that are not in our control as we just have a channel + * open anyway. + */ + pending = sms_find_pending(network->sms_queue, sig_sms->sms); + if (!pending) + return 0; + + switch (signal) { + case S_SMS_DELIVERED: + /* Remember the subscriber and clear the pending entry */ + network->sms_queue->pending -= 1; + subscr = subscr_get(pending->subscr); + sms_pending_free(pending); + /* Attempt to send another SMS to this subscriber */ + sms_send_next(subscr); + subscr_put(subscr); + break; + case S_SMS_MEM_EXCEEDED: + network->sms_queue->pending -= 1; + sms_pending_free(pending); + sms_queue_trigger(network->sms_queue); + break; + case S_SMS_UNKNOWN_ERROR: + /* + * There can be many reasons for this failure. E.g. the paging + * timed out, the subscriber was not paged at all, or there was + * a protocol error. The current strategy is to try sending the + * next SMS for busy/oom and to retransmit when we have paged. + * + * When the paging expires three times we will disable the + * subscriber. If we have some kind of other transmit error we + * should flag the SMS as bad. + */ + switch (sig_sms->paging_result) { + case 0: + /* BAD SMS? */ + db_sms_inc_deliver_attempts(sig_sms->sms); + sms_pending_failed(pending, 0); + break; + case GSM_PAGING_EXPIRED: + sms_pending_failed(pending, 1); + break; + + case GSM_PAGING_OOM: + case GSM_PAGING_BUSY: + network->sms_queue->pending -= 1; + sms_pending_free(pending); + sms_queue_trigger(network->sms_queue); + break; + default: + LOGP(DLSMS, LOGL_ERROR, "Unhandled result: %d\n", + sig_sms->paging_result); + } + break; + default: + LOGP(DLSMS, LOGL_ERROR, "Unhandled result: %d\n", + sig_sms->paging_result); + } + + return 0; +} + +/* VTY helper functions */ +int sms_queue_stats(struct gsm_sms_queue *smsq, struct vty *vty) +{ + struct gsm_sms_pending *pending; + + vty_out(vty, "SMSqueue with max_pending: %d pending: %d%s", + smsq->max_pending, smsq->pending, VTY_NEWLINE); + + llist_for_each_entry(pending, &smsq->pending_sms, entry) + vty_out(vty, " SMS Pending for Subscriber: %llu SMS: %llu Failed: %d.%s", + pending->subscr->id, pending->sms_id, + pending->failed_attempts, VTY_NEWLINE); + return 0; +} + +int sms_queue_set_max_pending(struct gsm_sms_queue *smsq, int max_pending) +{ + LOGP(DLSMS, LOGL_NOTICE, "SMSqueue old max: %d new: %d\n", + smsq->max_pending, max_pending); + smsq->max_pending = max_pending; + return 0; +} + +int sms_queue_set_max_failure(struct gsm_sms_queue *smsq, int max_fail) +{ + LOGP(DLSMS, LOGL_NOTICE, "SMSqueue max failure old: %d new: %d\n", + smsq->max_fail, max_fail); + smsq->max_fail = max_fail; + return 0; +} + +int sms_queue_clear(struct gsm_sms_queue *smsq) +{ + struct gsm_sms_pending *pending, *tmp; + + llist_for_each_entry_safe(pending, tmp, &smsq->pending_sms, entry) { + LOGP(DLSMS, LOGL_NOTICE, + "SMSqueue clearing for sub %llu\n", pending->subscr->id); + sms_pending_free(pending); + } + + smsq->pending = 0; + return 0; +} diff --git a/src/libmsc/token_auth.c b/src/libmsc/token_auth.c new file mode 100644 index 000000000..5af1e980b --- /dev/null +++ b/src/libmsc/token_auth.c @@ -0,0 +1,160 @@ +/* SMS based token authentication for ad-hoc GSM networks */ + +/* (C) 2009 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 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 <stdio.h> +#include <osmocom/core/talloc.h> +#include <openbsc/signal.h> +#include <openbsc/gsm_data.h> +#include <openbsc/gsm_04_11.h> +#include <openbsc/gsm_04_08.h> +#include <openbsc/gsm_subscriber.h> +#include <openbsc/chan_alloc.h> +#include <openbsc/db.h> + +#define TOKEN_SMS_TEXT "HAR 2009 GSM. Register at http://har2009.gnumonks.org/ " \ + "Your IMSI is %s, auth token is %08X, phone no is %s." + +static char *build_sms_string(struct gsm_subscriber *subscr, uint32_t token) +{ + char *sms_str; + unsigned int len; + + len = strlen(subscr->imsi) + 8 + strlen(TOKEN_SMS_TEXT); + sms_str = talloc_size(tall_bsc_ctx, len); + if (!sms_str) + return NULL; + + snprintf(sms_str, len, TOKEN_SMS_TEXT, subscr->imsi, token, + subscr->extension); + sms_str[len-1] = '\0'; + + return sms_str; +} + +static int token_subscr_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_subscriber *subscr = signal_data; + struct gsm_sms *sms; + int rc = 0; + + if (signal != S_SUBSCR_ATTACHED) + return 0; + + if (subscr->group->net->auth_policy != GSM_AUTH_POLICY_TOKEN) + return 0; + + if (subscr->flags & GSM_SUBSCRIBER_FIRST_CONTACT) { + struct gsm_subscriber *sender; + uint32_t token; + char *sms_str; + + /* we've seen this subscriber for the first time. */ + rc = db_subscriber_alloc_token(subscr, &token); + if (rc != 0) { + rc = -EIO; + goto unauth; + } + + sms_str = build_sms_string(subscr, token); + if (!sms_str) { + rc = -ENOMEM; + goto unauth; + } + + + /* FIXME: don't use ID 1 static */ + sender = subscr_get_by_id(subscr->group, 1); + + sms = sms_from_text(subscr, sender, 0, sms_str); + + subscr_put(sender); + talloc_free(sms_str); + if (!sms) { + rc = -ENOMEM; + goto unauth; + } + + rc = gsm411_send_sms_subscr(subscr, sms); + + /* FIXME: else, delete the subscirber from database */ +unauth: + + /* make sure we don't allow him in again unless he clicks the web UI */ + subscr->authorized = 0; + db_sync_subscriber(subscr); + if (rc) { + struct gsm_subscriber_connection *conn = connection_for_subscr(subscr); + if (conn) { + uint8_t auth_rand[16]; + /* kick the subscriber off the network */ + gsm48_tx_mm_auth_req(conn, auth_rand, NULL, 0); + gsm48_tx_mm_auth_rej(conn); + /* FIXME: close the channel early ?*/ + //gsm48_send_rr_Release(lchan); + } + } + } + + return rc; +} + +static int token_sms_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct sms_signal_data *sig = signal_data; + struct gsm_sms *sms = sig->sms;; + struct gsm_subscriber_connection *conn; + uint8_t auth_rand[16]; + + + if (signal != S_SMS_DELIVERED) + return 0; + + + /* these are not the droids we've been looking for */ + if (!sms->receiver || + !(sms->receiver->flags & GSM_SUBSCRIBER_FIRST_CONTACT)) + return 0; + + + if (sms->receiver->group->net->auth_policy != GSM_AUTH_POLICY_TOKEN) + return 0; + + + conn = connection_for_subscr(sms->receiver); + if (conn) { + /* kick the subscriber off the network */ + gsm48_tx_mm_auth_req(conn, auth_rand, NULL, 0); + gsm48_tx_mm_auth_rej(conn); + /* FIXME: close the channel early ?*/ + //gsm48_send_rr_Release(lchan); + } + + return 0; +} + +//static __attribute__((constructor)) void on_dso_load_token(void) +void on_dso_load_token(void) +{ + osmo_signal_register_handler(SS_SUBSCR, token_subscr_cb, NULL); + osmo_signal_register_handler(SS_SMS, token_sms_cb, NULL); +} diff --git a/src/libmsc/transaction.c b/src/libmsc/transaction.c new file mode 100644 index 000000000..dba4bed17 --- /dev/null +++ b/src/libmsc/transaction.c @@ -0,0 +1,163 @@ +/* GSM 04.07 Transaction handling */ + +/* (C) 2009 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 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 <openbsc/transaction.h> +#include <openbsc/gsm_data.h> +#include <openbsc/mncc.h> +#include <openbsc/debug.h> +#include <osmocom/core/talloc.h> +#include <openbsc/gsm_subscriber.h> +#include <openbsc/gsm_04_08.h> +#include <openbsc/mncc.h> +#include <openbsc/paging.h> +#include <openbsc/osmo_msc.h> + +void *tall_trans_ctx; + +void _gsm48_cc_trans_free(struct gsm_trans *trans); + +struct gsm_trans *trans_find_by_id(struct gsm_subscriber_connection *conn, + uint8_t proto, uint8_t trans_id) +{ + struct gsm_trans *trans; + struct gsm_network *net = conn->network; + struct gsm_subscriber *subscr = conn->subscr; + + llist_for_each_entry(trans, &net->trans_list, entry) { + if (trans->subscr == subscr && + trans->protocol == proto && + trans->transaction_id == trans_id) + return trans; + } + return NULL; +} + +struct gsm_trans *trans_find_by_callref(struct gsm_network *net, + uint32_t callref) +{ + struct gsm_trans *trans; + + llist_for_each_entry(trans, &net->trans_list, entry) { + if (trans->callref == callref) + return trans; + } + return NULL; +} + +struct gsm_trans *trans_alloc(struct gsm_network *net, + struct gsm_subscriber *subscr, + uint8_t protocol, uint8_t trans_id, + uint32_t callref) +{ + struct gsm_trans *trans; + + DEBUGP(DCC, "subscr=%p, net=%p\n", subscr, net); + + trans = talloc_zero(tall_trans_ctx, struct gsm_trans); + if (!trans) + return NULL; + + trans->subscr = subscr; + subscr_get(trans->subscr); + + trans->protocol = protocol; + trans->transaction_id = trans_id; + trans->callref = callref; + + trans->net = net; + llist_add_tail(&trans->entry, &net->trans_list); + + return trans; +} + +void trans_free(struct gsm_trans *trans) +{ + switch (trans->protocol) { + case GSM48_PDISC_CC: + _gsm48_cc_trans_free(trans); + break; + case GSM48_PDISC_SMS: + _gsm411_sms_trans_free(trans); + break; + } + + if (trans->paging_request) { + subscr_remove_request(trans->paging_request); + trans->paging_request = NULL; + } + + if (trans->subscr) { + subscr_put(trans->subscr); + trans->subscr = NULL; + } + + llist_del(&trans->entry); + + if (trans->conn) + msc_release_connection(trans->conn); + + trans->conn = NULL; + talloc_free(trans); +} + +/* allocate an unused transaction ID for the given subscriber + * in the given protocol using the ti_flag specified */ +int trans_assign_trans_id(struct gsm_network *net, struct gsm_subscriber *subscr, + uint8_t protocol, uint8_t ti_flag) +{ + struct gsm_trans *trans; + unsigned int used_tid_bitmask = 0; + int i, j, h; + + if (ti_flag) + ti_flag = 0x8; + + /* generate bitmask of already-used TIDs for this (subscr,proto) */ + llist_for_each_entry(trans, &net->trans_list, entry) { + if (trans->subscr != subscr || + trans->protocol != protocol || + trans->transaction_id == 0xff) + continue; + used_tid_bitmask |= (1 << trans->transaction_id); + } + + /* find a new one, trying to go in a 'circular' pattern */ + for (h = 6; h > 0; h--) + if (used_tid_bitmask & (1 << (h | ti_flag))) + break; + for (i = 0; i < 7; i++) { + j = ((h + i) % 7) | ti_flag; + if ((used_tid_bitmask & (1 << j)) == 0) + return j; + } + + return -1; +} + +int trans_has_conn(const struct gsm_subscriber_connection *conn) +{ + struct gsm_trans *trans; + + llist_for_each_entry(trans, &conn->network->trans_list, entry) + if (trans->conn == conn) + return 1; + + return 0; +} diff --git a/src/libmsc/ussd.c b/src/libmsc/ussd.c new file mode 100644 index 000000000..f12c1f281 --- /dev/null +++ b/src/libmsc/ussd.c @@ -0,0 +1,95 @@ +/* Network-specific handling of mobile-originated USSDs. */ + +/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org> + * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009 by Mike Haben <michael.haben@btinternet.com> + * + * 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/>. + * + */ + +/* This module defines the network-specific handling of mobile-originated + USSD messages. */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include <openbsc/gsm_04_80.h> +#include <openbsc/gsm_subscriber.h> +#include <openbsc/debug.h> +#include <openbsc/osmo_msc.h> + +/* Declarations of USSD strings to be recognised */ +const char USSD_TEXT_OWN_NUMBER[] = "*#100#"; + +/* Forward declarations of network-specific handler functions */ +static int send_own_number(struct gsm_subscriber_connection *conn, const struct msgb *msg, const struct ss_request *req); + + +/* Entrypoint - handler function common to all mobile-originated USSDs */ +int handle_rcv_ussd(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + int rc; + struct ss_request req; + struct gsm48_hdr *gh; + + memset(&req, 0, sizeof(req)); + gh = msgb_l3(msg); + rc = gsm0480_decode_ss_request(gh, msgb_l3len(msg), &req); + if (!rc) { + DEBUGP(DMM, "Unhandled SS\n"); + rc = gsm0480_send_ussd_reject(conn, msg, &req); + msc_release_connection(conn); + return rc; + } + + /* Interrogation or releaseComplete? */ + if (req.ussd_text[0] == '\0' || req.ussd_text[0] == 0xFF) { + if (req.ss_code > 0) { + /* Assume interrogateSS or modification of it and reject */ + rc = gsm0480_send_ussd_reject(conn, msg, &req); + msc_release_connection(conn); + return rc; + } + /* Still assuming a Release-Complete and returning */ + return 0; + } + + if (!strcmp(USSD_TEXT_OWN_NUMBER, (const char *)req.ussd_text)) { + DEBUGP(DMM, "USSD: Own number requested\n"); + rc = send_own_number(conn, msg, &req); + } else { + DEBUGP(DMM, "Unhandled USSD %s\n", req.ussd_text); + rc = gsm0480_send_ussd_reject(conn, msg, &req); + } + + /* check if we can release it */ + msc_release_connection(conn); + return rc; +} + +/* A network-specific handler function */ +static int send_own_number(struct gsm_subscriber_connection *conn, const struct msgb *msg, const struct ss_request *req) +{ + char *own_number = conn->subscr->extension; + char response_string[GSM_EXTENSION_LENGTH + 20]; + + /* Need trailing CR as EOT character */ + snprintf(response_string, sizeof(response_string), "Your extension is %s\r", own_number); + return gsm0480_send_ussd_response(conn, msg, response_string, req); +} diff --git a/src/libmsc/vty_interface_layer3.c b/src/libmsc/vty_interface_layer3.c new file mode 100644 index 000000000..e50329104 --- /dev/null +++ b/src/libmsc/vty_interface_layer3.c @@ -0,0 +1,1210 @@ +/* OpenBSC interface to quagga VTY */ +/* (C) 2009 by Harald Welte <laforge@gnumonks.org> + * (C) 2009-2011 by Holger Hans Peter Freyther + * 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 <stdlib.h> +#include <limits.h> +#include <unistd.h> +#include <stdbool.h> +#include <inttypes.h> +#include <time.h> + +#include <osmocom/vty/command.h> +#include <osmocom/vty/buffer.h> +#include <osmocom/vty/vty.h> + +#include <arpa/inet.h> + +#include <osmocom/core/linuxlist.h> +#include <openbsc/gsm_data.h> +#include <openbsc/gsm_subscriber.h> +#include <openbsc/bsc_subscriber.h> +#include <openbsc/silent_call.h> +#include <openbsc/gsm_04_11.h> +#include <osmocom/abis/e1_input.h> +#include <openbsc/abis_nm.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/core/utils.h> +#include <openbsc/db.h> +#include <osmocom/core/talloc.h> +#include <openbsc/signal.h> +#include <openbsc/debug.h> +#include <openbsc/vty.h> +#include <openbsc/gsm_04_80.h> +#include <openbsc/chan_alloc.h> +#include <openbsc/sms_queue.h> +#include <openbsc/mncc_int.h> +#include <openbsc/handover.h> + +#include <osmocom/vty/logging.h> + +#include "meas_feed.h" + +extern struct gsm_network *gsmnet_from_vty(struct vty *v); + +static void subscr_dump_full_vty(struct vty *vty, struct gsm_subscriber *subscr) +{ + int rc; + int reqs; + struct gsm_auth_info ainfo; + struct gsm_auth_tuple atuple; + struct llist_head *entry; + char expire_time[200]; + + vty_out(vty, " ID: %llu, Authorized: %d%s", subscr->id, + subscr->authorized, VTY_NEWLINE); + if (strlen(subscr->name)) + vty_out(vty, " Name: '%s'%s", subscr->name, VTY_NEWLINE); + if (strlen(subscr->extension)) + vty_out(vty, " Extension: %s%s", subscr->extension, + VTY_NEWLINE); + vty_out(vty, " LAC: %d/0x%x%s", + subscr->lac, subscr->lac, VTY_NEWLINE); + vty_out(vty, " IMSI: %s%s", subscr->imsi, VTY_NEWLINE); + if (subscr->tmsi != GSM_RESERVED_TMSI) + vty_out(vty, " TMSI: %08X%s", subscr->tmsi, + VTY_NEWLINE); + + rc = db_get_authinfo_for_subscr(&ainfo, subscr); + if (!rc) { + vty_out(vty, " A3A8 algorithm id: %d%s", + ainfo.auth_algo, VTY_NEWLINE); + vty_out(vty, " A3A8 Ki: %s%s", + osmo_hexdump(ainfo.a3a8_ki, ainfo.a3a8_ki_len), + VTY_NEWLINE); + } + + rc = db_get_lastauthtuple_for_subscr(&atuple, subscr); + if (!rc) { + vty_out(vty, " A3A8 last tuple (used %d times):%s", + atuple.use_count, VTY_NEWLINE); + vty_out(vty, " seq # : %d%s", + atuple.key_seq, VTY_NEWLINE); + vty_out(vty, " RAND : %s%s", + osmo_hexdump(atuple.vec.rand, sizeof(atuple.vec.rand)), + VTY_NEWLINE); + vty_out(vty, " SRES : %s%s", + osmo_hexdump(atuple.vec.sres, sizeof(atuple.vec.sres)), + VTY_NEWLINE); + vty_out(vty, " Kc : %s%s", + osmo_hexdump(atuple.vec.kc, sizeof(atuple.vec.kc)), + VTY_NEWLINE); + } + + /* print the expiration time of a subscriber */ + strftime(expire_time, sizeof(expire_time), + "%a, %d %b %Y %T %z", localtime(&subscr->expire_lu)); + expire_time[sizeof(expire_time) - 1] = '\0'; + vty_out(vty, " Expiration Time: %s%s", expire_time, VTY_NEWLINE); + + reqs = 0; + llist_for_each(entry, &subscr->requests) + reqs += 1; + vty_out(vty, " Paging: %s paging Requests: %d%s", + subscr->is_paging ? "is" : "not", reqs, VTY_NEWLINE); + vty_out(vty, " Use count: %u%s", subscr->use_count, VTY_NEWLINE); +} + + +/* Subscriber */ +DEFUN(show_subscr_cache, + show_subscr_cache_cmd, + "show subscriber cache", + SHOW_STR "Show information about subscribers\n" + "Display contents of subscriber cache\n") +{ + struct gsm_subscriber *subscr; + + llist_for_each_entry(subscr, &active_subscribers, entry) { + vty_out(vty, " Subscriber:%s", VTY_NEWLINE); + subscr_dump_full_vty(vty, subscr); + } + + return CMD_SUCCESS; +} + +DEFUN(sms_send_pend, + sms_send_pend_cmd, + "sms send pending", + "SMS related commands\n" "SMS Sending related commands\n" + "Send all pending SMS") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_sms *sms; + int id = 0; + + while (1) { + sms = db_sms_get_unsent_by_subscr(gsmnet, id, UINT_MAX); + if (!sms) + break; + + gsm411_send_sms_subscr(sms->receiver, sms); + + id = sms->receiver->id + 1; + } + + return CMD_SUCCESS; +} + +static int _send_sms_str(struct gsm_subscriber *receiver, + struct gsm_subscriber *sender, + char *str, uint8_t tp_pid) +{ + struct gsm_sms *sms; + + sms = sms_from_text(receiver, sender, 0, str); + sms->protocol_id = tp_pid; + + /* store in database for the queue */ + if (db_sms_store(sms) != 0) { + LOGP(DLSMS, LOGL_ERROR, "Failed to store SMS in Database\n"); + sms_free(sms); + return CMD_WARNING; + } + LOGP(DLSMS, LOGL_DEBUG, "SMS stored in DB\n"); + + sms_free(sms); + sms_queue_trigger(receiver->group->net->sms_queue); + return CMD_SUCCESS; +} + +static struct gsm_subscriber *get_subscr_by_argv(struct gsm_network *gsmnet, + const char *type, + const char *id) +{ + if (!strcmp(type, "extension")) + return subscr_get_by_extension(gsmnet->subscr_group, id); + else if (!strcmp(type, "imsi")) + return subscr_get_by_imsi(gsmnet->subscr_group, id); + else if (!strcmp(type, "tmsi")) + return subscr_get_by_tmsi(gsmnet->subscr_group, atoi(id)); + else if (!strcmp(type, "id")) + return subscr_get_by_id(gsmnet->subscr_group, atoi(id)); + + return NULL; +} +#define SUBSCR_TYPES "(extension|imsi|tmsi|id)" +#define SUBSCR_HELP "Operations on a Subscriber\n" \ + "Identify subscriber by extension (phone number)\n" \ + "Identify subscriber by IMSI\n" \ + "Identify subscriber by TMSI\n" \ + "Identify subscriber by database ID\n" \ + "Identifier for the subscriber\n" + +DEFUN(show_subscr, + show_subscr_cmd, + "show subscriber " SUBSCR_TYPES " ID", + SHOW_STR SUBSCR_HELP) +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = + get_subscr_by_argv(gsmnet, argv[0], argv[1]); + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + subscr_dump_full_vty(vty, subscr); + + subscr_put(subscr); + + return CMD_SUCCESS; +} + +DEFUN(subscriber_create, + subscriber_create_cmd, + "subscriber create imsi ID", + "Operations on a Subscriber\n" \ + "Create new subscriber\n" \ + "Identify the subscriber by his IMSI\n" \ + "Identifier for the subscriber\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr; + + subscr = subscr_get_by_imsi(gsmnet->subscr_group, argv[0]); + if (subscr) + db_sync_subscriber(subscr); + else { + subscr = subscr_create_subscriber(gsmnet->subscr_group, argv[0]); + + if (!subscr) { + vty_out(vty, "%% No subscriber created for IMSI %s%s", + argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + } + + /* Show info about the created subscriber. */ + subscr_dump_full_vty(vty, subscr); + + subscr_put(subscr); + + return CMD_SUCCESS; +} + +DEFUN(subscriber_send_pending_sms, + subscriber_send_pending_sms_cmd, + "subscriber " SUBSCR_TYPES " ID sms pending-send", + SUBSCR_HELP "SMS Operations\n" "Send pending SMS\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr; + struct gsm_sms *sms; + + subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]); + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + sms = db_sms_get_unsent_by_subscr(gsmnet, subscr->id, UINT_MAX); + if (sms) + gsm411_send_sms_subscr(sms->receiver, sms); + + subscr_put(subscr); + + return CMD_SUCCESS; +} + +DEFUN(subscriber_send_sms, + subscriber_send_sms_cmd, + "subscriber " SUBSCR_TYPES " ID sms sender " SUBSCR_TYPES " SENDER_ID send .LINE", + SUBSCR_HELP "SMS Operations\n" SUBSCR_HELP "Send SMS\n" "Actual SMS Text\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]); + struct gsm_subscriber *sender = get_subscr_by_argv(gsmnet, argv[2], argv[3]); + char *str; + int rc; + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + rc = CMD_WARNING; + goto err; + } + + if (!sender) { + vty_out(vty, "%% No sender found for %s %s%s", + argv[2], argv[3], VTY_NEWLINE); + rc = CMD_WARNING; + goto err; + } + + str = argv_concat(argv, argc, 4); + rc = _send_sms_str(subscr, sender, str, 0); + talloc_free(str); + +err: + if (sender) + subscr_put(sender); + + if (subscr) + subscr_put(subscr); + + return rc; +} + +DEFUN(subscriber_silent_sms, + subscriber_silent_sms_cmd, + + "subscriber " SUBSCR_TYPES " ID silent-sms sender " SUBSCR_TYPES " SENDER_ID send .LINE", + SUBSCR_HELP "Silent SMS Operations\n" SUBSCR_HELP "Send SMS\n" "Actual SMS Text\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]); + struct gsm_subscriber *sender = get_subscr_by_argv(gsmnet, argv[2], argv[3]); + char *str; + int rc; + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + rc = CMD_WARNING; + goto err; + } + + if (!sender) { + vty_out(vty, "%% No sender found for %s %s%s", + argv[2], argv[3], VTY_NEWLINE); + rc = CMD_WARNING; + goto err; + } + + str = argv_concat(argv, argc, 4); + rc = _send_sms_str(subscr, sender, str, 64); + talloc_free(str); + +err: + if (sender) + subscr_put(sender); + + if (subscr) + subscr_put(subscr); + + return rc; +} + +#define CHAN_TYPES "(any|tch/f|tch/any|sdcch)" +#define CHAN_TYPE_HELP \ + "Any channel\n" \ + "TCH/F channel\n" \ + "Any TCH channel\n" \ + "SDCCH channel\n" + +DEFUN(subscriber_silent_call_start, + subscriber_silent_call_start_cmd, + "subscriber " SUBSCR_TYPES " ID silent-call start (any|tch/f|tch/any|sdcch)", + SUBSCR_HELP "Silent call operation\n" "Start silent call\n" + CHAN_TYPE_HELP) +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]); + int rc, type; + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[2], "tch/f")) + type = RSL_CHANNEED_TCH_F; + else if (!strcmp(argv[2], "tch/any")) + type = RSL_CHANNEED_TCH_ForH; + else if (!strcmp(argv[2], "sdcch")) + type = RSL_CHANNEED_SDCCH; + else + type = RSL_CHANNEED_ANY; /* Defaults to ANY */ + + rc = gsm_silent_call_start(subscr, vty, type); + if (rc <= 0) { + vty_out(vty, "%% Subscriber not attached%s", + VTY_NEWLINE); + subscr_put(subscr); + return CMD_WARNING; + } + + subscr_put(subscr); + + return CMD_SUCCESS; +} + +DEFUN(subscriber_silent_call_stop, + subscriber_silent_call_stop_cmd, + "subscriber " SUBSCR_TYPES " ID silent-call stop", + SUBSCR_HELP "Silent call operation\n" "Stop silent call\n" + CHAN_TYPE_HELP) +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]); + int rc; + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + rc = gsm_silent_call_stop(subscr); + if (rc < 0) { + subscr_put(subscr); + return CMD_WARNING; + } + + subscr_put(subscr); + + return CMD_SUCCESS; +} + +DEFUN(subscriber_ussd_notify, + subscriber_ussd_notify_cmd, + "subscriber " SUBSCR_TYPES " ID ussd-notify (0|1|2) .TEXT", + SUBSCR_HELP "Send a USSD notify to the subscriber\n" + "Alerting Level 0\n" + "Alerting Level 1\n" + "Alerting Level 2\n" + "Text of USSD message to send\n") +{ + char *text; + struct gsm_subscriber_connection *conn; + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]); + int level; + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + level = atoi(argv[2]); + text = argv_concat(argv, argc, 3); + if (!text) { + subscr_put(subscr); + return CMD_WARNING; + } + + conn = connection_for_subscr(subscr); + if (!conn) { + vty_out(vty, "%% An active connection is required for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + subscr_put(subscr); + talloc_free(text); + return CMD_WARNING; + } + + msc_send_ussd_notify(conn, level, text); + msc_send_ussd_release_complete(conn); + + subscr_put(subscr); + talloc_free(text); + return CMD_SUCCESS; +} + +DEFUN(ena_subscr_delete, + ena_subscr_delete_cmd, + "subscriber " SUBSCR_TYPES " ID delete", + SUBSCR_HELP "Delete subscriber in HLR\n") +{ + int rc; + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = + get_subscr_by_argv(gsmnet, argv[0], argv[1]); + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + if (subscr->use_count != 1) { + vty_out(vty, "Removing active subscriber%s", VTY_NEWLINE); + } + + rc = db_subscriber_delete(subscr); + subscr_put(subscr); + + if (rc != 0) { + vty_out(vty, "Failed to remove subscriber%s", VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(ena_subscr_expire, + ena_subscr_expire_cmd, + "subscriber " SUBSCR_TYPES " ID expire", + SUBSCR_HELP "Expire the subscriber Now\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = + get_subscr_by_argv(gsmnet, argv[0], argv[1]); + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + subscr->expire_lu = time(0); + db_sync_subscriber(subscr); + subscr_put(subscr); + + return CMD_SUCCESS; +} + +DEFUN(ena_subscr_authorized, + ena_subscr_authorized_cmd, + "subscriber " SUBSCR_TYPES " ID authorized (0|1)", + SUBSCR_HELP "(De-)Authorize subscriber in HLR\n" + "Subscriber should NOT be authorized\n" + "Subscriber should be authorized\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = + get_subscr_by_argv(gsmnet, argv[0], argv[1]); + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + subscr->authorized = atoi(argv[2]); + db_sync_subscriber(subscr); + + subscr_put(subscr); + + return CMD_SUCCESS; +} + +DEFUN(ena_subscr_name, + ena_subscr_name_cmd, + "subscriber " SUBSCR_TYPES " ID name .NAME", + SUBSCR_HELP "Set the name of the subscriber\n" + "Name of the Subscriber\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = + get_subscr_by_argv(gsmnet, argv[0], argv[1]); + char *name; + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + name = argv_concat(argv, argc, 2); + if (!name) { + subscr_put(subscr); + return CMD_WARNING; + } + + if (strlen(name) > sizeof(subscr->name)-1) { + vty_out(vty, + "%% NAME is too long, max. %zu characters are allowed%s", + sizeof(subscr->name)-1, VTY_NEWLINE); + subscr_put(subscr); + return CMD_WARNING; + } + + osmo_strlcpy(subscr->name, name, sizeof(subscr->name)); + talloc_free(name); + db_sync_subscriber(subscr); + + subscr_put(subscr); + + return CMD_SUCCESS; +} + +DEFUN(ena_subscr_extension, + ena_subscr_extension_cmd, + "subscriber " SUBSCR_TYPES " ID extension EXTENSION", + SUBSCR_HELP "Set the extension (phone number) of the subscriber\n" + "Extension (phone number)\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = + get_subscr_by_argv(gsmnet, argv[0], argv[1]); + const char *ext = argv[2]; + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + if (strlen(ext) > sizeof(subscr->extension)-1) { + vty_out(vty, + "%% EXTENSION is too long, max. %zu characters are allowed%s", + sizeof(subscr->extension)-1, VTY_NEWLINE); + return CMD_WARNING; + } + + osmo_strlcpy(subscr->extension, ext, sizeof(subscr->extension)); + db_sync_subscriber(subscr); + + subscr_put(subscr); + + return CMD_SUCCESS; +} + +DEFUN(ena_subscr_handover, + ena_subscr_handover_cmd, + "subscriber " SUBSCR_TYPES " ID handover BTS_NR", + SUBSCR_HELP "Handover the active connection\n" + "Number of the BTS to handover to\n") +{ + int ret; + struct gsm_subscriber_connection *conn; + struct gsm_bts *bts; + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = + get_subscr_by_argv(gsmnet, argv[0], argv[1]); + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s.%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + conn = connection_for_subscr(subscr); + if (!conn) { + vty_out(vty, "%% No active connection for subscriber %s %s.%s", + argv[0], argv[1], VTY_NEWLINE); + subscr_put(subscr); + return CMD_WARNING; + } + + bts = gsm_bts_num(gsmnet, atoi(argv[2])); + if (!bts) { + vty_out(vty, "%% BTS with number(%d) could not be found.%s", + atoi(argv[2]), VTY_NEWLINE); + subscr_put(subscr); + return CMD_WARNING; + } + + /* now start the handover */ + ret = bsc_handover_start(conn->lchan, bts); + if (ret != 0) { + vty_out(vty, "%% Handover failed with errno %d.%s", + ret, VTY_NEWLINE); + } else { + vty_out(vty, "%% Handover started from %s", + gsm_lchan_name(conn->lchan)); + vty_out(vty, " to %s.%s", gsm_lchan_name(conn->ho_lchan), + VTY_NEWLINE); + } + + subscr_put(subscr); + return CMD_SUCCESS; +} + +#define A3A8_ALG_TYPES "(none|xor|comp128v1)" +#define A3A8_ALG_HELP \ + "Use No A3A8 algorithm\n" \ + "Use XOR algorithm\n" \ + "Use COMP128v1 algorithm\n" + +DEFUN(ena_subscr_a3a8, + ena_subscr_a3a8_cmd, + "subscriber " SUBSCR_TYPES " ID a3a8 " A3A8_ALG_TYPES " [KI]", + SUBSCR_HELP "Set a3a8 parameters for the subscriber\n" + A3A8_ALG_HELP "Encryption Key Ki\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = + get_subscr_by_argv(gsmnet, argv[0], argv[1]); + const char *alg_str = argv[2]; + const char *ki_str = argc == 4 ? argv[3] : NULL; + struct gsm_auth_info ainfo; + int rc, minlen, maxlen; + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcasecmp(alg_str, "none")) { + ainfo.auth_algo = AUTH_ALGO_NONE; + minlen = maxlen = 0; + } else if (!strcasecmp(alg_str, "xor")) { + ainfo.auth_algo = AUTH_ALGO_XOR; + minlen = A38_XOR_MIN_KEY_LEN; + maxlen = A38_XOR_MAX_KEY_LEN; + } else if (!strcasecmp(alg_str, "comp128v1")) { + ainfo.auth_algo = AUTH_ALGO_COMP128v1; + minlen = maxlen = A38_COMP128_KEY_LEN; + } else { + /* Unknown method */ + subscr_put(subscr); + vty_out(vty, "%% Unknown auth method %s%s", + alg_str, VTY_NEWLINE); + return CMD_WARNING; + } + + if (ki_str) { + rc = osmo_hexparse(ki_str, ainfo.a3a8_ki, sizeof(ainfo.a3a8_ki)); + if ((rc > maxlen) || (rc < minlen)) { + subscr_put(subscr); + vty_out(vty, "%% Wrong Ki `%s'%s", + ki_str, VTY_NEWLINE); + return CMD_WARNING; + } + ainfo.a3a8_ki_len = rc; + } else { + ainfo.a3a8_ki_len = 0; + if (minlen) { + subscr_put(subscr); + vty_out(vty, "%% Missing Ki argument%s", VTY_NEWLINE); + return CMD_WARNING; + } + } + + rc = db_sync_authinfo_for_subscr( + ainfo.auth_algo == AUTH_ALGO_NONE ? NULL : &ainfo, + subscr); + + /* the last tuple probably invalid with the new auth settings */ + db_sync_lastauthtuple_for_subscr(NULL, subscr); + subscr_put(subscr); + + if (rc) { + vty_out(vty, "%% Operation has failed%s", VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(subscriber_purge, + subscriber_purge_cmd, + "subscriber purge-inactive", + "Operations on a Subscriber\n" "Purge subscribers with a zero use count.\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + int purged; + + purged = subscr_purge_inactive(net->subscr_group); + vty_out(vty, "%d subscriber(s) were purged.%s", purged, VTY_NEWLINE); + return CMD_SUCCESS; +} + +DEFUN(subscriber_update, + subscriber_update_cmd, + "subscriber " SUBSCR_TYPES " ID update", + SUBSCR_HELP "Update the subscriber data from the dabase.\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]); + + if (!subscr) { + vty_out(vty, "%% No subscriber found for %s %s%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + subscr_update_from_db(subscr); + subscr_put(subscr); + return CMD_SUCCESS; +} + +static int scall_cbfn(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct scall_signal_data *sigdata = signal_data; + struct vty *vty = sigdata->data; + + switch (signal) { + case S_SCALL_SUCCESS: + vty_out(vty, "%% silent call on ARFCN %u timeslot %u%s", + sigdata->conn->lchan->ts->trx->arfcn, sigdata->conn->lchan->ts->nr, + VTY_NEWLINE); + break; + case S_SCALL_EXPIRED: + vty_out(vty, "%% silent call expired paging%s", VTY_NEWLINE); + break; + } + return 0; +} + +DEFUN(show_stats, + show_stats_cmd, + "show statistics", + SHOW_STR "Display network statistics\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + + openbsc_vty_print_statistics(vty, net); + vty_out(vty, "Location Update : %lu attach, %lu normal, %lu periodic%s", + net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_ATTACH].current, + net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_NORMAL].current, + net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_PERIODIC].current, + VTY_NEWLINE); + vty_out(vty, "IMSI Detach Indications : %lu%s", + net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_DETACH].current, + VTY_NEWLINE); + vty_out(vty, "Location Updating Results: %lu completed, %lu failed%s", + net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_COMPLETED].current, + net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_FAILED].current, + VTY_NEWLINE); + vty_out(vty, "Handover : %lu attempted, %lu no_channel, %lu timeout, " + "%lu completed, %lu failed%s", + net->msc_ctrs->ctr[BSC_CTR_HANDOVER_ATTEMPTED].current, + net->msc_ctrs->ctr[BSC_CTR_HANDOVER_NO_CHANNEL].current, + net->msc_ctrs->ctr[BSC_CTR_HANDOVER_TIMEOUT].current, + net->msc_ctrs->ctr[BSC_CTR_HANDOVER_COMPLETED].current, + net->msc_ctrs->ctr[BSC_CTR_HANDOVER_FAILED].current, + VTY_NEWLINE); + vty_out(vty, "SMS MO : %lu submitted, %lu no receiver%s", + net->msc_ctrs->ctr[MSC_CTR_SMS_SUBMITTED].current, + net->msc_ctrs->ctr[MSC_CTR_SMS_NO_RECEIVER].current, + VTY_NEWLINE); + vty_out(vty, "SMS MT : %lu delivered, %lu no memory, %lu other error%s", + net->msc_ctrs->ctr[MSC_CTR_SMS_DELIVERED].current, + net->msc_ctrs->ctr[MSC_CTR_SMS_RP_ERR_MEM].current, + net->msc_ctrs->ctr[MSC_CTR_SMS_RP_ERR_OTHER].current, + VTY_NEWLINE); + vty_out(vty, "MO Calls : %lu setup, %lu connect ack%s", + net->msc_ctrs->ctr[MSC_CTR_CALL_MO_SETUP].current, + net->msc_ctrs->ctr[MSC_CTR_CALL_MO_CONNECT_ACK].current, + VTY_NEWLINE); + vty_out(vty, "MT Calls : %lu setup, %lu connect%s", + net->msc_ctrs->ctr[MSC_CTR_CALL_MT_SETUP].current, + net->msc_ctrs->ctr[MSC_CTR_CALL_MT_CONNECT].current, + VTY_NEWLINE); + return CMD_SUCCESS; +} + +DEFUN(show_smsqueue, + show_smsqueue_cmd, + "show sms-queue", + SHOW_STR "Display SMSqueue statistics\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + + sms_queue_stats(net->sms_queue, vty); + return CMD_SUCCESS; +} + +DEFUN(smsqueue_trigger, + smsqueue_trigger_cmd, + "sms-queue trigger", + "SMS Queue\n" "Trigger sending messages\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + + sms_queue_trigger(net->sms_queue); + return CMD_SUCCESS; +} + +DEFUN(smsqueue_max, + smsqueue_max_cmd, + "sms-queue max-pending <1-500>", + "SMS Queue\n" "SMS to deliver in parallel\n" "Amount\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + + sms_queue_set_max_pending(net->sms_queue, atoi(argv[0])); + return CMD_SUCCESS; +} + +DEFUN(smsqueue_clear, + smsqueue_clear_cmd, + "sms-queue clear", + "SMS Queue\n" "Clear the queue of pending SMS\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + + sms_queue_clear(net->sms_queue); + return CMD_SUCCESS; +} + +DEFUN(smsqueue_fail, + smsqueue_fail_cmd, + "sms-queue max-failure <1-500>", + "SMS Queue\n" "Maximum amount of delivery failures\n" "Amount\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + + sms_queue_set_max_failure(net->sms_queue, atoi(argv[0])); + return CMD_SUCCESS; +} + + +DEFUN(cfg_mncc_int, cfg_mncc_int_cmd, + "mncc-int", "Configure internal MNCC handler") +{ + vty->node = MNCC_INT_NODE; + + return CMD_SUCCESS; +} + +static struct cmd_node mncc_int_node = { + MNCC_INT_NODE, + "%s(config-mncc-int)# ", + 1, +}; + +static const struct value_string tchf_codec_names[] = { + { GSM48_CMODE_SPEECH_V1, "fr" }, + { GSM48_CMODE_SPEECH_EFR, "efr" }, + { GSM48_CMODE_SPEECH_AMR, "amr" }, + { 0, NULL } +}; + +static const struct value_string tchh_codec_names[] = { + { GSM48_CMODE_SPEECH_V1, "hr" }, + { GSM48_CMODE_SPEECH_AMR, "amr" }, + { 0, NULL } +}; + +static int config_write_mncc_int(struct vty *vty) +{ + uint16_t meas_port; + char *meas_host; + const char *meas_scenario; + + meas_feed_cfg_get(&meas_host, &meas_port); + meas_scenario = meas_feed_scenario_get(); + + vty_out(vty, "mncc-int%s", VTY_NEWLINE); + vty_out(vty, " default-codec tch-f %s%s", + get_value_string(tchf_codec_names, mncc_int.def_codec[0]), + VTY_NEWLINE); + vty_out(vty, " default-codec tch-h %s%s", + get_value_string(tchh_codec_names, mncc_int.def_codec[1]), + VTY_NEWLINE); + if (meas_port) + vty_out(vty, " meas-feed destination %s %u%s", + meas_host, meas_port, VTY_NEWLINE); + if (strlen(meas_scenario) > 0) + vty_out(vty, " meas-feed scenario %s%s", + meas_scenario, VTY_NEWLINE); + + + return CMD_SUCCESS; +} + +DEFUN(mnccint_def_codec_f, + mnccint_def_codec_f_cmd, + "default-codec tch-f (fr|efr|amr)", + "Set default codec\n" "Codec for TCH/F\n" + "Full-Rate\n" "Enhanced Full-Rate\n" "Adaptive Multi-Rate\n") +{ + mncc_int.def_codec[0] = get_string_value(tchf_codec_names, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(mnccint_def_codec_h, + mnccint_def_codec_h_cmd, + "default-codec tch-h (hr|amr)", + "Set default codec\n" "Codec for TCH/H\n" + "Half-Rate\n" "Adaptive Multi-Rate\n") +{ + mncc_int.def_codec[1] = get_string_value(tchh_codec_names, argv[0]); + + return CMD_SUCCESS; +} + +#define OBSOLETE_MSG "Obsolete\n" +/* this is just for backwards compatibility as the sms code moved into + * libosmocore and old config files no longer parse... */ +DEFUN_DEPRECATED(log_level_sms, log_level_sms_cmd, + "logging level sms (everything|debug|info|notice|error|fatal)", + ".HIDDEN\n" OBSOLETE_MSG OBSOLETE_MSG OBSOLETE_MSG OBSOLETE_MSG + OBSOLETE_MSG OBSOLETE_MSG OBSOLETE_MSG OBSOLETE_MSG) +{ + vty_out(vty, "%% 'logging level sms' is now called 'logging level " + "lsms', please update your config %s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +#define MEAS_STR "Measurement export related\n" +DEFUN(mnccint_meas_feed, mnccint_meas_feed_cmd, + "meas-feed destination ADDR <0-65535>", + MEAS_STR "destination\n" "address or hostname\n" "port number\n") +{ + int rc; + + rc = meas_feed_cfg_set(argv[0], atoi(argv[1])); + if (rc < 0) + return CMD_WARNING; + + return CMD_SUCCESS; +} + +DEFUN(meas_feed_scenario, meas_feed_scenario_cmd, + "meas-feed scenario NAME", + MEAS_STR "scenario\n" "Name up to 31 characters included in report\n") +{ + meas_feed_scenario_set(argv[0]); + + return CMD_SUCCESS; +} + + +DEFUN(logging_fltr_imsi, + logging_fltr_imsi_cmd, + "logging filter imsi IMSI", + LOGGING_STR FILTER_STR + "Filter log messages by IMSI\n" "IMSI to be used as filter\n") +{ + struct gsm_subscriber *vlr_subscr; + struct bsc_subscr *bsc_subscr; + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + struct log_target *tgt = osmo_log_vty2tgt(vty); + const char *imsi = argv[0]; + + if (!tgt) + return CMD_WARNING; + + vlr_subscr = subscr_get_by_imsi(gsmnet->subscr_group, imsi); + bsc_subscr = bsc_subscr_find_by_imsi(gsmnet->bsc_subscribers, imsi); + + if (!vlr_subscr && !bsc_subscr) { + vty_out(vty, "%%no subscriber with IMSI(%s)%s", + argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + log_set_filter_vlr_subscr(tgt, vlr_subscr); + log_set_filter_bsc_subscr(tgt, bsc_subscr); + return CMD_SUCCESS; +} + +static struct cmd_node nitb_node = { + NITB_NODE, + "%s(config-nitb)# ", + 1, +}; + +DEFUN(cfg_nitb, cfg_nitb_cmd, + "nitb", "Configure NITB options") +{ + vty->node = NITB_NODE; + return CMD_SUCCESS; +} + +/* Note: limit on the parameter length is set by internal vty code limitations */ +DEFUN(cfg_nitb_subscr_random, cfg_nitb_subscr_random_cmd, + "subscriber-create-on-demand random <1-9999999999> <2-9999999999>", + "Set random parameters for a new record when a subscriber is first seen.\n" + "Set random parameters for a new record when a subscriber is first seen.\n" + "Minimum for subscriber extension\n""Maximum for subscriber extension\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + uint64_t mi = atoll(argv[0]), ma = atoll(argv[1]); + gsmnet->auto_create_subscr = true; + gsmnet->auto_assign_exten = true; + if (mi >= ma) { + vty_out(vty, "Incorrect range: %s >= %s, expected MIN < MAX%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + gsmnet->ext_min = mi; + gsmnet->ext_max = ma; + return CMD_SUCCESS; +} + +DEFUN(cfg_nitb_subscr_create, cfg_nitb_subscr_create_cmd, + "subscriber-create-on-demand [no-extension]", + "Make a new record when a subscriber is first seen.\n" + "Do not automatically assign extension to created subscribers\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + gsmnet->auto_create_subscr = true; + gsmnet->auto_assign_exten = argc ? false : true; + return CMD_SUCCESS; +} + +DEFUN(cfg_nitb_no_subscr_create, cfg_nitb_no_subscr_create_cmd, + "no subscriber-create-on-demand", + NO_STR "Make a new record when a subscriber is first seen.\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + gsmnet->auto_create_subscr = false; + return CMD_SUCCESS; +} + +DEFUN(cfg_nitb_assign_tmsi, cfg_nitb_assign_tmsi_cmd, + "assign-tmsi", + "Assign TMSI during Location Updating.\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + gsmnet->avoid_tmsi = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_nitb_no_assign_tmsi, cfg_nitb_no_assign_tmsi_cmd, + "no assign-tmsi", + NO_STR "Assign TMSI during Location Updating.\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + gsmnet->avoid_tmsi = 1; + return CMD_SUCCESS; +} + +static int config_write_nitb(struct vty *vty) +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + + vty_out(vty, "nitb%s", VTY_NEWLINE); + if (!gsmnet->auto_create_subscr) + vty_out(vty, " no subscriber-create-on-demand%s", VTY_NEWLINE); + else + vty_out(vty, " subscriber-create-on-demand%s%s", + gsmnet->auto_assign_exten ? "" : " no-extension", + VTY_NEWLINE); + + if (gsmnet->ext_min != GSM_MIN_EXTEN || gsmnet->ext_max != GSM_MAX_EXTEN) + vty_out(vty, " subscriber-create-on-demand random %"PRIu64" %" + PRIu64"%s", gsmnet->ext_min, gsmnet->ext_max, + VTY_NEWLINE); + vty_out(vty, " %sassign-tmsi%s", + gsmnet->avoid_tmsi ? "no " : "", VTY_NEWLINE); + return CMD_SUCCESS; +} + +int bsc_vty_init_extra(void) +{ + osmo_signal_register_handler(SS_SCALL, scall_cbfn, NULL); + + install_element_ve(&show_subscr_cmd); + install_element_ve(&show_subscr_cache_cmd); + + install_element_ve(&sms_send_pend_cmd); + + install_element_ve(&subscriber_create_cmd); + install_element_ve(&subscriber_send_sms_cmd); + install_element_ve(&subscriber_silent_sms_cmd); + install_element_ve(&subscriber_silent_call_start_cmd); + install_element_ve(&subscriber_silent_call_stop_cmd); + install_element_ve(&subscriber_ussd_notify_cmd); + install_element_ve(&subscriber_update_cmd); + install_element_ve(&show_stats_cmd); + install_element_ve(&show_smsqueue_cmd); + install_element_ve(&logging_fltr_imsi_cmd); + + install_element(ENABLE_NODE, &ena_subscr_delete_cmd); + install_element(ENABLE_NODE, &ena_subscr_expire_cmd); + install_element(ENABLE_NODE, &ena_subscr_name_cmd); + install_element(ENABLE_NODE, &ena_subscr_extension_cmd); + install_element(ENABLE_NODE, &ena_subscr_authorized_cmd); + install_element(ENABLE_NODE, &ena_subscr_a3a8_cmd); + install_element(ENABLE_NODE, &ena_subscr_handover_cmd); + install_element(ENABLE_NODE, &subscriber_purge_cmd); + install_element(ENABLE_NODE, &smsqueue_trigger_cmd); + install_element(ENABLE_NODE, &smsqueue_max_cmd); + install_element(ENABLE_NODE, &smsqueue_clear_cmd); + install_element(ENABLE_NODE, &smsqueue_fail_cmd); + install_element(ENABLE_NODE, &subscriber_send_pending_sms_cmd); + install_element(ENABLE_NODE, &meas_feed_scenario_cmd); + + install_element(CONFIG_NODE, &cfg_mncc_int_cmd); + install_node(&mncc_int_node, config_write_mncc_int); + vty_install_default(MNCC_INT_NODE); + install_element(MNCC_INT_NODE, &mnccint_def_codec_f_cmd); + install_element(MNCC_INT_NODE, &mnccint_def_codec_h_cmd); + install_element(MNCC_INT_NODE, &mnccint_meas_feed_cmd); + install_element(MNCC_INT_NODE, &meas_feed_scenario_cmd); + + install_element(CFG_LOG_NODE, &log_level_sms_cmd); + install_element(CFG_LOG_NODE, &logging_fltr_imsi_cmd); + + + install_element(CONFIG_NODE, &cfg_nitb_cmd); + install_node(&nitb_node, config_write_nitb); + install_element(NITB_NODE, &cfg_nitb_subscr_create_cmd); + install_element(NITB_NODE, &cfg_nitb_subscr_random_cmd); + install_element(NITB_NODE, &cfg_nitb_no_subscr_create_cmd); + install_element(NITB_NODE, &cfg_nitb_assign_tmsi_cmd); + install_element(NITB_NODE, &cfg_nitb_no_assign_tmsi_cmd); + + return 0; +} |