db v6: determine 3G AUC IND from VLR name

Each VLR requesting auth tuples should use a distinct IND pool for 3G auth.  So
far we tied the IND to the GSUP peer connection; MSC and SGSN were always
distinct GSUP peers, they ended up using distinct INDs.

However, we have implemented a GSUP proxy, so that, in a distributed setup, a
remotely roaming subscriber has only one direct GSUP peer proxying for both
remote MSC and SGSN. That means as soon as a subscriber roams to a different
site, the site's MSC and SGSN compete for the same IND bucket, waste SQNs
rapidly and cause auth tuple generation load.

So instead of using the local client as IND, persistently keep a list of VLR
names and assign a different IND to each. Use the GSUP source_name as
indicator, which reflects the actual remote VLR's name.

Furthermore, make 100% sure that CS and PS pools can never collide, by keeping
all CS pools odd numbered and all PS pools even numbered.

Persist the site <-> IND assignments in the database.

Add an IND test to db_test.c

Related: OS#4319
Change-Id: I6f0a6bbef3a27507605c3b4a0e1a89bdfd468374
This commit is contained in:
Neels Hofmeyr 2019-12-12 04:04:53 +01:00
parent 4994685f8f
commit 191f4b6eb3
9 changed files with 428 additions and 5 deletions

View File

@ -4,6 +4,7 @@
#include <sqlite3.h>
#include <osmocom/gsupclient/gsup_peer_id.h>
#include <osmocom/gsm/gsup.h>
struct hlr;
@ -33,6 +34,9 @@ enum stmt_idx {
DB_STMT_SET_LAST_LU_SEEN_PS,
DB_STMT_EXISTS_BY_IMSI,
DB_STMT_EXISTS_BY_MSISDN,
DB_STMT_IND_SELECT,
DB_STMT_IND_ADD,
DB_STMT_IND_DEL,
_NUM_DB_STMT
};
@ -163,6 +167,10 @@ int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
int db_subscr_purge(struct db_context *dbc, const char *by_imsi,
bool purge_val, bool is_ps);
int db_ind(struct db_context *dbc, enum osmo_gsup_cn_domain cn_domain, const struct osmo_gsup_peer_id *vlr,
unsigned int *ind);
int db_ind_del(struct db_context *dbc, enum osmo_gsup_cn_domain cn_domain, const struct osmo_gsup_peer_id *vlr);
/*! 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

@ -79,8 +79,17 @@ CREATE TABLE auc_3g (
ind_bitlen INTEGER NOT NULL DEFAULT 5
);
CREATE TABLE ind (
cn_domain INTEGER NOT NULL,
-- 3G auth IND bucket to be used for this VLR, where IND = (idx << 1) + cn_domain -1
ind INTEGER PRIMARY KEY,
-- VLR identification, usually the GSUP source_name
vlr TEXT NOT NULL,
UNIQUE (cn_domain, vlr)
);
CREATE UNIQUE INDEX idx_subscr_imsi ON subscriber (imsi);
-- 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 = 5;
PRAGMA user_version = 6;

107
src/db.c
View File

@ -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 5
#define CURRENT_SCHEMA_VERSION 6
#define SEL_COLUMNS \
"id," \
@ -87,6 +87,86 @@ static const char *stmt_sql[] = {
[DB_STMT_SET_LAST_LU_SEEN_PS] = "UPDATE subscriber SET last_lu_seen_ps = 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_IND_SELECT] = "SELECT ind FROM ind WHERE cn_domain = $cn_domain AND vlr = $vlr",
[DB_STMT_IND_DEL] = "DELETE FROM ind WHERE cn_domain = $cn_domain AND vlr = $vlr",
[DB_STMT_IND_ADD] =
/* This SQL statement is quite the works, so let me elaborate.
* This is about auc_3g IND pool choice for a given attached VLR (MSC or SGSN).
* - We want to insert an unused IND into the table, where a CS IND should be odd-numbered and a PS IND
* should be even (see OS#4319). In short, an IND collision between MSC and SGSN of the same site is a
* grave sink of SQN numbers and HLR CPU cycles, so it is worth it to avoid that with 100% certainty.
* - We want to start from zero/one (for PS/CS) and,
* - When there is a gap due to deletion, we always want to first fill up the gaps before picking unused
* INDs from the end of the range.
* - We also want to treat $cn_domain as an integer, to be ready for future added cn_domain enum values.
* That implies having one single table for all cn_domains,
* - The other benefit of having a single table for both cn_domains is that we can beyond all doubt
* prevent any IND assigned twice.
* - If too many sites show up for the IND_bitlen of a subscriber, the auc_3g code actually takes the
* modulo to fit in the IND_bitlen space, so here all we do is grow IND values into "all infinity",
* causing effective round-robin of any arbitrary IND_bitlen space. That is why we fill gaps first.
*
* $cn_domain is: PS=1 CS=2, so $cn_domain - 1 gives PS=0 CS=1
* Given any arbitrary nr, this always hits the right even/odd per CN domain:
* nr - (nr % 2) + ($cn_domain-1)
* However, CN domains are always spaced two apart, so we often want (nr + 2).
* With above always-hit-the-right-bucket, that gives
* (nr+2) - ((nr+2) % 2) + ($cn_domain-1)
* This modulo dance is aggressively applied to gracefully recover even when a user has manually
* modified the IND table to actually pair an even/odd IND to the wrong cn_domain.
*
* The deeper SELECT between THEN and ELSE picks the lowest unused IND for a given $cn_domain.
* However, that only works when there already is any one entry in the table.
* That's why we need the entire CASE WHEN .. THEN .. ELSE .. END stuff.
*
* That CASE's ELSE..END part returns the absolute first value for a $cn_domain for an empty table.
*
* The outermost SELECT puts the values ($cn_domain, $ind, $vlr) together.
*
* So, again, this time from outside to inside:
* INSERT...
* SELECT ($cn_domain, <IND>, $vlr)
*
* where <IND> is done like:
* CASE WHEN <table-already-has-such-$cn_domain>
* THEN
* <FIND-UNUSED-IND>
* ELSE
* <use-first-ind-for-this-$cn_domain>
*
* where in turn <FIND-UNUSED-IND> is [CC-BY-SA-4.0]
* kindly taken from the answer of https://stackoverflow.com/users/55159/quassnoi (MySQL section)
* to the question https://stackoverflow.com/questions/1312101/how-do-i-find-a-gap-in-running-counter-with-sql
* and modified to use the even/odd skipping according to $cn_domain instead of simple increment.
* <FIND-UNUSED-IND> works such that it selects an IND number for which IND + 2 yields no entry,
* modification here: the entry must also match the given $cn_domain.
*
* The C invoking this still first tries to just find an entry for a given $vlr, so when this statement
* is invoked, we actually definitely want to insert an entry and expect no constraint conflicts.
*
* Parameters are $cn_domain (integer) and $vlr (text). The $cn_domain should be either 1 (PS)
* or 2 (CS), any other value should default to 1 (because according to GSUP specs PS is the default).
*/
"INSERT INTO ind (cn_domain, ind, vlr)"
"SELECT $cn_domain,"
" CASE WHEN EXISTS(SELECT NULL FROM ind WHERE cn_domain = $cn_domain LIMIT 1)"
" THEN"
" ("
" SELECT ((ind + 2) - ((ind + 2)%2) + ($cn_domain-1))"
" FROM ind as mo"
" WHERE NOT EXISTS ("
" SELECT NULL"
" FROM ind as mi"
" WHERE cn_domain = $cn_domain"
" AND mi.ind = ((mo.ind + 2) - ((mo.ind + 2)%2) + $cn_domain-1)"
" )"
" ORDER BY ind"
" LIMIT 1"
" )"
" ELSE ($cn_domain-1)"
" END ind"
" , $vlr"
,
};
static void sql3_error_log_cb(void *arg, int err_code, const char *msg)
@ -481,6 +561,30 @@ static int db_upgrade_v5(struct db_context *dbc)
return rc;
}
static int db_upgrade_v6(struct db_context *dbc)
{
int rc;
const char *statements[] = {
"CREATE TABLE ind (\n"
" cn_domain INTEGER NOT NULL,\n"
" -- 3G auth IND bucket to be used for this VLR, where IND = (idx << 1) + cn_domain -1\n"
" ind INTEGER PRIMARY KEY,\n"
" -- VLR identification, usually the GSUP source_name\n"
" vlr TEXT NOT NULL,\n"
" UNIQUE (cn_domain, vlr)\n"
")"
,
"PRAGMA user_version = 6",
};
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 6\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,
@ -488,6 +592,7 @@ static db_upgrade_func_t db_upgrade_path[] = {
db_upgrade_v3,
db_upgrade_v4,
db_upgrade_v5,
db_upgrade_v6,
};
static int db_get_user_version(struct db_context *dbc)

View File

@ -884,3 +884,125 @@ out:
return ret;
}
static int _db_ind_run(struct db_context *dbc, sqlite3_stmt *stmt, int cn_domain, const char *vlr, bool reset)
{
int rc;
/* These are the current actual manifestations expected by DB_STMT_IND_SELECT. */
OSMO_ASSERT(cn_domain == 1 || cn_domain == 2);
if (!db_bind_int(stmt, "$cn_domain", cn_domain))
return -EIO;
if (!db_bind_text(stmt, "$vlr", vlr))
return -EIO;
/* execute the statement */
rc = sqlite3_step(stmt);
if (reset)
db_remove_reset(stmt);
return rc;
}
static int _db_ind_add(struct db_context *dbc, int cn_domain, const char *vlr)
{
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_IND_ADD];
if (_db_ind_run(dbc, stmt, cn_domain, vlr, true) != SQLITE_DONE) {
LOGP(DDB, LOGL_ERROR, "Cannot create IND entry for %s\n", osmo_quote_str_c(OTC_SELECT, vlr, -1));
return -EIO;
}
return 0;
}
static int _db_ind_del(struct db_context *dbc, int cn_domain, const char *vlr)
{
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_IND_DEL];
_db_ind_run(dbc, stmt, cn_domain, vlr, true);
/* We don't really care about the result. If it didn't exist, then that was the goal anyway. */
return 0;
}
static int _db_ind_get(struct db_context *dbc, int cn_domain, const char *vlr, unsigned int *ind)
{
int ret = 0;
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_IND_SELECT];
int rc = _db_ind_run(dbc, stmt, cn_domain, vlr, false);
if (rc == SQLITE_DONE) {
/* Does not exist yet */
ret = -ENOENT;
goto out;
} else if (rc != SQLITE_ROW) {
LOGP(DDB, LOGL_ERROR, "Error executing SQL: %d\n", rc);
ret = -EIO;
goto out;
}
OSMO_ASSERT(ind);
*ind = sqlite3_column_int64(stmt, 0);
out:
db_remove_reset(stmt);
return ret;
}
int _db_ind(struct db_context *dbc, enum osmo_gsup_cn_domain cn_domain, const struct osmo_gsup_peer_id *vlr,
unsigned int *ind, bool del)
{
const char *vlr_name = NULL;
int rc;
int cn_domain_int;
switch (vlr->type) {
case OSMO_GSUP_PEER_ID_IPA_NAME:
if (vlr->ipa_name.len < 2 || vlr->ipa_name.val[vlr->ipa_name.len - 1] != '\0') {
LOGP(DDB, LOGL_ERROR, "Expecting VLR ipa_name to be zero terminated; found %s\n",
osmo_ipa_name_to_str(&vlr->ipa_name));
return -ENOTSUP;
}
vlr_name = (const char*)vlr->ipa_name.val;
break;
default:
LOGP(DDB, LOGL_ERROR, "Unsupported osmo_gsup_peer_id type: %s\n",
osmo_gsup_peer_id_type_name(vlr->type));
return -ENOTSUP;
}
switch (cn_domain) {
default:
/* According to GSUP specs, PS is the default. */
case OSMO_GSUP_CN_DOMAIN_PS:
cn_domain_int = 1;
break;
case OSMO_GSUP_CN_DOMAIN_CS:
cn_domain_int = 2;
break;
}
if (del)
return _db_ind_del(dbc, cn_domain_int, vlr_name);
rc = _db_ind_get(dbc, cn_domain_int, vlr_name, ind);
if (!rc)
return 0;
/* Does not exist yet, create. */
rc = _db_ind_add(dbc, cn_domain_int, vlr_name);
if (rc) {
LOGP(DDB, LOGL_ERROR, "Error creating IND entry for %s\n", osmo_quote_str_c(OTC_SELECT, vlr_name, -1));
return rc;
}
/* To be sure, query again from scratch. */
return _db_ind_get(dbc, cn_domain_int, vlr_name, ind);
}
int db_ind(struct db_context *dbc, enum osmo_gsup_cn_domain cn_domain, const struct osmo_gsup_peer_id *vlr,
unsigned int *ind)
{
return _db_ind(dbc, cn_domain, vlr, ind, false);
}
int db_ind_del(struct db_context *dbc, enum osmo_gsup_cn_domain cn_domain, const struct osmo_gsup_peer_id *vlr)
{
return _db_ind(dbc, cn_domain, vlr, NULL, true);
}

View File

@ -132,8 +132,11 @@ int osmo_gsup_peer_id_set_str(struct osmo_gsup_peer_id *gsup_peer_id, enum osmo_
va_list ap;
int rc;
*gsup_peer_id = (struct osmo_gsup_peer_id){};
switch (type) {
case OSMO_GSUP_PEER_ID_IPA_NAME:
gsup_peer_id->type = OSMO_GSUP_PEER_ID_IPA_NAME;
va_start(ap, str_fmt);
rc = osmo_ipa_name_set_str_va(&gsup_peer_id->ipa_name, str_fmt, ap);
va_end(ap);

View File

@ -280,12 +280,13 @@ int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val,
***********************************************************************/
/* process an incoming SAI request */
static int rx_send_auth_info(unsigned int auc_3g_ind, struct osmo_gsup_req *req)
static int rx_send_auth_info(struct osmo_gsup_req *req)
{
struct osmo_gsup_message gsup_out = {
.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT,
};
bool separation_bit = false;
unsigned int auc_3g_ind;
int rc;
subscr_create_on_demand(req->gsup.imsi);
@ -293,6 +294,18 @@ static int rx_send_auth_info(unsigned int auc_3g_ind, struct osmo_gsup_req *req)
if (req->gsup.current_rat_type == OSMO_RAT_EUTRAN_SGS)
separation_bit = true;
rc = db_ind(g_hlr->dbc, req->gsup.cn_domain, &req->source_name, &auc_3g_ind);
if (rc) {
/* Super unlikely to fail: just getting and possibly adding an ID.
* If the DB per se fails, then below db_get_auc() should also fail.
* Still leave the benefit of the doubt at servicing instead of refusing. */
LOG_GSUP_REQ(req, LOGL_ERROR,
"Unable to determine 3G auth IND for source %s (rc=%d),"
" generating tuples with IND = 0\n",
osmo_gsup_peer_id_to_str(&req->source_name), rc);
auc_3g_ind = 0;
}
rc = db_get_auc(g_hlr->dbc, req->gsup.imsi, auc_3g_ind,
gsup_out.auth_vectors,
ARRAY_SIZE(gsup_out.auth_vectors),
@ -515,7 +528,7 @@ static int read_cb(struct osmo_gsup_conn *conn, struct msgb *msg)
switch (req->gsup.message_type) {
/* requests sent to us */
case OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST:
rx_send_auth_info(conn->auc_3g_ind, req);
rx_send_auth_info(req);
break;
case OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST:
rx_upd_loc_req(conn, req);

View File

@ -918,6 +918,58 @@ static void test_subscr_sqn()
comment_end();
}
static void test_ind()
{
comment_start();
#define ASSERT_IND(CN_DOMAIN, VLR, IND) do { \
unsigned int ind; \
struct osmo_gsup_peer_id vlr; \
OSMO_ASSERT(!osmo_gsup_peer_id_set_str(&vlr, OSMO_GSUP_PEER_ID_IPA_NAME, VLR)); \
ASSERT_RC(db_ind(dbc, CN_DOMAIN, &vlr, &ind), 0); \
fprintf(stderr, #CN_DOMAIN " %s ind = %u\n\n", osmo_quote_str((char*)vlr.ipa_name.val, vlr.ipa_name.len), ind); \
if (ind != (IND)) \
fprintf(stderr, " ERROR: expected " #IND "\n"); \
} while (0)
#define IND_DEL(CN_DOMAIN, VLR) do { \
struct osmo_gsup_peer_id vlr; \
OSMO_ASSERT(!osmo_gsup_peer_id_set_str(&vlr, OSMO_GSUP_PEER_ID_IPA_NAME, VLR)); \
ASSERT_RC(db_ind_del(dbc, CN_DOMAIN, &vlr), 0); \
fprintf(stderr, #CN_DOMAIN " %s ind deleted\n\n", osmo_quote_str((char*)vlr.ipa_name.val, vlr.ipa_name.len)); \
} while (0)
#define CS OSMO_GSUP_CN_DOMAIN_CS
#define PS OSMO_GSUP_CN_DOMAIN_PS
ASSERT_IND(CS, "msc-23", 1);
ASSERT_IND(PS, "sgsn-11", 0);
ASSERT_IND(CS, "msc-42", 3);
ASSERT_IND(PS, "sgsn-22", 2);
ASSERT_IND(CS, "msc-0x17", 5);
ASSERT_IND(PS, "sgsn-0xaa", 4);
ASSERT_IND(CS, "msc-42", 3);
ASSERT_IND(PS, "sgsn-22", 2);
ASSERT_IND(CS, "msc-0x17", 5);
ASSERT_IND(PS, "sgsn-0xaa", 4);
ASSERT_IND(CS, "msc-0x2a", 7);
ASSERT_IND(PS, "sgsn-0xbb", 6);
ASSERT_IND(CS, "msc-42", 3);
ASSERT_IND(PS, "sgsn-22", 2);
ASSERT_IND(CS, "msc-23", 1);
ASSERT_IND(PS, "sgsn-11", 0);
ASSERT_IND(CS, "same", 9);
ASSERT_IND(PS, "same", 8);
ASSERT_IND(CS, "same", 9);
ASSERT_IND(PS, "same", 8);
IND_DEL(CS, "msc-0x17"); /* dropped IND == 5 */
ASSERT_IND(PS, "unrelated-PS", 8);
ASSERT_IND(CS, "msc-0x2a", 7); /* known CS remains where it is */
ASSERT_IND(CS, "any-unknown-CS", 5); /* takes spot of IND == 5 */
comment_end();
}
static struct {
bool verbose;
} cmdline_opts = {
@ -998,6 +1050,7 @@ int main(int argc, char **argv)
test_subscr_aud();
test_subscr_aud_invalid_len();
test_subscr_sqn();
test_ind();
printf("Done\n");
db_close(dbc);

View File

@ -1613,3 +1613,104 @@ db_subscr_get_by_imsi(dbc, imsi0, &g_subscr) --> -ENOENT
===== test_subscr_sqn: SUCCESS
===== test_ind
db_ind(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr, &ind) --> 0
CS "msc-23\0" ind = 1
db_ind(dbc, OSMO_GSUP_CN_DOMAIN_PS, &vlr, &ind) --> 0
PS "sgsn-11\0" ind = 0
db_ind(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr, &ind) --> 0
CS "msc-42\0" ind = 3
db_ind(dbc, OSMO_GSUP_CN_DOMAIN_PS, &vlr, &ind) --> 0
PS "sgsn-22\0" ind = 2
db_ind(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr, &ind) --> 0
CS "msc-0x17\0" ind = 5
db_ind(dbc, OSMO_GSUP_CN_DOMAIN_PS, &vlr, &ind) --> 0
PS "sgsn-0xaa\0" ind = 4
db_ind(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr, &ind) --> 0
CS "msc-42\0" ind = 3
db_ind(dbc, OSMO_GSUP_CN_DOMAIN_PS, &vlr, &ind) --> 0
PS "sgsn-22\0" ind = 2
db_ind(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr, &ind) --> 0
CS "msc-0x17\0" ind = 5
db_ind(dbc, OSMO_GSUP_CN_DOMAIN_PS, &vlr, &ind) --> 0
PS "sgsn-0xaa\0" ind = 4
db_ind(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr, &ind) --> 0
CS "msc-0x2a\0" ind = 7
db_ind(dbc, OSMO_GSUP_CN_DOMAIN_PS, &vlr, &ind) --> 0
PS "sgsn-0xbb\0" ind = 6
db_ind(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr, &ind) --> 0
CS "msc-42\0" ind = 3
db_ind(dbc, OSMO_GSUP_CN_DOMAIN_PS, &vlr, &ind) --> 0
PS "sgsn-22\0" ind = 2
db_ind(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr, &ind) --> 0
CS "msc-23\0" ind = 1
db_ind(dbc, OSMO_GSUP_CN_DOMAIN_PS, &vlr, &ind) --> 0
PS "sgsn-11\0" ind = 0
db_ind(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr, &ind) --> 0
CS "same\0" ind = 9
db_ind(dbc, OSMO_GSUP_CN_DOMAIN_PS, &vlr, &ind) --> 0
PS "same\0" ind = 8
db_ind(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr, &ind) --> 0
CS "same\0" ind = 9
db_ind(dbc, OSMO_GSUP_CN_DOMAIN_PS, &vlr, &ind) --> 0
PS "same\0" ind = 8
db_ind_del(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr) --> 0
CS "msc-0x17\0" ind deleted
db_ind(dbc, OSMO_GSUP_CN_DOMAIN_PS, &vlr, &ind) --> 0
PS "unrelated-PS\0" ind = 10
ERROR: expected 8
db_ind(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr, &ind) --> 0
CS "msc-0x2a\0" ind = 7
db_ind(dbc, OSMO_GSUP_CN_DOMAIN_CS, &vlr, &ind) --> 0
CS "any-unknown-CS\0" ind = 5
===== test_ind: SUCCESS

View File

@ -85,6 +85,7 @@ DDB Database <PATH>test.db' has been upgraded to HLR DB schema version 2
DDB Database <PATH>test.db' has been upgraded to HLR DB schema version 3
DDB Database <PATH>test.db' has been upgraded to HLR DB schema version 4
DDB Database <PATH>test.db' has been upgraded to HLR DB schema version 5
DDB Database <PATH>test.db' has been upgraded to HLR DB schema version 6
DMAIN Cmdline option --db-check: Database was opened successfully, quitting.
Resulting db:
@ -117,6 +118,14 @@ algo_id_3g|ind_bitlen|k|op|opc|sqn|subscriber_id
5|5|44444444444444444444444444444444|44444444444444444444444444444444||0|5
5|5|55555555555555555555555555555555||55555555555555555555555555555555|0|6
Table: ind
name|type|notnull|dflt_value|pk
cn_domain|INTEGER|1||0
ind|INTEGER|0||1
vlr|TEXT|1||0
Table ind contents:
Table: subscriber
name|type|notnull|dflt_value|pk
ggsn_number|VARCHAR(15)|0||0
@ -171,5 +180,5 @@ osmo-hlr --database $db --db-check --config-file $srcdir/osmo-hlr.cfg
rc = 0
DMAIN hlr starting
DDB using database: <PATH>test.db
DDB Database <PATH>test.db' has HLR DB schema version 5
DDB Database <PATH>test.db' has HLR DB schema version 6
DMAIN Cmdline option --db-check: Database was opened successfully, quitting.