Compare commits

...

5 Commits

Author SHA1 Message Date
Oliver Smith
6da2af924a src/lu_fsm.c: dealloc and cancel previous IMSI
FIXME: deallocation works, but the location cancel does not get sent
yet. (LU runs into a timeout and ME tries again, then the LU is
successful)

Related: OS#4476
Change-Id: I3993cbec8c5e96d704a58b396a55362e938c9cd2
2020-05-07 09:38:51 +02:00
Oliver Smith
fca8a0a935 src/hlr.c: resolve/scramble pseudo IMSI in GSUP
Resolve the pseudonymous IMSI to the real IMSI as soon as possible, when
a GSUP message arrives. Use the real IMSI internally in OsmoHLR, so the
existing logic does not need to be changed. Scramble the IMSI back to the
pseudonymous IMSI in one common place (gsup_server_send_req_response)
right before responding to GSUP messages.

Related: OS#4476
Change-Id: I400810f96efca7746dd4ba1bed666acaea425e8a
2020-05-06 15:09:01 +02:00
Oliver Smith
7b4e5b1390 VTY: alloc/dealloc/show imsi pseudo
Create VTY commands and related database functions to get, allocate and
deallocate pseudonymous IMSIs. As this is a proof of concept, the code
to retrieve the next pseudonymous IMSI has no performance optimizations
and might not use a good entropy source (I did not verify).

Related: OS#4476
Change-Id: Ia93ee58d5e03c801eb774b9483a9a4417d031959
2020-05-06 13:42:26 +02:00
Oliver Smith
502f10fb0c VTY: add imsi-pseudo option
Related: OS#4476
Change-Id: I2a01364cdba61dfd2597aff9b4a812291aaa325d
2020-05-06 08:59:46 +02:00
Oliver Smith
e2c192e8c8 sql: add table subscriber_imsi_pseudo
Related: OS#4476
Change-Id: I5d66ae11102dd616d1ce468034241493719f9db2
2020-05-06 08:57:19 +02:00
22 changed files with 609 additions and 3 deletions

View File

@@ -80,6 +80,10 @@ struct osmo_gsup_req {
/* A decoded GSUP message still points into the received msgb. For a decoded osmo_gsup_message to remain valid,
* we also need to keep the msgb. */
struct msgb *msg;
/* The pseudonymous IMSI in gsup->imsi is replaced with the real IMSI right after receiving the message. A
* copy of the pseudonymous IMSI is stored here for the reply. */
char imsi_pseudo[OSMO_IMSI_BUF_SIZE];
};
struct osmo_gsup_req *osmo_gsup_req_new(void *ctx, const struct osmo_cni_peer_id *from_peer, struct msgb *msg,

View File

@@ -9,6 +9,7 @@ noinst_HEADERS = \
hlr_ussd.h \
hlr_vty.h \
hlr_vty_subscr.h \
imsi_pseudo.h \
logging.h \
lu_fsm.h \
mslookup_server.h \

View File

@@ -33,6 +33,11 @@ enum stmt_idx {
DB_STMT_SET_LAST_LU_SEEN_PS,
DB_STMT_EXISTS_BY_IMSI,
DB_STMT_EXISTS_BY_MSISDN,
DB_STMT_PSEUDO_BY_ID,
DB_STMT_PSEUDO_INSERT,
DB_STMT_PSEUDO_DELETE,
DB_STMT_PSEUDO_NEXT,
DB_STMT_PSEUDO_RESOLVE,
_NUM_DB_STMT
};

View File

@@ -74,5 +74,7 @@ int osmo_gsup_create_insert_subscriber_data_msg(struct osmo_gsup_message *gsup,
uint8_t *msisdn_enc, size_t msisdn_enc_size,
uint8_t *apn_buf, size_t apn_buf_size,
enum osmo_gsup_cn_domain cn_domain);
void osmo_gsup_create_location_cancel_msg(struct osmo_gsup_message *gsup, const char *imsi,
enum osmo_gsup_cn_domain cn_domain, enum osmo_gsup_cancel_type cancel_type);
int osmo_gsup_forward_to_local_peer(struct osmo_gsup_server *server, const struct osmo_cni_peer_id *to_peer,
struct osmo_gsup_req *req, struct osmo_gsup_message *modified_gsup);

View File

@@ -71,6 +71,8 @@ struct hlr {
uint8_t subscr_create_on_demand_flags;
unsigned int subscr_create_on_demand_rand_msisdn_len;
bool imsi_pseudo;
struct {
bool allow_startup;
struct {

View File

@@ -0,0 +1,20 @@
#pragma once
#include <stdint.h>
#include <inttypes.h>
#include <osmocom/hlr/db.h>
#define LOGPSEUDO(id, level, fmt, args ...) LOGP(DPSEUDO, level, "subscriber_id='%" PRId64 "': " fmt, id, ## args)
struct imsi_pseudo_data {
int alloc_count; /* 0: none, 1: only current is allocated, 2: current and previous are allocated */
int64_t i; /* current imsi_pseudo_i */
char current[GSM23003_IMSI_MAX_DIGITS+1];
char previous[GSM23003_IMSI_MAX_DIGITS+1];
};
int db_get_imsi_pseudo_data(struct db_context *dbc, int64_t subscr_id, struct imsi_pseudo_data *data);
int db_alloc_imsi_pseudo(struct db_context *dbc, int64_t subscr_id, const char *imsi_pseudo, int64_t imsi_pseudo_i);
int db_dealloc_imsi_pseudo(struct db_context *dbc, const char *imsi_pseudo);
int db_get_imsi_pseudo_next(struct db_context *dbc, char *imsi_pseudo);
int db_get_imsi_pseudo_resolve(struct db_context *dbc, const char *imsi_pseudo, char *imsi);

View File

@@ -8,6 +8,7 @@ enum {
DGSUP,
DAUC,
DSS,
DPSEUDO,
DMSLOOKUP,
DLU,
DDGSM,

View File

@@ -62,6 +62,16 @@ CREATE TABLE subscriber_multi_msisdn (
msisdn VARCHAR(15) NOT NULL
);
CREATE TABLE subscriber_imsi_pseudo (
-- https://osmocom.org/projects/imsi-pseudo/wiki
id INTEGER PRIMARY KEY,
subscriber_id INTEGER NOT NULL, -- subscriber.id
imsi_pseudo VARCHAR(15) UNIQUE NOT NULL,
imsi_pseudo_i INTEGER default 0 NOT NULL
);
/* FIXME: index for imsi_pseudo and subscriber_id for better performance */
CREATE TABLE auc_2g (
subscriber_id INTEGER PRIMARY KEY, -- subscriber.id
algo_id_2g INTEGER NOT NULL, -- enum osmo_auth_algo value
@@ -83,4 +93,4 @@ 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;

View File

@@ -44,6 +44,7 @@ osmo_hlr_SOURCES = \
db.c \
db_auc.c \
db_hlr.c \
db_imsi_pseudo.c \
gsup_router.c \
gsup_server.c \
hlr.c \

View File

@@ -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 5
#define CURRENT_SCHEMA_VERSION 6
#define SEL_COLUMNS \
"id," \
@@ -85,6 +85,28 @@ 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_PSEUDO_BY_ID] =
"SELECT imsi_pseudo, imsi_pseudo_i"
" FROM subscriber_imsi_pseudo"
" WHERE subscriber_id = $subscriber_id"
" ORDER BY imsi_pseudo_i DESC",
[DB_STMT_PSEUDO_INSERT] =
"INSERT INTO subscriber_imsi_pseudo (subscriber_id, imsi_pseudo, imsi_pseudo_i)"
" VALUES($subscriber_id, $imsi_pseudo, $imsi_pseudo_i)",
[DB_STMT_PSEUDO_DELETE] =
"DELETE FROM subscriber_imsi_pseudo WHERE imsi_pseudo = $imsi_pseudo",
[DB_STMT_PSEUDO_NEXT] = /* Proof of concept! Verify performance and entropy usage before using in real world! */
"SELECT imsi"
" FROM subscriber"
" LEFT JOIN subscriber_imsi_pseudo ON imsi = imsi_pseudo"
" WHERE imsi_pseudo IS NULL"
" ORDER BY RANDOM()"
" LIMIT 1",
[DB_STMT_PSEUDO_RESOLVE] =
"SELECT imsi"
" FROM subscriber"
" LEFT JOIN subscriber_imsi_pseudo ON subscriber_id = subscriber.id"
" WHERE imsi_pseudo = $imsi_pseudo",
};
static void sql3_error_log_cb(void *arg, int err_code, const char *msg)
@@ -479,6 +501,28 @@ 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 subscriber_imsi_pseudo (\n"
"-- https://osmocom.org/projects/imsi-pseudo/wiki\n"
"id INTEGER PRIMARY KEY,\n"
"subscriber_id INTEGER NOT NULL, -- subscriber.id\n"
"imsi_pseudo VARCHAR(15) UNIQUE NOT NULL,\n"
"imsi_pseudo_i INTEGER default 0 NOT NULL\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,
@@ -486,6 +530,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)

177
src/db_imsi_pseudo.c Normal file
View File

@@ -0,0 +1,177 @@
/* (C) 2020 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <errno.h>
#include <inttypes.h>
#include <sqlite3.h>
#include <osmocom/hlr/logging.h>
#include <osmocom/hlr/db.h>
#include <osmocom/hlr/imsi_pseudo.h>
int db_get_imsi_pseudo_data(struct db_context *dbc, int64_t subscr_id, struct imsi_pseudo_data *data)
{
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_PSEUDO_BY_ID];
int i, rc, ret = 0;
memset(data, 0, sizeof(*data));
if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
return -EIO;
/* Retrieve up to two allocated pseudo IMSIs in three sqlite3 steps */
for (i = 0; i < 3; i++) {
rc = sqlite3_step(stmt);
switch (rc) {
case SQLITE_ROW:
data->alloc_count = i + 1;
switch (i) {
case 0:
/* First entry is always current (ORDER BY in SQL statement) */
copy_sqlite3_text_to_buf(data->current, stmt, 0);
data->i = sqlite3_column_int(stmt, 1);
break;
case 1:
copy_sqlite3_text_to_buf(data->previous, stmt, 0);
break;
case 2:
LOGPSEUDO(subscr_id, LOGL_ERROR, "more than two pseudonymous IMSI allocated\n");
ret = -EINVAL;
goto out;
}
break;
case SQLITE_DONE:
goto out;
default:
LOGPSEUDO(subscr_id, LOGL_ERROR, "error executing SQL: %d\n", rc);
ret = -EIO;
goto out;
}
}
out:
db_remove_reset(stmt);
return ret;
}
int db_alloc_imsi_pseudo(struct db_context *dbc, int64_t subscr_id, const char *imsi_pseudo, int64_t imsi_pseudo_i)
{
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_PSEUDO_INSERT];
int rc, ret = 0;
if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
return -EIO;
if (!db_bind_text(stmt, "$imsi_pseudo", imsi_pseudo))
return -EIO;
if (!db_bind_int64(stmt, "$imsi_pseudo_i", imsi_pseudo_i))
return -EIO;
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
LOGPSEUDO(subscr_id, LOGL_ERROR, "imsi_pseudo='%s', imsi_pseudo_i='%" PRId64 "': SQL error during"
" allocate: %d\n", imsi_pseudo, imsi_pseudo_i, rc);
ret = -EIO;
}
db_remove_reset(stmt);
return ret;
}
int db_dealloc_imsi_pseudo(struct db_context *dbc, const char *imsi_pseudo)
{
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_PSEUDO_DELETE];
int rc, ret = 0;
if (!db_bind_text(stmt, "$imsi_pseudo", imsi_pseudo))
return -EIO;
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
LOGP(DPSEUDO, LOGL_ERROR, "imsi_pseudo='%s': SQL error during deallocate: %d\n", imsi_pseudo, rc);
ret = -EIO;
}
db_remove_reset(stmt);
return ret;
}
/*! Get the next random free pseudo IMSI.
* \param[in] dbc database context.
* \param[out] imsi_pseudo buffer with length GSM23003_IMSI_MAX_DIGITS+1.
* \returns 0: success, -1: no next IMSI available, -2: SQL error. */
int db_get_imsi_pseudo_next(struct db_context *dbc, char *imsi_pseudo)
{
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_PSEUDO_NEXT];
const char *imsi;
int rc, ret = 0;
rc = sqlite3_step(stmt);
switch (rc) {
case SQLITE_ROW:
/* Can't use copy_sqlite3_text_to_buf, as it assumes the wrong size for imsi_pseudo */
imsi = (const char *)sqlite3_column_text(stmt, 0);
osmo_strlcpy(imsi_pseudo, imsi, GSM23003_IMSI_MAX_DIGITS+1);
break;
case SQLITE_DONE:
LOGP(DPSEUDO, LOGL_ERROR, "failed to get next pseudonymous IMSI: all IMSIs are already allocated as"
" pseudo IMSI\n");
ret = -1;
break;
default:
LOGP(DPSEUDO, LOGL_ERROR, "failed to get next pseudonymous IMSI, SQL error: %d\n", rc);
ret = -2;
break;
}
db_remove_reset(stmt);
return ret;
}
/*! Resolve a pseudo IMSI to the real IMSI.
* \param[in] dbc database context.
* \param[in] imsi_pseudo the IMSI to be resolved
* \param[out] imsi buffer with length GSM23003_IMSI_MAX_DIGITS+1.
* \returns 0: success, -1: no associated real IMSI, -2: SQL error. */
int db_get_imsi_pseudo_resolve(struct db_context *dbc, const char *imsi_pseudo, char *imsi)
{
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_PSEUDO_RESOLVE];
int rc, ret=0;
if (!db_bind_text(stmt, "$imsi_pseudo", imsi_pseudo))
return -EIO;
rc = sqlite3_step(stmt);
switch (rc) {
case SQLITE_ROW:
/* Can't use copy_sqlite3_text_to_buf, as it assumes the wrong size for imsi_pseudo */
osmo_strlcpy(imsi, (const char *)sqlite3_column_text(stmt, 0), GSM23003_IMSI_MAX_DIGITS + 1);
break;
case SQLITE_DONE:
LOGP(DPSEUDO, LOGL_NOTICE, "cannot resolve pseudonymous IMSI '%s': no associated real IMSI found\n",
imsi_pseudo);
ret = -1;
break;
default:
LOGP(DPSEUDO, LOGL_ERROR, "cannot resolve pseudonymous IMSI '%s': SQL error: %d\n", imsi_pseudo, rc);
ret = -2;
break;
}
db_remove_reset(stmt);
return ret;
}

View File

@@ -30,6 +30,7 @@
#include <osmocom/hlr/gsup_server.h>
#include <osmocom/hlr/gsup_router.h>
#include <osmocom/hlr/logging.h>
#define LOG_GSUP_CONN(conn, level, fmt, args...) \
LOGP(DLGSUP, level, "GSUP peer %s: " fmt, \
@@ -95,6 +96,11 @@ static void gsup_server_send_req_response(struct osmo_gsup_req *req, struct osmo
return;
}
if (req->imsi_pseudo[0]) {
LOGP(DPSEUDO, LOGL_DEBUG, "pseudo IMSI scramble: '%s' => '%s'\n", response->imsi, req->imsi_pseudo);
strncpy(response->imsi, req->imsi_pseudo, sizeof(response->imsi));
}
rc = osmo_gsup_encode(msg, response);
if (rc) {
LOG_GSUP_REQ(req, LOGL_ERROR, "Unable to encode: {%s}\n",
@@ -514,6 +520,29 @@ int osmo_gsup_create_insert_subscriber_data_msg(struct osmo_gsup_message *gsup,
return 0;
}
/**
* Populate a gsup message structure with a Location Cancel Message.
* All required memory buffers for data pointed to by pointers in struct omso_gsup_message
* must be allocated by the caller and should have the same lifetime as the gsup parameter.
*
* \param[out] gsup The gsup message to populate.
* \param[in] imsi The subscriber's IMSI.
* \param[in] cn_domain The CN Domain of the subscriber connection.
* \param[in] cancel_type The cancellation type.
*/
void osmo_gsup_create_location_cancel_msg(struct osmo_gsup_message *gsup, const char *imsi,
enum osmo_gsup_cn_domain cn_domain, enum osmo_gsup_cancel_type cancel_type)
{
OSMO_ASSERT(gsup);
*gsup = (struct osmo_gsup_message){
.message_type = OSMO_GSUP_MSGT_LOCATION_CANCEL_REQUEST,
.cn_domain = cn_domain,
.cancel_type = cancel_type
};
osmo_strlcpy(gsup->imsi, imsi, sizeof(gsup->imsi));
}
int osmo_gsup_forward_to_local_peer(struct osmo_gsup_server *server, const struct osmo_cni_peer_id *to_peer,
struct osmo_gsup_req *req, struct osmo_gsup_message *modified_gsup)
{

View File

@@ -48,6 +48,7 @@
#include <osmocom/hlr/rand.h>
#include <osmocom/hlr/hlr_vty.h>
#include <osmocom/hlr/hlr_ussd.h>
#include <osmocom/hlr/imsi_pseudo.h>
#include <osmocom/hlr/dgsm.h>
#include <osmocom/hlr/proxy.h>
#include <osmocom/hlr/lu_fsm.h>
@@ -512,6 +513,19 @@ static int read_cb(struct osmo_gsup_conn *conn, struct msgb *msg)
return 0;
}
/* Resolve pseudonymous IMSI */
if (g_hlr->imsi_pseudo) {
char imsi[OSMO_IMSI_BUF_SIZE];
if (db_get_imsi_pseudo_resolve(g_hlr->dbc, req->gsup.imsi, imsi) == 0) {
LOGP(DPSEUDO, LOGL_DEBUG, "pseudo IMSI resolve: '%s' => '%s'\n", req->gsup.imsi, imsi);
strncpy(req->imsi_pseudo, req->gsup.imsi, sizeof(req->imsi_pseudo));
strncpy((char *)req->gsup.imsi, imsi, sizeof(req->gsup.imsi));
} else {
osmo_gsup_req_respond_err(req, GMM_CAUSE_IMSI_UNKNOWN, "pseudonymous IMSI unknown");
return -1;
}
}
/* HLR related messages that are handled at this HLR instance */
switch (req->gsup.message_type) {
/* requests sent to us */

View File

@@ -94,6 +94,8 @@ static int config_write_hlr(struct vty *vty)
else
vty_out(vty, " subscriber-create-on-demand no-msisdn %s%s", flags_str, VTY_NEWLINE);
}
if (g_hlr->imsi_pseudo)
vty_out(vty, " imsi-pseudo%s", VTY_NEWLINE);
return CMD_SUCCESS;
}
@@ -408,6 +410,21 @@ DEFUN(cfg_no_subscr_create_on_demand, cfg_no_subscr_create_on_demand_cmd,
return CMD_SUCCESS;
}
DEFUN(cfg_imsi_pseudo, cfg_imsi_pseudo_cmd,
"imsi-pseudo",
"Enable IMSI Pseudonymization\n")
{
g_hlr->imsi_pseudo = true;
return CMD_SUCCESS;
}
DEFUN(cfg_no_imsi_pseudo, cfg_no_imsi_pseudo_cmd,
"no imsi-pseudo",
"Disable IMSI Pseudonymization\n")
{
g_hlr->imsi_pseudo = false;
return CMD_SUCCESS;
}
/***********************************************************************
* Common Code
***********************************************************************/
@@ -479,6 +496,8 @@ void hlr_vty_init(void)
install_element(HLR_NODE, &cfg_no_store_imei_cmd);
install_element(HLR_NODE, &cfg_subscr_create_on_demand_cmd);
install_element(HLR_NODE, &cfg_no_subscr_create_on_demand_cmd);
install_element(HLR_NODE, &cfg_imsi_pseudo_cmd);
install_element(HLR_NODE, &cfg_no_imsi_pseudo_cmd);
hlr_vty_subscriber_init();
}

View File

@@ -31,6 +31,7 @@
#include <osmocom/hlr/hlr.h>
#include <osmocom/hlr/db.h>
#include <osmocom/hlr/timestamp.h>
#include <osmocom/hlr/imsi_pseudo.h>
struct vty;
@@ -69,6 +70,18 @@ static void dump_last_lu_seen(struct vty *vty, const char *domain_label, time_t
}
}
static void dump_imsi_pseudo(struct vty *vty, struct hlr_subscriber *subscr)
{
struct imsi_pseudo_data data;
if (db_get_imsi_pseudo_data(g_hlr->dbc, subscr->id, &data) != 0)
return;
if (data.alloc_count >= 1)
vty_out(vty, " Pseudonymous IMSI (current): %s, i: %" PRId64 "%s", data.current, data.i, VTY_NEWLINE);
if (data.alloc_count == 2)
vty_out(vty, " Pseudonymous IMSI (previous): %s%s", data.previous, VTY_NEWLINE);
}
static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr)
{
int rc;
@@ -78,6 +91,7 @@ static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr)
vty_out(vty, " ID: %"PRIu64"%s", subscr->id, VTY_NEWLINE);
vty_out(vty, " IMSI: %s%s", *subscr->imsi ? subscr->imsi : "none", VTY_NEWLINE);
dump_imsi_pseudo(vty, subscr);
vty_out(vty, " MSISDN: %s%s", *subscr->msisdn ? subscr->msisdn : "none", VTY_NEWLINE);
if (*subscr->imei) {
@@ -203,6 +217,7 @@ static int get_subscr_by_argv(struct vty *vty, const char *type, const char *id,
#define SUBSCR_UPDATE SUBSCR "update "
#define SUBSCR_UPDATE_HELP SUBSCR_HELP "Set or update subscriber data\n"
#define SUBSCR_MSISDN_HELP "Set MSISDN (phone number) of the subscriber\n"
#define SUBSCR_IMSI_PSEUDO_HELP "Allocate or deallocate pseudonymous IMSI of the subscriber\n"
DEFUN(subscriber_show,
subscriber_show_cmd,
@@ -625,6 +640,85 @@ DEFUN(subscriber_nam,
return CMD_SUCCESS;
}
DEFUN(subscriber_imsi_pseudo_alloc,
subscriber_imsi_pseudo_alloc_cmd,
SUBSCR_UPDATE "imsi-pseudo alloc",
SUBSCR_UPDATE_HELP
SUBSCR_IMSI_PSEUDO_HELP
"Allocate a new pseudonymous IMSI (max. 2)\n")
{
struct hlr_subscriber subscr;
struct imsi_pseudo_data pseudo;
const char *id_type = argv[0];
const char *id = argv[1];
char imsi_pseudo[GSM23003_IMSI_MAX_DIGITS+1];
int rc;
if (get_subscr_by_argv(vty, id_type, id, &subscr))
return CMD_WARNING;
if (db_get_imsi_pseudo_data(g_hlr->dbc, subscr.id, &pseudo) != 0)
return CMD_WARNING;
if (pseudo.alloc_count == 2) {
vty_out(vty, "%% Error: subscriber already has two pseudonymous IMSI allocated%s", VTY_NEWLINE);
return CMD_WARNING;
}
rc = db_get_imsi_pseudo_next(g_hlr->dbc, imsi_pseudo);
switch (rc) {
case -1:
vty_out(vty, "%% Error: all IMSIs are already allocated as pseudonymous IMSI%s", VTY_NEWLINE);
return CMD_WARNING;
case -2:
vty_out(vty, "%% Error: failed to get next pseudonymous IMSI (SQL error)%s", VTY_NEWLINE);
return CMD_WARNING;
}
if (db_alloc_imsi_pseudo(g_hlr->dbc, subscr.id, imsi_pseudo, pseudo.i + 1) != 0) {
vty_out(vty, "%% Error: failed to allocate pseudonymous IMSI '%s'%s", imsi_pseudo, VTY_NEWLINE);
return CMD_WARNING;
}
vty_out(vty, "%% New pseudonymous IMSI allocated: %s%s", imsi_pseudo, VTY_NEWLINE);
return CMD_SUCCESS;
}
DEFUN(subscriber_imsi_pseudo_dealloc,
subscriber_imsi_pseudo_dealloc_cmd,
SUBSCR_UPDATE "imsi-pseudo dealloc IMSI_PSEUDO",
SUBSCR_UPDATE_HELP
SUBSCR_IMSI_PSEUDO_HELP
"Deallocate a pseudonymous IMSI\n"
"Pseudonymous IMSI to deallocate\n")
{
struct hlr_subscriber subscr;
struct imsi_pseudo_data pseudo;
const char *id_type = argv[0];
const char *id = argv[1];
const char *imsi_pseudo = argv[2];
if (get_subscr_by_argv(vty, id_type, id, &subscr))
return CMD_WARNING;
if (db_get_imsi_pseudo_data(g_hlr->dbc, subscr.id, &pseudo) != 0)
return CMD_WARNING;
if ((pseudo.alloc_count >= 1 && strcmp(imsi_pseudo, pseudo.current) == 0) ||
(pseudo.alloc_count == 2 && strcmp(imsi_pseudo, pseudo.previous) == 0)) {
/* Pseudonymous IMSI is allocated for given subscriber */
if (db_dealloc_imsi_pseudo(g_hlr->dbc, imsi_pseudo) != 0) {
vty_out(vty, "Failed to deallocate pseudonymous IMSI%s", VTY_NEWLINE);
return CMD_WARNING;
}
} else {
vty_out(vty, "%% Error: pseudonymous IMSI '%s' is not allocated to given subscriber%s", imsi_pseudo,
VTY_NEWLINE);
return CMD_WARNING;
}
return CMD_SUCCESS;
}
void hlr_vty_subscriber_init(void)
{
@@ -639,4 +733,6 @@ 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_imsi_pseudo_alloc_cmd);
install_element(ENABLE_NODE, &subscriber_imsi_pseudo_dealloc_cmd);
}

View File

@@ -25,6 +25,12 @@ const struct log_info_cat hlr_log_info_cat[] = {
.color = "\033[1;34m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DPSEUDO] = {
.name = "DPSEUDO",
.description = "IMSI Pseudonymization",
.color = "\033[1;36m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DMSLOOKUP] = {
.name = "DMSLOOKUP",
.description = "Mobile Subscriber Lookup",

View File

@@ -31,6 +31,7 @@
#include <osmocom/hlr/logging.h>
#include <osmocom/hlr/hlr.h>
#include <osmocom/hlr/gsup_server.h>
#include <osmocom/hlr/imsi_pseudo.h>
#include <osmocom/hlr/db.h>
@@ -80,6 +81,48 @@ static struct osmo_tdef_state_timeout lu_fsm_timeouts[32] = {
[LU_ST_WAIT_LOCATION_CANCEL_RESULT] = { .T = -4222 },
};
/*! Deallocate the previous pseudonymous IMSI, if the subscriber has two allocated ones and has initiated the LU with
* the newer pseudonymous IMSI.
* \param[in] subscr The subscriber.
* \param[in] imsi_pseudo The pseudonymous IMSI used in the location update.
* \param[out] imsi_pseudo_prev The pseudonymous IMSI that was deallocated if return == 0. Must be a buffer of size
* OSMO_IMSI_BUF.
* \returns 0: successful deallocation, >0: successful, no pseudo IMSI to deallocate, <0: error
*/
int imsi_pseudo_prev_dealloc(struct hlr_subscriber *subscr, const char *imsi_pseudo, char *imsi_pseudo_prev)
{
struct imsi_pseudo_data data;
if (db_get_imsi_pseudo_data(g_hlr->dbc, subscr->id, &data) != 0) {
LOGPSEUDO(subscr->id, LOGL_ERROR, "failed to get pseudo IMSI data from DB\n");
return -EIO;
}
if (data.alloc_count == 0) {
LOGPSEUDO(subscr->id, LOGL_ERROR, "does not have any pseudo IMSI allocated\n");
return -EIO;
}
if (data.alloc_count != 2) {
LOGPSEUDO(subscr->id, LOGL_DEBUG, "has only one pseudo IMSI allocated, not deallocating any\n");
return 1;
}
if (strcmp(data.previous, imsi_pseudo) == 0) {
LOGPSEUDO(subscr->id, LOGL_NOTICE, "did LU with previous pseudo IMSI (next pseudo IMSI SMS did not"
" arrive?)\n");
return 2;
}
LOGPSEUDO(subscr->id, LOGL_DEBUG, "used current pseudo IMSI '%s' in LU, deallocating previous: '%s'\n",
imsi_pseudo, data.previous);
if (db_dealloc_imsi_pseudo(g_hlr->dbc, data.previous) != 0) {
LOGPSEUDO(subscr->id, LOGL_ERROR, "failed to deallocate previous pseudo IMSI '%s'\n", data.previous);
return -EIO;
}
strncpy(imsi_pseudo_prev, data.previous, OSMO_IMSI_BUF_SIZE);
return 0;
}
#define lu_state_chg(lu, state) \
osmo_tdef_fsm_inst_state_chg((lu)->fi, state, lu_fsm_timeouts, g_hlr_tdefs, 5)
@@ -197,6 +240,32 @@ static void lu_start(struct osmo_gsup_req *update_location_req)
/* TODO: Reset Flag MS Purged (cs/ps) */
/* TODO: Control_Tracing_HLR / Control_Tracing_HLR_with_SGSN */
/* IMSI pseudonymization: deallocate and cancel previous pseudo IMSI */
if (g_hlr->imsi_pseudo) {
char imsi_pseudo_prev[OSMO_IMSI_BUF_SIZE];
int rc = imsi_pseudo_prev_dealloc(&lu->subscr, update_location_req->imsi_pseudo, imsi_pseudo_prev);
if (rc < 0) {
lu_failure(lu, GMM_CAUSE_NET_FAIL, "Failed to deallocate previous pseudonymous IMSI");
return;
}
if (rc == 0) {
struct osmo_gsup_message gsup;
enum osmo_gsup_cn_domain cn_domain = lu->is_ps
? OSMO_GSUP_CN_DOMAIN_PS
: OSMO_GSUP_CN_DOMAIN_CS;
osmo_gsup_create_location_cancel_msg(&gsup, imsi_pseudo_prev, cn_domain,
OSMO_GSUP_CANCEL_TYPE_WITHDRAW);
/* FIXME: _osmo_gsup_req_respond() fails with 'Invalid response (rc=1)' */
osmo_gsup_req_respond(update_location_req, &gsup, false, false);
lu_state_chg(lu, LU_ST_WAIT_LOCATION_CANCEL_RESULT);
return;
}
}
lu_state_chg(lu, LU_ST_WAIT_INSERT_DATA_RESULT);
}
@@ -285,6 +354,33 @@ void lu_fsm_wait_insert_data_result(struct osmo_fsm_inst *fi, uint32_t event, vo
}
}
void lu_fsm_wait_location_cancel_result(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct lu *lu = fi->priv;
struct osmo_gsup_req *req;
switch (event) {
case LU_EV_RX_GSUP:
req = data;
break;
default:
OSMO_ASSERT(false);
}
switch (req->gsup.message_type) {
case OSMO_GSUP_MSGT_LOCATION_CANCEL_ERROR:
LOG_LU(lu, LOGL_DEBUG, "got a location cancel error (but this is fine, remote didn't even know the"
" subscriber we tried to make it forget)\n");
/* fall through */
case OSMO_GSUP_MSGT_LOCATION_CANCEL_RESULT:
lu_state_chg(lu, LU_ST_WAIT_INSERT_DATA_RESULT);
break;
default:
osmo_gsup_req_respond_err(req, GMM_CAUSE_MSGT_INCOMP_P_STATE, "unexpected message type in this state");
break;
}
}
#define S(x) (1 << (x))
static const struct osmo_fsm_state lu_fsm_states[] = {
@@ -292,6 +388,7 @@ static const struct osmo_fsm_state lu_fsm_states[] = {
.name = "UNVALIDATED",
.out_state_mask = 0
| S(LU_ST_WAIT_INSERT_DATA_RESULT)
| S(LU_ST_WAIT_LOCATION_CANCEL_RESULT)
,
},
[LU_ST_WAIT_INSERT_DATA_RESULT] = {
@@ -302,6 +399,13 @@ static const struct osmo_fsm_state lu_fsm_states[] = {
.onenter = lu_fsm_wait_insert_data_result_onenter,
.action = lu_fsm_wait_insert_data_result,
},
[LU_ST_WAIT_LOCATION_CANCEL_RESULT] = {
.name = "WAIT_LOCATION_CANCEL_RESULT",
.in_event_mask = 0
| S(LU_EV_RX_GSUP)
,
.action = lu_fsm_wait_location_cancel_result,
},
};
static struct osmo_fsm lu_fsm = {

View File

@@ -45,6 +45,7 @@ if ENABLE_EXT_TESTS
python-tests:
# don't run vty and ctrl tests concurrently so that the ports don't conflict
$(MAKE) vty-test
$(MAKE) vty-test-imsi-pseudo
$(MAKE) ctrl-test
$(MAKE) db-upgrade-equivalence-test
else
@@ -68,6 +69,16 @@ vty-test:
$(U) $(srcdir)/$(VTY_TEST)
-rm -f $(VTY_TEST_DB) $(VTY_TEST_DB)-*
# IMSI pseudon VTY tests: don't share the DB with other VTY tests, so we can have deterministic "random" pseudo IMSIs
vty-test-imsi-pseudo:
-rm -f $(VTY_TEST_DB)
sqlite3 $(VTY_TEST_DB) < $(top_srcdir)/sql/hlr.sql
osmo_verify_transcript_vty.py -v \
-n OsmoHLR -p 4258 \
-r "$(top_builddir)/src/osmo-hlr -c $(top_srcdir)/doc/examples/osmo-hlr.cfg -l $(VTY_TEST_DB)" \
$(U) $(srcdir)/imsi_pseudo/imsi_pseudo.vty
-rm -f $(VTY_TEST_DB) $(VTY_TEST_DB)-*
CTRL_TEST_DB = hlr_ctrl_test.db
# To update the CTRL script from current application behavior,

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:
@@ -159,6 +160,15 @@ subscriber_id|INTEGER|0||0
Table subscriber_apn contents:
Table: subscriber_imsi_pseudo
name|type|notnull|dflt_value|pk
id|INTEGER|0||1
imsi_pseudo|VARCHAR(15)|1||0
imsi_pseudo_i|INTEGER|1|0|0
subscriber_id|INTEGER|1||0
Table subscriber_imsi_pseudo contents:
Table: subscriber_multi_msisdn
name|type|notnull|dflt_value|pk
msisdn|VARCHAR(15)|1||0
@@ -171,5 +181,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.

View File

@@ -0,0 +1,42 @@
OsmoHLR> enable
OsmoHLR# subscriber imsi 111111111111111 create
...
OsmoHLR# subscriber id 1 update imsi-pseudo alloc
% New pseudonymous IMSI allocated: 111111111111111
OsmoHLR# subscriber imsi 222222222222222 create
...
OsmoHLR# subscriber id 1 update imsi-pseudo alloc
% New pseudonymous IMSI allocated: 222222222222222
OsmoHLR# subscriber id 1 update imsi-pseudo dealloc 111111111111111
OsmoHLR# subscriber id 2 update imsi-pseudo alloc
% New pseudonymous IMSI allocated: 111111111111111
OsmoHLR# subscriber imsi 333333333333333 create
...
OsmoHLR# subscriber id 1 update imsi-pseudo alloc
% New pseudonymous IMSI allocated: 333333333333333
OsmoHLR# subscriber id 1 show
ID: 1
IMSI: 111111111111111
Pseudonymous IMSI (current): 333333333333333, i: 3
Pseudonymous IMSI (previous): 222222222222222
MSISDN: none
OsmoHLR# subscriber id 2 show
ID: 2
IMSI: 222222222222222
Pseudonymous IMSI (current): 111111111111111, i: 1
MSISDN: none
OsmoHLR# subscriber id 3 show
ID: 3
IMSI: 333333333333333
MSISDN: none
OsmoHLR# subscriber id 3 update imsi-pseudo alloc
% Error: all IMSIs are already allocated as pseudonymous IMSI
OsmoHLR# subscriber id 1 update imsi-pseudo dealloc 123
% Error: pseudonymous IMSI '123' is not allocated to given subscriber
OsmoHLR# subscriber imsi 444444444444444 create
...
OsmoHLR# subscriber id 1 update imsi-pseudo alloc
% Error: subscriber already has two pseudonymous IMSI allocated

View File

@@ -41,6 +41,7 @@ OsmoHLR(config-hlr)# ?
ncss-guard-timeout Set guard timer for NCSS (call independent SS) session activity
store-imei Save the IMEI in the database when receiving Check IMEI requests. Note that an MSC does not necessarily send Check IMEI requests (for OsmoMSC, you may want to set 'check-imei-rqd 1').
subscriber-create-on-demand Make a new record when a subscriber is first seen.
imsi-pseudo Enable IMSI Pseudonymization
OsmoHLR(config-hlr)# list
...
gsup
@@ -57,6 +58,8 @@ OsmoHLR(config-hlr)# list
no store-imei
subscriber-create-on-demand (no-msisdn|<3-15>) (none|cs|ps|cs+ps)
no subscriber-create-on-demand
imsi-pseudo
no imsi-pseudo
OsmoHLR(config-hlr)# gsup
OsmoHLR(config-hlr-gsup)# ?
@@ -90,6 +93,7 @@ log stderr
logging level db notice
logging level auc notice
logging level ss info
logging level pseudo notice
logging level mslookup notice
logging level lu notice
logging level dgsm notice

View File

@@ -13,6 +13,8 @@ OsmoHLR# list
subscriber (imsi|msisdn|id|imei) IDENT update aud3g milenage k K (op|opc) OP_C [ind-bitlen] [<0-28>]
subscriber (imsi|msisdn|id|imei) IDENT update imei (none|IMEI)
subscriber (imsi|msisdn|id|imei) IDENT update network-access-mode (none|cs|ps|cs+ps)
subscriber (imsi|msisdn|id|imei) IDENT update imsi-pseudo alloc
subscriber (imsi|msisdn|id|imei) IDENT update imsi-pseudo dealloc IMSI_PSEUDO
show mslookup services
OsmoHLR# subscriber?
@@ -132,6 +134,7 @@ OsmoHLR# subscriber imsi 123456789023000 update ?
aud3g Set UMTS authentication data (3G, and 2G with UMTS AKA)
imei Set IMEI of the subscriber (normally populated from MSC, no need to set this manually)
network-access-mode Set Network Access Mode (NAM) of the subscriber
imsi-pseudo Allocate or deallocate pseudonymous IMSI of the subscriber
OsmoHLR# subscriber imsi 123456789023000 update msisdn ?
none Remove MSISDN (phone number)