From ce172efc0bfe25f6e0d473100bc8d4f68de6db42 Mon Sep 17 00:00:00 2001 From: Neels Hofmeyr Date: Wed, 26 Dec 2018 01:49:53 +0100 Subject: 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 --- include/osmocom/hlr/db.h | 12 ++++ sql/hlr.sql | 12 +++- sql/upgrade_v2_to_v3.sql | 8 +++ src/db.c | 37 +++++++++- src/db_hlr.c | 121 +++++++++++++++++++++++++++++++- src/hlr_vty_subscr.c | 46 ++++++++++++ src/lu_fsm.c | 24 +++++++ tests/db_upgrade/create_subscribers.vty | 18 +++++ tests/db_upgrade/db_upgrade_test.ok | 11 ++- 9 files changed, 283 insertions(+), 6 deletions(-) create mode 100644 sql/upgrade_v2_to_v3.sql diff --git a/include/osmocom/hlr/db.h b/include/osmocom/hlr/db.h index 1f1bacb..5fbc846 100644 --- a/include/osmocom/hlr/db.h +++ b/include/osmocom/hlr/db.h @@ -5,6 +5,7 @@ #include #include +#include struct hlr; @@ -37,6 +38,8 @@ enum stmt_idx { DB_STMT_IND_ADD, DB_STMT_IND_SELECT, DB_STMT_IND_DEL, + DB_STMT_UPD_RAT_FLAG, + DB_STMT_RAT_BY_ID, _NUM_DB_STMT }; @@ -107,8 +110,13 @@ struct hlr_subscriber { /* talloc'd IPA unit name */ struct osmo_ipa_name vlr_via_proxy; struct osmo_ipa_name sgsn_via_proxy; + 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(). */ @@ -170,6 +178,10 @@ int db_subscr_purge(struct db_context *dbc, const char *by_imsi, int db_ind(struct db_context *dbc, const struct osmo_gsup_peer_id *vlr, unsigned int *ind); int db_ind_del(struct db_context *dbc, const struct osmo_gsup_peer_id *vlr); +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/sql/hlr.sql b/sql/hlr.sql index e855a6c..fd71aef 100644 --- a/sql/hlr.sql +++ b/sql/hlr.sql @@ -87,8 +87,18 @@ CREATE TABLE ind ( UNIQUE (vlr) ); +-- 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, + UNIQUE (subscriber_id, rat) +); + 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 = 6; +PRAGMA user_version = 7; 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; diff --git a/src/db.c b/src/db.c index c265ffa..fa868ea 100644 --- a/src/db.c +++ b/src/db.c @@ -30,7 +30,7 @@ #include "db_bootstrap.h" /* This constant is currently duplicated in sql/hlr.sql and must be kept in sync! */ -#define CURRENT_SCHEMA_VERSION 6 +#define CURRENT_SCHEMA_VERSION 7 #define SEL_COLUMNS \ "id," \ @@ -90,6 +90,12 @@ static const char *stmt_sql[] = { [DB_STMT_IND_ADD] = "INSERT INTO ind (vlr) VALUES ($vlr)", [DB_STMT_IND_SELECT] = "SELECT ind FROM ind WHERE vlr = $vlr", [DB_STMT_IND_DEL] = "DELETE FROM ind WHERE vlr = $vlr", + [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) @@ -507,6 +513,30 @@ static int db_upgrade_v6(struct db_context *dbc) return rc; } +static int db_upgrade_v7(struct db_context *dbc) +{ + int rc; + const char *statements[] = { + "-- Optional: add subscriber entries to allow or disallow specific RATs (2G or 3G or ...).\n" + "-- If a subscriber has no entry, that means that all RATs are allowed (backwards compat).\n" + "CREATE TABLE subscriber_rat (\n" + " subscriber_id INTEGER, -- subscriber.id\n" + " rat TEXT CHECK(rat in ('GERAN-A', 'UTRAN-Iu')) NOT NULL, -- Radio Access Technology, see enum ran_type\n" + " allowed BOOLEAN CHECK(allowed in (0, 1)) NOT NULL DEFAULT 0,\n" + " UNIQUE (subscriber_id, rat)\n" + ")", + "CREATE UNIQUE INDEX idx_subscr_rat_flag ON subscriber_rat (subscriber_id, rat)", + "PRAGMA user_version = 7", + }; + + rc = db_run_statements(dbc, statements, ARRAY_SIZE(statements)); + if (rc != SQLITE_DONE) { + LOGP(DDB, LOGL_ERROR, "Unable to update HLR database schema to version 7\n"); + return rc; + } + return rc; +} + typedef int (*db_upgrade_func_t)(struct db_context *dbc); static db_upgrade_func_t db_upgrade_path[] = { db_upgrade_v1, @@ -515,6 +545,7 @@ static db_upgrade_func_t db_upgrade_path[] = { db_upgrade_v4, db_upgrade_v5, db_upgrade_v6, + db_upgrade_v7, }; static int db_get_user_version(struct db_context *dbc) @@ -643,9 +674,9 @@ struct db_context *db_open(void *ctx, const char *fname, bool enable_sqlite_logg 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); diff --git a/src/db_hlr.c b/src/db_hlr.c index b13763a..ee53a39 100644 --- a/src/db_hlr.c +++ b/src/db_hlr.c @@ -483,7 +483,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); @@ -511,6 +511,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; @@ -987,3 +990,119 @@ int db_ind_del(struct db_context *dbc, const struct osmo_gsup_peer_id *vlr) { return _db_ind(dbc, vlr, NULL, true); } + +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; +} diff --git a/src/hlr_vty_subscr.c b/src/hlr_vty_subscr.c index dfbf72f..15e26ad 100644 --- a/src/hlr_vty_subscr.c +++ b/src/hlr_vty_subscr.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -76,6 +77,7 @@ static void dump_last_lu_seen(struct vty *vty, const char *domain_label, time_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; @@ -114,6 +116,10 @@ static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr) vty_out(vty, " PS purged%s", VTY_NEWLINE); dump_last_lu_seen(vty, "CS", subscr->last_lu_seen); dump_last_lu_seen(vty, "PS", subscr->last_lu_seen_ps); + 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->imsi) return; @@ -630,6 +636,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); @@ -643,4 +688,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); } diff --git a/src/lu_fsm.c b/src/lu_fsm.c index af3bed3..f5154c2 100644 --- a/src/lu_fsm.c +++ b/src/lu_fsm.c @@ -108,6 +108,8 @@ static void lu_start(struct osmo_gsup_req *update_location_req) { struct osmo_fsm_inst *fi; struct lu *lu; + bool any_rat_allowed; + int i; OSMO_ASSERT(update_location_req); OSMO_ASSERT(update_location_req->gsup.message_type == OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST); @@ -151,6 +153,28 @@ static void lu_start(struct osmo_gsup_req *update_location_req) return; } + /* 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. */ + any_rat_allowed = false; + for (i = 0; i < update_location_req->gsup.supported_rat_types_len; i++) { + enum osmo_rat_type rat = update_location_req->gsup.supported_rat_types[i]; + if (rat <= 0 || rat >= OSMO_RAT_COUNT) { + lu_failure(lu, GMM_CAUSE_COND_IE_ERR, "Invalid RAT type in GSUP request: %s", + osmo_rat_type_name(rat)); + return; + } + if (lu->subscr.rat_types[rat]) { + any_rat_allowed = true; + LOG_LU(lu, LOGL_DEBUG, "subscriber allowed on %s\n", osmo_rat_type_name(rat)); + } else { + LOG_LU(lu, LOGL_DEBUG, "subscriber not allowed on %s\n", osmo_rat_type_name(rat)); + } + } + if (!any_rat_allowed && update_location_req->gsup.supported_rat_types_len > 0) { + lu_failure(lu, GMM_CAUSE_NO_SUIT_CELL_IN_LA, "subscriber not allowed on any available RAT type"); + return; + } + /* TODO: Set subscriber tracing = deactive in VLR/SGSN */ #if 0 diff --git a/tests/db_upgrade/create_subscribers.vty b/tests/db_upgrade/create_subscribers.vty index 30eeba6..0edd22e 100644 --- a/tests/db_upgrade/create_subscribers.vty +++ b/tests/db_upgrade/create_subscribers.vty @@ -4,6 +4,9 @@ OsmoHLR# subscriber imsi 123456789012345 create ID: 1 IMSI: 123456789012345 MSISDN: none + GERAN-A: allowed + UTRAN-Iu: allowed + EUTRAN-SGs: allowed OsmoHLR# subscriber imsi 123456789012345 update msisdn 098765432109876 % Updated subscriber IMSI='123456789012345' to MSISDN='098765432109876' OsmoHLR# subscriber imsi 123456789012345 update aud2g comp128v1 ki BeefedCafeFaceAcedAddedDecadeFee @@ -13,11 +16,17 @@ OsmoHLR# subscriber imsi 111111111 create ID: 2 IMSI: 111111111 MSISDN: none + GERAN-A: allowed + UTRAN-Iu: allowed + EUTRAN-SGs: allowed OsmoHLR# subscriber imsi 222222222 create % Created subscriber 222222222 ID: 3 IMSI: 222222222 MSISDN: none + GERAN-A: allowed + UTRAN-Iu: allowed + EUTRAN-SGs: allowed OsmoHLR# subscriber imsi 222222222 update msisdn 22222 % Updated subscriber IMSI='222222222' to MSISDN='22222' OsmoHLR# subscriber imsi 333333 create @@ -25,6 +34,9 @@ OsmoHLR# subscriber imsi 333333 create ID: 4 IMSI: 333333 MSISDN: none + GERAN-A: allowed + UTRAN-Iu: allowed + EUTRAN-SGs: allowed OsmoHLR# subscriber imsi 333333 update msisdn 3 % Updated subscriber IMSI='333333' to MSISDN='3' OsmoHLR# subscriber imsi 333333 update aud2g comp128v2 ki 33333333333333333333333333333333 @@ -33,6 +45,9 @@ OsmoHLR# subscriber imsi 444444444444444 create ID: 5 IMSI: 444444444444444 MSISDN: none + GERAN-A: allowed + UTRAN-Iu: allowed + EUTRAN-SGs: allowed OsmoHLR# subscriber imsi 444444444444444 update msisdn 4444 % Updated subscriber IMSI='444444444444444' to MSISDN='4444' OsmoHLR# subscriber imsi 444444444444444 update aud3g milenage k 44444444444444444444444444444444 op 44444444444444444444444444444444 @@ -41,6 +56,9 @@ OsmoHLR# subscriber imsi 5555555 create ID: 6 IMSI: 5555555 MSISDN: none + GERAN-A: allowed + UTRAN-Iu: allowed + EUTRAN-SGs: allowed OsmoHLR# subscriber imsi 5555555 update msisdn 55555555555555 % Updated subscriber IMSI='5555555' to MSISDN='55555555555555' OsmoHLR# subscriber imsi 5555555 update aud2g xor ki 55555555555555555555555555555555 diff --git a/tests/db_upgrade/db_upgrade_test.ok b/tests/db_upgrade/db_upgrade_test.ok index 0a45f7c..a366b3d 100644 --- a/tests/db_upgrade/db_upgrade_test.ok +++ b/tests/db_upgrade/db_upgrade_test.ok @@ -86,6 +86,7 @@ DDB Database test.db' has been upgraded to HLR DB schema version 3 DDB Database test.db' has been upgraded to HLR DB schema version 4 DDB Database test.db' has been upgraded to HLR DB schema version 5 DDB Database test.db' has been upgraded to HLR DB schema version 6 +DDB Database test.db' has been upgraded to HLR DB schema version 7 DMAIN Cmdline option --db-check: Database was opened successfully, quitting. Resulting db: @@ -174,10 +175,18 @@ subscriber_id|INTEGER|0||0 Table subscriber_multi_msisdn contents: +Table: subscriber_rat +name|type|notnull|dflt_value|pk +allowed|BOOLEAN|1|0|0 +rat|TEXT|1||0 +subscriber_id|INTEGER|0||0 + +Table subscriber_rat contents: + Verify that osmo-hlr can open it: osmo-hlr --database $db --db-check --config-file $srcdir/osmo-hlr.cfg rc = 0 DMAIN hlr starting DDB using database: test.db -DDB Database test.db' has HLR DB schema version 6 +DDB Database test.db' has HLR DB schema version 7 DMAIN Cmdline option --db-check: Database was opened successfully, quitting. -- cgit v1.2.3