Compare commits

...

9 Commits

Author SHA1 Message Date
Neels Hofmeyr
c1322b59db add USSD handlers to enable/disable 2G access
Change-Id: Ib266bf6ebc7692362a443efbf9b4ae69aee671d6
2019-01-03 04:21:53 +01:00
Neels Hofmeyr
081af6b1de for ussd_get_ran, show current RAT
Change-Id: I81adb1785c1a46e9153a6914ef2c699e9c90b731
2019-01-03 04:21:53 +01:00
Neels Hofmeyr
a99310bf52 35c3 hack: *#102#: add a hint to *#301#, to enable/disable 3G
Change-Id: I338504038cffd4753d21d4ccbec97bad07435481
2019-01-03 04:21:53 +01:00
Vadim Yanitskiy
6f46dfc776 SS/USSD: update configuretion example
Change-Id: I3801968d29bae917bf8669bb4ac03a9c4f3cb96c
2019-01-03 04:21:53 +01:00
Vadim Yanitskiy
be9fd43c66 SS/USSD: add IUSEs to enable / disable UMTS
Change-Id: Icf85ec8dd71813a9bbf359b9011456844f398337
2019-01-03 04:21:53 +01:00
Vadim Yanitskiy
a60ad0db1e SS/USSD: implement an IUSE for getting RAN type(s)
Change-Id: I8d95413784b039df50a4cdcac49289060f0414c6
2019-01-03 04:21:53 +01:00
Neels Hofmeyr
0f9daa66a1 add column 'last_lu_rat', show last RAN type
Change-Id: I5d73b1d928b61175d3198326706b7f49ba50e16f
2019-01-03 04:21:53 +01:00
Neels Hofmeyr
2fb33f1c57 Add DB storage for enabling / disabling arbitrary RAT types.
Parse new GSUP IE indicating the RAT types employed by the subscriber.
Store allowed RAT types in the database.
Reject LU if used RAT type is not allowed.

So far we have only GERAN-A and UTRAN-Iu, but to be future compatible,
implement an arbitrary length list of RAT types: add DB table subscriber_rat.

Backwards compatibility: if there is no entry in subscriber_rat, all RAT types
shall be allowed. As soon as there is an entry, it can either be false to
forbid a RAT or true to still allow a RAT type.

Depends: I93850710ab55a605bf61b95063a69682a2899bb1 (libosmocore)
Change-Id: I3e399ca8a85421f77a9a15e608413d1507722955
2019-01-03 04:20:02 +01:00
Neels Hofmeyr
18a1b01ca8 change format of 'last LU seen'
So far, the time string format comes from ctime_r, and we manually add "UTC" to it.

The ctime_r format is wildly chaotic IMHO, mixing weekday, day-of-month and
hour and year in very unsorted ways.

Adding "UTC" to it is non-standard.

Instead use an ISO-8601 standardized time string via strftime().

Change-Id: I6731968f05050399f4dd43b241290186e0c59e1a
2018-12-25 17:32:10 +01:00
11 changed files with 611 additions and 54 deletions

View File

@@ -24,3 +24,8 @@ hlr
bind ip 127.0.0.1
ussd route prefix *#100# internal own-msisdn
ussd route prefix *#101# internal own-imsi
ussd route prefix *#102# internal get-ran
ussd route prefix *#200# internal gsm-off
ussd route prefix *#201# internal gsm-on
ussd route prefix *#300# internal umts-off
ussd route prefix *#301# internal umts-on

View File

@@ -40,7 +40,12 @@ CREATE TABLE subscriber (
-- Timestamp of last location update seen from subscriber
-- The value is a string which encodes a UTC timestamp in granularity of seconds.
last_lu_seen TIMESTAMP default NULL
last_lu_seen TIMESTAMP default NULL,
-- Last Radio Access Type list as sent during Location Updating Request.
-- This is usually just one RAT name, but can be a comma separated list of strings
-- of all the RAT types sent during Location Updating Request.
last_lu_rat TEXT default NULL
);
CREATE TABLE subscriber_apn (
@@ -70,8 +75,17 @@ CREATE TABLE auc_3g (
ind_bitlen INTEGER NOT NULL DEFAULT 5 -- nr of index bits at lower SQN end
);
-- Optional: add subscriber entries to allow or disallow specific RATs (2G or 3G or ...).
-- If a subscriber has no entry, that means that all RATs are allowed (backwards compat).
CREATE TABLE subscriber_rat (
subscriber_id INTEGER, -- subscriber.id
rat TEXT CHECK(rat in ('GERAN-A', 'UTRAN-Iu')) NOT NULL, -- Radio Access Technology, see enum ran_type
allowed BOOLEAN CHECK(allowed in (0, 1)) NOT NULL DEFAULT 0
);
CREATE UNIQUE INDEX idx_subscr_imsi ON subscriber (imsi);
CREATE UNIQUE INDEX idx_subscr_rat_flag ON subscriber_rat (subscriber_id, rat);
-- Set HLR database schema version number
-- Note: This constant is currently duplicated in src/db.c and must be kept in sync!
PRAGMA user_version = 1;
PRAGMA user_version = 3;

121
src/db.c
View File

@@ -28,7 +28,7 @@
#include "db_bootstrap.h"
/* This constant is currently duplicated in sql/hlr.sql and must be kept in sync! */
#define CURRENT_SCHEMA_VERSION 1
#define CURRENT_SCHEMA_VERSION 3
#define SEL_COLUMNS \
"id," \
@@ -44,7 +44,8 @@
"lmsi," \
"ms_purged_cs," \
"ms_purged_ps," \
"last_lu_seen"
"last_lu_seen," \
"last_lu_rat"
static const char *stmt_sql[] = {
[DB_STMT_SEL_BY_IMSI] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE imsi = ?",
@@ -75,7 +76,14 @@ static const char *stmt_sql[] = {
"INSERT INTO auc_3g (subscriber_id, algo_id_3g, k, op, opc, ind_bitlen)"
" VALUES($subscriber_id, $algo_id_3g, $k, $op, $opc, $ind_bitlen)",
[DB_STMT_AUC_3G_DELETE] = "DELETE FROM auc_3g WHERE subscriber_id = $subscriber_id",
[DB_STMT_SET_LAST_LU_SEEN] = "UPDATE subscriber SET last_lu_seen = datetime($val, 'unixepoch') WHERE id = $subscriber_id",
[DB_STMT_SET_LAST_LU_SEEN] = "UPDATE subscriber SET last_lu_seen = datetime($val, 'unixepoch'), last_lu_rat = $rat"
" WHERE id = $subscriber_id",
[DB_STMT_UPD_RAT_FLAG] = "INSERT OR REPLACE INTO subscriber_rat (subscriber_id, rat, allowed)"
" VALUES ($subscriber_id, $rat, $allowed)",
[DB_STMT_RAT_BY_ID] =
"SELECT rat, allowed"
" FROM subscriber_rat"
" WHERE subscriber_id = $subscriber_id",
};
static void sql3_error_log_cb(void *arg, int err_code, const char *msg)
@@ -290,6 +298,85 @@ db_upgrade_v1(struct db_context *dbc)
return rc;
}
static int db_upgrade_v2(struct db_context *dbc)
{
int i;
const char *stmts[] = {
"CREATE TABLE subscriber_rat",
"CREATE UNIQUE INDEX idx_subscr_rat_flag",
NULL
};
sqlite3_stmt *stmt = NULL;
for (i = 0; i < ARRAY_SIZE(stmts); i++) {
int rc;
int j;
const char *stmt_sql = NULL;
if (stmts[i] != NULL) {
for (j = 0; j < ARRAY_SIZE(stmt_bootstrap_sql); j++) {
if (strstr(stmt_bootstrap_sql[j], stmts[i])) {
/* make sure we have a unique match, hence also not break; here */
OSMO_ASSERT(!stmt_sql);
stmt_sql = stmt_bootstrap_sql[j];
}
}
} else
stmt_sql = "PRAGMA user_version = 2";
OSMO_ASSERT(stmt_sql);
rc = sqlite3_prepare_v2(dbc->db, stmt_sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
LOGP(DDB, LOGL_ERROR, "Unable to prepare SQL statement '%s'\n", stmt_sql);
return rc;
}
rc = sqlite3_step(stmt);
db_remove_reset(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) {
LOGP(DDB, LOGL_ERROR, "Unable to update HLR database schema to version 2: '%s'\n",
stmt_sql);
return rc;
}
}
return SQLITE_DONE;
}
static int db_upgrade_v3(struct db_context *dbc)
{
sqlite3_stmt *stmt;
int rc;
const char *update_stmt_sql = "ALTER TABLE subscriber ADD COLUMN last_lu_rat TEXT default NULL";
const char *set_schema_version_sql = "PRAGMA user_version = 3";
rc = sqlite3_prepare_v2(dbc->db, update_stmt_sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
LOGP(DDB, LOGL_ERROR, "Unable to prepare SQL statement '%s'\n", update_stmt_sql);
return rc;
}
rc = sqlite3_step(stmt);
db_remove_reset(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) {
LOGP(DDB, LOGL_ERROR, "Unable to update HLR database schema to version %d\n", 1);
return rc;
}
rc = sqlite3_prepare_v2(dbc->db, set_schema_version_sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
LOGP(DDB, LOGL_ERROR, "Unable to prepare SQL statement '%s'\n", set_schema_version_sql);
return rc;
}
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
LOGP(DDB, LOGL_ERROR, "Unable to update HLR database schema to version 3\n");
db_remove_reset(stmt);
sqlite3_finalize(stmt);
return rc;
}
static int db_get_user_version(struct db_context *dbc)
{
const char *user_version_sql = "PRAGMA user_version";
@@ -390,6 +477,8 @@ struct db_context *db_open(void *ctx, const char *fname, bool enable_sqlite_logg
LOGP(DDB, LOGL_NOTICE, "Database '%s' has HLR DB schema version %d\n", dbc->fname, version);
if (version < CURRENT_SCHEMA_VERSION && allow_upgrade) {
int orig_version = version;
switch (version) {
case 0:
rc = db_upgrade_v1(dbc);
@@ -400,21 +489,39 @@ struct db_context *db_open(void *ctx, const char *fname, bool enable_sqlite_logg
}
version = 1;
/* fall through */
case 1:
rc = db_upgrade_v2(dbc);
if (rc != SQLITE_DONE) {
LOGP(DDB, LOGL_ERROR, "Failed to upgrade HLR DB schema to version 2: (rc=%d) %s\n",
rc, sqlite3_errmsg(dbc->db));
goto out_free;
}
version = 2;
/* fall through */
case 2:
rc = db_upgrade_v3(dbc);
if (rc != SQLITE_DONE) {
LOGP(DDB, LOGL_ERROR, "Failed to upgrade HLR DB schema to version 3: (rc=%d) %s\n",
rc, sqlite3_errmsg(dbc->db));
goto out_free;
}
version = 2;
/* fall through */
/* case N: ... */
default:
break;
}
LOGP(DDB, LOGL_NOTICE, "Database '%s' has been upgraded to HLR DB schema version %d\n",
dbc->fname, version);
LOGP(DDB, LOGL_NOTICE, "Database '%s' has been upgraded from HLR DB schema version %d to %d\n",
dbc->fname, orig_version, version);
}
if (version != CURRENT_SCHEMA_VERSION) {
if (version < CURRENT_SCHEMA_VERSION) {
LOGP(DDB, LOGL_NOTICE, "HLR DB schema version %d is outdated\n", version);
if (!allow_upgrade) {
LOGP(DDB, LOGL_ERROR, "Not upgrading HLR database to schema version %d; "
LOGP(DDB, LOGL_ERROR, "Not upgrading HLR database from schema version %d to %d; "
"use the --db-upgrade option to allow HLR database upgrades\n",
CURRENT_SCHEMA_VERSION);
version, CURRENT_SCHEMA_VERSION);
}
} else
LOGP(DDB, LOGL_ERROR, "HLR DB schema version %d is unknown\n", version);

View File

@@ -3,6 +3,8 @@
#include <stdbool.h>
#include <sqlite3.h>
#include <osmocom/gsm/gsm_utils.h>
struct hlr;
enum stmt_idx {
@@ -26,6 +28,8 @@ enum stmt_idx {
DB_STMT_AUC_3G_INSERT,
DB_STMT_AUC_3G_DELETE,
DB_STMT_SET_LAST_LU_SEEN,
DB_STMT_UPD_RAT_FLAG,
DB_STMT_RAT_BY_ID,
_NUM_DB_STMT
};
@@ -85,8 +89,14 @@ struct hlr_subscriber {
bool ms_purged_cs;
bool ms_purged_ps;
time_t last_lu_seen;
char last_lu_rat[128];
bool rat_types[OSMO_RAT_COUNT];
};
static const struct hlr_subscriber hlr_subscriber_empty = {
.rat_types = { true, true, true },
};
/* A format string for use with strptime(3). This format string is
* used to parse the last_lu_seen column stored in the HLR database.
* See https://sqlite.org/lang_datefunc.html, function datetime(). */
@@ -131,13 +141,18 @@ int db_subscr_get_by_id(struct db_context *dbc, int64_t id,
struct hlr_subscriber *subscr);
int db_subscr_nam(struct db_context *dbc, const char *imsi, bool nam_val, bool is_ps);
int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
const char *vlr_or_sgsn_number, bool is_ps);
const char *vlr_or_sgsn_number, bool is_ps,
const enum osmo_rat_type rat_types[], size_t rat_types_len);
int db_subscr_purge(struct db_context *dbc, const char *by_imsi,
bool purge_val, bool is_ps);
int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps);
int db_subscr_set_rat_type_flag(struct db_context *dbc, int64_t subscr_id, enum osmo_rat_type rat, bool allowed);
int db_subscr_get_rat_types(struct db_context *dbc, struct hlr_subscriber *subscr);
int hlr_subscr_rat_flag(struct hlr *hlr, struct hlr_subscriber *subscr, enum osmo_rat_type rat, bool allowed);
/*! Call sqlite3_column_text() and copy result to a char[].
* \param[out] buf A char[] used as sizeof() arg(!) and osmo_strlcpy() target.
* \param[in] stmt An sqlite3_stmt*.

View File

@@ -409,7 +409,7 @@ static int db_sel(struct db_context *dbc, sqlite3_stmt *stmt, struct hlr_subscri
if (!subscr)
goto out;
*subscr = (struct hlr_subscriber){};
*subscr = hlr_subscriber_empty;
/* obtain the various columns */
subscr->id = sqlite3_column_int64(stmt, 0);
@@ -440,10 +440,14 @@ static int db_sel(struct db_context *dbc, sqlite3_stmt *stmt, struct hlr_subscri
}
}
}
copy_sqlite3_text_to_buf(subscr->last_lu_rat, stmt, 14);
out:
db_remove_reset(stmt);
if (ret == 0)
db_subscr_get_rat_types(dbc, subscr);
switch (ret) {
case 0:
*err = NULL;
@@ -595,11 +599,14 @@ out:
* -EIO on database errors.
*/
int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
const char *vlr_or_sgsn_number, bool is_ps)
const char *vlr_or_sgsn_number, bool is_ps,
const enum osmo_rat_type rat_types[], size_t rat_types_len)
{
sqlite3_stmt *stmt;
int rc, ret = 0;
struct timespec localtime;
char rat_types_str[128] = "";
int i;
stmt = dbc->stmt[is_ps ? DB_STMT_UPD_SGSN_BY_ID
: DB_STMT_UPD_VLR_BY_ID];
@@ -653,6 +660,21 @@ int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
goto out;
}
for (i = 0; i < rat_types_len; i++) {
char *pos = rat_types_str + strnlen(rat_types_str, sizeof(rat_types_str));
int len = sizeof(rat_types_str) - (pos - rat_types_str);
rc = snprintf(pos, len, "%s%s", pos == rat_types_str ? "" : ",", osmo_rat_type_name(rat_types[i]));
if (rc > len) {
osmo_strlcpy(rat_types_str + sizeof(rat_types_str) - 4, "...", 4);
break;
}
}
if (!db_bind_text(stmt, "$rat", rat_types_str)) {
ret = -EIO;
goto out;
}
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
LOGP(DAUC, LOGL_ERROR,
@@ -782,3 +804,119 @@ int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val,
}
return 0;
}
int db_subscr_set_rat_type_flag(struct db_context *dbc, int64_t subscr_id, enum osmo_rat_type rat, bool allowed)
{
int rc;
int ret = 0;
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_UPD_RAT_FLAG];
if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
return -EIO;
OSMO_ASSERT(rat >= 0 && rat < OSMO_RAT_COUNT);
if (!db_bind_text(stmt, "$rat", osmo_rat_type_name(rat)))
return -EIO;
if (!db_bind_int(stmt, "$allowed", allowed ? 1 : 0))
return -EIO;
/* execute the statement */
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
LOGP(DDB, LOGL_ERROR, "%s %s: SQL error: %s\n",
allowed ? "enable" : "disable", osmo_rat_type_name(rat),
sqlite3_errmsg(dbc->db));
ret = -EIO;
goto out;
}
/* verify execution result */
rc = sqlite3_changes(dbc->db);
if (!rc) {
LOGP(DDB, LOGL_ERROR, "Cannot %s %s: no such subscriber: ID=%" PRIu64 "\n",
allowed ? "enable" : "disable", osmo_rat_type_name(rat),
subscr_id);
ret = -ENOENT;
goto out;
} else if (rc != 1) {
LOGP(DDB, LOGL_ERROR, "%s %s: SQL modified %d rows (expected 1)\n",
allowed ? "enable" : "disable", osmo_rat_type_name(rat),
rc);
ret = -EIO;
}
out:
db_remove_reset(stmt);
return ret;
}
int db_subscr_get_rat_types(struct db_context *dbc, struct hlr_subscriber *subscr)
{
int rc;
int ret = 0;
int i;
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_RAT_BY_ID];
if (!db_bind_int64(stmt, "$subscriber_id", subscr->id))
return -EIO;
for (i = 0; i < OSMO_RAT_COUNT; i++)
subscr->rat_types[i] = true;
/* execute the statement */
while (1) {
enum osmo_rat_type rat;
bool allowed;
rc = sqlite3_step(stmt);
if (rc == SQLITE_DONE)
break;
if (rc != SQLITE_ROW)
return -rc;
rc = get_string_value(osmo_rat_type_names, (const char*)sqlite3_column_text(stmt, 0));
if (rc == -EINVAL) {
ret = -EINVAL;
goto out;
}
if (rc <= 0 || rc >= OSMO_RAT_COUNT) {
ret = -EINVAL;
goto out;
}
rat = rc;
allowed = sqlite3_column_int(stmt, 1);
subscr->rat_types[rat] = allowed;
LOGP(DAUC, LOGL_DEBUG, "db: imsi='%s' %s %s\n",
subscr->imsi, osmo_rat_type_name(rat), allowed ? "allowed" : "forbidden");
}
out:
db_remove_reset(stmt);
return ret;
}
int hlr_subscr_rat_flag(struct hlr *hlr, struct hlr_subscriber *subscr, enum osmo_rat_type rat, bool allowed)
{
int rc;
OSMO_ASSERT(rat >= 0 && rat < OSMO_RAT_COUNT);
db_subscr_get_rat_types(hlr->dbc, subscr);
if (subscr->rat_types[rat] == allowed) {
LOGHLR(subscr->imsi, LOGL_DEBUG, "Already has the requested value when asked to %s %s\n",
allowed ? "enable" : "disable", osmo_rat_type_name(rat));
return -ENOEXEC;
}
rc = db_subscr_set_rat_type_flag(hlr->dbc, subscr->id, rat, allowed);
if (rc)
return rc > 0? -rc : rc;
/* FIXME: If we're disabling, send message to VLR to detach subscriber */
return 0;
}

View File

@@ -263,6 +263,9 @@ static int rx_upd_loc_req(struct osmo_gsup_conn *conn,
{
struct hlr_subscriber *subscr;
struct lu_operation *luop = lu_op_alloc_conn(conn);
int i;
bool allowed;
if (!luop) {
LOGP(DMAIN, LOGL_ERROR, "LU REQ from conn without addr?\n");
return -EINVAL;
@@ -308,6 +311,34 @@ static int rx_upd_loc_req(struct osmo_gsup_conn *conn,
return 0;
}
/* Check if any available RAT type is allowed. See 3GPP TS 29.010 3.2 'Routeing area updating' and 3.8 'Location
* update' for the "No Suitable cells in location area" error code. */
allowed = false;
LOGP(DAUC, LOGL_DEBUG, "LU: IMSI='%s' on %s sent RAT types: %zu\n", subscr->imsi,
gsup->cn_domain == OSMO_GSUP_CN_DOMAIN_CS ? "CS" : "PS", gsup->rat_types_len);
for (i = 0; i < gsup->rat_types_len; i++) {
enum osmo_rat_type rat = gsup->rat_types[i];
if (rat <= 0 || rat >= OSMO_RAT_COUNT) {
lu_op_tx_error(luop, GMM_CAUSE_COND_IE_ERR);
return 0;
}
if (luop->subscr.rat_types[rat]) {
allowed = true;
LOGP(DAUC, LOGL_DEBUG, "LU: IMSI='%s' allowed on %s\n",
subscr->imsi, osmo_rat_type_name(rat));
} else {
LOGP(DAUC, LOGL_DEBUG, "LU: IMSI='%s' not allowed on %s\n",
subscr->imsi, osmo_rat_type_name(rat));
}
}
if (!allowed && gsup->rat_types_len > 0) {
LOGP(DAUC, LOGL_DEBUG, "ISMI='%s' not allowed on %s%s\n",
subscr->imsi, osmo_rat_type_name(gsup->rat_types[0]),
gsup->rat_types_len > 1 ? " (nor on the other available RAT types)" : "");
lu_op_tx_error(luop, GMM_CAUSE_NO_SUIT_CELL_IN_LA);
return 0;
}
/* TODO: Set subscriber tracing = deactive in VLR/SGSN */
#if 0
@@ -325,7 +356,8 @@ static int rx_upd_loc_req(struct osmo_gsup_conn *conn,
LOGP(DAUC, LOGL_DEBUG, "IMSI='%s': storing %s = %s\n",
subscr->imsi, luop->is_ps ? "SGSN number" : "VLR number",
osmo_quote_str((const char*)luop->peer, -1));
if (db_subscr_lu(g_hlr->dbc, subscr->id, (const char *)luop->peer, luop->is_ps))
if (db_subscr_lu(g_hlr->dbc, subscr->id, (const char *)luop->peer, luop->is_ps,
gsup->rat_types, gsup->rat_types_len))
LOGP(DAUC, LOGL_ERROR, "IMSI='%s': Cannot update %s in the database\n",
subscr->imsi, luop->is_ps ? "SGSN number" : "VLR number");

View File

@@ -320,6 +320,150 @@ static int handle_ussd_own_imsi(struct osmo_gsup_conn *conn, struct ss_session *
return 0;
}
static int handle_ussd_get_ran(struct osmo_gsup_conn *conn, struct ss_session *ss,
const struct osmo_gsup_message *gsup,
const struct ss_request *req)
{
struct hlr_subscriber subscr;
char response[512];
int rc;
const char *rat;
rc = db_subscr_get_by_imsi(g_hlr->dbc, ss->imsi, &subscr);
switch (rc) {
case 0:
if (!*subscr.last_lu_rat)
rat = "nothing, you don't exist";
else if (!strcmp(subscr.last_lu_rat, "GERAN-A"))
rat = "2G";
else if (!strcmp(subscr.last_lu_rat, "UTRAN-Iu"))
rat = "3G";
else
rat = subscr.last_lu_rat;
snprintf(response, sizeof(response),
"Now on %s. Available:%s%s."
" (2G on: *#201# off: *#200# -- 3G on: *#301# off: *#300#)", rat,
subscr.rat_types[OSMO_RAT_GERAN_A]? " 2G" : "",
subscr.rat_types[OSMO_RAT_UTRAN_IU]? " 3G" : "");
rc = ss_tx_ussd_7bit(ss, true, req->invoke_id, response);
break;
case -ENOENT:
rc = ss_tx_error(ss, true, GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER);
break;
case -EIO:
default:
rc = ss_tx_error(ss, true, GSM0480_ERR_CODE_SYSTEM_FAILURE);
break;
}
return rc;
}
static int handle_ussd_gsm_on(struct osmo_gsup_conn *conn, struct ss_session *ss,
const struct osmo_gsup_message *gsup,
const struct ss_request *req)
{
struct hlr_subscriber subscr;
int rc;
rc = db_subscr_get_by_imsi(g_hlr->dbc, ss->imsi, &subscr);
switch (rc) {
case 0:
hlr_subscr_rat_flag(g_hlr, &subscr, OSMO_RAT_GERAN_A, true);
rc = ss_tx_ussd_7bit(ss, true, req->invoke_id,
"Enabled GERAN-A (2G)");
break;
case -ENOENT:
rc = ss_tx_error(ss, true, GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER);
break;
case -EIO:
default:
rc = ss_tx_error(ss, true, GSM0480_ERR_CODE_SYSTEM_FAILURE);
break;
}
return rc;
}
static int handle_ussd_gsm_off(struct osmo_gsup_conn *conn, struct ss_session *ss,
const struct osmo_gsup_message *gsup,
const struct ss_request *req)
{
struct hlr_subscriber subscr;
int rc;
rc = db_subscr_get_by_imsi(g_hlr->dbc, ss->imsi, &subscr);
switch (rc) {
case 0:
hlr_subscr_rat_flag(g_hlr, &subscr, OSMO_RAT_GERAN_A, false);
rc = ss_tx_ussd_7bit(ss, true, req->invoke_id,
"Disabled GERAN-A (2G)");
break;
case -ENOENT:
rc = ss_tx_error(ss, true, GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER);
break;
case -EIO:
default:
rc = ss_tx_error(ss, true, GSM0480_ERR_CODE_SYSTEM_FAILURE);
break;
}
return rc;
}
static int handle_ussd_umts_on(struct osmo_gsup_conn *conn, struct ss_session *ss,
const struct osmo_gsup_message *gsup,
const struct ss_request *req)
{
struct hlr_subscriber subscr;
int rc;
rc = db_subscr_get_by_imsi(g_hlr->dbc, ss->imsi, &subscr);
switch (rc) {
case 0:
hlr_subscr_rat_flag(g_hlr, &subscr, OSMO_RAT_UTRAN_IU, true);
rc = ss_tx_ussd_7bit(ss, true, req->invoke_id,
"Enabled UTRAN-Iu (3G)");
break;
case -ENOENT:
rc = ss_tx_error(ss, true, GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER);
break;
case -EIO:
default:
rc = ss_tx_error(ss, true, GSM0480_ERR_CODE_SYSTEM_FAILURE);
break;
}
return rc;
}
static int handle_ussd_umts_off(struct osmo_gsup_conn *conn, struct ss_session *ss,
const struct osmo_gsup_message *gsup,
const struct ss_request *req)
{
struct hlr_subscriber subscr;
int rc;
rc = db_subscr_get_by_imsi(g_hlr->dbc, ss->imsi, &subscr);
switch (rc) {
case 0:
hlr_subscr_rat_flag(g_hlr, &subscr, OSMO_RAT_UTRAN_IU, false);
rc = ss_tx_ussd_7bit(ss, true, req->invoke_id,
"Disabled UTRAN-Iu (3G)");
break;
case -ENOENT:
rc = ss_tx_error(ss, true, GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER);
break;
case -EIO:
default:
rc = ss_tx_error(ss, true, GSM0480_ERR_CODE_SYSTEM_FAILURE);
break;
}
return rc;
}
static const struct hlr_iuse hlr_iuses[] = {
{
@@ -330,6 +474,26 @@ static const struct hlr_iuse hlr_iuses[] = {
.name = "own-imsi",
.handle_ussd = handle_ussd_own_imsi,
},
{
.name = "get-ran",
.handle_ussd = handle_ussd_get_ran,
},
{
.name = "gsm-on",
.handle_ussd = handle_ussd_gsm_on,
},
{
.name = "gsm-off",
.handle_ussd = handle_ussd_gsm_off,
},
{
.name = "umts-on",
.handle_ussd = handle_ussd_umts_on,
},
{
.name = "umts-off",
.handle_ussd = handle_ussd_umts_off,
},
};
const struct hlr_iuse *iuse_find(const char *name)

View File

@@ -133,10 +133,13 @@ DEFUN(cfg_hlr_gsup_bind_ip,
#define UROUTE_STR "Routing Configuration\n"
#define PREFIX_STR "Prefix-Matching Route\n" "USSD Prefix\n"
#define INT_CHOICE "(own-msisdn|own-imsi)"
#define INT_CHOICE "(own-msisdn|own-imsi|get-ran|gsm-on|gsm-off|umts-on|umts-off)"
#define INT_STR "Internal USSD Handler\n" \
"Respond with subscribers' own MSISDN\n" \
"Respond with subscribers' own IMSI\n"
"Respond with subscribers' own IMSI\n" \
"Respond with available RAN types\n" \
"Enable UMTS service\n" \
"Disable UMTS service\n"
#define EXT_STR "External USSD Handler\n" \
"Name of External USSD Handler (IPA CCM ID)\n"

View File

@@ -27,6 +27,7 @@
#include <osmocom/vty/vty.h>
#include <osmocom/vty/command.h>
#include <osmocom/core/utils.h>
#include <osmocom/gsm/gsm_utils.h>
#include "hlr.h"
#include "db.h"
@@ -35,24 +36,23 @@ struct vty;
#define hexdump_buf(buf) osmo_hexdump_nospc((void*)buf, sizeof(buf))
static char *
get_datestr(const time_t *t, char *datebuf)
static char *get_datestr(const time_t *t)
{
char *p, *s = ctime_r(t, datebuf);
static char buf[32];
struct tm tm;
/* Strip trailing newline. */
p = strchr(s, '\n');
if (p)
*p = '\0';
return s;
tm = *gmtime(t);
strftime(buf, sizeof(buf), "%FT%T+00:00", &tm);
return buf;
}
static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr)
{
int rc;
int i;
struct osmo_sub_auth_data aud2g;
struct osmo_sub_auth_data aud3g;
char datebuf[26]; /* for ctime_r(3) */
vty_out(vty, " ID: %"PRIu64"%s", subscr->id, VTY_NEWLINE);
@@ -79,7 +79,15 @@ static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr)
if (subscr->ms_purged_ps)
vty_out(vty, " PS purged%s", VTY_NEWLINE);
if (subscr->last_lu_seen)
vty_out(vty, " last LU seen: %s UTC%s", get_datestr(&subscr->last_lu_seen, datebuf), VTY_NEWLINE);
vty_out(vty, " last LU seen: %s%s", get_datestr(&subscr->last_lu_seen), VTY_NEWLINE);
if (subscr->last_lu_rat[0])
vty_out(vty, " last LU RAT: %s%s", subscr->last_lu_rat, VTY_NEWLINE);
for (i = OSMO_RAT_UNKNOWN + 1; i < ARRAY_SIZE(subscr->rat_types); i++) {
vty_out(vty, " %s: %s%s", osmo_rat_type_name(i), subscr->rat_types[i] ? "allowed" : "forbidden",
VTY_NEWLINE);
}
if (subscr->ms_purged_cs)
vty_out(vty, " CS purged%s", VTY_NEWLINE);
if (!*subscr->imsi)
return;
@@ -508,6 +516,45 @@ DEFUN(subscriber_aud3g,
return CMD_SUCCESS;
}
DEFUN(subscriber_rat,
subscriber_rat_cmd,
SUBSCR_UPDATE "rat (geran-a|utran-iu) (allowed|forbidden)",
SUBSCR_UPDATE_HELP
"Allow or forbid specific Radio Access Types\n"
"Set access to GERAN-A\n"
"Set access to UTRAN-Iu\n"
"Allow access\n"
"Forbid access\n")
{
struct hlr_subscriber subscr;
const char *id_type = argv[0];
const char *id = argv[1];
const char *rat_str = argv[2];
const char *allowed_forbidden = argv[3];
enum osmo_rat_type rat;
bool allowed;
int rc;
if (strcmp(rat_str, "geran-a") == 0)
rat = OSMO_RAT_GERAN_A;
else if (strcmp(rat_str, "utran-iu") == 0)
rat = OSMO_RAT_UTRAN_IU;
allowed = (strcmp(allowed_forbidden, "allowed") == 0);
if (get_subscr_by_argv(vty, id_type, id, &subscr))
return CMD_WARNING;
rc = hlr_subscr_rat_flag(g_hlr, &subscr, rat, allowed);
if (rc && rc != -ENOEXEC) {
vty_out(vty, "%% Error: cannot set %s to %s%s",
osmo_rat_type_name(rat), allowed ? "allowed" : "forbidden", VTY_NEWLINE);
return CMD_WARNING;
}
return CMD_SUCCESS;
}
void hlr_vty_subscriber_init(void)
{
install_element_ve(&subscriber_show_cmd);
@@ -519,4 +566,5 @@ void hlr_vty_subscriber_init(void)
install_element(ENABLE_NODE, &subscriber_aud2g_cmd);
install_element(ENABLE_NODE, &subscriber_no_aud3g_cmd);
install_element(ENABLE_NODE, &subscriber_aud3g_cmd);
install_element(ENABLE_NODE, &subscriber_rat_cmd);
}

View File

@@ -159,6 +159,7 @@ void dump_subscr(struct hlr_subscriber *subscr)
Pfo(lmsi, "0x%x", subscr);
Pb(true, ms_purged_cs);
Pb(true, ms_purged_ps);
Ps(last_lu_rat);
fprintf(stderr, "}\n");
#undef Ps
#undef Pd
@@ -212,6 +213,7 @@ static const char *imsi1 = "123456789000001";
static const char *imsi2 = "123456789000002";
static const char *short_imsi = "123456";
static const char *unknown_imsi = "999999999";
static const enum osmo_rat_type rat_types[2] = { OSMO_RAT_GERAN_A, OSMO_RAT_UTRAN_IU, };
static void test_subscr_create_update_sel_delete()
{
@@ -337,39 +339,44 @@ static void test_subscr_create_update_sel_delete()
comment("Record LU for PS and CS (SGSN and VLR names)");
ASSERT_RC(db_subscr_lu(dbc, id0, "5952", true), 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "5952", true, NULL, 0), 0);
ASSERT_SEL(id, id0, 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "712", false), 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "712", false, NULL, 0), 0);
ASSERT_SEL(id, id0, 0);
comment("Record LU for PS and CS (SGSN and VLR names) *again*");
ASSERT_RC(db_subscr_lu(dbc, id0, "111", true), 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "111", true, NULL, 0), 0);
ASSERT_SEL(id, id0, 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "111", true), 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "111", true, NULL, 0), 0);
ASSERT_SEL(id, id0, 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "222", false), 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "222", false, NULL, 0), 0);
ASSERT_SEL(id, id0, 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "222", false), 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "222", false, NULL, 0), 0);
ASSERT_SEL(id, id0, 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "333", false, rat_types, 1), 0);
ASSERT_SEL(id, id0, 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "333", false, rat_types, 1), 0);
ASSERT_SEL(id, id0, 0);
comment("Unset LU info for PS and CS (SGSN and VLR names)");
ASSERT_RC(db_subscr_lu(dbc, id0, "", true), 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "", true, NULL, 0), 0);
ASSERT_SEL(id, id0, 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "", false), 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "", false, NULL, 0), 0);
ASSERT_SEL(id, id0, 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "111", true), 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "222", false), 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "111", true, NULL, 0), 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "222", false, NULL, 0), 0);
ASSERT_SEL(id, id0, 0);
ASSERT_RC(db_subscr_lu(dbc, id0, NULL, true), 0);
ASSERT_RC(db_subscr_lu(dbc, id0, NULL, true, NULL, 0), 0);
ASSERT_SEL(id, id0, 0);
ASSERT_RC(db_subscr_lu(dbc, id0, NULL, false), 0);
ASSERT_RC(db_subscr_lu(dbc, id0, NULL, false, NULL, 0), 0);
ASSERT_SEL(id, id0, 0);
comment("Record LU for non-existent ID");
ASSERT_RC(db_subscr_lu(dbc, 99999, "5952", true), -ENOENT);
ASSERT_RC(db_subscr_lu(dbc, 99999, "712", false), -ENOENT);
ASSERT_RC(db_subscr_lu(dbc, 99999, "5952", true, NULL, 0), -ENOENT);
ASSERT_RC(db_subscr_lu(dbc, 99999, "712", false, NULL, 0), -ENOENT);
ASSERT_SEL(id, 99999, -ENOENT);
comment("Purge and un-purge PS and CS");

View File

@@ -373,7 +373,7 @@ DAUC Cannot disable CS: no such subscriber: IMSI='foobar'
--- Record LU for PS and CS (SGSN and VLR names)
db_subscr_lu(dbc, id0, "5952", true) --> 0
db_subscr_lu(dbc, id0, "5952", true, NULL, 0) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -383,7 +383,7 @@ struct hlr_subscriber {
.sgsn_number = '5952',
}
db_subscr_lu(dbc, id0, "712", false) --> 0
db_subscr_lu(dbc, id0, "712", false, NULL, 0) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -397,7 +397,7 @@ struct hlr_subscriber {
--- Record LU for PS and CS (SGSN and VLR names) *again*
db_subscr_lu(dbc, id0, "111", true) --> 0
db_subscr_lu(dbc, id0, "111", true, NULL, 0) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -408,7 +408,7 @@ struct hlr_subscriber {
.sgsn_number = '111',
}
db_subscr_lu(dbc, id0, "111", true) --> 0
db_subscr_lu(dbc, id0, "111", true, NULL, 0) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -419,7 +419,7 @@ struct hlr_subscriber {
.sgsn_number = '111',
}
db_subscr_lu(dbc, id0, "222", false) --> 0
db_subscr_lu(dbc, id0, "222", false, NULL, 0) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -430,7 +430,7 @@ struct hlr_subscriber {
.sgsn_number = '111',
}
db_subscr_lu(dbc, id0, "222", false) --> 0
db_subscr_lu(dbc, id0, "222", false, NULL, 0) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -441,20 +441,44 @@ struct hlr_subscriber {
.sgsn_number = '111',
}
db_subscr_lu(dbc, id0, "333", false, rat_types, 1) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
.id = 1,
.imsi = '123456789000000',
.msisdn = '543210123456789',
.vlr_number = '333',
.sgsn_number = '111',
.last_lu_rat = 'GERAN-A',
}
db_subscr_lu(dbc, id0, "333", false, rat_types, 1) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
.id = 1,
.imsi = '123456789000000',
.msisdn = '543210123456789',
.vlr_number = '333',
.sgsn_number = '111',
.last_lu_rat = 'GERAN-A',
}
--- Unset LU info for PS and CS (SGSN and VLR names)
db_subscr_lu(dbc, id0, "", true) --> 0
db_subscr_lu(dbc, id0, "", true, NULL, 0) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
.id = 1,
.imsi = '123456789000000',
.msisdn = '543210123456789',
.vlr_number = '222',
.vlr_number = '333',
}
db_subscr_lu(dbc, id0, "", false) --> 0
db_subscr_lu(dbc, id0, "", false, NULL, 0) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -463,9 +487,9 @@ struct hlr_subscriber {
.msisdn = '543210123456789',
}
db_subscr_lu(dbc, id0, "111", true) --> 0
db_subscr_lu(dbc, id0, "111", true, NULL, 0) --> 0
db_subscr_lu(dbc, id0, "222", false) --> 0
db_subscr_lu(dbc, id0, "222", false, NULL, 0) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -476,7 +500,7 @@ struct hlr_subscriber {
.sgsn_number = '111',
}
db_subscr_lu(dbc, id0, NULL, true) --> 0
db_subscr_lu(dbc, id0, NULL, true, NULL, 0) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -486,7 +510,7 @@ struct hlr_subscriber {
.vlr_number = '222',
}
db_subscr_lu(dbc, id0, NULL, false) --> 0
db_subscr_lu(dbc, id0, NULL, false, NULL, 0) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -498,10 +522,10 @@ struct hlr_subscriber {
--- Record LU for non-existent ID
db_subscr_lu(dbc, 99999, "5952", true) --> -ENOENT
db_subscr_lu(dbc, 99999, "5952", true, NULL, 0) --> -ENOENT
DAUC Cannot update SGSN number for subscriber ID=99999: no such subscriber
db_subscr_lu(dbc, 99999, "712", false) --> -ENOENT
db_subscr_lu(dbc, 99999, "712", false, NULL, 0) --> -ENOENT
DAUC Cannot update VLR number for subscriber ID=99999: no such subscriber
db_subscr_get_by_id(dbc, 99999, &g_subscr) --> -ENOENT