From 89fda3024a0c9a422cc5e5033b016ce0ab6c1ff3 Mon Sep 17 00:00:00 2001 From: Keith Date: Tue, 19 Jan 2021 07:01:33 +0100 Subject: [PATCH] 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 --- include/osmocom/hlr/db.h | 9 +++ src/db.c | 7 +++ src/db_hlr.c | 90 ++++++++++++++++++++++++++++ src/hlr_vty_subscr.c | 123 +++++++++++++++++++++++++++++++++++++-- tests/test_nodes.vty | 4 ++ 5 files changed, 228 insertions(+), 5 deletions(-) diff --git a/include/osmocom/hlr/db.h b/include/osmocom/hlr/db.h index ca336a0f..f70df83b 100644 --- a/include/osmocom/hlr/db.h +++ b/include/osmocom/hlr/db.h @@ -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, diff --git a/src/db.c b/src/db.c index 5ec20e24..09a17b64 100644 --- a/src/db.c +++ b/src/db.c @@ -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 = ?", diff --git a/src/db_hlr.c b/src/db_hlr.c index 6ba43c29..a181c0c5 100644 --- a/src/db_hlr.c +++ b/src/db_hlr.c @@ -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. diff --git a/src/hlr_vty_subscr.c b/src/hlr_vty_subscr.c index f5066c16..ad16045c 100644 --- a/src/hlr_vty_subscr.c +++ b/src/hlr_vty_subscr.c @@ -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); diff --git a/tests/test_nodes.vty b/tests/test_nodes.vty index cf1707a5..5848fe14 100644 --- a/tests/test_nodes.vty +++ b/tests/test_nodes.vty @@ -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