diff options
author | Neels Hofmeyr <neels@hofmeyr.de> | 2018-12-26 01:49:53 +0100 |
---|---|---|
committer | Neels Hofmeyr <neels@hofmeyr.de> | 2019-08-20 04:40:07 +0200 |
commit | f1949f7b99f2d6779f9d67da7554322963235c34 (patch) | |
tree | f4debd44118b27a1a91d7fc4a86f84b6ad97f878 | |
parent | f302de93dd03cd81c89838f986be12aafc4f903c (diff) |
Add DB storage for enabling / disabling arbitrary RAT types.
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
-rw-r--r-- | sql/hlr.sql | 11 | ||||
-rw-r--r-- | sql/upgrade_v2_to_v3.sql | 8 | ||||
-rw-r--r-- | src/db.c | 72 | ||||
-rw-r--r-- | src/db.h | 13 | ||||
-rw-r--r-- | src/db_hlr.c | 121 | ||||
-rw-r--r-- | src/hlr.c | 31 | ||||
-rw-r--r-- | src/hlr_vty_subscr.c | 48 | ||||
-rw-r--r-- | src/luop.h | 3 |
8 files changed, 300 insertions, 7 deletions
diff --git a/sql/hlr.sql b/sql/hlr.sql index 10838f2..6ff561b 100644 --- a/sql/hlr.sql +++ b/sql/hlr.sql @@ -72,8 +72,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 = 2; +PRAGMA user_version = 3; diff --git a/sql/upgrade_v2_to_v3.sql b/sql/upgrade_v2_to_v3.sql new file mode 100644 index 0000000..7d475c1 --- /dev/null +++ b/sql/upgrade_v2_to_v3.sql @@ -0,0 +1,8 @@ + +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 NOT NULL DEFAULT 0, +); + +PRAGMA user_version = 3; @@ -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 2 +#define CURRENT_SCHEMA_VERSION 3 #define SEL_COLUMNS \ "id," \ @@ -81,6 +81,12 @@ static const char *stmt_sql[] = { [DB_STMT_SET_LAST_LU_SEEN] = "UPDATE subscriber SET last_lu_seen = datetime($val, 'unixepoch') WHERE id = $subscriber_id", [DB_STMT_EXISTS_BY_IMSI] = "SELECT 1 FROM subscriber WHERE imsi = $imsi", [DB_STMT_EXISTS_BY_MSISDN] = "SELECT 1 FROM subscriber WHERE msisdn = $msisdn", + [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) @@ -329,6 +335,51 @@ static int db_upgrade_v2(struct db_context *dbc) return rc; } +static int db_upgrade_v3(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 = 3"; + 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_get_user_version(struct db_context *dbc) { const char *user_version_sql = "PRAGMA user_version"; @@ -440,6 +491,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); @@ -459,21 +512,30 @@ struct db_context *db_open(void *ctx, const char *fname, bool enable_sqlite_logg } 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 2: (rc=%d) %s\n", + rc, sqlite3_errmsg(dbc->db)); + goto out_free; + } + version = 3; + /* 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); @@ -3,6 +3,8 @@ #include <stdbool.h> #include <sqlite3.h> +#include <osmocom/gsm/gsm_utils.h> + struct hlr; enum stmt_idx { @@ -30,6 +32,8 @@ enum stmt_idx { DB_STMT_SET_LAST_LU_SEEN, DB_STMT_EXISTS_BY_IMSI, DB_STMT_EXISTS_BY_MSISDN, + DB_STMT_UPD_RAT_FLAG, + DB_STMT_RAT_BY_ID, _NUM_DB_STMT }; @@ -95,8 +99,13 @@ struct hlr_subscriber { bool ms_purged_cs; bool ms_purged_ps; time_t last_lu_seen; + 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(). */ @@ -156,6 +165,10 @@ int db_subscr_purge(struct db_context *dbc, const char *by_imsi, 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*. diff --git a/src/db_hlr.c b/src/db_hlr.c index 694ac53..91452e5 100644 --- a/src/db_hlr.c +++ b/src/db_hlr.c @@ -461,7 +461,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); @@ -497,6 +497,9 @@ static int db_sel(struct db_context *dbc, sqlite3_stmt *stmt, struct hlr_subscri out: db_remove_reset(stmt); + if (ret == 0) + db_subscr_get_rat_types(dbc, subscr); + switch (ret) { case 0: *err = NULL; @@ -909,3 +912,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; +} @@ -342,6 +342,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; @@ -389,6 +392,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 diff --git a/src/hlr_vty_subscr.c b/src/hlr_vty_subscr.c index b53227a..de78ef9 100644 --- a/src/hlr_vty_subscr.c +++ b/src/hlr_vty_subscr.c @@ -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" @@ -49,6 +50,7 @@ static char *get_datestr(const time_t *t) 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; @@ -87,6 +89,12 @@ static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr) vty_out(vty, " PS purged%s", VTY_NEWLINE); if (subscr->last_lu_seen) vty_out(vty, " last LU seen: %s%s", get_datestr(&subscr->last_lu_seen), 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; @@ -603,6 +611,45 @@ DEFUN(subscriber_nam, } +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); @@ -616,4 +663,5 @@ void hlr_vty_subscriber_init(void) install_element(ENABLE_NODE, &subscriber_aud3g_cmd); install_element(ENABLE_NODE, &subscriber_imei_cmd); install_element(ENABLE_NODE, &subscriber_nam_cmd); + install_element(ENABLE_NODE, &subscriber_rat_cmd); } @@ -54,6 +54,9 @@ struct lu_operation { enum lu_state state; /*! CS (false) or PS (true) Location Update? */ bool is_ps; + /*! RAT type indicator: coming in on GERAN-A? UTRAN-Iu? */ + enum osmo_rat_type via_rat; + /*! currently running timer */ struct osmo_timer_list timer; |