Add vty command to show summary of all or filtered subscribers

Adds the following commands:

show subscribers all - Display summary of all entries in HLR
show subscribers (imsi|msisdn|cs|ps) ... As above but filter on search field/string
show subscribers last seen - Display only subscribers with data in
Last LU update field, and sorts by Last LU.

Change-Id: I7f0573381a6d0d13841ac6d42d50f0e8389decf4
This commit is contained in:
Keith Whyte 2021-01-19 07:01:33 +01:00 committed by laforge
parent dd746949d0
commit 89fda3024a
5 changed files with 228 additions and 5 deletions

View File

@ -8,6 +8,12 @@
struct hlr;
enum stmt_idx {
DB_STMT_SEL_ALL,
DB_STMT_SEL_ALL_ORDER_LAST_SEEN,
DB_STMT_SEL_FILTER_MSISDN,
DB_STMT_SEL_FILTER_IMSI,
DB_STMT_SEL_FILTER_CS,
DB_STMT_SEL_FILTER_PS,
DB_STMT_SEL_BY_IMSI,
DB_STMT_SEL_BY_MSISDN,
DB_STMT_SEL_BY_ID,
@ -148,6 +154,9 @@ int db_subscr_update_imei_by_imsi(struct db_context *dbc, const char* imsi, cons
int db_subscr_exists_by_imsi(struct db_context *dbc, const char *imsi);
int db_subscr_exists_by_msisdn(struct db_context *dbc, const char *msisdn);
int db_subscrs_get(struct db_context *dbc, const char *filter_type, const char *filter,
void (*get_cb)(struct hlr_subscriber *subscr, void *data), void *data,
int *count, const char **err);
int db_subscr_get_by_imsi(struct db_context *dbc, const char *imsi,
struct hlr_subscriber *subscr);
int db_subscr_get_by_msisdn(struct db_context *dbc, const char *msisdn,

View File

@ -51,6 +51,13 @@
"sgsn_via_proxy"
static const char *stmt_sql[] = {
[DB_STMT_SEL_ALL] = "SELECT " SEL_COLUMNS " FROM subscriber;",
[DB_STMT_SEL_ALL_ORDER_LAST_SEEN] = "SELECT " SEL_COLUMNS " FROM subscriber "
"WHERE last_lu_seen IS NOT NULL ORDER BY last_lu_seen;",
[DB_STMT_SEL_FILTER_MSISDN] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE msisdn LIKE $search ORDER BY msisdn",
[DB_STMT_SEL_FILTER_IMSI] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE imsi LIKE $search ORDER BY imsi",
[DB_STMT_SEL_FILTER_CS] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE nam_cs = $search ORDER BY last_lu_seen",
[DB_STMT_SEL_FILTER_PS] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE nam_ps = $search ORDER BY last_lu_seen",
[DB_STMT_SEL_BY_IMSI] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE imsi = ?",
[DB_STMT_SEL_BY_MSISDN] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE msisdn = ?",
[DB_STMT_SEL_BY_ID] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE id = ?",

View File

@ -625,6 +625,96 @@ int db_subscr_get_by_msisdn(struct db_context *dbc, const char *msisdn,
return rc;
}
/*! Retrieve subscriber data from the HLR database.
* \param[in,out] dbc database context.
* \param[in] filter_type ASCII string of identifier type to search.
* \param[in] filter ASCII string to search.
* \param[in] get_cb pointer to call back function for data.
* \param[in,out] data pointer to pass to callback function.
* \param[in,out] count counter for number of matched subscribers.
* \param[in,our] err
* \returns 0 on success, -ENOENT if no subscriber was found, -EIO on
* database error.
*/
int db_subscrs_get(struct db_context *dbc, const char *filter_type, const char *filter,
void (*get_cb)(struct hlr_subscriber *subscr, void *data), void *data,
int *count, const char **err)
{
sqlite3_stmt *stmt;
char search[256];
int rc;
struct hlr_subscriber subscr;
bool show_ls = false;
if (!filter_type) {
stmt = dbc->stmt[DB_STMT_SEL_ALL];
} else if (strcmp(filter_type, "imsi") == 0) {
stmt = dbc->stmt[DB_STMT_SEL_FILTER_IMSI];
} else if (strcmp(filter_type, "msisdn") == 0) {
stmt = dbc->stmt[DB_STMT_SEL_FILTER_MSISDN];
} else if (strcmp(filter_type, "cs") == 0) {
stmt = dbc->stmt[DB_STMT_SEL_FILTER_CS];
} else if (strcmp(filter_type, "ps") == 0) {
stmt = dbc->stmt[DB_STMT_SEL_FILTER_PS];
} else if (strcmp(filter_type, "last_lu_seen") == 0) {
show_ls = true;
stmt = dbc->stmt[DB_STMT_SEL_ALL_ORDER_LAST_SEEN];
} else {
return -EIO;
}
if (filter && strcmp(filter_type, "last_lu_seen") != 0) {
if (strcmp(filter, "on") == 0) {
sprintf(search, "%s", "1");
} else if (strcmp(filter, "off") == 0) {
sprintf(search, "%s", "0");
} else {
sprintf(search, "%%%s%%", filter);
}
if (!db_bind_text(stmt, "$search", search)) {
*err = sqlite3_errmsg(dbc->db);
return -EIO;
}
}
rc = sqlite3_step(stmt);
if (rc == SQLITE_DONE) {
db_remove_reset(stmt);
*err = "No matching subscriber(s)";
return -ENOENT;
}
while (rc == SQLITE_ROW) {
subscr = (struct hlr_subscriber){
.id = sqlite3_column_int64(stmt, 0),};
copy_sqlite3_text_to_buf(subscr.imsi, stmt, 1);
copy_sqlite3_text_to_buf(subscr.msisdn, stmt, 2);
copy_sqlite3_text_to_buf(subscr.imei, stmt, 3);
subscr.nam_cs = sqlite3_column_int(stmt, 9);
subscr.nam_ps = sqlite3_column_int(stmt, 10);
if (show_ls)
parse_last_lu_seen(&subscr.last_lu_seen, (const char *)sqlite3_column_text(stmt, 14),
subscr.imsi, "CS");
get_cb(&subscr, data);
rc = sqlite3_step(stmt);
(*count)++;
}
db_remove_reset(stmt);
if (rc != SQLITE_DONE) {
*err = sqlite3_errmsg(dbc->db);
return -EIO;
} else if (rc == SQLITE_DONE) {
*err = NULL;
return 0;
} else {
*err = sqlite3_errmsg(dbc->db);
LOGP(DAUC, LOGL_ERROR, "Cannot read subscribers from db:: %s\n", *err);
return rc;
}
}
/*! Retrieve subscriber data from the HLR database.
* \param[in,out] dbc database context.
* \param[in] id ID of the subscriber in the HLR db.

View File

@ -44,13 +44,14 @@ static char *get_datestr(const time_t *t, char *buf, size_t bufsize)
return buf;
}
static void dump_last_lu_seen(struct vty *vty, const char *domain_label, time_t last_lu_seen)
static void dump_last_lu_seen(struct vty *vty, const char *domain_label, time_t last_lu_seen, bool only_age)
{
uint32_t age;
char datebuf[32];
if (!last_lu_seen)
return;
vty_out(vty, " last LU seen on %s: %s", domain_label, get_datestr(&last_lu_seen, datebuf, sizeof(datebuf)));
if (!only_age)
vty_out(vty, " last LU seen on %s: %s", domain_label, get_datestr(&last_lu_seen, datebuf, sizeof(datebuf)));
if (!timestamp_age(&last_lu_seen, &age))
vty_out(vty, " (invalid timestamp)%s", VTY_NEWLINE);
else {
@ -64,7 +65,10 @@ static void dump_last_lu_seen(struct vty *vty, const char *domain_label, time_t
UNIT_AGO("h", 60*60);
UNIT_AGO("m", 60);
UNIT_AGO("s", 1);
vty_out(vty, " ago)%s", VTY_NEWLINE);
if (!only_age)
vty_out(vty, " ago)%s", VTY_NEWLINE);
else
vty_out(vty, " ago)");
#undef UNIT_AGO
}
}
@ -108,8 +112,8 @@ static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr)
vty_out(vty, " PS disabled%s", VTY_NEWLINE);
if (subscr->ms_purged_ps)
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);
dump_last_lu_seen(vty, "CS", subscr->last_lu_seen, false);
dump_last_lu_seen(vty, "PS", subscr->last_lu_seen_ps, false);
if (!*subscr->imsi)
return;
@ -159,6 +163,28 @@ static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr)
}
}
static void subscr_dump_summary_vty(struct hlr_subscriber *subscr, void *data)
{
struct vty *vty = data;
vty_out(vty, "%-5"PRIu64" %-12s %-16s", subscr->id,
*subscr->msisdn ? subscr->msisdn : "none",
*subscr->imsi ? subscr->imsi : "none");
if (*subscr->imei) {
char checksum = osmo_luhn(subscr->imei, 14);
if (checksum == -EINVAL)
vty_out(vty, " %-14s (INVALID LENGTH!)", subscr->imei);
else
vty_out(vty, " %-14s%c", subscr->imei, checksum);
} else {
vty_out(vty," ------------- ");
}
vty_out(vty, " %-2s%-2s ", subscr->nam_cs ? "CS" : "", subscr->nam_ps ? "PS" : "");
if (subscr->last_lu_seen)
dump_last_lu_seen(vty, "CS", subscr->last_lu_seen, true);
vty_out_newline(vty);
}
static int get_subscr_by_argv(struct vty *vty, const char *type, const char *id, struct hlr_subscriber *subscr)
{
char imei_buf[GSM23003_IMEI_NUM_DIGITS_NO_CHK+1];
@ -186,11 +212,52 @@ static int get_subscr_by_argv(struct vty *vty, const char *type, const char *id,
return rc;
}
static void dump_summary_table_vty(struct vty *vty, bool header, bool show_ls)
{
const char *texts = "ID MSISDN IMSI IMEI NAM";
const char *lines = "----- ------------ ---------------- ---------------- -----";
const char *ls_text = " LAST SEEN";
const char *ls_line = " ------------";
if (header) {
if (!show_ls)
vty_out(vty, "%s%s%s%s", texts, VTY_NEWLINE, lines, VTY_NEWLINE);
else
vty_out(vty, "%s%s%s%s%s%s", texts, ls_text, VTY_NEWLINE, lines, ls_line, VTY_NEWLINE);
} else {
if (!show_ls)
vty_out(vty, "%s%s%s%s", lines, VTY_NEWLINE, texts, VTY_NEWLINE);
else
vty_out(vty, "%s%s%s%s%s%s", lines, ls_line, VTY_NEWLINE, texts, ls_text, VTY_NEWLINE);
}
}
static int get_subscrs(struct vty *vty, const char *filter_type, const char *filter)
{
int rc = -1;
int count = 0;
const char *err;
bool show_ls = (filter_type && strcmp(filter_type, "last_lu_seen") == 0);
dump_summary_table_vty(vty, true, show_ls);
rc = db_subscrs_get(g_hlr->dbc, filter_type, filter, subscr_dump_summary_vty, vty, &count, &err);
if (count > 40) {
dump_summary_table_vty(vty, false, show_ls);
}
if (count > 0)
vty_out(vty, " Subscribers Shown: %d%s", count, VTY_NEWLINE);
if (rc)
vty_out(vty, "%% %s%s", err, VTY_NEWLINE);
return rc;
}
#define SUBSCR_CMD "subscriber "
#define SUBSCR_CMD_HELP "Subscriber management commands\n"
#define SUBSCR_SHOW_HELP "Show subscriber information\n"
#define SUBSCRS_SHOW_HELP "Show all subscribers (with filter possibility)\n"
#define SUBSCR_ID "(imsi|msisdn|id|imei) IDENT"
#define SUBSCR_FILTER "(imsi|msisdn) FILTER"
#define SUBSCR_ID_HELP \
"Identify subscriber by IMSI\n" \
"Identify subscriber by MSISDN (phone number)\n" \
@ -225,6 +292,48 @@ ALIAS(subscriber_show, show_subscriber_cmd,
"show " SUBSCR_CMD SUBSCR_ID,
SHOW_STR SUBSCR_SHOW_HELP SUBSCR_ID_HELP);
DEFUN(show_subscriber_all,
show_subscriber_all_cmd,
"show subscribers all",
SHOW_STR SUBSCRS_SHOW_HELP "Show summary of all subscribers\n")
{
if (get_subscrs(vty, NULL, NULL))
return CMD_WARNING;
return CMD_SUCCESS;
}
DEFUN(show_subscriber_filtered,
show_subscriber_filtered_cmd,
"show subscribers " SUBSCR_FILTER,
SHOW_STR SUBSCRS_SHOW_HELP
"Filter Subscribers by IMSI\n" "Filter Subscribers by MSISDN\n" "String to match in msisdn or imsi\n")
{
const char *filter_type = argv[0];
const char *filter = argv[1];
if (get_subscrs(vty, filter_type, filter))
return CMD_WARNING;
return CMD_SUCCESS;
}
ALIAS(show_subscriber_filtered, show_subscriber_filtered_cmd2,
"show subscribers (cs|ps) (on|off)",
SHOW_STR SUBSCR_SHOW_HELP
"Filter Subscribers by CS Network Access Mode\n" "Filter Subscribers by PS Network Access Mode\n"
"Authorised\n" "Not Authorised\n");
DEFUN(show_subscriber_order_last_seen, show_subscriber_order_last_seen_cmd,
"show subscribers last-seen",
SHOW_STR SUBSCR_SHOW_HELP "Show Subscribers Ordered by Last Seen Time\n")
{
if (get_subscrs(vty, "last_lu_seen", NULL))
return CMD_WARNING;
return CMD_SUCCESS;
}
DEFUN(subscriber_create,
subscriber_create_cmd,
SUBSCR_CMD "imsi IDENT create",
@ -678,6 +787,10 @@ DEFUN(subscriber_nam,
void hlr_vty_subscriber_init(void)
{
install_element_ve(&show_subscriber_all_cmd);
install_element_ve(&show_subscriber_filtered_cmd);
install_element_ve(&show_subscriber_filtered_cmd2);
install_element_ve(&show_subscriber_order_last_seen_cmd);
install_element_ve(&subscriber_show_cmd);
install_element_ve(&show_subscriber_cmd);
install_element(ENABLE_NODE, &subscriber_create_cmd);

View File

@ -13,6 +13,10 @@ OsmoHLR> ?
OsmoHLR> list
...
show gsup-connections
show subscribers all
show subscribers (imsi|msisdn) FILTER
show subscribers (cs|ps) (on|off)
show subscribers last-seen
subscriber (imsi|msisdn|id|imei) IDENT show
show subscriber (imsi|msisdn|id|imei) IDENT
show mslookup services