Compare commits

...

22 Commits

Author SHA1 Message Date
Neels Hofmeyr
045d7be024 forwarding fu
Change-Id: Id55cc48b0e7b818e34b90eed2da8008cceefc711
2019-11-11 07:05:07 +01:00
Neels Hofmeyr
adeb6e7b5f add osmo_gsup_req for GSUP request->response association
TODO

Change-Id: I179ebb0385b5b355f4740e14d43be97bf93622e3
2019-11-11 05:46:27 +01:00
Neels Hofmeyr
0f2cfe6c79 gsup server read_cb: use non-static osmo_gsup_message
Change-Id: Ie23c8de1021e0b6aa4f4e02c22255ab90e99c065
2019-11-11 05:31:59 +01:00
Neels Hofmeyr
288a9ddd21 proxy fu
Change-Id: Ib2b88bcb9a21e5ceb37f1be0615824f50e778242
2019-11-11 05:31:59 +01:00
Neels Hofmeyr
f3598c4b2c only one proxy for both cs and ps domains
Change-Id: I09a18c6a9f0acbc23a543f32e67146ef4b10673b
2019-11-11 05:31:59 +01:00
Neels Hofmeyr
7ca9bc517a dgsm fu
Change-Id: Id66461ef017c2cf4848c08505ff2e8c0d0a3259a
2019-11-11 05:31:59 +01:00
Neels Hofmeyr
37ec56d39c fake_msc
Change-Id: I095cbad10cb65ee6101cc8bbde8e99e395d62cd7
2019-11-11 05:31:59 +01:00
Oliver Smith
f91e26ed02 Rename dns_{de,en}code_* -> osmo_mdns_{de,en}code_*
Change-Id: Ib270fc640e0e73cdb4b7c2090672d1117230b74b
2019-11-11 05:31:59 +01:00
Neels Hofmeyr
2975bcbc2f hlr mslookup and gsup proxy works for the first time
Change-Id: I546ca786308fed557703321bed2935e0adcbe0ee
2019-11-11 05:31:59 +01:00
Oliver Smith
749e42902d mdns records: use llist_add_tail, not llist_add
mslookup code isn't iterating over them in reverse anymore.

Change-Id: I8d8320e127bb3eb6c1cacface53eefd267fddab6
2019-11-11 05:31:59 +01:00
Oliver Smith
b0c00522b0 Adjust to rename: dns_encode_txt_record -> osmo_mdns_encode_txt_record
Change-Id: I5338c8568435f0c4bbe5aeb1ba7db1eb80f26168
2019-11-11 05:31:59 +01:00
Neels Hofmeyr
613e1b87f0 fu
Change-Id: I7bedbe9ffe72371f450c76130c09856864a0d20b
2019-11-11 05:31:59 +01:00
Neels Hofmeyr
0a5eac957a dgsm: osmo-hlr is opening mDNS server and client sockets
Change-Id: I377b3bab7334c3212e40c4cf19aa223ac1be1644
2019-11-11 05:31:59 +01:00
Neels Hofmeyr
82f4521319 add global_title
Change-Id: Iebf70d79a8fd44c394df0782b2100e91bf5baa34
2019-11-11 05:31:59 +01:00
Neels Hofmeyr
c1863eb53b db schema 4: add column subscriber.vlr_via_proxy
This is required for Distributed GSM: remember whether the most recent Location
Updating has been received directly from an MSC (empty) or via a GSUP proxy.

In a proxy situation, the responsible MSC to be stored in vlr_bumber is found
in the source_name GSUP IE, and the GSUP link's IPA unit name that the GSUP
request was immediately received from is to be stored in vlr_via_proxy.

Change-Id: I0f7f6cabf9d6927baba1cbb36983b89d0608099d
2019-11-11 05:31:59 +01:00
Neels Hofmeyr
3e142ab230 dgsm wip
Change-Id: I3c91d8e3cec7e4d87f9f56250908faa95c823925
2019-11-11 05:31:59 +01:00
Neels Hofmeyr
774d114635 add osmo_gsup_conn_send_err_reply()
Remove hlr.c's static gsup_send_err_reply(), and create new
osmo_gsup_conn_send_err_reply(), as used in osmo-msc. It includes more of the
newer IEs in the response, like an SS/USSD session id.

Prepares for adding D-GSM / MS lookup, which will need this function moved to a
non-static context.

Change-Id: I792fd9993ab2a323af58782a357d71205c43b72a
2019-11-11 05:31:59 +01:00
Neels Hofmeyr
40b52d9b58 add osmo_gsup_msgb_alloc()
Throughout osmo-hlr's code, the GSUP msgb allocation is duplicated as:

  msgb_alloc_headroom(1024+16, 16, "foo");

Instead, use one common function to keep the magic numbers in one place.

Change-Id: I40e99b5bc4fd8f750da7643c03b2119ac3bfd95e
2019-11-11 05:31:59 +01:00
Neels Hofmeyr
30c1590599 db upgrade: remove some code dup
Instead of a switch() for each version number with identical switch cases
except for the function name, use an array of function pointers and loop.

Also print a success message after each individual version upgrade, instead of
only one in the end (see change in db_upgrade_test.ok).

Change-Id: I1736af3d9a3f02e29db836966ac15ce49f94737b
2019-11-11 05:31:59 +01:00
Neels Hofmeyr
e8e2388ea7 db.c: code dup: add db_run_statements() for arrays of statements
The db bootstrap as well as the upgrade code all execute a number of
statements, and have massive code dup around each statement execution. Instead
have one db_run_statements() that takes an array of strings and runs all.

Change-Id: I2721dfc0a9aadcc7f5ac81a1c0fa87452098996f
2019-11-11 05:31:59 +01:00
Neels Hofmeyr
f83c5a85e3 hlr db schema 3: hlr_number -> msc_number
The osmo-hlr DB schema indicates a hlr_number column and references it as 3GPP
TS 23.008 chapter 2.4.6. However, chapter 2.4.6 refers to the "MSC number",
while the "HLR number" is chapter 2.4.7.

Taking a closer look, 2.4.6 says "The MSC number is [...] stored in the HLR",
while 2.4.7 says "The HLR number may be stored in the VLR". As quite obvious,
the HLR does not store the HLR number. This was a typo from the start.

The osmo-hlr code base so far does not use the hlr_number column at all, so we
get away with renaming the column without any effects on the code base.
However, let's rather make this a new schema version to be safe.

Change-Id: I527e8627b24b79f3e9eec32675c7f5a3a6d25440
2019-11-11 05:31:59 +01:00
Neels Hofmeyr
359bbcb9f7 fix upgrade test in presence of ~/.sqliterc
db_upgrade_test.sh:

- If an ~/.sqliterc file exists, it causes output of '-- Loading resources from
  ~/.sqliterc'. Use -batch option to omit that.

- To make sure that column headers are off when required, add -noheaders in
  some places.

Change-Id: I279a39984563594a4a3914b2ce3d803ad9468fe8
2019-11-11 05:29:44 +01:00
56 changed files with 3689 additions and 1152 deletions

View File

@@ -38,6 +38,7 @@ PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.2.0)
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.2.0)
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.2.0)
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.2.0)
PKG_CHECK_MODULES(LIBOSMOMSLOOKUP, libosmomslookup >= 1.2.0)
PKG_CHECK_MODULES(LIBOSMOABIS, libosmoabis >= 0.6.0)
PKG_CHECK_MODULES(SQLITE3, sqlite3)
@@ -184,7 +185,7 @@ AC_OUTPUT(
tests/auc/Makefile
tests/auc/gen_ts_55_205_test_sets/Makefile
tests/gsup_server/Makefile
tests/gsup/Makefile
tests/db/Makefile
tests/db_upgrade/Makefile
tests/mslookup_manual_test/Makefile
)

View File

@@ -52,7 +52,7 @@ transceiving only RAND and SRES, may be applicable. (See 3GPP TS 33.102, chapter
|aud3g.ind_bitlen|5|Nr of index bits at lower SQN end
|apn||
|vlr_number||3GPP TS 23.008 chapter 2.4.5
|hlr_number||3GPP TS 23.008 chapter 2.4.6
|msc_number||3GPP TS 23.008 chapter 2.4.6
|sgsn_number||3GPP TS 23.008 chapter 2.4.8.1
|sgsn_address||3GPP TS 23.008 chapter 2.13.10
|ggsn_number||3GPP TS 23.008 chapter 2.4.8.2

View File

@@ -39,6 +39,8 @@ struct osmo_gsup_client;
/* Expects message in msg->l2h */
typedef int (*osmo_gsup_client_read_cb_t)(struct osmo_gsup_client *gsupc, struct msgb *msg);
typedef bool (*osmo_gsup_client_up_down_cb_t)(struct osmo_gsup_client *gsupc, bool up);
struct osmo_gsup_client {
const char *unit_name; /* same as ipa_dev->unit_name, for backwards compat */
@@ -54,8 +56,18 @@ struct osmo_gsup_client {
int got_ipa_pong;
struct ipaccess_unit *ipa_dev; /* identification information sent to IPA server */
osmo_gsup_client_up_down_cb_t up_down_cb;
};
struct osmo_gsup_client *osmo_gsup_client_create3(void *talloc_ctx,
struct ipaccess_unit *ipa_dev,
const char *ip_addr,
unsigned int tcp_port,
struct osmo_oap_client_config *oapc_config,
osmo_gsup_client_read_cb_t read_cb,
osmo_gsup_client_up_down_cb_t up_down_cb,
void *data);
struct osmo_gsup_client *osmo_gsup_client_create2(void *talloc_ctx,
struct ipaccess_unit *ipa_dev,
const char *ip_addr,

View File

@@ -12,7 +12,7 @@ CREATE TABLE subscriber (
-- Chapter 2.4.5
vlr_number VARCHAR(15),
-- Chapter 2.4.6
hlr_number VARCHAR(15),
msc_number VARCHAR(15),
-- Chapter 2.4.8.1
sgsn_number VARCHAR(15),
-- Chapter 2.13.10
@@ -42,7 +42,11 @@ CREATE TABLE subscriber (
-- Timestamp of last location update seen from subscriber
-- The value is a string which encodes a UTC timestamp in granularity of seconds.
last_lu_seen TIMESTAMP default NULL
last_lu_seen TIMESTAMP default NULL,
-- When a LU was received via a proxy, that proxy's hlr_number is stored here,
-- while vlr_number reflects the MSC on the far side of that proxy.
vlr_via_proxy VARCHAR,
sgsn_via_proxy VARCHAR
);
CREATE TABLE subscriber_apn (
@@ -77,4 +81,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 = 2;
PRAGMA user_version = 4;

View File

@@ -6,6 +6,7 @@ AM_CFLAGS = \
$(LIBOSMOGSM_CFLAGS) \
$(LIBOSMOVTY_CFLAGS) \
$(LIBOSMOCTRL_CFLAGS) \
$(LIBOSMOMSLOOKUP_CFLAGS) \
$(LIBOSMOABIS_CFLAGS) \
$(SQLITE3_CFLAGS) \
$(NULL)
@@ -27,7 +28,6 @@ noinst_HEADERS = \
auc.h \
db.h \
hlr.h \
luop.h \
gsup_router.h \
gsup_server.h \
logging.h \
@@ -37,6 +37,13 @@ noinst_HEADERS = \
hlr_vty_subscr.h \
hlr_ussd.h \
db_bootstrap.h \
proxy.h \
dgsm.h \
remote_hlr.h \
global_title.h \
mslookup_server.h \
mslookup_server_mdns.h \
lu_fsm.h \
$(NULL)
bin_PROGRAMS = \
@@ -49,7 +56,6 @@ osmo_hlr_SOURCES = \
auc.c \
ctrl.c \
db.c \
luop.c \
db_auc.c \
db_hlr.c \
gsup_router.c \
@@ -61,13 +67,23 @@ osmo_hlr_SOURCES = \
hlr_vty_subscr.c \
gsup_send.c \
hlr_ussd.c \
proxy.c \
dgsm.c \
dgsm_vty.c \
remote_hlr.c \
mslookup_server.c \
mslookup_server_mdns.c \
global_title.c \
lu_fsm.c \
$(NULL)
osmo_hlr_LDADD = \
$(top_builddir)/src/gsupclient/libosmo-gsup-client.la \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOGSM_LIBS) \
$(LIBOSMOVTY_LIBS) \
$(LIBOSMOCTRL_LIBS) \
$(LIBOSMOMSLOOKUP_LIBS) \
$(LIBOSMOABIS_LIBS) \
$(SQLITE3_LIBS) \
$(NULL)
@@ -79,6 +95,7 @@ osmo_hlr_db_tool_SOURCES = \
logging.c \
rand_urandom.c \
dbd_decode_binary.c \
global_title.c \
$(NULL)
osmo_hlr_db_tool_LDADD = \

300
src/db.c
View File

@@ -22,13 +22,14 @@
#include <stdbool.h>
#include <sqlite3.h>
#include <string.h>
#include <errno.h>
#include "logging.h"
#include "db.h"
#include "db_bootstrap.h"
/* This constant is currently duplicated in sql/hlr.sql and must be kept in sync! */
#define CURRENT_SCHEMA_VERSION 2
#define CURRENT_SCHEMA_VERSION 4
#define SEL_COLUMNS \
"id," \
@@ -45,15 +46,17 @@
"lmsi," \
"ms_purged_cs," \
"ms_purged_ps," \
"last_lu_seen"
"last_lu_seen," \
"vlr_via_proxy," \
"sgsn_via_proxy"
static const char *stmt_sql[] = {
[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 = ?",
[DB_STMT_SEL_BY_IMEI] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE imei = ?",
[DB_STMT_UPD_VLR_BY_ID] = "UPDATE subscriber SET vlr_number = $number WHERE id = $subscriber_id",
[DB_STMT_UPD_SGSN_BY_ID] = "UPDATE subscriber SET sgsn_number = $number WHERE id = $subscriber_id",
[DB_STMT_UPD_VLR_BY_ID] = "UPDATE subscriber SET vlr_number = $number, vlr_via_proxy = $proxy WHERE id = $subscriber_id",
[DB_STMT_UPD_SGSN_BY_ID] = "UPDATE subscriber SET sgsn_number = $number, sgsn_via_proxy = $proxy WHERE id = $subscriber_id",
[DB_STMT_UPD_IMEI_BY_IMSI] = "UPDATE subscriber SET imei = $imei WHERE imsi = $imsi",
[DB_STMT_AUC_BY_IMSI] =
"SELECT id, algo_id_2g, ki, algo_id_3g, k, op, opc, sqn, ind_bitlen"
@@ -81,6 +84,13 @@ static const char *stmt_sql[] = {
[DB_STMT_SET_LAST_LU_SEEN] = "UPDATE subscriber SET last_lu_seen = 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",
#if 0
[DB_STMT_PROXY_UPDATE] = "INSERT OR REPLACE INTO"
" proxy (imsi, remote_ip, remote_port)"
" VALUES ($imsi, $remote_ip, $remote_port)",
[DB_STMT_PROXY_GET_BY_IMSI] = "SELECT imsi, remote_ip, remote_port FROM proxy WHERE imsi = $imsi",
#endif
};
static void sql3_error_log_cb(void *arg, int err_code, const char *msg)
@@ -181,6 +191,25 @@ bool db_bind_int64(sqlite3_stmt *stmt, const char *param_name, int64_t nr)
return true;
}
bool db_bind_null(sqlite3_stmt *stmt, const char *param_name)
{
int rc;
int idx = param_name ? sqlite3_bind_parameter_index(stmt, param_name) : 1;
if (idx < 1) {
LOGP(DDB, LOGL_ERROR, "Error composing SQL, cannot bind parameter '%s'\n",
param_name);
return false;
}
rc = sqlite3_bind_null(stmt, idx);
if (rc != SQLITE_OK) {
LOGP(DDB, LOGL_ERROR, "Error binding NULL to SQL parameter %s: %d\n",
param_name ? param_name : "#1", rc);
db_remove_reset(stmt);
return false;
}
return true;
}
void db_close(struct db_context *dbc)
{
unsigned int i;
@@ -201,28 +230,38 @@ void db_close(struct db_context *dbc)
talloc_free(dbc);
}
static int db_bootstrap(struct db_context *dbc)
static int db_run_statements(struct db_context *dbc, const char **statements, size_t statements_count)
{
int rc;
int i;
for (i = 0; i < ARRAY_SIZE(stmt_bootstrap_sql); i++) {
int rc;
for (i = 0; i < statements_count; i++) {
const char *stmt_str = statements[i];
sqlite3_stmt *stmt;
rc = sqlite3_prepare_v2(dbc->db, stmt_bootstrap_sql[i], -1, &stmt, NULL);
rc = sqlite3_prepare_v2(dbc->db, stmt_str, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
LOGP(DDB, LOGL_ERROR, "Unable to prepare SQL statement '%s'\n", stmt_bootstrap_sql[i]);
LOGP(DDB, LOGL_ERROR, "Unable to prepare SQL statement '%s'\n", stmt_str);
return rc;
}
rc = sqlite3_step(stmt);
db_remove_reset(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) {
LOGP(DDB, LOGL_ERROR, "Cannot bootstrap database: SQL error: (%d) %s,"
" during stmt '%s'",
rc, sqlite3_errmsg(dbc->db), stmt_bootstrap_sql[i]);
LOGP(DDB, LOGL_ERROR, "SQL error: (%d) %s, during stmt '%s'",
rc, sqlite3_errmsg(dbc->db), stmt_str);
return rc;
}
}
return rc;
}
static int db_bootstrap(struct db_context *dbc)
{
int rc = db_run_statements(dbc, stmt_bootstrap_sql, ARRAY_SIZE(stmt_bootstrap_sql));
if (rc != SQLITE_DONE) {
LOGP(DDB, LOGL_ERROR, "Cannot bootstrap database\n");
return rc;
}
return SQLITE_OK;
}
@@ -263,72 +302,181 @@ static bool db_is_bootstrapped_v0(struct db_context *dbc)
static int
db_upgrade_v1(struct db_context *dbc)
{
sqlite3_stmt *stmt;
int rc;
const char *update_stmt_sql = "ALTER TABLE subscriber ADD COLUMN last_lu_seen TIMESTAMP default NULL";
const char *set_schema_version_sql = "PRAGMA user_version = 1";
const char *statements[] = {
"ALTER TABLE subscriber ADD COLUMN last_lu_seen TIMESTAMP default NULL",
"PRAGMA user_version = 1",
};
rc = sqlite3_prepare_v2(dbc->db, update_stmt_sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
LOGP(DDB, LOGL_ERROR, "Unable to prepare SQL statement '%s'\n", update_stmt_sql);
return rc;
}
rc = sqlite3_step(stmt);
db_remove_reset(stmt);
sqlite3_finalize(stmt);
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 %d\n", 1);
LOGP(DDB, LOGL_ERROR, "Unable to update HLR database schema to version 1\n");
return rc;
}
rc = sqlite3_prepare_v2(dbc->db, set_schema_version_sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
LOGP(DDB, LOGL_ERROR, "Unable to prepare SQL statement '%s'\n", set_schema_version_sql);
return rc;
}
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
LOGP(DDB, LOGL_ERROR, "Unable to update HLR database schema to version %d\n", 1);
db_remove_reset(stmt);
sqlite3_finalize(stmt);
return rc;
}
static int db_upgrade_v2(struct db_context *dbc)
{
sqlite3_stmt *stmt;
int rc;
const char *update_stmt_sql = "ALTER TABLE subscriber ADD COLUMN imei VARCHAR(14)";
const char *set_schema_version_sql = "PRAGMA user_version = 2";
const char *statements[] = {
"ALTER TABLE subscriber ADD COLUMN imei VARCHAR(14)",
"PRAGMA user_version = 2",
};
rc = sqlite3_prepare_v2(dbc->db, update_stmt_sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
LOGP(DDB, LOGL_ERROR, "Unable to prepare SQL statement '%s'\n", update_stmt_sql);
return rc;
}
rc = sqlite3_step(stmt);
db_remove_reset(stmt);
sqlite3_finalize(stmt);
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 2\n");
return rc;
}
rc = sqlite3_prepare_v2(dbc->db, set_schema_version_sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
LOGP(DDB, LOGL_ERROR, "Unable to prepare SQL statement '%s'\n", set_schema_version_sql);
return rc;
}
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
LOGP(DDB, LOGL_ERROR, "Unable to update HLR database schema to version 2\n");
db_remove_reset(stmt);
sqlite3_finalize(stmt);
return rc;
}
static int db_upgrade_v3(struct db_context *dbc)
{
int rc;
/* A newer SQLite version would allow simply 'ATLER TABLE subscriber RENAME COLUMN hlr_number TO msc_number'.
* This is a really expensive workaround for that in order to cover earlier SQLite versions as well:
* Create a new table with the new column name and copy the data over (https://www.sqlite.org/faq.html#q11).
*/
#define SUBSCR_V3_CREATE \
"(\n" \
"-- OsmoHLR's DB scheme is modelled roughly after TS 23.008 version 13.3.0\n" \
" id INTEGER PRIMARY KEY,\n" \
" -- Chapter 2.1.1.1\n" \
" imsi VARCHAR(15) UNIQUE NOT NULL,\n" \
" -- Chapter 2.1.2\n" \
" msisdn VARCHAR(15) UNIQUE,\n" \
" -- Chapter 2.2.3: Most recent / current IMEISV\n" \
" imeisv VARCHAR,\n" \
" -- Chapter 2.1.9: Most recent / current IMEI\n" \
" imei VARCHAR(14),\n" \
" -- Chapter 2.4.5\n" \
" vlr_number VARCHAR(15),\n" \
" -- Chapter 2.4.6\n" \
" msc_number VARCHAR(15),\n" \
" -- Chapter 2.4.8.1\n" \
" sgsn_number VARCHAR(15),\n" \
" -- Chapter 2.13.10\n" \
" sgsn_address VARCHAR,\n" \
" -- Chapter 2.4.8.2\n" \
" ggsn_number VARCHAR(15),\n" \
" -- Chapter 2.4.9.2\n" \
" gmlc_number VARCHAR(15),\n" \
" -- Chapter 2.4.23\n" \
" smsc_number VARCHAR(15),\n" \
" -- Chapter 2.4.24\n" \
" periodic_lu_tmr INTEGER,\n" \
" -- Chapter 2.13.115\n" \
" periodic_rau_tau_tmr INTEGER,\n" \
" -- Chapter 2.1.1.2: network access mode\n" \
" nam_cs BOOLEAN NOT NULL DEFAULT 1,\n" \
" nam_ps BOOLEAN NOT NULL DEFAULT 1,\n" \
" -- Chapter 2.1.8\n" \
" lmsi INTEGER,\n" \
\
" -- The below purged flags might not even be stored non-volatile,\n" \
" -- refer to TS 23.012 Chapter 3.6.1.4\n" \
" -- Chapter 2.7.5\n" \
" ms_purged_cs BOOLEAN NOT NULL DEFAULT 0,\n" \
" -- Chapter 2.7.6\n" \
" ms_purged_ps BOOLEAN NOT NULL DEFAULT 0,\n" \
\
" -- Timestamp of last location update seen from subscriber\n" \
" -- The value is a string which encodes a UTC timestamp in granularity of seconds.\n" \
" last_lu_seen TIMESTAMP default NULL\n" \
")\n"
#define SUBSCR_V2_COLUMN_NAMES \
"id," \
"imsi," \
"msisdn," \
"imeisv," \
"imei," \
"vlr_number," \
"hlr_number," \
"sgsn_number," \
"sgsn_address," \
"ggsn_number," \
"gmlc_number," \
"smsc_number," \
"periodic_lu_tmr," \
"periodic_rau_tau_tmr," \
"nam_cs," \
"nam_ps," \
"lmsi," \
"ms_purged_cs," \
"ms_purged_ps," \
"last_lu_seen"
#define SUBSCR_V3_COLUMN_NAMES \
"id," \
"imsi," \
"msisdn," \
"imeisv," \
"imei," \
"vlr_number," \
"msc_number," \
"sgsn_number," \
"sgsn_address," \
"ggsn_number," \
"gmlc_number," \
"smsc_number," \
"periodic_lu_tmr," \
"periodic_rau_tau_tmr," \
"nam_cs," \
"nam_ps," \
"lmsi," \
"ms_purged_cs," \
"ms_purged_ps," \
"last_lu_seen"
const char *statements[] = {
"BEGIN TRANSACTION",
"CREATE TEMPORARY TABLE subscriber_backup" SUBSCR_V3_CREATE,
"INSERT INTO subscriber_backup SELECT " SUBSCR_V2_COLUMN_NAMES " FROM subscriber",
"DROP TABLE subscriber",
"CREATE TABLE subscriber" SUBSCR_V3_CREATE,
"INSERT INTO subscriber SELECT " SUBSCR_V3_COLUMN_NAMES " FROM subscriber_backup",
"DROP TABLE subscriber_backup",
"COMMIT",
"PRAGMA user_version = 3",
};
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 3\n");
return rc;
}
return rc;
}
static int db_upgrade_v4(struct db_context *dbc)
{
int rc;
const char *statements[] = {
"ALTER TABLE subscriber ADD COLUMN vlr_via_proxy VARCHAR",
"ALTER TABLE subscriber ADD COLUMN sgsn_via_proxy VARCHAR",
"PRAGMA user_version = 4",
};
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 4\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,
db_upgrade_v2,
db_upgrade_v3,
db_upgrade_v4,
};
static int db_get_user_version(struct db_context *dbc)
{
const char *user_version_sql = "PRAGMA user_version";
@@ -439,32 +587,16 @@ struct db_context *db_open(void *ctx, const char *fname, bool enable_sqlite_logg
LOGP(DDB, LOGL_NOTICE, "Database '%s' has HLR DB schema version %d\n", dbc->fname, version);
if (version < CURRENT_SCHEMA_VERSION && allow_upgrade) {
switch (version) {
case 0:
rc = db_upgrade_v1(dbc);
if (rc != SQLITE_DONE) {
LOGP(DDB, LOGL_ERROR, "Failed to upgrade HLR DB schema to version 1: (rc=%d) %s\n",
rc, sqlite3_errmsg(dbc->db));
goto out_free;
}
version = 1;
/* fall through */
case 1:
rc = db_upgrade_v2(dbc);
if (rc != SQLITE_DONE) {
LOGP(DDB, LOGL_ERROR, "Failed to upgrade HLR DB schema to version 2: (rc=%d) %s\n",
rc, sqlite3_errmsg(dbc->db));
goto out_free;
}
version = 2;
/* fall through */
/* case N: ... */
default:
break;
for (; allow_upgrade && (version < ARRAY_SIZE(db_upgrade_path)); version++) {
db_upgrade_func_t upgrade_func = db_upgrade_path[version];
rc = upgrade_func(dbc);
if (rc != SQLITE_DONE) {
LOGP(DDB, LOGL_ERROR, "Failed to upgrade HLR DB schema to version %d: (rc=%d) %s\n",
version+1, rc, sqlite3_errmsg(dbc->db));
goto out_free;
}
LOGP(DDB, LOGL_NOTICE, "Database '%s' has been upgraded to HLR DB schema version %d\n",
dbc->fname, version);
dbc->fname, version+1);
}
if (version != CURRENT_SCHEMA_VERSION) {

View File

@@ -3,6 +3,8 @@
#include <stdbool.h>
#include <sqlite3.h>
#include "global_title.h"
struct hlr;
enum stmt_idx {
@@ -30,6 +32,10 @@ enum stmt_idx {
DB_STMT_SET_LAST_LU_SEEN,
DB_STMT_EXISTS_BY_IMSI,
DB_STMT_EXISTS_BY_MSISDN,
#if 0
DB_STMT_PROXY_UPDATE,
DB_STMT_PROXY_GET_BY_IMSI,
#endif
_NUM_DB_STMT
};
@@ -48,6 +54,7 @@ void db_remove_reset(sqlite3_stmt *stmt);
bool db_bind_text(sqlite3_stmt *stmt, const char *param_name, const char *text);
bool db_bind_int(sqlite3_stmt *stmt, const char *param_name, int nr);
bool db_bind_int64(sqlite3_stmt *stmt, const char *param_name, int64_t nr);
bool db_bind_null(sqlite3_stmt *stmt, const char *param_name);
void db_close(struct db_context *dbc);
struct db_context *db_open(void *ctx, const char *fname, bool enable_sqlite3_logging, bool allow_upgrades);
@@ -95,6 +102,8 @@ struct hlr_subscriber {
bool ms_purged_cs;
bool ms_purged_ps;
time_t last_lu_seen;
/* talloc'd IPA unit name */
struct global_title vlr_via_proxy;
};
/* A format string for use with strptime(3). This format string is
@@ -149,13 +158,12 @@ int db_subscr_get_by_id(struct db_context *dbc, int64_t id,
int db_subscr_get_by_imei(struct db_context *dbc, const char *imei, struct hlr_subscriber *subscr);
int db_subscr_nam(struct db_context *dbc, const char *imsi, bool nam_val, bool is_ps);
int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
const char *vlr_or_sgsn_number, bool is_ps);
const struct global_title *vlr_name, bool is_ps,
const struct global_title *via_proxy);
int db_subscr_purge(struct db_context *dbc, const char *by_imsi,
bool purge_val, bool is_ps);
int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps);
/*! 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*.
@@ -166,3 +174,14 @@ int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val,
const char *_txt = (const char *) sqlite3_column_text(stmt, idx); \
osmo_strlcpy(buf, _txt, sizeof(buf)); \
} while (0)
/*! Call sqlite3_column_text() and copy result to a struct global_title.
* \param[out] gt A struct global_title* to write to.
* \param[in] stmt An sqlite3_stmt*.
* \param[in] idx Index in stmt's returned columns.
*/
#define copy_sqlite3_text_to_gt(gt, stmt, idx) \
do { \
const char *_txt = (const char *) sqlite3_column_text(stmt, idx); \
global_title_set_str(gt, _txt); \
} while (0)

View File

@@ -37,7 +37,6 @@
#include "hlr.h"
#include "db.h"
#include "gsup_server.h"
#include "luop.h"
#define LOGHLR(imsi, level, fmt, args ...) LOGP(DAUC, level, "IMSI='%s': " fmt, imsi, ## args)
@@ -493,6 +492,7 @@ static int db_sel(struct db_context *dbc, sqlite3_stmt *stmt, struct hlr_subscri
}
}
}
copy_sqlite3_text_to_gt(&subscr->vlr_via_proxy, stmt, 15);
out:
db_remove_reset(stmt);
@@ -722,7 +722,8 @@ out:
* -EIO on database errors.
*/
int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
const char *vlr_or_sgsn_number, bool is_ps)
const struct global_title *vlr_name, bool is_ps,
const struct global_title *via_proxy)
{
sqlite3_stmt *stmt;
int rc, ret = 0;
@@ -734,9 +735,17 @@ int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
return -EIO;
if (!db_bind_text(stmt, "$number", vlr_or_sgsn_number))
if (!db_bind_text(stmt, "$number", (char*)vlr_name->val))
return -EIO;
if (via_proxy && via_proxy->len) {
if (!db_bind_text(stmt, "$proxy", (char*)via_proxy->val))
return -EIO;
} else {
if (!db_bind_null(stmt, "$proxy"))
return -EIO;
}
/* execute the statement */
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
@@ -861,51 +870,3 @@ out:
return ret;
}
/*! Update nam_cs/nam_ps in the db and trigger notifications to GSUP clients.
* \param[in,out] hlr Global hlr context.
* \param[in] subscr Subscriber from a fresh db_subscr_get_by_*() call.
* \param[in] nam_val True to enable CS/PS, false to disable.
* \param[in] is_ps True to enable/disable PS, false for CS.
* \returns 0 on success, ENOEXEC if there is no need to change, a negative
* value on error.
*/
int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps)
{
int rc;
struct lu_operation *luop;
struct osmo_gsup_conn *co;
bool is_val = is_ps? subscr->nam_ps : subscr->nam_cs;
if (is_val == nam_val) {
LOGHLR(subscr->imsi, LOGL_DEBUG, "Already has the requested value when asked to %s %s\n",
nam_val ? "enable" : "disable", is_ps ? "PS" : "CS");
return ENOEXEC;
}
rc = db_subscr_nam(hlr->dbc, subscr->imsi, nam_val, is_ps);
if (rc)
return rc > 0? -rc : rc;
/* If we're disabling, send a notice out to the GSUP client that is
* responsible. Otherwise no need. */
if (nam_val)
return 0;
/* FIXME: only send to single SGSN where latest update for IMSI came from */
llist_for_each_entry(co, &hlr->gs->clients, list) {
luop = lu_op_alloc_conn(co);
if (!luop) {
LOGHLR(subscr->imsi, LOGL_ERROR,
"Cannot notify GSUP client, cannot allocate lu_operation,"
" for %s:%u\n",
co && co->conn && co->conn->server? co->conn->server->addr : "unset",
co && co->conn && co->conn->server? co->conn->server->port : 0);
continue;
}
luop->subscr = *subscr;
lu_op_tx_del_subscr_data(luop);
lu_op_free(luop);
}
return 0;
}

515
src/dgsm.c Normal file
View File

@@ -0,0 +1,515 @@
#include <errno.h>
#include <osmocom/core/logging.h>
#include <osmocom/mslookup/mslookup_client.h>
#include <osmocom/mslookup/mslookup_client_mdns.h>
#include <osmocom/gsupclient/gsup_client.h>
#include "logging.h"
#include "hlr.h"
#include "db.h"
#include "gsup_router.h"
#include "gsup_server.h"
#include "dgsm.h"
#include "proxy.h"
#include "remote_hlr.h"
#include "mslookup_server_mdns.h"
#include "global_title.h"
void *dgsm_ctx = NULL;
const struct global_title dgsm_config_msc_wildcard = {};
struct dgsm_msc_config *dgsm_config_msc_get(const struct global_title *msc_name, bool create)
{
struct dgsm_msc_config *msc;
if (!msc_name)
return NULL;
llist_for_each_entry(msc, &g_hlr->mslookup.vty.server.msc_configs, entry) {
if (global_title_cmp(&msc->name, msc_name))
continue;
return msc;
}
if (!create)
return NULL;
msc = talloc_zero(dgsm_ctx, struct dgsm_msc_config);
OSMO_ASSERT(msc);
INIT_LLIST_HEAD(&msc->service_hosts);
msc->name = *msc_name;
return msc;
}
struct dgsm_service_host *dgsm_config_msc_service_get(struct dgsm_msc_config *msc, const char *service, bool create)
{
struct dgsm_service_host *e;
if (!msc)
return NULL;
llist_for_each_entry(e, &msc->service_hosts, entry) {
if (!strcmp(e->service, service))
return e;
}
if (!create)
return NULL;
e = talloc_zero(msc, struct dgsm_service_host);
OSMO_ASSERT(e);
OSMO_STRLCPY_ARRAY(e->service, service);
llist_add_tail(&e->entry, &msc->service_hosts);
return e;
}
struct dgsm_service_host *dgsm_config_service_get(const struct global_title *msc_name, const char *service)
{
struct dgsm_msc_config *msc = dgsm_config_msc_get(msc_name, false);
if (!msc)
return NULL;
return dgsm_config_msc_service_get(msc, service, false);
}
int dgsm_config_msc_service_set(struct dgsm_msc_config *msc, const char *service, const struct osmo_sockaddr_str *addr)
{
struct dgsm_service_host *e;
if (!service || !service[0]
|| strlen(service) > OSMO_MSLOOKUP_SERVICE_MAXLEN)
return -EINVAL;
if (!addr || !osmo_sockaddr_str_is_nonzero(addr))
return -EINVAL;
e = dgsm_config_msc_service_get(msc, service, true);
if (!e)
return -EINVAL;
switch (addr->af) {
case AF_INET:
e->host_v4 = *addr;
break;
case AF_INET6:
e->host_v6 = *addr;
break;
default:
return -EINVAL;
}
return 0;
}
int dgsm_config_service_set(const struct global_title *msc_name, const char *service, const struct osmo_sockaddr_str *addr)
{
struct dgsm_msc_config *msc;
msc = dgsm_config_msc_get(msc_name, true);
if (!msc)
return -EINVAL;
return dgsm_config_msc_service_set(msc, service, addr);
}
int dgsm_config_msc_service_del(struct dgsm_msc_config *msc, const char *service, const struct osmo_sockaddr_str *addr)
{
struct dgsm_service_host *e, *n;
if (!msc)
return -ENOENT;
llist_for_each_entry_safe(e, n, &msc->service_hosts, entry) {
if (service && strcmp(service, e->service))
continue;
if (addr) {
if (!osmo_sockaddr_str_cmp(addr, &e->host_v4)) {
e->host_v4 = (struct osmo_sockaddr_str){};
/* Removed one addr. If the other is still there, keep the entry. */
if (osmo_sockaddr_str_is_nonzero(&e->host_v6))
continue;
} else if (!osmo_sockaddr_str_cmp(addr, &e->host_v6)) {
e->host_v6 = (struct osmo_sockaddr_str){};
/* Removed one addr. If the other is still there, keep the entry. */
if (osmo_sockaddr_str_is_nonzero(&e->host_v4))
continue;
} else
/* No addr match, keep the entry. */
continue;
/* Addr matched and none is left. Delete. */
}
llist_del(&e->entry);
talloc_free(e);
}
return 0;
}
int dgsm_config_service_del(const struct global_title *msc_name,
const char *service, const struct osmo_sockaddr_str *addr)
{
return dgsm_config_msc_service_del(dgsm_config_msc_get(msc_name, false),
service, addr);
}
static void *dgsm_pending_messages_ctx = NULL;
struct pending_gsup_message {
struct llist_head entry;
struct osmo_gsup_req *req;
struct timeval received_at;
};
static LLIST_HEAD(pending_gsup_messages);
/* Defer a GSUP message until we know a remote HLR to proxy to.
* Where to send this GSUP message is indicated by its IMSI: as soon as an MS lookup has yielded the IMSI's home HLR,
* that's where the message should go. */
static void defer_gsup_req(struct osmo_gsup_req *req)
{
struct pending_gsup_message *m;
m = talloc_zero(dgsm_pending_messages_ctx, struct pending_gsup_message);
OSMO_ASSERT(m);
m->req = req;
timestamp_update(&m->received_at);
llist_add_tail(&m->entry, &pending_gsup_messages);
}
/* Unable to resolve remote HLR for this IMSI, Answer with error back to the sender. */
static void defer_gsup_message_err(struct pending_gsup_message *m)
{
osmo_gsup_req_respond_err(m->req, GMM_CAUSE_IMSI_UNKNOWN, "could not reach home HLR");
m->req = NULL;
}
/* Forward spooled message for this IMSI to remote HLR. */
static void defer_gsup_message_send(struct pending_gsup_message *m, struct remote_hlr *remote_hlr)
{
LOG_GSUP_REQ(m->req, LOGL_INFO, "Forwarding deferred message to " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr));
/* If sending fails, still discard. */
if (!remote_hlr->gsupc || !remote_hlr->gsupc->is_connected) {
LOGP(DDGSM, LOGL_ERROR, "GSUP link to remote HLR is not connected: " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr));
defer_gsup_message_err(m);
return;
}
remote_hlr_gsup_forward(remote_hlr, m->req);
m->req = NULL;
}
/* Result of looking for remote HLR. If it failed, pass remote_hlr as NULL. */
static void defer_gsup_message_pop(const char *imsi, struct remote_hlr *remote_hlr)
{
struct pending_gsup_message *m, *n;
if (remote_hlr)
LOG_DGSM(imsi, LOGL_DEBUG, "Sending spooled GSUP messages to remote HLR at " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr));
else
LOG_DGSM(imsi, LOGL_ERROR, "No remote HLR found, dropping spooled GSUP messages\n");
llist_for_each_entry_safe(m, n, &pending_gsup_messages, entry) {
if (strcmp(m->req->gsup.imsi, imsi))
continue;
if (!remote_hlr)
defer_gsup_message_err(m);
else
defer_gsup_message_send(m, remote_hlr);
llist_del(&m->entry);
talloc_free(m);
}
}
void dgsm_send_to_remote_hlr(const struct proxy_subscr *proxy_subscr, struct osmo_gsup_req *req)
{
struct remote_hlr *remote_hlr;
if (!osmo_sockaddr_str_is_nonzero(&proxy_subscr->remote_hlr_addr)) {
/* We don't know the remote target yet. Still waiting for an MS lookup response. */
LOG_GSUP_REQ(req, LOGL_DEBUG, "Proxy: deferring until remote HLR is known\n");
defer_gsup_req(req);
return;
}
LOG_GSUP_REQ(req, LOGL_INFO, "Proxy: forwarding to " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&proxy_subscr->remote_hlr_addr));
remote_hlr = remote_hlr_get(&proxy_subscr->remote_hlr_addr, true);
if (!remote_hlr) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL,
"Proxy: Failed to establish connection to remote HLR " OSMO_SOCKADDR_STR_FMT,
OSMO_SOCKADDR_STR_FMT_ARGS(&proxy_subscr->remote_hlr_addr));
return;
}
if (!remote_hlr->gsupc || !remote_hlr->gsupc->is_connected) {
/* GSUP link is still busy establishing... */
LOG_GSUP_REQ(req, LOGL_DEBUG, "Proxy: deferring until link to remote HLR is up\n");
defer_gsup_req(req);
return;
}
remote_hlr_gsup_forward(remote_hlr, req);
}
static void resolve_hlr_result_cb(struct osmo_mslookup_client *client,
uint32_t request_handle,
const struct osmo_mslookup_query *query,
const struct osmo_mslookup_result *result)
{
struct proxy *proxy = g_hlr->proxy;
const struct proxy_subscr *proxy_subscr;
struct proxy_subscr proxy_subscr_new;
struct remote_hlr *remote_hlr;
/* A remote HLR is answering back, indicating that it is the home HLR for a given IMSI.
* There should be a mostly empty proxy entry for that IMSI.
* Add the remote address data in the proxy. */
if (query->id.type != OSMO_MSLOOKUP_ID_IMSI) {
LOGP(DDGSM, LOGL_ERROR, "Expected IMSI ID type in mslookup query+result: %s\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
return;
}
proxy_subscr = proxy_subscr_get_by_imsi(proxy, query->id.imsi);
if (!proxy_subscr) {
LOG_DGSM(query->id.imsi, LOGL_ERROR, "No proxy entry for mslookup result: %s\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
defer_gsup_message_pop(query->id.imsi, NULL);
return;
}
if (result->rc != OSMO_MSLOOKUP_RC_OK) {
LOG_DGSM(query->id.imsi, LOGL_ERROR, "Failed to resolve remote HLR: %s\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
defer_gsup_message_pop(query->id.imsi, NULL);
proxy_subscr_del(proxy, proxy_subscr->imsi);
return;
}
/* Store the address. Make a copy to modify. */
proxy_subscr_new = *proxy_subscr;
proxy_subscr = &proxy_subscr_new;
if (osmo_sockaddr_str_is_nonzero(&result->host_v4))
proxy_subscr_new.remote_hlr_addr = result->host_v4;
else if (osmo_sockaddr_str_is_nonzero(&result->host_v6))
proxy_subscr_new.remote_hlr_addr = result->host_v6;
else {
LOG_DGSM(query->id.imsi, LOGL_ERROR, "Invalid address for remote HLR: %s\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
defer_gsup_message_pop(query->id.imsi, NULL);
proxy_subscr_del(proxy, proxy_subscr->imsi);
return;
}
if (proxy_subscr_update(proxy, proxy_subscr)) {
LOG_DGSM(query->id.imsi, LOGL_ERROR, "Failed to store proxy entry for remote HLR: %s\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
defer_gsup_message_pop(query->id.imsi, NULL);
proxy_subscr_del(proxy, proxy_subscr->imsi);
return;
}
LOG_DGSM(proxy_subscr->imsi, LOGL_DEBUG, "Stored remote hlr address for this IMSI: " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&proxy_subscr->remote_hlr_addr));
remote_hlr = remote_hlr_get(&proxy_subscr->remote_hlr_addr, true);
if (!remote_hlr) {
defer_gsup_message_pop(query->id.imsi, NULL);
proxy_subscr_del(proxy, proxy_subscr->imsi);
return;
}
if (!remote_hlr->gsupc || !remote_hlr->gsupc->is_connected) {
LOG_DGSM(query->id.imsi, LOGL_DEBUG, "Resolved remote HLR, waiting for link-up: %s\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
return;
}
LOG_DGSM(query->id.imsi, LOGL_DEBUG, "Resolved remote HLR, sending spooled GSUP messages: %s\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
defer_gsup_message_pop(query->id.imsi, remote_hlr);
}
static bool remote_hlr_up_yield(struct proxy *proxy, const struct proxy_subscr *proxy_subscr, void *data)
{
struct remote_hlr *remote_hlr = data;
defer_gsup_message_pop(proxy_subscr->imsi, remote_hlr);
return true;
}
void dgsm_remote_hlr_up(struct remote_hlr *remote_hlr)
{
LOGP(DDGSM, LOGL_NOTICE, "link to remote HLR is up, sending spooled GSUP messages: " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr));
/* Send all spooled GSUP messaged for IMSIs that are waiting for this link to establish. */
proxy_subscrs_get_by_remote_hlr(g_hlr->proxy, &remote_hlr->addr, remote_hlr_up_yield, remote_hlr);
}
/* Return true when the message has been handled by D-GSM. */
bool dgsm_check_forward_gsup_msg(struct osmo_gsup_req *req)
{
const struct proxy_subscr *proxy_subscr;
struct proxy_subscr proxy_subscr_new;
struct proxy *proxy = g_hlr->proxy;
struct osmo_mslookup_query query;
struct osmo_mslookup_query_handling handling;
uint32_t request_handle;
/* If the IMSI is known in the local HLR, then we won't proxy. */
if (db_subscr_exists_by_imsi(g_hlr->dbc, req->gsup.imsi) == 0)
return false;
/* Are we already forwarding this IMSI to a remote HLR? */
proxy_subscr = proxy_subscr_get_by_imsi(proxy, req->gsup.imsi);
if (proxy_subscr)
goto yes_we_are_proxying;
/* The IMSI is not known locally, so we want to proxy to a remote HLR, but no proxy entry exists yet. We need to
* look up the subscriber in remote HLRs via D-GSM mslookup, forward GSUP and reply once a result is back from
* there. Defer message and kick off MS lookup. */
/* Kick off an mslookup for the remote HLR. */
if (!g_hlr->mslookup.client.client) {
LOG_GSUP_REQ(req, LOGL_DEBUG, "mslookup client not running, cannot query remote home HLR\n");
return false;
}
query = (struct osmo_mslookup_query){
.id = {
.type = OSMO_MSLOOKUP_ID_IMSI,
}
};
OSMO_STRLCPY_ARRAY(query.id.imsi, req->gsup.imsi);
OSMO_STRLCPY_ARRAY(query.service, OSMO_MSLOOKUP_SERVICE_HLR_GSUP);
handling = (struct osmo_mslookup_query_handling){
.result_timeout_milliseconds = g_hlr->mslookup.client.result_timeout_milliseconds,
.result_cb = resolve_hlr_result_cb,
};
request_handle = osmo_mslookup_client_request(g_hlr->mslookup.client.client, &query, &handling);
if (!request_handle) {
LOG_DGSM(req->gsup.imsi, LOGL_ERROR, "Error dispatching mslookup query for home HLR: %s\n",
osmo_mslookup_result_name_c(OTC_SELECT, &query, NULL));
proxy_subscr_del(proxy, req->gsup.imsi);
return false;
}
/* Add a proxy entry without a remote address to indicate that we are busy querying for a remote HLR. */
proxy_subscr_new = (struct proxy_subscr){};
OSMO_STRLCPY_ARRAY(proxy_subscr_new.imsi, req->gsup.imsi);
proxy_subscr = &proxy_subscr_new;
proxy_subscr_update(proxy, proxy_subscr);
yes_we_are_proxying:
OSMO_ASSERT(proxy_subscr);
/* If the remote HLR is already known, directly forward the GSUP message; otherwise, spool the GSUP message
* until the remote HLR will respond / until timeout aborts. */
dgsm_send_to_remote_hlr(proxy_subscr, req);
return true;
}
void dgsm_init(void *ctx)
{
dgsm_ctx = talloc_named_const(ctx, 0, "dgsm");
dgsm_pending_messages_ctx = talloc_named_const(dgsm_ctx, 0, "dgsm_pending_messages");
INIT_LLIST_HEAD(&g_hlr->mslookup.vty.server.msc_configs);
g_hlr->mslookup.server.max_age = 60 * 60;
g_hlr->mslookup.client.result_timeout_milliseconds = 2000;
g_hlr->gsup_unit_name.unit_name = "HLR";
g_hlr->gsup_unit_name.serno = "unnamed-HLR";
g_hlr->gsup_unit_name.swversion = PACKAGE_NAME "-" PACKAGE_VERSION;
osmo_sockaddr_str_from_str(&g_hlr->mslookup.vty.server.mdns.bind_addr,
OSMO_MSLOOKUP_MDNS_IP4, OSMO_MSLOOKUP_MDNS_PORT);
osmo_sockaddr_str_from_str(&g_hlr->mslookup.vty.client.mdns.query_addr,
OSMO_MSLOOKUP_MDNS_IP4, OSMO_MSLOOKUP_MDNS_PORT);
}
void dgsm_start(void *ctx)
{
g_hlr->mslookup.client.client = osmo_mslookup_client_new(dgsm_ctx);
OSMO_ASSERT(g_hlr->mslookup.client.client);
g_hlr->mslookup.allow_startup = true;
dgsm_config_apply();
}
static void dgsm_mdns_server_config_apply()
{
/* Check whether to start/stop/restart mDNS server */
bool should_run;
bool should_stop;
if (!g_hlr->mslookup.allow_startup)
return;
should_run = g_hlr->mslookup.vty.server.enable && g_hlr->mslookup.vty.server.mdns.enable;
should_stop = g_hlr->mslookup.server.mdns
&& (!should_run
|| osmo_sockaddr_str_cmp(&g_hlr->mslookup.vty.server.mdns.bind_addr,
&g_hlr->mslookup.server.mdns->bind_addr));
if (should_stop) {
osmo_mslookup_server_mdns_stop(g_hlr->mslookup.server.mdns);
g_hlr->mslookup.server.mdns = NULL;
LOGP(DDGSM, LOGL_NOTICE, "Stopped mslookup mDNS server\n");
}
if (should_run && !g_hlr->mslookup.server.mdns) {
g_hlr->mslookup.server.mdns =
osmo_mslookup_server_mdns_start(g_hlr, &g_hlr->mslookup.vty.server.mdns.bind_addr);
if (!g_hlr->mslookup.server.mdns)
LOGP(DDGSM, LOGL_ERROR, "Failed to start mslookup mDNS server on " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.server.mdns->bind_addr));
else
LOGP(DDGSM, LOGL_NOTICE, "Started mslookup mDNS server, receiving mDNS requests at multicast "
OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.server.mdns->bind_addr));
}
}
static void dgsm_mdns_client_config_apply()
{
if (!g_hlr->mslookup.allow_startup)
return;
/* Check whether to start/stop/restart mDNS client */
const struct osmo_sockaddr_str *current_bind_addr;
current_bind_addr = osmo_mslookup_client_method_mdns_get_bind_addr(g_hlr->mslookup.client.mdns);
bool should_run = g_hlr->mslookup.vty.client.enable && g_hlr->mslookup.vty.client.mdns.enable;
bool should_stop = g_hlr->mslookup.client.mdns &&
(!should_run
|| osmo_sockaddr_str_cmp(&g_hlr->mslookup.vty.client.mdns.query_addr,
current_bind_addr));
if (should_stop) {
osmo_mslookup_client_method_del(g_hlr->mslookup.client.client, g_hlr->mslookup.client.mdns);
g_hlr->mslookup.client.mdns = NULL;
LOGP(DDGSM, LOGL_NOTICE, "Stopped mslookup mDNS client\n");
}
if (should_run && !g_hlr->mslookup.client.mdns) {
g_hlr->mslookup.client.mdns =
osmo_mslookup_client_add_mdns(g_hlr->mslookup.client.client,
g_hlr->mslookup.vty.client.mdns.query_addr.ip,
g_hlr->mslookup.vty.client.mdns.query_addr.port,
true);
if (!g_hlr->mslookup.client.mdns)
LOGP(DDGSM, LOGL_ERROR, "Failed to start mslookup mDNS client with target "
OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.vty.client.mdns.query_addr));
else
LOGP(DDGSM, LOGL_NOTICE, "Started mslookup mDNS client, sending mDNS requests to multicast " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.vty.client.mdns.query_addr));
}
}
void dgsm_config_apply()
{
dgsm_mdns_server_config_apply();
dgsm_mdns_client_config_apply();
}

75
src/dgsm.h Normal file
View File

@@ -0,0 +1,75 @@
#pragma once
#include <osmocom/mslookup/mslookup.h>
#include "gsup_server.h"
#include "global_title.h"
#define LOG_DGSM(imsi, level, fmt, args...) \
LOGP(DDGSM, level, "(IMSI-%s) " fmt, imsi, ##args)
struct vty;
struct remote_hlr;
extern void *dgsm_ctx;
struct dgsm_service_host {
struct llist_head entry;
char service[OSMO_MSLOOKUP_SERVICE_MAXLEN+1];
struct osmo_sockaddr_str host_v4;
struct osmo_sockaddr_str host_v6;
};
struct dgsm_msc_config {
struct llist_head entry;
struct global_title name;
struct llist_head service_hosts;
};
/* "Sketch pad" where the VTY can store config items without yet applying. The changes will be applied by e.g.
* dgsm_mdns_server_config_apply() and dgsm_mdns_client_config_apply(). */
struct dgsm_config {
struct {
/* Whether to listen for incoming MS Lookup requests */
bool enable;
struct {
bool enable;
struct osmo_sockaddr_str bind_addr;
} mdns;
struct llist_head msc_configs;
} server;
struct {
/* Whether to ask remote HLRs via MS Lookup if an IMSI is not known locally. */
bool enable;
struct timeval timeout;
struct {
/* Whether to use mDNS for IMSI MS Lookup */
bool enable;
struct osmo_sockaddr_str query_addr;
} mdns;
} client;
};
void dgsm_config_apply();
struct dgsm_service_host *dgsm_config_service_get(const struct global_title *msc_name, const char *service);
int dgsm_config_service_set(const struct global_title *msc_name, const char *service, const struct osmo_sockaddr_str *addr);
int dgsm_config_service_del(const struct global_title *msc_name, const char *service, const struct osmo_sockaddr_str *addr);
struct dgsm_service_host *dgsm_config_msc_service_get(struct dgsm_msc_config *msc, const char *service, bool create);
int dgsm_config_msc_service_set(struct dgsm_msc_config *msc, const char *service, const struct osmo_sockaddr_str *addr);
int dgsm_config_msc_service_del(struct dgsm_msc_config *msc, const char *service, const struct osmo_sockaddr_str *addr);
extern const struct global_title dgsm_config_msc_wildcard;
struct dgsm_msc_config *dgsm_config_msc_get(const struct global_title *msc_name, bool create);
void dgsm_init(void *ctx);
void dgsm_start(void *ctx);
bool dgsm_check_forward_gsup_msg(struct osmo_gsup_req *req);
void dgsm_vty_init();
void dgsm_remote_hlr_up(struct remote_hlr *remote_hlr);

355
src/dgsm_vty.c Normal file
View File

@@ -0,0 +1,355 @@
#include <osmocom/vty/vty.h>
#include <osmocom/vty/command.h>
#include "hlr_vty.h"
#include "dgsm.h"
struct cmd_node mslookup_node = {
MSLOOKUP_NODE,
"%s(config-mslookup)# ",
1,
};
DEFUN(cfg_mslookup,
cfg_mslookup_cmd,
"mslookup",
"Configure Distributed GSM / multicast MS Lookup")
{
vty->node = MSLOOKUP_NODE;
return CMD_SUCCESS;
}
DEFUN(cfg_mslookup_mdns,
cfg_mslookup_mdns_cmd,
"mdns",
"Convenience shortcut: enable both server and client for DNS/mDNS MS Lookup with default config\n")
{
g_hlr->mslookup.vty.server.enable = true;
g_hlr->mslookup.vty.server.mdns.enable = true;
g_hlr->mslookup.vty.client.enable = true;
g_hlr->mslookup.vty.client.mdns.enable = true;
dgsm_config_apply();
return CMD_SUCCESS;
}
DEFUN(cfg_mslookup_no_mdns,
cfg_mslookup_no_mdns_cmd,
"no mdns",
NO_STR "Disable both server and client for DNS/mDNS MS Lookup\n")
{
g_hlr->mslookup.vty.server.mdns.enable = false;
g_hlr->mslookup.vty.client.mdns.enable = false;
dgsm_config_apply();
return CMD_SUCCESS;
}
struct cmd_node mslookup_server_node = {
MSLOOKUP_SERVER_NODE,
"%s(config-mslookup-server)# ",
1,
};
DEFUN(cfg_mslookup_server,
cfg_mslookup_server_cmd,
"server",
"Enable and configure Distributed GSM / multicast MS Lookup server")
{
vty->node = MSLOOKUP_SERVER_NODE;
g_hlr->mslookup.vty.server.enable = true;
dgsm_config_apply();
return CMD_SUCCESS;
}
DEFUN(cfg_mslookup_no_server,
cfg_mslookup_no_server_cmd,
"no server",
NO_STR "Disable Distributed GSM / multicast MS Lookup server")
{
g_hlr->mslookup.vty.server.enable = false;
dgsm_config_apply();
return CMD_SUCCESS;
}
#define MDNS_STR "Configure mslookup by multicast DNS\n"
#define MDNS_BIND_STR MDNS_STR "Configure where the mDNS server listens for MS Lookup requests\n"
#define IP46_STR "IPv4 address like 1.2.3.4 or IPv6 address like a:b:c:d::1\n"
#define PORT_STR "Port number\n"
DEFUN(cfg_mslookup_server_mdns_bind,
cfg_mslookup_server_mdns_bind_cmd,
"mdns [bind] [IP] [<1-65535>]",
MDNS_BIND_STR IP46_STR PORT_STR)
{
const char *ip_str = argc > 1? argv[1] : g_hlr->mslookup.vty.server.mdns.bind_addr.ip;
const char *port_str = argc > 2? argv[2] : NULL;
uint16_t port_nr = port_str ? atoi(port_str) : g_hlr->mslookup.vty.server.mdns.bind_addr.port;
struct osmo_sockaddr_str addr;
if (osmo_sockaddr_str_from_str(&addr, ip_str, port_nr)
|| !osmo_sockaddr_str_is_nonzero(&addr)) {
vty_out(vty, "%% MS Lookup server: Invalid mDNS bind address: %s %u%s",
ip_str, port_nr, VTY_NEWLINE);
return CMD_WARNING;
}
g_hlr->mslookup.vty.server.mdns.bind_addr = addr;
g_hlr->mslookup.vty.server.mdns.enable = true;
dgsm_config_apply();
return CMD_SUCCESS;
}
DEFUN(cfg_mslookup_server_no_mdns,
cfg_mslookup_server_no_mdns_cmd,
"no mdns",
NO_STR "Disable server for DNS/mDNS MS Lookup (do not answer remote requests)\n")
{
g_hlr->mslookup.vty.server.mdns.enable = false;
dgsm_config_apply();
return CMD_SUCCESS;
}
struct cmd_node mslookup_server_msc_node = {
MSLOOKUP_SERVER_MSC_NODE,
"%s(config-mslookup-server-msc)# ",
1,
};
DEFUN(cfg_mslookup_server_msc,
cfg_mslookup_server_msc_cmd,
"msc .UNIT_NAME",
"Configure services for individual local MSCs\n"
"IPA Unit Name of the local MSC to configure\n")
{
struct global_title msc_name;
struct dgsm_msc_config *msc;
global_title_set_str(&msc_name, argv_concat(argv, argc, 0));
msc = dgsm_config_msc_get(&msc_name, true);
if (!msc) {
vty_out(vty, "%% Error creating MSC %s%s", global_title_name(&msc_name), VTY_NEWLINE);
return CMD_WARNING;
}
vty->node = MSLOOKUP_SERVER_MSC_NODE;
vty->index = msc;
return CMD_SUCCESS;
}
#define SERVICE_NAME_STR \
"MS Lookup service name, e.g. " OSMO_MSLOOKUP_SERVICE_SIP " or " OSMO_MSLOOKUP_SERVICE_SMPP "\n"
#define SERVICE_AND_NAME_STR \
"Configure addresses of local services, as sent in replies to remote MS Lookup requests.\n" \
SERVICE_NAME_STR
DEFUN(cfg_mslookup_server_msc_service,
cfg_mslookup_server_msc_service_cmd,
"service NAME at IP <1-65535>",
SERVICE_AND_NAME_STR "at\n" IP46_STR PORT_STR)
{
/* If this command is run on the 'server' node, it produces an empty unit name and serves as wildcard for all
* MSCs. If on a 'server' / 'msc' node, set services only for that MSC Unit Name. */
struct dgsm_msc_config *msc = (vty->node == MSLOOKUP_SERVER_MSC_NODE) ? vty->index : NULL;
const char *service = argv[0];
const char *ip_str = argv[1];
const char *port_str = argv[2];
struct osmo_sockaddr_str addr;
/* On the mslookup.server node, set services on the wildcard msc, without a particular name. */
if (vty->node == MSLOOKUP_SERVER_NODE)
msc = dgsm_config_msc_get(&dgsm_config_msc_wildcard, true);
if (!msc) {
vty_out(vty, "%% Error: no MSC object on this node%s", VTY_NEWLINE);
return CMD_WARNING;
}
if (osmo_sockaddr_str_from_str(&addr, ip_str, atoi(port_str))
|| !osmo_sockaddr_str_is_nonzero(&addr)) {
vty_out(vty, "%% MS Lookup server: Invalid address for service %s: %s %s%s",
service, ip_str, port_str, VTY_NEWLINE);
return CMD_WARNING;
}
if (dgsm_config_msc_service_set(msc, service, &addr)) {
vty_out(vty, "%% MS Lookup server: Error setting service %s to %s %s%s",
service, ip_str, port_str, VTY_NEWLINE);
return CMD_WARNING;
}
return CMD_SUCCESS;
}
#define NO_SERVICE_AND_NAME_STR NO_STR "Remove one or more service address entries\n" SERVICE_NAME_STR
DEFUN(cfg_mslookup_server_msc_no_service,
cfg_mslookup_server_msc_no_service_cmd,
"no service NAME",
NO_SERVICE_AND_NAME_STR)
{
/* If this command is run on the 'server' node, it produces an empty unit name and serves as wildcard for all
* MSCs. If on a 'server' / 'msc' node, set services only for that MSC Unit Name. */
struct dgsm_msc_config *msc = (vty->node == MSLOOKUP_SERVER_MSC_NODE) ? vty->index : NULL;
const char *service = argv[0];
if (dgsm_config_msc_service_del(msc, service, NULL)) {
vty_out(vty, "%% MS Lookup server: Error removing service %s%s",
service, VTY_NEWLINE);
return CMD_WARNING;
}
return CMD_SUCCESS;
}
DEFUN(cfg_mslookup_server_msc_no_service_addr,
cfg_mslookup_server_msc_no_service_addr_cmd,
"no service NAME at IP <1-65535>",
NO_SERVICE_AND_NAME_STR "at\n" IP46_STR PORT_STR)
{
/* If this command is run on the 'server' node, it produces an empty unit name and serves as wildcard for all
* MSCs. If on a 'server' / 'msc' node, set services only for that MSC Unit Name. */
struct dgsm_msc_config *msc = (vty->node == MSLOOKUP_SERVER_MSC_NODE) ? vty->index : NULL;
const char *service = argv[0];
const char *ip_str = argv[1];
const char *port_str = argv[2];
struct osmo_sockaddr_str addr;
if (osmo_sockaddr_str_from_str(&addr, ip_str, atoi(port_str))
|| !osmo_sockaddr_str_is_nonzero(&addr)) {
vty_out(vty, "%% MS Lookup server: Invalid address for 'no service' %s: %s %s%s",
service, ip_str, port_str, VTY_NEWLINE);
return CMD_WARNING;
}
if (dgsm_config_service_del(&msc->name, service, &addr)) {
vty_out(vty, "%% MS Lookup server: Error removing service %s to %s %s%s",
service, ip_str, port_str, VTY_NEWLINE);
return CMD_WARNING;
}
return CMD_SUCCESS;
}
struct cmd_node mslookup_client_node = {
MSLOOKUP_CLIENT_NODE,
"%s(config-mslookup-client)# ",
1,
};
DEFUN(cfg_mslookup_client,
cfg_mslookup_client_cmd,
"client",
"Enable and configure Distributed GSM / multicast MS Lookup client")
{
vty->node = MSLOOKUP_CLIENT_NODE;
g_hlr->mslookup.vty.client.enable = true;
dgsm_config_apply();
return CMD_SUCCESS;
}
DEFUN(cfg_mslookup_no_client,
cfg_mslookup_no_client_cmd,
"no client",
NO_STR "Disable Distributed GSM / multicast MS Lookup client")
{
g_hlr->mslookup.vty.client.enable = false;
dgsm_config_apply();
return CMD_SUCCESS;
}
#define MDNS_TO_STR MDNS_STR "Configure to which multicast address mDNS MS Lookup requests are sent\n"
DEFUN(cfg_mslookup_client_timeout,
cfg_mslookup_client_timeout_cmd,
"timeout <1-100000>",
"How long should the mslookup client wait for remote responses before evaluating received results\n"
"timeout in milliseconds\n")
{
uint32_t val = atol(argv[0]);
g_hlr->mslookup.vty.client.timeout.tv_sec = val / 1000;
g_hlr->mslookup.vty.client.timeout.tv_usec = (val % 1000) * 1000;
return CMD_SUCCESS;
}
#define EXIT_HINT() \
if (vty->type != VTY_FILE) \
vty_out(vty, "%% 'exit' this node to apply changes%s", VTY_NEWLINE)
DEFUN(cfg_mslookup_client_mdns,
cfg_mslookup_client_mdns_cmd,
"mdns [to] [IP] [<1-65535>]",
MDNS_STR "Configure multicast address to send mDNS mslookup requests to\n" IP46_STR PORT_STR)
{
const char *ip_str = argc > 1? argv[1] : g_hlr->mslookup.vty.client.mdns.query_addr.ip;
const char *port_str = argc > 2? argv[2] : NULL;
uint16_t port_nr = port_str ? atoi(port_str) : g_hlr->mslookup.vty.client.mdns.query_addr.port;
struct osmo_sockaddr_str addr;
if (osmo_sockaddr_str_from_str(&addr, ip_str, port_nr)
|| !osmo_sockaddr_str_is_nonzero(&addr)) {
vty_out(vty, "%% MS Lookup client: Invalid mDNS target address: %s %u%s",
ip_str, port_nr, VTY_NEWLINE);
return CMD_WARNING;
}
g_hlr->mslookup.vty.client.mdns.query_addr = addr;
g_hlr->mslookup.vty.client.mdns.enable = true;
dgsm_config_apply();
return CMD_SUCCESS;
}
DEFUN(cfg_mslookup_client_no_mdns,
cfg_mslookup_client_no_mdns_cmd,
"no mdns",
NO_STR "Disable mDNS client, do not query remote services by mDNS\n")
{
g_hlr->mslookup.vty.client.mdns.enable = false;
dgsm_config_apply();
return CMD_SUCCESS;
}
int config_write_mslookup(struct vty *vty)
{
return CMD_SUCCESS;
}
int config_write_mslookup_server(struct vty *vty)
{
return CMD_SUCCESS;
}
int config_write_mslookup_server_msc(struct vty *vty)
{
return CMD_SUCCESS;
}
int config_write_mslookup_client(struct vty *vty)
{
return CMD_SUCCESS;
}
void dgsm_vty_init()
{
install_element(CONFIG_NODE, &cfg_mslookup_cmd);
install_node(&mslookup_node, config_write_mslookup);
install_element(MSLOOKUP_NODE, &cfg_mslookup_mdns_cmd);
install_element(MSLOOKUP_NODE, &cfg_mslookup_no_mdns_cmd);
install_element(MSLOOKUP_NODE, &cfg_mslookup_server_cmd);
install_element(MSLOOKUP_NODE, &cfg_mslookup_no_server_cmd);
install_node(&mslookup_server_node, config_write_mslookup_server);
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_mdns_bind_cmd);
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_no_mdns_cmd);
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_msc_service_cmd);
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_msc_no_service_cmd);
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_msc_no_service_addr_cmd);
install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_msc_cmd);
install_node(&mslookup_server_msc_node, config_write_mslookup_server_msc);
install_element(MSLOOKUP_SERVER_MSC_NODE, &cfg_mslookup_server_msc_service_cmd);
install_element(MSLOOKUP_SERVER_MSC_NODE, &cfg_mslookup_server_msc_no_service_cmd);
install_element(MSLOOKUP_SERVER_MSC_NODE, &cfg_mslookup_server_msc_no_service_addr_cmd);
install_element(MSLOOKUP_NODE, &cfg_mslookup_client_cmd);
install_element(MSLOOKUP_NODE, &cfg_mslookup_no_client_cmd);
install_node(&mslookup_client_node, config_write_mslookup_client);
install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_timeout_cmd);
install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_mdns_cmd);
install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_no_mdns_cmd);
}

68
src/global_title.c Normal file
View File

@@ -0,0 +1,68 @@
#include <errno.h>
#include <string.h>
#include <osmocom/core/utils.h>
#include "global_title.h"
int global_title_set(struct global_title *gt, const uint8_t *val, size_t len)
{
if (!val || !len) {
*gt = (struct global_title){};
return 0;
}
if (len > sizeof(gt->val))
return -ENOSPC;
gt->len = len;
memcpy(gt->val, val, len);
return 0;
}
int global_title_set_str(struct global_title *gt, const char *str_fmt, ...)
{
va_list ap;
if (!str_fmt)
return global_title_set(gt, NULL, 0);
va_start(ap, str_fmt);
vsnprintf((char*)(gt->val), sizeof(gt->val), str_fmt, ap);
va_end(ap);
gt->len = strlen((char*)(gt->val))+1;
return 0;
}
int global_title_cmp(const struct global_title *a, const struct global_title *b)
{
int cmp;
if (a == b)
return 0;
if (!a)
return -1;
if (!b)
return 1;
if (!a->len && !b->len)
return 0;
if (!a->len && b->len)
return -1;
if (!b->len && a->len)
return 1;
if (a->len == b->len)
return memcmp(a->val, b->val, a->len);
else if (a->len < b->len) {
cmp = memcmp(a->val, b->val, a->len);
if (!cmp)
cmp = -1;
return cmp;
} else {
/* a->len > b->len */
cmp = memcmp(a->val, b->val, b->len);
if (!cmp)
cmp = 1;
return cmp;
}
}
const char *global_title_name(const struct global_title *gt)
{
return osmo_quote_str_c(OTC_SELECT, (char*)gt->val, gt->len);
}

17
src/global_title.h Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
#include <unistd.h>
#include <stdint.h>
/* Arbitrary length blob, not necessarily zero-terminated.
* In osmo-hlr, struct hlr_subscriber is mostly used as static reference and cannot serve as talloc context, which is
* why this is also implemented as a fixed-maximum-size buffer instead of a talloc'd arbitrary sized buffer.
*/
struct global_title {
size_t len;
uint8_t val[128];
};
int global_title_set(struct global_title *gt, const uint8_t *val, size_t len);
int global_title_set_str(struct global_title *gt, const char *str_fmt, ...);
int global_title_cmp(const struct global_title *a, const struct global_title *b);
const char *global_title_name(const struct global_title *gt);

View File

@@ -47,6 +47,11 @@ struct osmo_gsup_conn *gsup_route_find(struct osmo_gsup_server *gs,
return NULL;
}
struct osmo_gsup_conn *gsup_route_find_gt(struct osmo_gsup_server *gs, const struct global_title *gt)
{
return gsup_route_find(gs, gt->val, gt->len);
}
/*! Find a GSUP connection's route (to read the IPA address from the route).
* \param[in] conn GSUP connection
* \return GSUP route
@@ -67,10 +72,15 @@ struct gsup_route *gsup_route_find_by_conn(const struct osmo_gsup_conn *conn)
int gsup_route_add(struct osmo_gsup_conn *conn, const uint8_t *addr, size_t addrlen)
{
struct gsup_route *gr;
struct osmo_gsup_conn *exists_on_conn;
/* Check if we already have a route for this address */
if (gsup_route_find(conn->server, addr, addrlen))
return -EEXIST;
exists_on_conn = gsup_route_find(conn->server, addr, addrlen);
if (exists_on_conn) {
if (exists_on_conn != conn)
return -EEXIST;
return 0;
}
/* allocate new route and populate it */
gr = talloc_zero(conn->server, struct gsup_route);
@@ -86,6 +96,11 @@ int gsup_route_add(struct osmo_gsup_conn *conn, const uint8_t *addr, size_t addr
return 0;
}
int gsup_route_add_gt(struct osmo_gsup_conn *conn, const struct global_title *gt)
{
return gsup_route_add(conn, gt->val, gt->len);
}
/* delete all routes for the given connection */
int gsup_route_del_conn(struct osmo_gsup_conn *conn)
{
@@ -95,7 +110,7 @@ int gsup_route_del_conn(struct osmo_gsup_conn *conn)
llist_for_each_entry_safe(gr, gr2, &conn->server->routes, list) {
if (gr->conn == conn) {
LOGP(DMAIN, LOGL_INFO, "Removing GSUP route for %s (GSUP disconnect)\n",
gr->addr);
osmo_quote_str_c(OTC_SELECT, (char*)gr->addr, talloc_total_size(gr->addr)));
llist_del(&gr->list);
talloc_free(gr);
num_deleted++;

View File

@@ -1,6 +1,7 @@
#pragma once
#include <stdint.h>
#include "global_title.h"
#include "gsup_server.h"
struct gsup_route {
@@ -12,10 +13,12 @@ struct gsup_route {
struct osmo_gsup_conn *gsup_route_find(struct osmo_gsup_server *gs,
const uint8_t *addr, size_t addrlen);
struct osmo_gsup_conn *gsup_route_find_gt(struct osmo_gsup_server *gs, const struct global_title *gt);
struct gsup_route *gsup_route_find_by_conn(const struct osmo_gsup_conn *conn);
/* add a new route for the given address to the given conn */
int gsup_route_add_gt(struct osmo_gsup_conn *conn, const struct global_title *gt);
int gsup_route_add(struct osmo_gsup_conn *conn, const uint8_t *addr, size_t addrlen);
/* delete all routes for the given connection */
@@ -24,3 +27,6 @@ int gsup_route_del_conn(struct osmo_gsup_conn *conn);
int osmo_gsup_addr_send(struct osmo_gsup_server *gs,
const uint8_t *addr, size_t addrlen,
struct msgb *msg);
int osmo_gsup_gt_send(struct osmo_gsup_server *gs, const struct global_title *gt, struct msgb *msg);
int osmo_gsup_gt_enc_send(struct osmo_gsup_server *gs, const struct global_title *gt,
const struct osmo_gsup_message *gsup);

View File

@@ -42,7 +42,8 @@ int osmo_gsup_addr_send(struct osmo_gsup_server *gs,
conn = gsup_route_find(gs, addr, addrlen);
if (!conn) {
DEBUGP(DLGSUP, "Cannot find route for addr %s\n", osmo_quote_str((const char*)addr, addrlen));
LOGP(DLGSUP, LOGL_ERROR,
"Cannot find route for addr %s\n", osmo_quote_str((const char*)addr, addrlen));
msgb_free(msg);
return -ENODEV;
}
@@ -50,3 +51,41 @@ int osmo_gsup_addr_send(struct osmo_gsup_server *gs,
return osmo_gsup_conn_send(conn, msg);
}
/*! Send a msgb to a given address using routing.
* \param[in] gs gsup server
* \param[in] gt IPA unit name of the client (SGSN, MSC/VLR, proxy).
* \param[in] msg message buffer
*/
int osmo_gsup_gt_send(struct osmo_gsup_server *gs, const struct global_title *gt, struct msgb *msg)
{
if (gt->val[gt->len - 1]) {
/* Is not nul terminated. But for legacy reasons we (still) require that. */
if (gt->len >= sizeof(gt->val)) {
LOGP(DLGSUP, LOGL_ERROR, "Global title (IPA unit name) is too long: %s\n",
global_title_name(gt));
return -EINVAL;
}
struct global_title gt2 = *gt;
gt2.val[gt->len] = '\0';
gt2.len++;
return osmo_gsup_addr_send(gs, gt2.val, gt2.len, msg);
}
return osmo_gsup_addr_send(gs, gt->val, gt->len, msg);
}
int osmo_gsup_gt_enc_send(struct osmo_gsup_server *gs, const struct global_title *gt,
const struct osmo_gsup_message *gsup)
{
struct msgb *msg = osmo_gsup_msgb_alloc("GSUP Tx");
int rc;
rc = osmo_gsup_encode(msg, gsup);
if (rc) {
LOGP(DLGSUP, LOGL_ERROR, "IMSI-%s: Cannot encode GSUP: %s\n",
gsup->imsi, osmo_gsup_message_type_name(gsup->message_type));
msgb_free(msg);
return -EINVAL;
}
LOGP(DLGSUP, LOGL_DEBUG, "IMSI-%s: Tx: %s\n", gsup->imsi, osmo_gsup_message_type_name(gsup->message_type));
return osmo_gsup_gt_send(gs, gt, msg);
}

View File

@@ -26,10 +26,19 @@
#include <osmocom/abis/ipaccess.h>
#include <osmocom/gsm/gsm48_ie.h>
#include <osmocom/gsm/apn.h>
#include <osmocom/gsm/gsm23003.h>
#include "hlr.h"
#include "gsup_server.h"
#include "gsup_router.h"
struct msgb *osmo_gsup_msgb_alloc(const char *label)
{
struct msgb *msg = msgb_alloc_headroom(1024+512, 512, label);
OSMO_ASSERT(msg);
return msg;
}
static void osmo_gsup_server_send(struct osmo_gsup_conn *conn,
int proto_ext, struct msgb *msg_tx)
{
@@ -50,6 +59,229 @@ int osmo_gsup_conn_send(struct osmo_gsup_conn *conn, struct msgb *msg)
return 0;
}
/* Only for requests originating here. When answering to a remote request, rather use osmo_gsup_req_respond() or
* osmo_gsup_req_respond_err(). */
int osmo_gsup_conn_enc_send(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup)
{
struct msgb *msg = osmo_gsup_msgb_alloc("GSUP Tx");
int rc;
rc = osmo_gsup_encode(msg, gsup);
if (rc) {
LOG_GSUP_CONN(conn, LOGL_ERROR, "Cannot encode GSUP: %s\n",
osmo_gsup_message_type_name(gsup->message_type));
msgb_free(msg);
return -EINVAL;
}
LOG_GSUP_CONN(conn, LOGL_DEBUG, "Tx: %s\n", osmo_gsup_message_type_name(gsup->message_type));
rc = osmo_gsup_conn_send(conn, msg);
if (rc)
LOG_GSUP_CONN(conn, LOGL_ERROR, "Unable to send: %s\n",
osmo_gsup_message_type_name(gsup->message_type));
return rc;
}
struct osmo_gsup_req *osmo_gsup_req_new(struct osmo_gsup_conn *conn, struct msgb *msg)
{
static unsigned int next_req_nr = 1;
struct osmo_gsup_req *req;
struct osmo_gsup_message gsup_err;
int rc;
if (!msgb_l2(msg) || !msgb_l2len(msg)) {
LOGP(DLGSUP, LOGL_ERROR, "Rx GSUP message: missing or empty L2 data\n");
msgb_free(msg);
return NULL;
}
req = talloc_zero(conn->server, struct osmo_gsup_req);
OSMO_ASSERT(req);
req->nr = next_req_nr++;
req->server = conn->server;
req->msg = msg;
req->source_name = conn->peer_name;
rc = osmo_gsup_decode(msgb_l2(req->msg), msgb_l2len(req->msg), (struct osmo_gsup_message*)&req->gsup);
if (rc < 0) {
LOGP(DLGSUP, LOGL_ERROR, "Rx GSUP message: cannot decode (rc=%d)\n", rc);
osmo_gsup_req_free(req);
return NULL;
}
LOG_GSUP_REQ(req, LOGL_DEBUG, "new\n");
if (req->gsup.source_name_len) {
if (global_title_set(&req->source_name, req->gsup.source_name, req->gsup.source_name_len)) {
LOGP(DLGSUP, LOGL_ERROR, "cannot decode GSUP message's source_name, message is not routable\n");
goto unroutable_error;
}
if (global_title_cmp(&req->source_name, &conn->peer_name)) {
/* The source of the GSUP message is not the immediate GSUP peer, but that peer is our proxy for that
* source. Add it to the routes for this conn (so we can route responses back). */
if (gsup_route_add_gt(conn, &req->source_name)) {
LOGP(DLGSUP, LOGL_ERROR,
"GSUP message received from %s via peer %s, but there already exists a different"
" route to this source, message is not routable\n",
global_title_name(&req->source_name),
global_title_name(&conn->peer_name));
goto unroutable_error;
}
req->via_proxy = conn->peer_name;
}
}
if (!osmo_imsi_str_valid(req->gsup.imsi)) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO, "invalid IMSI: %s",
osmo_quote_str(req->gsup.imsi, -1));
return NULL;
}
return req;
unroutable_error:
gsup_err = (struct osmo_gsup_message){
.message_type = OSMO_GSUP_MSGT_E_ROUTING_ERROR,
.destination_name = req->gsup.destination_name,
.destination_name_len = req->gsup.destination_name_len,
.source_name = req->gsup.source_name,
.source_name_len = req->gsup.source_name_len,
};
osmo_gsup_set_reply(&req->gsup, &gsup_err);
osmo_gsup_conn_enc_send(conn, &gsup_err);
osmo_gsup_req_free(req);
return NULL;
}
void _osmo_gsup_req_free(struct osmo_gsup_req *req)
{
if (req->msg)
msgb_free(req->msg);
talloc_free(req);
}
int osmo_gsup_req_respond_nonfinal(struct osmo_gsup_req *req, struct osmo_gsup_message *response)
{
struct osmo_gsup_conn *conn;
struct msgb *msg;
int rc;
conn = gsup_route_find_gt(req->server, &req->source_name);
if (!conn) {
LOG_GSUP_REQ(req, LOGL_ERROR, "GSUP client that sent this request was disconnected, cannot respond\n");
return -EINVAL;
}
osmo_gsup_set_reply(&req->gsup, response);
msg = osmo_gsup_msgb_alloc("GSUP response");
rc = osmo_gsup_encode(msg, response);
if (rc) {
LOG_GSUP_REQ(req, LOGL_ERROR, "Unable to encode response: %s\n",
osmo_gsup_message_type_name(response->message_type));
return -EINVAL;
}
LOG_GSUP_REQ(req, LOGL_DEBUG, "Tx response: %s\n", osmo_gsup_message_type_name(response->message_type));
rc = osmo_gsup_conn_send(conn, msg);
if (rc)
LOG_GSUP_REQ(req, LOGL_ERROR, "Unable to send response: %s\n",
osmo_gsup_message_type_name(response->message_type));
return rc;
}
/* Make sure the response message contains all IEs that are required to be a valid response for the received GSUP
* request, and send back to the requesting peer. */
int osmo_gsup_req_respond(struct osmo_gsup_req *req, struct osmo_gsup_message *response)
{
int rc = osmo_gsup_req_respond_nonfinal(req, response);
osmo_gsup_req_free(req);
return rc;
}
int osmo_gsup_req_respond_msgt(struct osmo_gsup_req *req, enum osmo_gsup_message_type message_type)
{
struct osmo_gsup_message response = {
.message_type = message_type,
};
return osmo_gsup_req_respond(req, &response);
}
#define osmo_gsup_req_respond_err(REQ, CAUSE, FMT, args...) do { \
LOG_GSUP_REQ(REQ, LOGL_ERROR, "%s: " FMT "\n", \
get_value_string(gsm48_gmm_cause_names, CAUSE), ##args); \
_osmo_gsup_req_respond_err(REQ, CAUSE); \
} while(0)
void _osmo_gsup_req_respond_err(struct osmo_gsup_req *req, enum gsm48_gmm_cause cause)
{
struct osmo_gsup_message response = {
.cause = cause,
.message_type = OSMO_GSUP_TO_MSGT_ERROR(req->gsup.message_type),
};
/* No need to answer if we couldn't parse an ERROR message type, only REQUESTs need an error reply. */
if (!OSMO_GSUP_IS_MSGT_REQUEST(req->gsup.message_type)) {
osmo_gsup_req_free(req);
return;
}
if (req->gsup.session_state != OSMO_GSUP_SESSION_STATE_NONE)
response.session_state = OSMO_GSUP_SESSION_STATE_END;
osmo_gsup_req_respond(req, &response);
}
/* Encode an error reponse to the given GSUP message with the given cause.
* Determine the error message type via OSMO_GSUP_TO_MSGT_ERROR(gsup_orig->message_type).
* Only send an error response if the original message is a Request message.
* On failure, log an error, but don't return anything: if an error occurs while trying to report an earlier error,
* there is nothing we can do really except log the error (there are no callers that would use the return code).
*/
void osmo_gsup_conn_send_err_reply(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup_orig,
enum gsm48_gmm_cause cause)
{
struct osmo_gsup_message gsup_reply;
struct msgb *msg_out;
int rc;
/* No need to answer if we couldn't parse an ERROR message type, only REQUESTs need an error reply. */
if (!OSMO_GSUP_IS_MSGT_REQUEST(gsup_orig->message_type))
return;
gsup_reply = (struct osmo_gsup_message){
.cause = cause,
.message_type = OSMO_GSUP_TO_MSGT_ERROR(gsup_orig->message_type),
.message_class = gsup_orig->message_class,
/* RP-Message-Reference is mandatory for SM Service */
.sm_rp_mr = gsup_orig->sm_rp_mr,
};
osmo_gsup_set_reply(gsup_orig, &gsup_reply);
if (gsup_orig->session_state != OSMO_GSUP_SESSION_STATE_NONE)
gsup_reply.session_state = OSMO_GSUP_SESSION_STATE_END;
msg_out = osmo_gsup_msgb_alloc("GSUP ERR response");
rc = osmo_gsup_encode(msg_out, &gsup_reply);
if (rc) {
LOGP(DLGSUP, LOGL_ERROR, "%s: Unable to encode error response %s (rc=%d)\n",
osmo_quote_str(gsup_orig->imsi, -1), osmo_gsup_message_type_name(gsup_reply.message_type),
rc);
return;
}
LOGP(DLGSUP, LOGL_DEBUG, "%s: GSUP tx %s\n",
osmo_quote_str(gsup_orig->imsi, -1), osmo_gsup_message_type_name(gsup_reply.message_type));
rc = osmo_gsup_conn_send(conn, msg_out);
if (rc)
LOGP(DLGSUP, LOGL_ERROR, "%s: Unable to send error response %s (rc=%d)\n",
osmo_quote_str(gsup_orig->imsi, -1), osmo_gsup_message_type_name(gsup_reply.message_type),
rc);
}
static int osmo_gsup_conn_oap_handle(struct osmo_gsup_conn *conn,
struct msgb *msg_rx)
{
@@ -195,10 +427,18 @@ static int osmo_gsup_server_ccm_cb(struct ipa_server_conn *conn,
return -EINVAL;
}
gsup_route_add(clnt, addr, addr_len);
global_title_set(&clnt->peer_name, addr, addr_len);
gsup_route_add_gt(clnt, &clnt->peer_name);
return 0;
}
static void osmo_gsup_conn_free(struct osmo_gsup_conn *conn)
{
gsup_route_del_conn(conn);
llist_del(&conn->list);
talloc_free(conn);
}
static int osmo_gsup_server_closed_cb(struct ipa_server_conn *conn)
{
struct osmo_gsup_conn *clnt = (struct osmo_gsup_conn *)conn->data;
@@ -206,10 +446,7 @@ static int osmo_gsup_server_closed_cb(struct ipa_server_conn *conn)
LOGP(DLGSUP, LOGL_INFO, "Lost GSUP client %s:%d\n",
conn->addr, conn->port);
gsup_route_del_conn(clnt);
llist_del(&clnt->list);
talloc_free(clnt);
osmo_gsup_conn_free(clnt);
return 0;
}
@@ -291,8 +528,7 @@ failed:
struct osmo_gsup_server *
osmo_gsup_server_create(void *ctx, const char *ip_addr, uint16_t tcp_port,
osmo_gsup_read_cb_t read_cb,
struct llist_head *lu_op_lst, void *priv)
osmo_gsup_read_cb_t read_cb, void *priv)
{
struct osmo_gsup_server *gsups;
int rc;
@@ -318,8 +554,6 @@ osmo_gsup_server_create(void *ctx, const char *ip_addr, uint16_t tcp_port,
if (rc < 0)
goto failed;
gsups->luop = lu_op_lst;
return gsups;
failed:
@@ -360,6 +594,7 @@ int osmo_gsup_configure_wildcard_apn(struct osmo_gsup_message *gsup,
return 0;
}
/**
* Populate a gsup message structure with an Insert Subscriber Data Message.
* All required memory buffers for data pointed to by pointers in struct omso_gsup_message
@@ -376,39 +611,41 @@ int osmo_gsup_configure_wildcard_apn(struct osmo_gsup_message *gsup,
* \returns 0 on success, and negative on error.
*/
int osmo_gsup_create_insert_subscriber_data_msg(struct osmo_gsup_message *gsup, const char *imsi, const char *msisdn,
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)
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)
{
int len;
int len;
OSMO_ASSERT(gsup);
OSMO_ASSERT(gsup);
*gsup = (struct osmo_gsup_message){
.message_type = OSMO_GSUP_MSGT_INSERT_DATA_REQUEST,
};
gsup->message_type = OSMO_GSUP_MSGT_INSERT_DATA_REQUEST;
osmo_strlcpy(gsup->imsi, imsi, sizeof(gsup->imsi));
osmo_strlcpy(gsup->imsi, imsi, sizeof(gsup->imsi));
if (msisdn_enc_size < OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN)
return -ENOSPC;
if (msisdn_enc_size < OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN)
return -ENOSPC;
OSMO_ASSERT(msisdn_enc);
len = gsm48_encode_bcd_number(msisdn_enc, msisdn_enc_size, 0, msisdn);
if (len < 1) {
LOGP(DLGSUP, LOGL_ERROR, "%s: Error: cannot encode MSISDN '%s'\n", imsi, msisdn);
return -ENOSPC;
}
gsup->msisdn_enc = msisdn_enc;
gsup->msisdn_enc_len = len;
OSMO_ASSERT(msisdn_enc);
len = gsm48_encode_bcd_number(msisdn_enc, msisdn_enc_size, 0, msisdn);
if (len < 1) {
LOGP(DLGSUP, LOGL_ERROR, "%s: Error: cannot encode MSISDN '%s'\n", imsi, msisdn);
return -ENOSPC;
}
gsup->msisdn_enc = msisdn_enc;
gsup->msisdn_enc_len = len;
#pragma message "FIXME: deal with encoding the following data: gsup.hlr_enc"
#pragma message "FIXME: deal with encoding the following data: gsup.hlr_enc"
gsup->cn_domain = cn_domain;
if (gsup->cn_domain == OSMO_GSUP_CN_DOMAIN_PS) {
OSMO_ASSERT(apn_buf_size >= APN_MAXLEN);
OSMO_ASSERT(apn_buf);
/* FIXME: PDP infos - use more fine-grained access control
instead of wildcard APN */
osmo_gsup_configure_wildcard_apn(gsup, apn_buf, apn_buf_size);
}
gsup->cn_domain = cn_domain;
if (gsup->cn_domain == OSMO_GSUP_CN_DOMAIN_PS) {
OSMO_ASSERT(apn_buf_size >= APN_MAXLEN);
OSMO_ASSERT(apn_buf);
/* FIXME: PDP infos - use more fine-grained access control
instead of wildcard APN */
osmo_gsup_configure_wildcard_apn(gsup, apn_buf, apn_buf_size);
}
return 0;
return 0;
}

View File

@@ -2,14 +2,42 @@
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/socket.h>
#include <osmocom/abis/ipa.h>
#include <osmocom/abis/ipaccess.h>
#include <osmocom/gsm/gsup.h>
#include "global_title.h"
#ifndef OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN
#define OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN 43 /* TS 24.008 10.5.4.7 */
#endif
#define LOG_GSUP_CONN(conn, level, fmt, args...) \
LOGP(DLGSUP, level, "GSUP from %s: " fmt, \
conn && conn->server && conn->server->link ? \
osmo_sock_get_name2_c(OTC_SELECT, conn->server->link->ofd.fd) \
: "?", \
##args)
#if 0
#define LOG_GSUP_CONN_MSG(conn, gsup_msg, level, fmt, args...) \
LOG_GSUP_CONN(conn, level, "IMSI-%s %s: " fmt, (gsup_msg)->imsi, \
osmo_gsup_message_type_name((gsup_msg)->message_type), \
##args)
#endif
#define LOG_GSUP_REQ_CAT(req, subsys, level, fmt, args...) \
LOGP(subsys, level, "GSUP %u: %s: IMSI-%s %s: " fmt, \
(req) ? (req)->nr : 0, \
(req) ? global_title_name(&(req)->source_name) : "NULL", \
(req) ? (req)->gsup.imsi : "NULL", \
(req) ? osmo_gsup_message_type_name((req)->gsup.message_type) : "NULL", \
##args)
#define LOG_GSUP_REQ(req, level, fmt, args...) \
LOG_GSUP_REQ_CAT(req, DLGSUP, level, fmt, ##args)
struct osmo_gsup_conn;
/* Expects message in msg->l2h */
@@ -22,9 +50,6 @@ struct osmo_gsup_server {
/* list of osmo_gsup_conn */
struct llist_head clients;
/* lu_operations list */
struct llist_head *luop;
struct ipa_server_link *link;
osmo_gsup_read_cb_t read_cb;
struct llist_head routes;
@@ -45,10 +70,57 @@ struct osmo_gsup_conn {
/* Set when Location Update is received: */
bool supports_cs; /* client supports OSMO_GSUP_CN_DOMAIN_CS */
bool supports_ps; /* client supports OSMO_GSUP_CN_DOMAIN_PS */
/* The IPA unit name received on this link. Routes with more unit names serviced by this link may exist in
* osmo_gsup_server->routes, but this is the name the immediate peer identified as in the IPA handshake. */
struct global_title peer_name;
};
/* Keep track of an incoming request to which osmo-hlr composes (or routes back) a response.
* Particularly, if a request contained a source_name, we need to add this as destination_name in the response for any
* intermediate GSUP proxies to be able to route back to the initial requestor. */
struct osmo_gsup_req {
struct osmo_gsup_server *server;
/* Identify this request by number, for logging. */
unsigned int nr;
/* 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;
/* Decoded msg. */
int decode_rc;
const struct osmo_gsup_message gsup;
/* The ultimate source of this message: the source_name form the GSUP message, or, if not present, then the
* immediate GSUP peer. GSUP messages going via a proxy reflect the initial source in the source_name.
* This source_name is implicitly added to the routes for the conn the message was received on. */
struct global_title source_name;
/* If the source_name is not an immediate GSUP peer, this is set to the closest intermediate peer between here
* and source_name. */
struct global_title via_proxy;
};
struct osmo_gsup_req *osmo_gsup_req_new(struct osmo_gsup_conn *conn, struct msgb *msg);
#define osmo_gsup_req_free(REQ) do { \
LOG_GSUP_REQ(REQ, LOGL_DEBUG, "free\n"); \
_osmo_gsup_req_free(REQ); \
} while(0)
void _osmo_gsup_req_free(struct osmo_gsup_req *req);
int osmo_gsup_req_respond(struct osmo_gsup_req *req, struct osmo_gsup_message *response);
int osmo_gsup_req_respond_nonfinal(struct osmo_gsup_req *req, struct osmo_gsup_message *response);
int osmo_gsup_req_respond_msgt(struct osmo_gsup_req *req, enum osmo_gsup_message_type message_type);
#define osmo_gsup_req_respond_err(REQ, CAUSE, FMT, args...) do { \
LOG_GSUP_REQ(REQ, LOGL_ERROR, "%s: " FMT "\n", \
get_value_string(gsm48_gmm_cause_names, CAUSE), ##args); \
_osmo_gsup_req_respond_err(REQ, CAUSE); \
} while(0)
void _osmo_gsup_req_respond_err(struct osmo_gsup_req *req, enum gsm48_gmm_cause cause);
struct msgb *osmo_gsup_msgb_alloc(const char *label);
int osmo_gsup_conn_send(struct osmo_gsup_conn *conn, struct msgb *msg);
int osmo_gsup_conn_enc_send(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup);
int osmo_gsup_conn_ccm_get(const struct osmo_gsup_conn *clnt, uint8_t **addr,
uint8_t tag);
@@ -56,7 +128,6 @@ struct osmo_gsup_server *osmo_gsup_server_create(void *ctx,
const char *ip_addr,
uint16_t tcp_port,
osmo_gsup_read_cb_t read_cb,
struct llist_head *lu_op_lst,
void *priv);
void osmo_gsup_server_destroy(struct osmo_gsup_server *gsups);

View File

@@ -97,7 +97,14 @@ static void connect_timer_cb(void *gsupc_)
if (gsupc->is_connected)
return;
if (gsupc->up_down_cb) {
/* When the up_down_cb() returns false, the user asks us not to retry connecting. */
if (!gsupc->up_down_cb(gsupc, false))
return;
}
gsup_client_connect(gsupc);
}
static void client_send(struct osmo_gsup_client *gsupc, int proto_ext,
@@ -139,9 +146,18 @@ static void gsup_client_updown_cb(struct ipa_client_conn *link, int up)
gsup_client_oap_register(gsupc);
osmo_timer_del(&gsupc->connect_timer);
if (gsupc->up_down_cb)
gsupc->up_down_cb(gsupc, true);
} else {
osmo_timer_del(&gsupc->ping_timer);
if (gsupc->up_down_cb) {
/* When the up_down_cb() returns false, the user asks us not to retry connecting. */
if (!gsupc->up_down_cb(gsupc, false))
return;
}
osmo_timer_schedule(&gsupc->connect_timer,
OSMO_GSUP_CLIENT_RECONNECT_INTERVAL, 0);
}
@@ -258,6 +274,57 @@ static void start_test_procedure(struct osmo_gsup_client *gsupc)
gsup_client_send_ping(gsupc);
}
struct osmo_gsup_client *osmo_gsup_client_create3(void *talloc_ctx,
struct ipaccess_unit *ipa_dev,
const char *ip_addr,
unsigned int tcp_port,
struct osmo_oap_client_config *oapc_config,
osmo_gsup_client_read_cb_t read_cb,
osmo_gsup_client_up_down_cb_t up_down_cb,
void *data)
{
struct osmo_gsup_client *gsupc;
int rc;
OSMO_ASSERT(ipa_dev->unit_name);
gsupc = talloc_zero(talloc_ctx, struct osmo_gsup_client);
OSMO_ASSERT(gsupc);
*gsupc = (struct osmo_gsup_client){
.unit_name = (const char *)ipa_dev->unit_name, /* API backwards compat */
.ipa_dev = ipa_dev,
.read_cb = read_cb,
.up_down_cb = up_down_cb,
.data = data,
};
/* a NULL oapc_config will mark oap_state disabled. */
rc = osmo_oap_client_init(oapc_config, &gsupc->oap_state);
if (rc != 0)
goto failed;
gsupc->link = ipa_client_conn_create(gsupc,
/* no e1inp */ NULL,
0,
ip_addr, tcp_port,
gsup_client_updown_cb,
gsup_client_read_cb,
/* default write_cb */ NULL,
gsupc);
if (!gsupc->link)
goto failed;
osmo_timer_setup(&gsupc->connect_timer, connect_timer_cb, gsupc);
rc = gsup_client_connect(gsupc);
if (rc < 0)
goto failed;
return gsupc;
failed:
osmo_gsup_client_destroy(gsupc);
return NULL;
}
/*!
* Create a gsup client connecting to the specified IP address and TCP port.
* Use the provided ipaccess unit as the client-side identifier; ipa_dev should
@@ -278,44 +345,8 @@ struct osmo_gsup_client *osmo_gsup_client_create2(void *talloc_ctx,
osmo_gsup_client_read_cb_t read_cb,
struct osmo_oap_client_config *oapc_config)
{
struct osmo_gsup_client *gsupc;
int rc;
gsupc = talloc_zero(talloc_ctx, struct osmo_gsup_client);
OSMO_ASSERT(gsupc);
gsupc->unit_name = (const char *)ipa_dev->unit_name; /* API backwards compat */
gsupc->ipa_dev = ipa_dev;
/* a NULL oapc_config will mark oap_state disabled. */
rc = osmo_oap_client_init(oapc_config, &gsupc->oap_state);
if (rc != 0)
goto failed;
gsupc->link = ipa_client_conn_create(gsupc,
/* no e1inp */ NULL,
0,
ip_addr, tcp_port,
gsup_client_updown_cb,
gsup_client_read_cb,
/* default write_cb */ NULL,
gsupc);
if (!gsupc->link)
goto failed;
osmo_timer_setup(&gsupc->connect_timer, connect_timer_cb, gsupc);
rc = gsup_client_connect(gsupc);
if (rc < 0)
goto failed;
gsupc->read_cb = read_cb;
return gsupc;
failed:
osmo_gsup_client_destroy(gsupc);
return NULL;
return osmo_gsup_client_create3(talloc_ctx, ipa_dev, ip_addr, tcp_port, oapc_config,
read_cb, NULL, NULL);
}
/**

512
src/hlr.c
View File

@@ -36,6 +36,8 @@
#include <osmocom/gsm/gsm48_ie.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/gsm/protocol/gsm_23_003.h>
#include <osmocom/gsm/gsm23003.h>
#include <osmocom/mslookup/mslookup_client.h>
#include "db.h"
#include "hlr.h"
@@ -44,14 +46,25 @@
#include "gsup_server.h"
#include "gsup_router.h"
#include "rand.h"
#include "luop.h"
#include "hlr_vty.h"
#include "hlr_ussd.h"
#include "dgsm.h"
#include "proxy.h"
#include "global_title.h"
#include "lu_fsm.h"
struct hlr *g_hlr;
static void *hlr_ctx = NULL;
static int quit = 0;
#define RAN_TDEFS \
struct osmo_tdef g_hlr_tdefs[] = {
/* 4222 is also the OSMO_GSUP_PORT */
{ .T = -4222, .default_val = 30, .desc = "GSUP Update Location timeout" },
{}
};
/* Trigger 'Insert Subscriber Data' messages to all connected GSUP clients.
*
* \param[in] subscr A subscriber we have new data to send for.
@@ -69,6 +82,8 @@ osmo_hlr_subscriber_update_notify(struct hlr_subscriber *subscr)
return;
}
/* FIXME: send only to current vlr_number and sgsn_number */
llist_for_each_entry(co, &g_hlr->gs->clients, list) {
struct osmo_gsup_message gsup = { };
uint8_t msisdn_enc[OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN];
@@ -128,7 +143,7 @@ osmo_hlr_subscriber_update_notify(struct hlr_subscriber *subscr)
}
/* Send ISD to MSC/SGSN */
msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP ISD UPDATE");
msg_out = osmo_gsup_msgb_alloc("GSUP ISD UPDATE");
if (msg_out == NULL) {
LOGP(DLGSUP, LOGL_ERROR,
"IMSI='%s': Cannot notify GSUP client; could not allocate msg buffer "
@@ -222,136 +237,92 @@ static int subscr_create_on_demand(const char *imsi)
return 0;
}
/*! Update nam_cs/nam_ps in the db and trigger notifications to GSUP clients.
* \param[in,out] hlr Global hlr context.
* \param[in] subscr Subscriber from a fresh db_subscr_get_by_*() call.
* \param[in] nam_val True to enable CS/PS, false to disable.
* \param[in] is_ps True to enable/disable PS, false for CS.
* \returns 0 on success, ENOEXEC if there is no need to change, a negative
* value on error.
*/
int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps)
{
int rc;
bool is_val = is_ps? subscr->nam_ps : subscr->nam_cs;
struct global_title vlr_name;
struct osmo_gsup_message gsup_del_data = {
.message_type = OSMO_GSUP_MSGT_DELETE_DATA_REQUEST,
};
OSMO_STRLCPY_ARRAY(gsup_del_data.imsi, subscr->imsi);
if (is_val == nam_val) {
LOGP(DAUC, LOGL_DEBUG, "IMSI-%s: Already has the requested value when asked to %s %s\n",
subscr->imsi, nam_val ? "enable" : "disable", is_ps ? "PS" : "CS");
return ENOEXEC;
}
rc = db_subscr_nam(hlr->dbc, subscr->imsi, nam_val, is_ps);
if (rc)
return rc > 0? -rc : rc;
/* If we're disabling, send a notice out to the GSUP client that is
* responsible. Otherwise no need. */
if (nam_val)
return 0;
if (subscr->vlr_number && global_title_set_str(&vlr_name, subscr->vlr_number))
osmo_gsup_gt_enc_send(g_hlr->gs, &vlr_name, &gsup_del_data);
if (subscr->sgsn_number && global_title_set_str(&vlr_name, subscr->sgsn_number))
osmo_gsup_gt_enc_send(g_hlr->gs, &vlr_name, &gsup_del_data);
return 0;
}
/***********************************************************************
* Send Auth Info handling
***********************************************************************/
/* process an incoming SAI request */
static int rx_send_auth_info(struct osmo_gsup_conn *conn,
const struct osmo_gsup_message *gsup,
struct db_context *dbc)
static int rx_send_auth_info(unsigned int auc_3g_ind, struct osmo_gsup_req *req)
{
struct osmo_gsup_message gsup_out;
struct msgb *msg_out;
struct osmo_gsup_message gsup_out = {
.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT,
};
int rc;
subscr_create_on_demand(gsup->imsi);
subscr_create_on_demand(req->gsup.imsi);
/* initialize return message structure */
memset(&gsup_out, 0, sizeof(gsup_out));
memcpy(&gsup_out.imsi, &gsup->imsi, sizeof(gsup_out.imsi));
rc = db_get_auc(dbc, gsup->imsi, conn->auc_3g_ind,
rc = db_get_auc(g_hlr->dbc, req->gsup.imsi, auc_3g_ind,
gsup_out.auth_vectors,
ARRAY_SIZE(gsup_out.auth_vectors),
gsup->rand, gsup->auts);
req->gsup.rand, req->gsup.auts);
if (rc <= 0) {
gsup_out.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR;
switch (rc) {
case 0:
/* 0 means "0 tuples generated", which shouldn't happen.
* Treat the same as "no auth data". */
case -ENOKEY:
LOGP(DAUC, LOGL_NOTICE, "%s: IMSI known, but has no auth data;"
" Returning slightly inaccurate cause 'IMSI Unknown' via GSUP\n",
gsup->imsi);
gsup_out.cause = GMM_CAUSE_IMSI_UNKNOWN;
break;
osmo_gsup_req_respond_err(req, GMM_CAUSE_IMSI_UNKNOWN,
"IMSI known, but has no auth data;"
" Returning slightly inaccurate cause 'IMSI Unknown' via GSUP");
return rc;
case -ENOENT:
LOGP(DAUC, LOGL_NOTICE, "%s: IMSI not known\n", gsup->imsi);
gsup_out.cause = GMM_CAUSE_IMSI_UNKNOWN;
break;
osmo_gsup_req_respond_err(req, GMM_CAUSE_IMSI_UNKNOWN, "IMSI unknown");
return rc;
default:
LOGP(DAUC, LOGL_ERROR, "%s: failure to look up IMSI in db\n", gsup->imsi);
gsup_out.cause = GMM_CAUSE_NET_FAIL;
break;
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "failure to look up IMSI in db");
return rc;
}
} else {
gsup_out.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT;
gsup_out.num_auth_vectors = rc;
}
msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP AUC response");
osmo_gsup_encode(msg_out, &gsup_out);
return osmo_gsup_conn_send(conn, msg_out);
gsup_out.num_auth_vectors = rc;
osmo_gsup_req_respond(req, &gsup_out);
return 0;
}
/***********************************************************************
* LU Operation State / Structure
***********************************************************************/
static LLIST_HEAD(g_lu_ops);
/*! Receive Cancel Location Result from old VLR/SGSN */
void lu_op_rx_cancel_old_ack(struct lu_operation *luop,
const struct osmo_gsup_message *gsup)
/*! Receive Update Location Request, creates new lu_operation */
static int rx_upd_loc_req(struct osmo_gsup_conn *conn, struct osmo_gsup_req *req)
{
OSMO_ASSERT(luop->state == LU_S_CANCEL_SENT);
/* FIXME: Check for spoofing */
osmo_timer_del(&luop->timer);
/* FIXME */
lu_op_tx_insert_subscr_data(luop);
}
/*! Receive Insert Subscriber Data Result from new VLR/SGSN */
static void lu_op_rx_insert_subscr_data_ack(struct lu_operation *luop,
const struct osmo_gsup_message *gsup)
{
OSMO_ASSERT(luop->state == LU_S_ISD_SENT);
/* FIXME: Check for spoofing */
osmo_timer_del(&luop->timer);
/* Subscriber_Present_HLR */
/* CS only: Check_SS_required? -> MAP-FW-CHECK_SS_IND.req */
/* Send final ACK towards inquiring VLR/SGSN */
lu_op_tx_ack(luop);
}
/*! Receive GSUP message for given \ref lu_operation */
void lu_op_rx_gsup(struct lu_operation *luop,
const struct osmo_gsup_message *gsup)
{
switch (gsup->message_type) {
case OSMO_GSUP_MSGT_INSERT_DATA_ERROR:
/* FIXME */
break;
case OSMO_GSUP_MSGT_INSERT_DATA_RESULT:
lu_op_rx_insert_subscr_data_ack(luop, gsup);
break;
case OSMO_GSUP_MSGT_LOCATION_CANCEL_ERROR:
/* FIXME */
break;
case OSMO_GSUP_MSGT_LOCATION_CANCEL_RESULT:
lu_op_rx_cancel_old_ack(luop, gsup);
break;
default:
LOGP(DMAIN, LOGL_ERROR, "Unhandled GSUP msg_type 0x%02x\n",
gsup->message_type);
break;
}
}
/*! Receive Update Location Request, creates new \ref lu_operation */
static int rx_upd_loc_req(struct osmo_gsup_conn *conn,
const struct osmo_gsup_message *gsup)
{
struct hlr_subscriber *subscr;
struct lu_operation *luop = lu_op_alloc_conn(conn);
if (!luop) {
LOGP(DMAIN, LOGL_ERROR, "LU REQ from conn without addr?\n");
return -EINVAL;
}
subscr = &luop->subscr;
lu_op_statechg(luop, LU_S_LU_RECEIVED);
switch (gsup->cn_domain) {
switch (req->gsup.cn_domain) {
case OSMO_GSUP_CN_DOMAIN_CS:
conn->supports_cs = true;
break;
@@ -362,144 +333,64 @@ static int rx_upd_loc_req(struct osmo_gsup_conn *conn,
* a request, the PS Domain is assumed." */
case OSMO_GSUP_CN_DOMAIN_PS:
conn->supports_ps = true;
luop->is_ps = true;
break;
}
llist_add(&luop->list, &g_lu_ops);
subscr_create_on_demand(gsup->imsi);
/* Roughly follwing "Process Update_Location_HLR" of TS 09.02 */
/* check if subscriber is known at all */
if (!lu_op_fill_subscr(luop, g_hlr->dbc, gsup->imsi)) {
/* Send Error back: Subscriber Unknown in HLR */
osmo_strlcpy(luop->subscr.imsi, gsup->imsi, sizeof(luop->subscr.imsi));
lu_op_tx_error(luop, GMM_CAUSE_IMSI_UNKNOWN);
return 0;
}
/* Check if subscriber is generally permitted on CS or PS
* service (as requested) */
if (!luop->is_ps && !luop->subscr.nam_cs) {
lu_op_tx_error(luop, GMM_CAUSE_PLMN_NOTALLOWED);
return 0;
} else if (luop->is_ps && !luop->subscr.nam_ps) {
lu_op_tx_error(luop, GMM_CAUSE_GPRS_NOTALLOWED);
return 0;
}
/* TODO: Set subscriber tracing = deactive in VLR/SGSN */
#if 0
/* Cancel in old VLR/SGSN, if new VLR/SGSN differs from old */
if (luop->is_ps == false &&
strcmp(subscr->vlr_number, vlr_number)) {
lu_op_tx_cancel_old(luop);
} else if (luop->is_ps == true &&
strcmp(subscr->sgsn_number, sgsn_number)) {
lu_op_tx_cancel_old(luop);
} else
#endif
/* Store the VLR / SGSN number with the subscriber, so we know where it was last seen. */
LOGP(DAUC, LOGL_DEBUG, "IMSI='%s': storing %s = %s\n",
subscr->imsi, luop->is_ps ? "SGSN number" : "VLR number",
osmo_quote_str((const char*)luop->peer, -1));
if (db_subscr_lu(g_hlr->dbc, subscr->id, (const char *)luop->peer, luop->is_ps))
LOGP(DAUC, LOGL_ERROR, "IMSI='%s': Cannot update %s in the database\n",
subscr->imsi, luop->is_ps ? "SGSN number" : "VLR number");
/* TODO: Subscriber allowed to roam in PLMN? */
/* TODO: Update RoutingInfo */
/* TODO: Reset Flag MS Purged (cs/ps) */
/* TODO: Control_Tracing_HLR / Control_Tracing_HLR_with_SGSN */
lu_op_tx_insert_subscr_data(luop);
subscr_create_on_demand(req->gsup.imsi);
lu_rx_gsup(req);
return 0;
}
static int rx_purge_ms_req(struct osmo_gsup_conn *conn,
const struct osmo_gsup_message *gsup)
static int rx_purge_ms_req(struct osmo_gsup_req *req)
{
struct osmo_gsup_message gsup_reply = {0};
struct msgb *msg_out;
bool is_ps = false;
bool is_ps = (req->gsup.cn_domain != OSMO_GSUP_CN_DOMAIN_CS);
int rc;
LOGP(DAUC, LOGL_INFO, "%s: Purge MS (%s)\n", gsup->imsi,
is_ps ? "PS" : "CS");
memcpy(gsup_reply.imsi, gsup->imsi, sizeof(gsup_reply.imsi));
if (gsup->cn_domain == OSMO_GSUP_CN_DOMAIN_PS)
is_ps = true;
LOG_GSUP_REQ_CAT(req, DAUC, LOGL_INFO, "Purge MS (%s)\n", is_ps ? "PS" : "CS");
/* FIXME: check if the VLR that sends the purge is the same that
* we have on record. Only update if yes */
/* Perform the actual update of the DB */
rc = db_subscr_purge(g_hlr->dbc, gsup->imsi, true, is_ps);
rc = db_subscr_purge(g_hlr->dbc, req->gsup.imsi, true, is_ps);
if (rc == 0)
gsup_reply.message_type = OSMO_GSUP_MSGT_PURGE_MS_RESULT;
else if (rc == -ENOENT) {
gsup_reply.message_type = OSMO_GSUP_MSGT_PURGE_MS_ERROR;
gsup_reply.cause = GMM_CAUSE_IMSI_UNKNOWN;
} else {
gsup_reply.message_type = OSMO_GSUP_MSGT_PURGE_MS_ERROR;
gsup_reply.cause = GMM_CAUSE_NET_FAIL;
}
msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP AUC response");
osmo_gsup_encode(msg_out, &gsup_reply);
return osmo_gsup_conn_send(conn, msg_out);
osmo_gsup_req_respond_msgt(req, OSMO_GSUP_MSGT_PURGE_MS_RESULT);
else if (rc == -ENOENT)
osmo_gsup_req_respond_err(req, GMM_CAUSE_IMSI_UNKNOWN, "IMSI unknown");
else
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "db error");
return rc;
}
static int gsup_send_err_reply(struct osmo_gsup_conn *conn, const char *imsi,
enum osmo_gsup_message_type type_in, uint8_t err_cause)
static int rx_check_imei_req(struct osmo_gsup_req *req)
{
int type_err = OSMO_GSUP_TO_MSGT_ERROR(type_in);
struct osmo_gsup_message gsup_reply = {0};
struct msgb *msg_out;
OSMO_STRLCPY_ARRAY(gsup_reply.imsi, imsi);
gsup_reply.message_type = type_err;
gsup_reply.cause = err_cause;
msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP ERR response");
OSMO_ASSERT(msg_out);
osmo_gsup_encode(msg_out, &gsup_reply);
LOGP(DMAIN, LOGL_NOTICE, "Tx %s\n", osmo_gsup_message_type_name(type_err));
return osmo_gsup_conn_send(conn, msg_out);
}
static int rx_check_imei_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup)
{
struct osmo_gsup_message gsup_reply = {0};
struct msgb *msg_out;
struct osmo_gsup_message gsup_reply;
char imei[GSM23003_IMEI_NUM_DIGITS_NO_CHK+1] = {0};
const struct osmo_gsup_message *gsup = &req->gsup;
int rc;
/* Require IMEI */
if (!gsup->imei_enc) {
LOGP(DMAIN, LOGL_ERROR, "%s: missing IMEI\n", gsup->imsi);
gsup_send_err_reply(conn, gsup->imsi, gsup->message_type, GMM_CAUSE_INV_MAND_INFO);
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO, "missing IMEI");
return -1;
}
/* Decode IMEI (fails if IMEI is too long) */
rc = gsm48_decode_bcd_number2(imei, sizeof(imei), gsup->imei_enc, gsup->imei_enc_len, 0);
if (rc < 0) {
LOGP(DMAIN, LOGL_ERROR, "%s: failed to decode IMEI (rc: %i)\n", gsup->imsi, rc);
gsup_send_err_reply(conn, gsup->imsi, gsup->message_type, GMM_CAUSE_INV_MAND_INFO);
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO,
"failed to decode IMEI %s (rc: %d)",
osmo_hexdump_c(OTC_SELECT, gsup->imei_enc, gsup->imei_enc_len),
rc);
return -1;
}
/* Check if IMEI is too short */
if (strlen(imei) != GSM23003_IMEI_NUM_DIGITS_NO_CHK) {
LOGP(DMAIN, LOGL_ERROR, "%s: wrong encoded IMEI length (IMEI: '%s', %lu, %i)\n", gsup->imsi, imei,
strlen(imei), GSM23003_IMEI_NUM_DIGITS_NO_CHK);
gsup_send_err_reply(conn, gsup->imsi, gsup->message_type, GMM_CAUSE_INV_MAND_INFO);
if (!osmo_imei_str_valid(imei, false)) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO,
"invalid IMEI: %s", osmo_quote_str_c(OTC_SELECT, imei, -1));
return -1;
}
@@ -509,7 +400,7 @@ static int rx_check_imei_req(struct osmo_gsup_conn *conn, const struct osmo_gsup
if (g_hlr->store_imei) {
LOGP(DAUC, LOGL_DEBUG, "IMSI='%s': storing IMEI = %s\n", gsup->imsi, imei);
if (db_subscr_update_imei_by_imsi(g_hlr->dbc, gsup->imsi, imei) < 0) {
gsup_send_err_reply(conn, gsup->imsi, gsup->message_type, GMM_CAUSE_INV_MAND_INFO);
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO, "Failed to store IMEI in HLR db");
return -1;
}
} else {
@@ -517,176 +408,146 @@ static int rx_check_imei_req(struct osmo_gsup_conn *conn, const struct osmo_gsup
LOGP(DMAIN, LOGL_INFO, "IMSI='%s': has IMEI = %s (consider setting 'store-imei')\n", gsup->imsi, imei);
struct hlr_subscriber subscr;
if (db_subscr_get_by_imsi(g_hlr->dbc, gsup->imsi, &subscr) < 0) {
gsup_send_err_reply(conn, gsup->imsi, gsup->message_type, GMM_CAUSE_INV_MAND_INFO);
osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO, "IMSI unknown");
return -1;
}
}
/* Accept all IMEIs */
gsup_reply.imei_result = OSMO_GSUP_IMEI_RESULT_ACK;
gsup_reply.message_type = OSMO_GSUP_MSGT_CHECK_IMEI_RESULT;
msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP Check_IMEI response");
memcpy(gsup_reply.imsi, gsup->imsi, sizeof(gsup_reply.imsi));
osmo_gsup_encode(msg_out, &gsup_reply);
return osmo_gsup_conn_send(conn, msg_out);
gsup_reply = (struct osmo_gsup_message){
.message_type = OSMO_GSUP_MSGT_CHECK_IMEI_RESULT,
.imei_result = OSMO_GSUP_IMEI_RESULT_ACK,
};
return osmo_gsup_req_respond(req, &gsup_reply);
}
static char namebuf[255];
#define LOGP_GSUP_FWD(gsup, level, fmt, args ...) \
LOGP(DMAIN, level, "Forward %s (class=%s, IMSI=%s, %s->%s): " fmt, \
osmo_gsup_message_type_name(gsup->message_type), \
osmo_gsup_message_class_name(gsup->message_class), \
gsup->imsi, \
osmo_quote_str((const char *)gsup->source_name, gsup->source_name_len), \
osmo_quote_str_buf2(namebuf, sizeof(namebuf), (const char *)gsup->destination_name, gsup->destination_name_len), \
osmo_gsup_message_type_name((gsup)->message_type), \
osmo_gsup_message_class_name((gsup)->message_class), \
(gsup)->imsi, \
osmo_quote_str((const char *)(gsup)->source_name, (gsup)->source_name_len), \
osmo_quote_str_buf2(namebuf, sizeof(namebuf), (const char *)(gsup)->destination_name, (gsup)->destination_name_len), \
## args)
static int read_cb_forward(struct osmo_gsup_conn *conn, struct msgb *msg, const struct osmo_gsup_message *gsup)
static int read_cb_forward(struct osmo_gsup_req *req)
{
int ret = -EINVAL;
struct osmo_gsup_message *gsup_err;
/* FIXME: it would be better if the msgb never were deallocated immediately by osmo_gsup_addr_send(), which a
* select-loop volatile talloc context could facilitate. Then we would still be able to access gsup-> members
* (pointing into the msgb) even after sending failed, and we wouldn't need to copy this data before sending: */
/* Prepare error message (before IEs get deallocated) */
gsup_err = talloc_zero(hlr_ctx, struct osmo_gsup_message);
OSMO_STRLCPY_ARRAY(gsup_err->imsi, gsup->imsi);
gsup_err->message_class = gsup->message_class;
gsup_err->destination_name = talloc_memdup(gsup_err, gsup->destination_name, gsup->destination_name_len);
gsup_err->destination_name_len = gsup->destination_name_len;
gsup_err->message_type = gsup->message_type;
gsup_err->session_state = gsup->session_state;
gsup_err->session_id = gsup->session_id;
gsup_err->source_name = talloc_memdup(gsup_err, gsup->source_name, gsup->source_name_len);
gsup_err->source_name_len = gsup->source_name_len;
const struct osmo_gsup_message *gsup = &req->gsup;
struct osmo_gsup_message gsup_err;
struct msgb *forward_msg;
struct global_title destination_name;
/* Check for routing IEs */
if (!gsup->source_name || !gsup->source_name_len || !gsup->destination_name || !gsup->destination_name_len) {
LOGP_GSUP_FWD(gsup, LOGL_ERROR, "missing routing IEs\n");
goto end;
if (!req->gsup.source_name[0] || !req->gsup.source_name_len
|| !req->gsup.destination_name[0] || !req->gsup.destination_name_len) {
LOGP_GSUP_FWD(&req->gsup, LOGL_ERROR, "missing routing IEs\n");
goto routing_error;
}
/* Verify source name (e.g. "MSC-00-00-00-00-00-00") */
if (gsup_route_find(conn->server, gsup->source_name, gsup->source_name_len) != conn) {
LOGP_GSUP_FWD(gsup, LOGL_ERROR, "mismatching source name\n");
goto end;
if (global_title_set(&destination_name, req->gsup.destination_name, req->gsup.destination_name_len)) {
LOGP_GSUP_FWD(&req->gsup, LOGL_ERROR, "invalid destination name\n");
goto routing_error;
}
/* Forward message without re-encoding (so we don't remove unknown IEs) */
LOGP_GSUP_FWD(gsup, LOGL_INFO, "checks passed, forwarding\n");
LOG_GSUP_REQ(req, LOGL_INFO, "Forwarding to %s\n", global_title_name(&destination_name));
/* Remove incoming IPA header to be able to prepend an outgoing IPA header */
msgb_pull_to_l2(msg);
ret = osmo_gsup_addr_send(g_hlr->gs, gsup->destination_name, gsup->destination_name_len, msg);
/* AT THIS POINT, THE msg MAY BE DEALLOCATED and the data like gsup->imsi, gsup->source_name etc may all be
* invalid and cause segfaults. */
msg = NULL;
gsup = NULL;
if (ret == -ENODEV)
LOGP_GSUP_FWD(gsup_err, LOGL_ERROR, "destination not connected\n");
else if (ret)
LOGP_GSUP_FWD(gsup_err, LOGL_ERROR, "unknown error %i\n", ret);
end:
/* Send error back to source */
/* Forward message without re-encoding (so we don't remove unknown IEs).
* Copy GSUP part to forward, removing incoming IPA header to be able to prepend an outgoing IPA header */
forward_msg = osmo_gsup_msgb_alloc("GSUP forward");
forward_msg->l2h = msgb_put(forward_msg, msgb_l2len(req->msg));
memcpy(forward_msg->l2h, msgb_l2(req->msg), msgb_l2len(req->msg));
ret = osmo_gsup_gt_send(g_hlr->gs, &destination_name, forward_msg);
if (ret) {
struct msgb *msg_err = msgb_alloc_headroom(1024+16, 16, "GSUP forward ERR response");
OSMO_ASSERT(msg_err);
gsup_err->message_type = OSMO_GSUP_MSGT_E_ROUTING_ERROR;
osmo_gsup_encode(msg_err, gsup_err);
LOGP_GSUP_FWD(gsup_err, LOGL_NOTICE, "Tx %s\n", osmo_gsup_message_type_name(gsup_err->message_type));
osmo_gsup_conn_send(conn, msg_err);
LOGP_GSUP_FWD(gsup, LOGL_ERROR, "%s (rc=%d)\n",
ret == -ENODEV ? "destination not connected" : "unknown error",
ret);
goto routing_error;
}
talloc_free(gsup_err);
if (msg)
msgb_free(msg);
return ret;
osmo_gsup_req_free(req);
return 0;
routing_error:
gsup_err = (struct osmo_gsup_message){
.message_type = OSMO_GSUP_MSGT_E_ROUTING_ERROR,
.destination_name = gsup->destination_name,
.destination_name_len = gsup->destination_name_len,
.source_name = gsup->source_name,
.source_name_len = gsup->source_name_len,
};
osmo_gsup_req_respond(req, &gsup_err);
return -1;
}
static int read_cb(struct osmo_gsup_conn *conn, struct msgb *msg)
{
static struct osmo_gsup_message gsup;
int rc;
struct osmo_gsup_req *req = osmo_gsup_req_new(conn, msg);
if (!msgb_l2(msg) || !msgb_l2len(msg)) {
LOGP(DMAIN, LOGL_ERROR, "missing or empty L2 data\n");
msgb_free(msg);
if (!req)
return -EINVAL;
/* If the GSUP recipient is other than this HLR, forward. */
if (req->gsup.destination_name_len) {
struct global_title destination_name;
struct global_title my_name;
global_title_set_str(&my_name, g_hlr->gsup_unit_name.serno);
if (!global_title_set(&destination_name, req->gsup.destination_name, req->gsup.destination_name_len)
&& global_title_cmp(&destination_name, &my_name)) {
return read_cb_forward(req);
}
}
rc = osmo_gsup_decode(msgb_l2(msg), msgb_l2len(msg), &gsup);
if (rc < 0) {
LOGP(DMAIN, LOGL_ERROR, "error in GSUP decode: %d\n", rc);
msgb_free(msg);
return rc;
/* Distributed GSM: check whether to proxy for / lookup a remote HLR.
* It would require less database hits to do this only if a local-only operation fails with "unknown IMSI", but
* it becomes semantically easier if we do this once-off ahead of time. */
if (osmo_mslookup_client_active(g_hlr->mslookup.client.client)) {
if (dgsm_check_forward_gsup_msg(req))
return 0;
}
/* 3GPP TS 23.003 Section 2.2 clearly states that an IMSI with less than 5
* digits is impossible. Even 5 digits is a highly theoretical case */
if (strlen(gsup.imsi) < 5) { /* TODO: move this check to libosmogsm/gsup.c? */
LOGP(DMAIN, LOGL_ERROR, "IMSI too short: %s\n", osmo_quote_str(gsup.imsi, -1));
gsup_send_err_reply(conn, gsup.imsi, gsup.message_type, GMM_CAUSE_INV_MAND_INFO);
msgb_free(msg);
return -EINVAL;
}
if (gsup.destination_name_len)
return read_cb_forward(conn, msg, &gsup);
switch (gsup.message_type) {
switch (req->gsup.message_type) {
/* requests sent to us */
case OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST:
rx_send_auth_info(conn, &gsup, g_hlr->dbc);
rx_send_auth_info(conn->auc_3g_ind, req);
break;
case OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST:
rx_upd_loc_req(conn, &gsup);
rx_upd_loc_req(conn, req);
break;
case OSMO_GSUP_MSGT_PURGE_MS_REQUEST:
rx_purge_ms_req(conn, &gsup);
rx_purge_ms_req(req);
break;
/* responses to requests sent by us */
case OSMO_GSUP_MSGT_DELETE_DATA_ERROR:
LOGP(DMAIN, LOGL_ERROR, "Error while deleting subscriber data "
"for IMSI %s\n", gsup.imsi);
LOG_GSUP_REQ(req, LOGL_ERROR, "Peer responds with: Error while deleting subscriber data\n");
osmo_gsup_req_free(req);
break;
case OSMO_GSUP_MSGT_DELETE_DATA_RESULT:
LOGP(DMAIN, LOGL_ERROR, "Deleting subscriber data for IMSI %s\n",
gsup.imsi);
LOG_GSUP_REQ(req, LOGL_DEBUG, "Peer responds with: Subscriber data deleted\n");
osmo_gsup_req_free(req);
break;
case OSMO_GSUP_MSGT_PROC_SS_REQUEST:
case OSMO_GSUP_MSGT_PROC_SS_RESULT:
rx_proc_ss_req(conn, &gsup);
rx_proc_ss_req(req);
break;
case OSMO_GSUP_MSGT_PROC_SS_ERROR:
rx_proc_ss_error(conn, &gsup);
rx_proc_ss_error(req);
break;
case OSMO_GSUP_MSGT_INSERT_DATA_ERROR:
case OSMO_GSUP_MSGT_INSERT_DATA_RESULT:
case OSMO_GSUP_MSGT_LOCATION_CANCEL_ERROR:
case OSMO_GSUP_MSGT_LOCATION_CANCEL_RESULT:
{
struct lu_operation *luop = lu_op_by_imsi(gsup.imsi,
&g_lu_ops);
if (!luop) {
LOGP(DMAIN, LOGL_ERROR, "GSUP message %s for "
"unknown IMSI %s\n",
osmo_gsup_message_type_name(gsup.message_type),
gsup.imsi);
break;
}
lu_op_rx_gsup(luop, &gsup);
}
lu_rx_gsup(req);
break;
case OSMO_GSUP_MSGT_CHECK_IMEI_REQUEST:
rx_check_imei_req(conn, &gsup);
rx_check_imei_req(req);
break;
default:
LOGP(DMAIN, LOGL_DEBUG, "Unhandled GSUP message type %s\n",
osmo_gsup_message_type_name(gsup.message_type));
osmo_gsup_message_type_name(req->gsup.message_type));
osmo_gsup_req_free(req);
break;
}
msgb_free(msg);
return 0;
}
@@ -842,12 +703,17 @@ int main(int argc, char **argv)
/* Init default (call independent) SS session guard timeout value */
g_hlr->ncss_guard_timeout = NCSS_GUARD_TIMEOUT_DEFAULT;
g_hlr->proxy = proxy_init(g_hlr);
rc = osmo_init_logging2(hlr_ctx, &hlr_log_info);
if (rc < 0) {
fprintf(stderr, "Error initializing logging\n");
exit(1);
}
/* Set up llists and objects, startup is happening from VTY commands. */
dgsm_init(hlr_ctx);
osmo_stats_init(hlr_ctx);
vty_init(&vty_info);
ctrl_vty_init(hlr_ctx);
@@ -897,7 +763,7 @@ int main(int argc, char **argv)
g_hlr->gs = osmo_gsup_server_create(hlr_ctx, g_hlr->gsup_bind_addr, OSMO_GSUP_PORT,
read_cb, &g_lu_ops, g_hlr);
read_cb, g_hlr);
if (!g_hlr->gs) {
LOGP(DMAIN, LOGL_FATAL, "Error starting GSUP server\n");
exit(1);
@@ -906,6 +772,8 @@ int main(int argc, char **argv)
g_hlr->ctrl_bind_addr = ctrl_vty_get_bind_addr();
g_hlr->ctrl = hlr_controlif_setup(g_hlr);
dgsm_start(hlr_ctx);
osmo_init_ignore_signals();
signal(SIGINT, &signal_hdlr);
signal(SIGTERM, &signal_hdlr);
@@ -920,7 +788,7 @@ int main(int argc, char **argv)
}
while (!quit)
osmo_select_main(0);
osmo_select_main_ctx(0);
osmo_gsup_server_destroy(g_hlr->gs);
db_close(g_hlr->dbc);

View File

@@ -24,10 +24,17 @@
#include <stdbool.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/gsm/ipa.h>
#include <osmocom/core/tdef.h>
#include "dgsm.h"
#define HLR_DEFAULT_DB_FILE_PATH "hlr.db"
struct hlr_euse;
struct osmo_gsup_conn;
enum osmo_gsup_message_type;
extern struct osmo_tdef g_hlr_tdefs[];
struct hlr {
/* GSUP server pointer */
@@ -43,6 +50,7 @@ struct hlr {
/* Local bind addr */
char *gsup_bind_addr;
struct ipaccess_unit gsup_unit_name;
struct llist_head euse_list;
struct hlr_euse *euse_default;
@@ -61,6 +69,24 @@ struct hlr {
/* Bitmask of DB_SUBSCR_FLAG_* */
uint8_t subscr_create_on_demand_flags;
unsigned int subscr_create_on_demand_rand_msisdn_len;
struct {
bool allow_startup;
struct dgsm_config vty;
struct {
struct osmo_mslookup_server_mdns *mdns;
uint32_t max_age;
} server;
struct {
unsigned int result_timeout_milliseconds;
struct osmo_mslookup_client *client;
struct osmo_mslookup_client_method *mdns;
} client;
} mslookup;
struct proxy *proxy;
};
extern struct hlr *g_hlr;
@@ -68,3 +94,4 @@ extern struct hlr *g_hlr;
struct hlr_subscriber;
void osmo_hlr_subscriber_update_notify(struct hlr_subscriber *subscr);
int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps);

View File

@@ -170,12 +170,14 @@ struct ss_session {
/* subscriber's vlr_number
* MO USSD: originating MSC's vlr_number
* MT USSD: looked up once per session and cached here */
uint8_t *vlr_number;
size_t vlr_number_len;
struct global_title vlr_name;
/* we don't keep a pointer to the osmo_gsup_{route,conn} towards the MSC/VLR here,
* as this might change during inter-VLR hand-over, and we simply look-up the serving MSC/VLR
* every time we receive an USSD component from the EUSE */
struct osmo_gsup_req *initial_req_from_ms;
struct osmo_gsup_req *initial_req_from_euse;
};
struct ss_session *ss_session_find(struct hlr *hlr, const char *imsi, uint32_t session_id)
@@ -191,6 +193,10 @@ struct ss_session *ss_session_find(struct hlr *hlr, const char *imsi, uint32_t s
void ss_session_free(struct ss_session *ss)
{
osmo_timer_del(&ss->timeout);
if (ss->initial_req_from_ms)
osmo_gsup_req_free(ss->initial_req_from_ms);
if (ss->initial_req_from_euse)
osmo_gsup_req_free(ss->initial_req_from_euse);
llist_del(&ss->list);
talloc_free(ss);
}
@@ -230,59 +236,73 @@ struct ss_session *ss_session_alloc(struct hlr *hlr, const char *imsi, uint32_t
***********************************************************************/
/* Resolve the target MSC by ss->imsi and send GSUP message. */
static int ss_gsup_send(struct ss_session *ss, struct osmo_gsup_server *gs, struct msgb *msg)
static int ss_gsup_send_to_ms(struct ss_session *ss, struct osmo_gsup_server *gs, struct osmo_gsup_message *gsup)
{
struct hlr_subscriber subscr = {};
struct msgb *msg;
int rc;
if (ss->initial_req_from_ms) {
/* If this is a response to an incoming GSUP request from the MS, respond via osmo_gsup_req_respond() to
* make sure that all required routing information is kept intact.
* Use osmo_gsup_req_respond_nonfinal() to not deallocate the ss->initial_req_from_ms */
osmo_gsup_req_respond_nonfinal(ss->initial_req_from_ms, gsup);
return 0;
}
msg = osmo_gsup_msgb_alloc("GSUP USSD FW");
rc = osmo_gsup_encode(msg, gsup);
if (rc) {
LOGPSS(ss, LOGL_ERROR, "Failed to encode GSUP message\n");
return rc;
}
/* Use vlr_number as looked up by the caller, or look up now. */
if (!ss->vlr_number) {
if (!ss->vlr_name.len) {
rc = db_subscr_get_by_imsi(g_hlr->dbc, ss->imsi, &subscr);
if (rc < 0) {
LOGPSS(ss, LOGL_ERROR, "Cannot find subscriber, cannot route GSUP message\n");
msgb_free(msg);
return -EINVAL;
}
ss->vlr_number = (uint8_t *)talloc_strdup(ss, subscr.vlr_number);
ss->vlr_number_len = strlen(subscr.vlr_number) + 1;
global_title_set_str(&ss->vlr_name, subscr.vlr_number);
}
/* Check for empty string (all vlr_number strings end in "\0", because otherwise gsup_route_find() fails) */
if (ss->vlr_number_len == 1) {
if (ss->vlr_name.len <= 1) {
LOGPSS(ss, LOGL_ERROR, "Cannot send GSUP message, no VLR number stored for subscriber\n");
msgb_free(msg);
return -EINVAL;
}
LOGPSS(ss, LOGL_DEBUG, "Tx SS/USSD to VLR %s\n", osmo_quote_str((char *)ss->vlr_number, ss->vlr_number_len));
return osmo_gsup_addr_send(gs, ss->vlr_number, ss->vlr_number_len, msg);
LOGPSS(ss, LOGL_DEBUG, "Tx SS/USSD to VLR %s\n", global_title_name(&ss->vlr_name));
return osmo_gsup_gt_send(gs, &ss->vlr_name, msg);
}
static int ss_tx_to_ms(struct ss_session *ss, enum osmo_gsup_message_type gsup_msg_type,
bool final, struct msgb *ss_msg)
bool final, struct msgb *ss_msg)
{
struct osmo_gsup_message resp = {0};
struct msgb *resp_msg;
struct osmo_gsup_message resp = {
.message_type = gsup_msg_type,
.session_id = ss->session_id,
};
int rc;
resp.message_type = gsup_msg_type;
OSMO_STRLCPY_ARRAY(resp.imsi, ss->imsi);
if (final)
resp.session_state = OSMO_GSUP_SESSION_STATE_END;
else
resp.session_state = OSMO_GSUP_SESSION_STATE_CONTINUE;
resp.session_id = ss->session_id;
if (ss_msg) {
resp.ss_info = msgb_data(ss_msg);
resp.ss_info_len = msgb_length(ss_msg);
}
resp_msg = msgb_alloc_headroom(4000, 64, __func__);
OSMO_ASSERT(resp_msg);
osmo_gsup_encode(resp_msg, &resp);
msgb_free(ss_msg);
rc = ss_gsup_send_to_ms(ss, g_hlr->gs, &resp);
return ss_gsup_send(ss, g_hlr->gs, resp_msg);
msgb_free(ss_msg);
return rc;
}
#if 0
@@ -297,7 +317,7 @@ static int ss_tx_reject(struct ss_session *ss, int invoke_id, uint8_t problem_ta
}
#endif
static int ss_tx_error(struct ss_session *ss, uint8_t invoke_id, uint8_t error_code)
static int ss_tx_to_ms_error(struct ss_session *ss, uint8_t invoke_id, uint8_t error_code)
{
struct msgb *msg = gsm0480_gen_return_error(invoke_id, error_code);
LOGPSS(ss, LOGL_NOTICE, "Tx ReturnError(%u, 0x%02x)\n", invoke_id, error_code);
@@ -305,7 +325,7 @@ static int ss_tx_error(struct ss_session *ss, uint8_t invoke_id, uint8_t error_c
return ss_tx_to_ms(ss, OSMO_GSUP_MSGT_PROC_SS_RESULT, true, msg);
}
static int ss_tx_ussd_7bit(struct ss_session *ss, bool final, uint8_t invoke_id, const char *text)
static int ss_tx_to_ms_ussd_7bit(struct ss_session *ss, bool final, uint8_t invoke_id, const char *text)
{
struct msgb *msg = gsm0480_gen_ussd_resp_7bit(invoke_id, text);
LOGPSS(ss, LOGL_INFO, "Tx USSD '%s'\n", text);
@@ -319,7 +339,7 @@ static int ss_tx_ussd_7bit(struct ss_session *ss, bool final, uint8_t invoke_id,
#include "db.h"
static int handle_ussd_own_msisdn(struct osmo_gsup_conn *conn, struct ss_session *ss,
static int handle_ussd_own_msisdn(struct ss_session *ss,
const struct osmo_gsup_message *gsup, const struct ss_request *req)
{
struct hlr_subscriber subscr;
@@ -333,25 +353,25 @@ static int handle_ussd_own_msisdn(struct osmo_gsup_conn *conn, struct ss_session
snprintf(buf, sizeof(buf), "You have no MSISDN!");
else
snprintf(buf, sizeof(buf), "Your extension is %s", subscr.msisdn);
ss_tx_ussd_7bit(ss, true, req->invoke_id, buf);
ss_tx_to_ms_ussd_7bit(ss, true, req->invoke_id, buf);
break;
case -ENOENT:
ss_tx_error(ss, req->invoke_id, GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER);
ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER);
break;
case -EIO:
default:
ss_tx_error(ss, req->invoke_id, GSM0480_ERR_CODE_SYSTEM_FAILURE);
ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_SYSTEM_FAILURE);
break;
}
return 0;
}
static int handle_ussd_own_imsi(struct osmo_gsup_conn *conn, struct ss_session *ss,
static int handle_ussd_own_imsi(struct ss_session *ss,
const struct osmo_gsup_message *gsup, const struct ss_request *req)
{
char buf[GSM0480_USSD_7BIT_STRING_LEN+1];
snprintf(buf, sizeof(buf), "Your IMSI is %s", ss->imsi);
ss_tx_ussd_7bit(ss, true, req->invoke_id, buf);
ss_tx_to_ms_ussd_7bit(ss, true, req->invoke_id, buf);
return 0;
}
@@ -398,37 +418,26 @@ static bool ss_op_is_ussd(uint8_t opcode)
}
/* is this GSUP connection an EUSE (true) or not (false)? */
static bool conn_is_euse(struct osmo_gsup_conn *conn)
static bool peer_name_is_euse(const struct global_title *peer_name)
{
int rc;
uint8_t *addr;
rc = osmo_gsup_conn_ccm_get(conn, &addr, IPAC_IDTAG_SERNR);
if (rc <= 5)
if (peer_name->len <= 5)
return false;
if (!strncmp((char *)addr, "EUSE-", 5))
if (!strncmp((char *)(peer_name->val), "EUSE-", 5))
return true;
else
return false;
}
static struct hlr_euse *euse_by_conn(struct osmo_gsup_conn *conn)
static struct hlr_euse *euse_by_name(const struct global_title *peer_name)
{
int rc;
char *addr;
struct hlr *hlr = conn->server->priv;
rc = osmo_gsup_conn_ccm_get(conn, (uint8_t **) &addr, IPAC_IDTAG_SERNR);
if (rc <= 5)
return NULL;
if (strncmp(addr, "EUSE-", 5))
if (!peer_name_is_euse(peer_name))
return NULL;
return euse_find(hlr, addr+5);
return euse_find(g_hlr, (const char*)(peer_name->val)+5);
}
static int handle_ss(struct ss_session *ss, const struct osmo_gsup_message *gsup,
const struct ss_request *req)
static int handle_ss(struct ss_session *ss, bool is_euse_originated, const struct osmo_gsup_message *gsup,
const struct ss_request *req)
{
uint8_t comp_type = gsup->ss_info[0];
@@ -441,17 +450,16 @@ static int handle_ss(struct ss_session *ss, const struct osmo_gsup_message *gsup
* we don't handle "structured" SS requests at all.
*/
LOGPSS(ss, LOGL_NOTICE, "Structured SS requests are not supported, rejecting...\n");
ss_tx_error(ss, req->invoke_id, GSM0480_ERR_CODE_FACILITY_NOT_SUPPORTED);
ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_FACILITY_NOT_SUPPORTED);
return -ENOTSUP;
}
/* Handle a USSD GSUP message for a given SS Session received from VLR or EUSE */
static int handle_ussd(struct osmo_gsup_conn *conn, struct ss_session *ss,
const struct osmo_gsup_message *gsup, const struct ss_request *req)
static int handle_ussd(struct ss_session *ss, bool is_euse_originated, const struct osmo_gsup_message *gsup,
const struct ss_request *req)
{
uint8_t comp_type = gsup->ss_info[0];
struct msgb *msg_out;
bool is_euse_originated = conn_is_euse(conn);
LOGPSS(ss, LOGL_INFO, "USSD CompType=%s, OpCode=%s '%s'\n",
gsm0480_comp_type_name(comp_type), gsm0480_op_code_name(req->opcode),
@@ -459,38 +467,39 @@ static int handle_ussd(struct osmo_gsup_conn *conn, struct ss_session *ss,
if ((ss->is_external && !ss->u.euse) || !ss->u.iuse) {
LOGPSS(ss, LOGL_NOTICE, "USSD for unknown code '%s'\n", req->ussd_text);
ss_tx_error(ss, req->invoke_id, GSM0480_ERR_CODE_SS_NOT_AVAILABLE);
ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_SS_NOT_AVAILABLE);
return 0;
}
if (is_euse_originated) {
msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP USSD FW");
OSMO_ASSERT(msg_out);
/* Received from EUSE, Forward to VLR */
osmo_gsup_encode(msg_out, gsup);
ss_gsup_send(ss, conn->server, msg_out);
/* Need a non-const osmo_gsup_message, because sending might modify some (routing related?) parts. */
struct osmo_gsup_message forward = *gsup;
ss_gsup_send_to_ms(ss, g_hlr->gs, &forward);
} else {
/* Received from VLR (MS) */
if (ss->is_external) {
/* Forward to EUSE */
char addr[128];
strcpy(addr, "EUSE-");
osmo_strlcpy(addr+5, ss->u.euse->name, sizeof(addr)-5);
conn = gsup_route_find(conn->server, (uint8_t *)addr, strlen(addr)+1);
struct global_title euse_name;
struct osmo_gsup_conn *conn;
global_title_set_str(&euse_name, "EUSE-%s", ss->u.euse->name);
conn = gsup_route_find_gt(g_hlr->gs, &euse_name);
if (!conn) {
LOGPSS(ss, LOGL_ERROR, "Cannot find conn for EUSE %s\n", addr);
ss_tx_error(ss, req->invoke_id, GSM0480_ERR_CODE_SYSTEM_FAILURE);
LOGPSS(ss, LOGL_ERROR, "Cannot find conn for EUSE %s\n",
global_title_name(&euse_name));
ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_SYSTEM_FAILURE);
} else {
msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP USSD FW");
msg_out = osmo_gsup_msgb_alloc("GSUP USSD FW");
OSMO_ASSERT(msg_out);
osmo_gsup_encode(msg_out, gsup);
osmo_gsup_conn_send(conn, msg_out);
}
} else {
/* Handle internally */
ss->u.iuse->handle_ussd(conn, ss, gsup, req);
ss->u.iuse->handle_ussd(ss, gsup, req);
/* Release session immediately */
ss_session_free(ss);
return 0;
}
}
@@ -500,12 +509,16 @@ static int handle_ussd(struct osmo_gsup_conn *conn, struct ss_session *ss,
/* this function is called for any SS_REQ/SS_RESP messages from both the MSC/VLR side as well
* as from the EUSE side */
int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup)
void rx_proc_ss_req(struct osmo_gsup_req *gsup_req)
{
struct hlr *hlr = conn->server->priv;
struct hlr *hlr = g_hlr;
struct ss_session *ss;
struct ss_request req = {0};
struct gsup_route *gsup_rt;
const struct osmo_gsup_message *gsup = &gsup_req->gsup;
/* Remember whether this function should free the incoming gsup_req: if it is placed as ss->initial_req_from_*,
* do not free it here. If not, free it here. */
struct osmo_gsup_req *free_gsup_req = gsup_req;
bool is_euse_originated = peer_name_is_euse(&gsup_req->source_name);
LOGP(DSS, LOGL_DEBUG, "%s/0x%08x: Process SS (%s)\n", gsup->imsi, gsup->session_id,
osmo_gsup_session_state_name(gsup->session_state));
@@ -516,14 +529,15 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *
LOGP(DSS, LOGL_ERROR, "%s/0x%082x: Unable to parse SS request: %s\n",
gsup->imsi, gsup->session_id,
osmo_hexdump(gsup->ss_info, gsup->ss_info_len));
/* FIXME: Send a Reject component? */
goto out_err;
osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_INV_MAND_INFO, "error parsing SS request");
return;
}
} else if (gsup->session_state != OSMO_GSUP_SESSION_STATE_END) {
LOGP(DSS, LOGL_ERROR, "%s/0x%082x: Missing SS payload for '%s'\n",
gsup->imsi, gsup->session_id,
osmo_gsup_session_state_name(gsup->session_state));
goto out_err;
osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_INV_MAND_INFO, "missing SS payload");
return;
}
switch (gsup->session_state) {
@@ -532,32 +546,29 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *
if (ss_session_find(hlr, gsup->imsi, gsup->session_id)) {
LOGP(DSS, LOGL_ERROR, "%s/0x%08x: BEGIN with non-unique session ID!\n",
gsup->imsi, gsup->session_id);
goto out_err;
osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_INV_MAND_INFO, "BEGIN with non-unique session ID");
return;
}
ss = ss_session_alloc(hlr, gsup->imsi, gsup->session_id);
if (!ss) {
LOGP(DSS, LOGL_ERROR, "%s/0x%08x: Unable to allocate SS session\n",
gsup->imsi, gsup->session_id);
goto out_err;
osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_NET_FAIL, "Unable to allocate SS session");
return;
}
/* Get IPA name from VLR conn and save as ss->vlr_number */
if (!conn_is_euse(conn)) {
gsup_rt = gsup_route_find_by_conn(conn);
if (gsup_rt) {
ss->vlr_number = (uint8_t *)talloc_strdup(ss, (const char *)gsup_rt->addr);
ss->vlr_number_len = strlen((const char *)gsup_rt->addr) + 1;
LOGPSS(ss, LOGL_DEBUG, "Destination IPA name retrieved from GSUP route: %s\n",
osmo_quote_str((const char *)ss->vlr_number, ss->vlr_number_len));
} else {
LOGPSS(ss, LOGL_NOTICE, "Could not find GSUP route, therefore can't set the destination"
" IPA name. We'll try to look it up later, but this should not"
" have happened.\n");
}
if (!is_euse_originated) {
ss->initial_req_from_ms = gsup_req;
free_gsup_req = NULL;
ss->vlr_name = gsup_req->source_name;
} else {
ss->initial_req_from_euse = gsup_req;
free_gsup_req = NULL;
}
if (ss_op_is_ussd(req.opcode)) {
if (conn_is_euse(conn)) {
if (is_euse_originated) {
/* EUSE->VLR: MT USSD. EUSE is known ('conn'), VLR is to be resolved */
ss->u.euse = euse_by_conn(conn);
ss->u.euse = euse_by_name(&gsup_req->source_name);
} else {
/* VLR->EUSE: MO USSD. VLR is known ('conn'), EUSE is to be resolved */
struct hlr_ussd_route *rt;
@@ -578,10 +589,10 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *
}
}
/* dispatch unstructured SS to routing */
handle_ussd(conn, ss, gsup, &req);
handle_ussd(ss, is_euse_originated, &gsup_req->gsup, &req);
} else {
/* dispatch non-call SS to internal code */
handle_ss(ss, gsup, &req);
handle_ss(ss, is_euse_originated, &gsup_req->gsup, &req);
}
break;
case OSMO_GSUP_SESSION_STATE_CONTINUE:
@@ -589,7 +600,8 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *
if (!ss) {
LOGP(DSS, LOGL_ERROR, "%s/0x%08x: CONTINUE for unknown SS session\n",
gsup->imsi, gsup->session_id);
goto out_err;
osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_INV_MAND_INFO, "CONTINUE for unknown SS session");
return;
}
/* Reschedule self-destruction timer */
@@ -598,10 +610,10 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *
if (ss_op_is_ussd(req.opcode)) {
/* dispatch unstructured SS to routing */
handle_ussd(conn, ss, gsup, &req);
handle_ussd(ss, is_euse_originated, &gsup_req->gsup, &req);
} else {
/* dispatch non-call SS to internal code */
handle_ss(ss, gsup, &req);
handle_ss(ss, is_euse_originated, &gsup_req->gsup, &req);
}
break;
case OSMO_GSUP_SESSION_STATE_END:
@@ -609,17 +621,17 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *
if (!ss) {
LOGP(DSS, LOGL_ERROR, "%s/0x%08x: END for unknown SS session\n",
gsup->imsi, gsup->session_id);
goto out_err;
return;
}
/* SS payload is optional for END */
if (gsup->ss_info && gsup->ss_info_len) {
if (ss_op_is_ussd(req.opcode)) {
/* dispatch unstructured SS to routing */
handle_ussd(conn, ss, gsup, &req);
handle_ussd(ss, is_euse_originated, &gsup_req->gsup, &req);
} else {
/* dispatch non-call SS to internal code */
handle_ss(ss, gsup, &req);
handle_ss(ss, is_euse_originated, &gsup_req->gsup, &req);
}
}
@@ -628,18 +640,15 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *
default:
LOGP(DSS, LOGL_ERROR, "%s/0x%08x: Unknown SS State %d\n", gsup->imsi,
gsup->session_id, gsup->session_state);
goto out_err;
break;
}
return 0;
out_err:
return 0;
if (free_gsup_req)
osmo_gsup_req_free(free_gsup_req);
}
int rx_proc_ss_error(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup)
void rx_proc_ss_error(struct osmo_gsup_req *req)
{
LOGP(DSS, LOGL_NOTICE, "%s/0x%08x: Process SS ERROR (%s)\n", gsup->imsi, gsup->session_id,
osmo_gsup_session_state_name(gsup->session_state));
return 0;
LOGP(DSS, LOGL_NOTICE, "%s/0x%08x: Process SS ERROR (%s)\n", req->gsup.imsi, req->gsup.session_id,
osmo_gsup_session_state_name(req->gsup.session_state));
}

View File

@@ -46,8 +46,8 @@ struct hlr_ussd_route *ussd_route_prefix_alloc_ext(struct hlr *hlr, const char *
struct hlr_euse *euse);
void ussd_route_del(struct hlr_ussd_route *rt);
int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup);
int rx_proc_ss_error(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup);
void rx_proc_ss_req(struct osmo_gsup_req *req);
void rx_proc_ss_error(struct osmo_gsup_req *req);
struct ss_session;
struct ss_request;
@@ -56,6 +56,5 @@ struct ss_request;
struct hlr_iuse {
const char *name;
/* call-back to be called for any incoming USSD messages for this IUSE */
int (*handle_ussd)(struct osmo_gsup_conn *conn, struct ss_session *ss,
const struct osmo_gsup_message *gsup, const struct ss_request *req);
int (*handle_ussd)(struct ss_session *ss, const struct osmo_gsup_message *gsup, const struct ss_request *req);
};

View File

@@ -39,6 +39,7 @@
#include "hlr_vty_subscr.h"
#include "hlr_ussd.h"
#include "gsup_server.h"
#include "dgsm.h"
struct cmd_node hlr_node = {
HLR_NODE,
@@ -146,6 +147,24 @@ DEFUN(cfg_hlr_gsup_bind_ip,
return CMD_SUCCESS;
}
DEFUN(cfg_hlr_gsup_ipa_name,
cfg_hlr_gsup_ipa_name_cmd,
"ipa-name NAME",
"Set the IPA name of this HLR, for proxying to remote HLRs\n"
"A globally unique name for this HLR. For example: PLMN + redundancy server number: HLR-901-70-0. "
"This name is used for GSUP routing and must be set if multiple HLRs interconnect (e.g. mslookup "
"for Distributed GSM).\n")
{
if (vty->type != VTY_FILE) {
vty_out(vty, "gsup/ipa-name: The GSUP IPA name cannot be changed at run-time; "
"It can only be set in the configuraton file.%s", VTY_NEWLINE);
return CMD_WARNING;
}
g_hlr->gsup_unit_name.serno = talloc_strdup(g_hlr, argv[0]);
return CMD_SUCCESS;
}
/***********************************************************************
* USSD Entity
***********************************************************************/
@@ -403,6 +422,17 @@ int hlr_vty_go_parent(struct vty *vty)
vty->index = NULL;
vty->index_sub = NULL;
break;
case MSLOOKUP_CLIENT_NODE:
case MSLOOKUP_SERVER_NODE:
vty->node = CONFIG_NODE;
vty->index = NULL;
vty->index_sub = NULL;
break;
case MSLOOKUP_SERVER_MSC_NODE:
vty->node = CONFIG_NODE;
vty->index = NULL;
vty->index_sub = NULL;
break;
default:
case HLR_NODE:
vty->node = CONFIG_NODE;
@@ -444,6 +474,7 @@ void hlr_vty_init(void)
install_node(&gsup_node, config_write_hlr_gsup);
install_element(GSUP_NODE, &cfg_hlr_gsup_bind_ip_cmd);
install_element(GSUP_NODE, &cfg_hlr_gsup_ipa_name_cmd);
install_element(HLR_NODE, &cfg_database_cmd);
@@ -462,4 +493,5 @@ void hlr_vty_init(void)
install_element(HLR_NODE, &cfg_no_subscr_create_on_demand_cmd);
hlr_vty_subscriber_init();
dgsm_vty_init();
}

View File

@@ -31,6 +31,10 @@ enum hlr_vty_node {
HLR_NODE = _LAST_OSMOVTY_NODE + 1,
GSUP_NODE,
EUSE_NODE,
MSLOOKUP_NODE,
MSLOOKUP_SERVER_NODE,
MSLOOKUP_SERVER_MSC_NODE,
MSLOOKUP_CLIENT_NODE,
};
int hlr_vty_is_config_node(struct vty *vty, int node);

View File

@@ -25,6 +25,18 @@ const struct log_info_cat hlr_log_info_cat[] = {
.color = "\033[1;34m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DDGSM] = {
.name = "DDGSM",
.description = "Distributed GSM: MS lookup and proxy",
.color = "\033[1;35m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DLU] = {
.name = "DLU",
.description = "Location Updating",
.color = "\033[1;33m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
};

View File

@@ -8,6 +8,8 @@ enum {
DGSUP,
DAUC,
DSS,
DDGSM,
DLU,
};
extern const struct log_info hlr_log_info;

287
src/lu_fsm.c Normal file
View File

@@ -0,0 +1,287 @@
/* Roughly follwing "Process Update_Location_HLR" of TS 09.02 */
#include <osmocom/core/utils.h>
#include <osmocom/core/tdef.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/fsm.h>
#include <osmocom/gsm/apn.h>
#include <osmocom/gsm/gsm48_ie.h>
#include "logging.h"
#include "hlr.h"
#include "gsup_server.h"
#include "db.h"
#define LOG_LU(lu, level, fmt, args...) \
LOGPFSML((lu)? (lu)->fi : NULL, level, fmt, ##args)
#define LOG_LU_REQ(lu, req, level, fmt, args...) \
LOGPFSML((lu)? (lu)->fi : NULL, level, "%s:" fmt, \
osmo_gsup_message_type_name((req)->gsup.message_type), ##args)
struct lu {
struct llist_head entry;
struct osmo_fsm_inst *fi;
struct osmo_gsup_req *update_location_req;
/* Subscriber state at time of initial Update Location Request */
struct hlr_subscriber subscr;
bool is_ps;
/* VLR requesting the LU. */
struct global_title vlr_name;
/* If the LU request was received via a proxy and not immediately from a local VLR, this indicates the closest
* peer that forwarded the GSUP message. */
struct global_title via_proxy;
};
LLIST_HEAD(g_all_lu);
enum lu_fsm_event {
LU_EV_RX_GSUP,
};
enum lu_fsm_state {
LU_ST_UNVALIDATED,
LU_ST_WAIT_INSERT_DATA_RESULT,
LU_ST_WAIT_LOCATION_CANCEL_RESULT,
};
static const struct value_string lu_fsm_event_names[] = {
OSMO_VALUE_STRING(LU_EV_RX_GSUP),
{}
};
static struct osmo_tdef_state_timeout lu_fsm_timeouts[32] = {
[LU_ST_WAIT_INSERT_DATA_RESULT] = { .T = -4222 },
[LU_ST_WAIT_LOCATION_CANCEL_RESULT] = { .T = -4222 },
};
#define lu_state_chg(lu, state) \
osmo_tdef_fsm_inst_state_chg((lu)->fi, state, lu_fsm_timeouts, g_hlr_tdefs, 5)
static void lu_success(struct lu *lu)
{
if (!lu->update_location_req)
LOG_LU(lu, LOGL_ERROR, "No request for this LU\n");
else
osmo_gsup_req_respond_msgt(lu->update_location_req, OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT);
lu->update_location_req = NULL;
osmo_fsm_inst_term(lu->fi, OSMO_FSM_TERM_REGULAR, NULL);
}
#define lu_failure(LU, CAUSE, log_msg, args...) do { \
if (!(LU)->update_location_req) \
LOG_LU(LU, LOGL_ERROR, "No request for this LU\n"); \
else \
osmo_gsup_req_respond_err((LU)->update_location_req, CAUSE, log_msg, ##args); \
(LU)->update_location_req = NULL; \
osmo_fsm_inst_term((LU)->fi, OSMO_FSM_TERM_REGULAR, NULL); \
} while(0)
static struct osmo_fsm lu_fsm;
static void lu_start(struct osmo_gsup_req *update_location_req)
{
struct osmo_fsm_inst *fi;
struct lu *lu;
OSMO_ASSERT(update_location_req);
OSMO_ASSERT(update_location_req->gsup.message_type == OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST);
fi = osmo_fsm_inst_alloc(&lu_fsm, g_hlr, NULL, LOGL_DEBUG, update_location_req->gsup.imsi);
OSMO_ASSERT(fi);
lu = talloc(fi, struct lu);
OSMO_ASSERT(lu);
fi->priv = lu;
*lu = (struct lu){
.fi = fi,
.update_location_req = update_location_req,
.vlr_name = update_location_req->source_name,
.via_proxy = update_location_req->via_proxy,
/* According to GSUP specs, OSMO_GSUP_CN_DOMAIN_PS is the default. */
.is_ps = (update_location_req->gsup.cn_domain != OSMO_GSUP_CN_DOMAIN_CS),
};
llist_add(&lu->entry, &g_all_lu);
osmo_fsm_inst_update_id_f_sanitize(fi, '_', "%s:IMSI-%s", lu->is_ps ? "PS" : "CS", update_location_req->gsup.imsi);
if (!lu->vlr_name.len) {
lu_failure(lu, GMM_CAUSE_NET_FAIL, "LU without a VLR");
return;
}
if (db_subscr_get_by_imsi(g_hlr->dbc, update_location_req->gsup.imsi, &lu->subscr) < 0) {
lu_failure(lu, GMM_CAUSE_IMSI_UNKNOWN, "Subscriber does not exist");
return;
}
/* Check if subscriber is generally permitted on CS or PS
* service (as requested) */
if (!lu->is_ps && !lu->subscr.nam_cs) {
lu_failure(lu, GMM_CAUSE_PLMN_NOTALLOWED, "nam_cs == false");
return;
}
if (lu->is_ps && !lu->subscr.nam_ps) {
lu_failure(lu, GMM_CAUSE_GPRS_NOTALLOWED, "nam_ps == false");
return;
}
/* TODO: Set subscriber tracing = deactive in VLR/SGSN */
#if 0
/* Cancel in old VLR/SGSN, if new VLR/SGSN differs from old */
if (!lu->is_ps && strcmp(subscr->vlr_number, vlr_number)) {
lu_op_tx_cancel_old(lu);
} else if (lu->is_ps && strcmp(subscr->sgsn_number, sgsn_number)) {
lu_op_tx_cancel_old(lu);
}
#endif
/* Store the VLR / SGSN number with the subscriber, so we know where it was last seen. */
if (lu->via_proxy.len) {
LOG_GSUP_REQ(update_location_req, LOGL_DEBUG, "storing %s = %s, via proxy %s\n",
lu->is_ps ? "SGSN number" : "VLR number",
global_title_name(&lu->vlr_name),
global_title_name(&lu->via_proxy));
} else {
LOG_GSUP_REQ(update_location_req, LOGL_DEBUG, "storing %s = %s\n",
lu->is_ps ? "SGSN number" : "VLR number",
global_title_name(&lu->vlr_name));
}
if (db_subscr_lu(g_hlr->dbc, lu->subscr.id, &lu->vlr_name, lu->is_ps, &lu->via_proxy)) {
lu_failure(lu, GMM_CAUSE_NET_FAIL, "Cannot update %s in the database",
lu->is_ps ? "SGSN number" : "VLR number");
return;
}
/* TODO: Subscriber allowed to roam in PLMN? */
/* TODO: Update RoutingInfo */
/* TODO: Reset Flag MS Purged (cs/ps) */
/* TODO: Control_Tracing_HLR / Control_Tracing_HLR_with_SGSN */
lu_state_chg(lu, LU_ST_WAIT_INSERT_DATA_RESULT);
}
void lu_rx_gsup(struct osmo_gsup_req *req)
{
struct lu *lu;
if (req->gsup.message_type == OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST)
return lu_start(req);
llist_for_each_entry(lu, &g_all_lu, entry) {
if (strcmp(lu->subscr.imsi, req->gsup.imsi))
continue;
if (osmo_fsm_inst_dispatch(lu->fi, LU_EV_RX_GSUP, req)) {
LOG_LU_REQ(lu, req, LOGL_ERROR, "Cannot receive GSUP messages in this state\n");
osmo_gsup_req_respond_err(req, GMM_CAUSE_MSGT_INCOMP_P_STATE,
"LU does not accept GSUP rx");
}
return;
}
osmo_gsup_req_respond_err(req, GMM_CAUSE_MSGT_INCOMP_P_STATE, "No Location Updating in progress for this IMSI");
}
static int lu_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
struct lu *lu = fi->priv;
lu_failure(lu, GSM_CAUSE_NET_FAIL, "Timeout");
return 0;
}
static void lu_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct lu *lu = fi->priv;
if (lu->update_location_req)
osmo_gsup_req_respond_err(lu->update_location_req, GSM_CAUSE_NET_FAIL, "LU aborted");
lu->update_location_req = NULL;
llist_del(&lu->entry);
}
static void lu_fsm_wait_insert_data_result_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
/* Transmit Insert Data Request to the VLR */
struct lu *lu = fi->priv;
struct hlr_subscriber *subscr = &lu->subscr;
struct osmo_gsup_message gsup;
uint8_t msisdn_enc[OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN];
uint8_t apn[APN_MAXLEN];
if (osmo_gsup_create_insert_subscriber_data_msg(&gsup, subscr->imsi,
subscr->msisdn, msisdn_enc, sizeof(msisdn_enc),
apn, sizeof(apn),
lu->is_ps? OSMO_GSUP_CN_DOMAIN_PS : OSMO_GSUP_CN_DOMAIN_CS)) {
lu_failure(lu, GMM_CAUSE_NET_FAIL, "cannot encode Insert Subscriber Data message");
return;
}
if (osmo_gsup_req_respond_nonfinal(lu->update_location_req, &gsup))
lu_failure(lu, GMM_CAUSE_NET_FAIL, "cannot send %s", osmo_gsup_message_type_name(gsup.message_type));
}
void lu_fsm_wait_insert_data_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_INSERT_DATA_RESULT:
osmo_gsup_req_free(req);
lu_success(lu);
break;
case OSMO_GSUP_MSGT_INSERT_DATA_ERROR:
lu_failure(lu, GMM_CAUSE_NET_FAIL, "Rx %s", osmo_gsup_message_type_name(req->gsup.message_type));
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[] = {
[LU_ST_UNVALIDATED] = {
.name = "UNVALIDATED",
.out_state_mask = 0
| S(LU_ST_WAIT_INSERT_DATA_RESULT)
,
},
[LU_ST_WAIT_INSERT_DATA_RESULT] = {
.name = "WAIT_INSERT_DATA_RESULT",
.in_event_mask = 0
| S(LU_EV_RX_GSUP)
,
.onenter = lu_fsm_wait_insert_data_result_onenter,
.action = lu_fsm_wait_insert_data_result,
},
};
static struct osmo_fsm lu_fsm = {
.name = "lu",
.states = lu_fsm_states,
.num_states = ARRAY_SIZE(lu_fsm_states),
.log_subsys = DLU,
.event_names = lu_fsm_event_names,
.timer_cb = lu_fsm_timer_cb,
.cleanup = lu_fsm_cleanup,
};
static __attribute__((constructor)) void lu_fsm_init()
{
OSMO_ASSERT(osmo_fsm_register(&lu_fsm) == 0);
}

2
src/lu_fsm.h Normal file
View File

@@ -0,0 +1,2 @@
void lu_rx_gsup(struct osmo_gsup_req *req);

View File

@@ -1,259 +0,0 @@
/* OsmoHLR TX/RX lu operations */
/* (C) 2017 sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Harald Welte <laforge@gnumonks.org>
*
* 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 <stdbool.h>
#include <string.h>
#include <errno.h>
#include <osmocom/core/logging.h>
#include <osmocom/gsm/gsup.h>
#include <osmocom/gsm/apn.h>
#include "gsup_server.h"
#include "gsup_router.h"
#include "logging.h"
#include "luop.h"
const struct value_string lu_state_names[] = {
{ LU_S_NULL, "NULL" },
{ LU_S_LU_RECEIVED, "LU RECEIVED" },
{ LU_S_CANCEL_SENT, "CANCEL SENT" },
{ LU_S_CANCEL_ACK_RECEIVED, "CANCEL-ACK RECEIVED" },
{ LU_S_ISD_SENT, "ISD SENT" },
{ LU_S_ISD_ACK_RECEIVED, "ISD-ACK RECEIVED" },
{ LU_S_COMPLETE, "COMPLETE" },
{ 0, NULL }
};
/* Transmit a given GSUP message for the given LU operation */
static void _luop_tx_gsup(struct lu_operation *luop,
const struct osmo_gsup_message *gsup)
{
struct msgb *msg_out;
msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP LUOP");
OSMO_ASSERT(msg_out);
osmo_gsup_encode(msg_out, gsup);
osmo_gsup_addr_send(luop->gsup_server, luop->peer,
talloc_total_size(luop->peer),
msg_out);
}
static inline void fill_gsup_msg(struct osmo_gsup_message *out,
const struct lu_operation *lu,
enum osmo_gsup_message_type mt)
{
memset(out, 0, sizeof(struct osmo_gsup_message));
if (lu)
osmo_strlcpy(out->imsi, lu->subscr.imsi,
GSM23003_IMSI_MAX_DIGITS + 1);
out->message_type = mt;
}
/* timer call-back in case LU operation doesn't receive an response */
static void lu_op_timer_cb(void *data)
{
struct lu_operation *luop = data;
DEBUGP(DMAIN, "LU OP timer expired in state %s\n",
get_value_string(lu_state_names, luop->state));
switch (luop->state) {
case LU_S_CANCEL_SENT:
break;
case LU_S_ISD_SENT:
break;
default:
break;
}
lu_op_tx_error(luop, GMM_CAUSE_NET_FAIL);
}
bool lu_op_fill_subscr(struct lu_operation *luop, struct db_context *dbc,
const char *imsi)
{
struct hlr_subscriber *subscr = &luop->subscr;
if (db_subscr_get_by_imsi(dbc, imsi, subscr) < 0)
return false;
return true;
}
struct lu_operation *lu_op_alloc(struct osmo_gsup_server *srv)
{
struct lu_operation *luop;
luop = talloc_zero(srv, struct lu_operation);
OSMO_ASSERT(luop);
luop->gsup_server = srv;
osmo_timer_setup(&luop->timer, lu_op_timer_cb, luop);
return luop;
}
void lu_op_free(struct lu_operation *luop)
{
/* Only attempt to remove when it was ever added to a list. */
if (luop->list.next)
llist_del(&luop->list);
/* Delete timer just in case it is still pending. */
osmo_timer_del(&luop->timer);
talloc_free(luop);
}
struct lu_operation *lu_op_alloc_conn(struct osmo_gsup_conn *conn)
{
uint8_t *peer_addr;
struct lu_operation *luop = lu_op_alloc(conn->server);
int rc = osmo_gsup_conn_ccm_get(conn, &peer_addr, IPAC_IDTAG_SERNR);
if (rc < 0) {
lu_op_free(luop);
return NULL;
}
luop->peer = talloc_memdup(luop, peer_addr, rc);
return luop;
}
/* FIXME: this doesn't seem to work at all */
struct lu_operation *lu_op_by_imsi(const char *imsi,
const struct llist_head *lst)
{
struct lu_operation *luop;
llist_for_each_entry(luop, lst, list) {
if (!strcmp(imsi, luop->subscr.imsi))
return luop;
}
return NULL;
}
void lu_op_statechg(struct lu_operation *luop, enum lu_state new_state)
{
enum lu_state old_state = luop->state;
DEBUGP(DMAIN, "LU OP state change: %s -> ",
get_value_string(lu_state_names, old_state));
DEBUGPC(DMAIN, "%s\n",
get_value_string(lu_state_names, new_state));
luop->state = new_state;
}
/*! Transmit UPD_LOC_ERROR and destroy lu_operation */
void lu_op_tx_error(struct lu_operation *luop, enum gsm48_gmm_cause cause)
{
struct osmo_gsup_message gsup;
DEBUGP(DMAIN, "%s: LU OP Tx Error (cause %s)\n",
luop->subscr.imsi, get_value_string(gsm48_gmm_cause_names,
cause));
fill_gsup_msg(&gsup, luop, OSMO_GSUP_MSGT_UPDATE_LOCATION_ERROR);
gsup.cause = cause;
_luop_tx_gsup(luop, &gsup);
lu_op_free(luop);
}
/*! Transmit UPD_LOC_RESULT and destroy lu_operation */
void lu_op_tx_ack(struct lu_operation *luop)
{
struct osmo_gsup_message gsup;
fill_gsup_msg(&gsup, luop, OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT);
//FIXME gsup.hlr_enc;
_luop_tx_gsup(luop, &gsup);
lu_op_free(luop);
}
/*! Send Cancel Location to old VLR/SGSN */
void lu_op_tx_cancel_old(struct lu_operation *luop)
{
struct osmo_gsup_message gsup;
OSMO_ASSERT(luop->state == LU_S_LU_RECEIVED);
fill_gsup_msg(&gsup, NULL, OSMO_GSUP_MSGT_LOCATION_CANCEL_REQUEST);
//gsup.cause = FIXME;
//gsup.cancel_type = FIXME;
_luop_tx_gsup(luop, &gsup);
lu_op_statechg(luop, LU_S_CANCEL_SENT);
osmo_timer_schedule(&luop->timer, CANCEL_TIMEOUT_SECS, 0);
}
/*! Transmit Insert Subscriber Data to new VLR/SGSN */
void lu_op_tx_insert_subscr_data(struct lu_operation *luop)
{
struct hlr_subscriber *subscr = &luop->subscr;
struct osmo_gsup_message gsup = { };
uint8_t msisdn_enc[OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN];
uint8_t apn[APN_MAXLEN];
enum osmo_gsup_cn_domain cn_domain;
OSMO_ASSERT(luop->state == LU_S_LU_RECEIVED ||
luop->state == LU_S_CANCEL_ACK_RECEIVED);
if (luop->is_ps)
cn_domain = OSMO_GSUP_CN_DOMAIN_PS;
else
cn_domain = OSMO_GSUP_CN_DOMAIN_CS;
if (osmo_gsup_create_insert_subscriber_data_msg(&gsup, subscr->imsi, subscr->msisdn, msisdn_enc,
sizeof(msisdn_enc), apn, sizeof(apn), cn_domain) != 0) {
LOGP(DMAIN, LOGL_ERROR,
"IMSI='%s': Cannot notify GSUP client; could not create gsup message "
"for %s\n", subscr->imsi, luop->peer);
return;
}
/* Send ISD to new VLR/SGSN */
_luop_tx_gsup(luop, &gsup);
lu_op_statechg(luop, LU_S_ISD_SENT);
osmo_timer_schedule(&luop->timer, ISD_TIMEOUT_SECS, 0);
}
/*! Transmit Delete Subscriber Data to new VLR/SGSN.
* The luop is not freed. */
void lu_op_tx_del_subscr_data(struct lu_operation *luop)
{
struct osmo_gsup_message gsup;
fill_gsup_msg(&gsup, luop, OSMO_GSUP_MSGT_DELETE_DATA_REQUEST);
gsup.cn_domain = OSMO_GSUP_CN_DOMAIN_PS;
/* Send ISD to new VLR/SGSN */
_luop_tx_gsup(luop, &gsup);
}

View File

@@ -1,81 +0,0 @@
/* OsmoHLR TX/RX lu operations */
/* (C) 2017 sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Harald Welte <laforge@gnumonks.org>
*
* 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/>.
*
*/
#pragma once
#include <stdbool.h>
#include <osmocom/core/timer.h>
#include <osmocom/gsm/gsup.h>
#include "db.h"
#include "gsup_server.h"
#define CANCEL_TIMEOUT_SECS 30
#define ISD_TIMEOUT_SECS 30
enum lu_state {
LU_S_NULL,
LU_S_LU_RECEIVED,
LU_S_CANCEL_SENT,
LU_S_CANCEL_ACK_RECEIVED,
LU_S_ISD_SENT,
LU_S_ISD_ACK_RECEIVED,
LU_S_COMPLETE,
};
extern const struct value_string lu_state_names[];
struct lu_operation {
/*! entry in global list of location update operations */
struct llist_head list;
/*! to which gsup_server do we belong */
struct osmo_gsup_server *gsup_server;
/*! state of the location update */
enum lu_state state;
/*! CS (false) or PS (true) Location Update? */
bool is_ps;
/*! currently running timer */
struct osmo_timer_list timer;
/*! subscriber related to this operation */
struct hlr_subscriber subscr;
/*! peer VLR/SGSN starting the request */
uint8_t *peer;
};
struct lu_operation *lu_op_alloc(struct osmo_gsup_server *srv);
struct lu_operation *lu_op_alloc_conn(struct osmo_gsup_conn *conn);
void lu_op_statechg(struct lu_operation *luop, enum lu_state new_state);
bool lu_op_fill_subscr(struct lu_operation *luop, struct db_context *dbc,
const char *imsi);
struct lu_operation *lu_op_by_imsi(const char *imsi,
const struct llist_head *lst);
void lu_op_tx_error(struct lu_operation *luop, enum gsm48_gmm_cause cause);
void lu_op_tx_ack(struct lu_operation *luop);
void lu_op_tx_cancel_old(struct lu_operation *luop);
void lu_op_tx_insert_subscr_data(struct lu_operation *luop);
void lu_op_tx_del_subscr_data(struct lu_operation *luop);
void lu_op_free(struct lu_operation *luop);

282
src/mslookup_server.c Normal file
View File

@@ -0,0 +1,282 @@
#include <string.h>
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/mslookup/mslookup.h>
#include "logging.h"
#include "hlr.h"
#include "db.h"
#include "dgsm.h"
#include "mslookup_server.h"
#include "proxy.h"
static const struct osmo_mslookup_result not_found = {
.rc = OSMO_MSLOOKUP_RC_NOT_FOUND,
};
static void set_result(struct osmo_mslookup_result *result,
const struct dgsm_service_host *service_host,
uint32_t age)
{
if (!osmo_sockaddr_str_is_nonzero(&service_host->host_v4)
&& !osmo_sockaddr_str_is_nonzero(&service_host->host_v6)) {
*result = not_found;
return;
}
result->rc = OSMO_MSLOOKUP_RC_OK;
result->host_v4 = service_host->host_v4;
result->host_v6 = service_host->host_v6;
result->age = age;
}
/* A remote entity is asking us whether we are the home HLR of the given subscriber. */
static void mslookup_server_rx_hlr_gsup(const struct osmo_mslookup_query *query,
struct osmo_mslookup_result *result)
{
struct dgsm_service_host *host;
int rc;
switch (query->id.type) {
case OSMO_MSLOOKUP_ID_IMSI:
rc = db_subscr_exists_by_imsi(g_hlr->dbc, query->id.imsi);
break;
case OSMO_MSLOOKUP_ID_MSISDN:
rc = db_subscr_exists_by_msisdn(g_hlr->dbc, query->id.msisdn);
break;
default:
LOGP(DDGSM, LOGL_ERROR, "Unknown mslookup ID type: %d\n", query->id.type);
*result = not_found;
return;
}
if (rc) {
LOGP(DDGSM, LOGL_DEBUG, "%s: does not exist in local HLR\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
*result = not_found;
return;
}
LOGP(DDGSM, LOGL_DEBUG, "%s: found in local HLR\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
/* Find a HLR/GSUP service set for the server (no MSC unit name) */
host = dgsm_config_service_get(&dgsm_config_msc_wildcard, OSMO_MSLOOKUP_SERVICE_HLR_GSUP);
if (!host) {
struct dgsm_service_host gsup_bind = {};
/* Try to use the locally configured GSUP bind address */
osmo_sockaddr_str_from_str(&gsup_bind.host_v4, g_hlr->gsup_bind_addr, OSMO_GSUP_PORT);
if (gsup_bind.host_v4.af == AF_INET6) {
gsup_bind.host_v6 = gsup_bind.host_v4;
gsup_bind.host_v4 = (struct osmo_sockaddr_str){};
}
set_result(result, &gsup_bind, 0);
if (result->rc != OSMO_MSLOOKUP_RC_OK) {
LOGP(DDGSM, LOGL_ERROR,
"%s: subscriber found, but no service '" OSMO_MSLOOKUP_SERVICE_HLR_GSUP "' configured,"
" and cannot use configured GSUP bind address %s in mslookup response."
" Cannot service HLR lookup request\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
osmo_quote_str(g_hlr->gsup_bind_addr, -1));
}
return;
}
set_result(result, host, 0);
if (result->rc != OSMO_MSLOOKUP_RC_OK) {
LOGP(DDGSM, LOGL_ERROR,
"Subscriber found, but error in service '" OSMO_MSLOOKUP_SERVICE_HLR_GSUP "' config:"
" v4: " OSMO_SOCKADDR_STR_FMT " v6: " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&host->host_v4),
OSMO_SOCKADDR_STR_FMT_ARGS(&host->host_v6));
}
}
/* Look in the local HLR record: If the subscriber is "at home" in this HLR and is also currently located at a local
* MSC, we will find a valid location updating with vlr_number, and no vlr_via_proxy entry. */
static bool subscriber_has_done_lu_here_hlr(const struct osmo_mslookup_query *query,
uint32_t *lu_age,
struct global_title *local_msc_name)
{
struct hlr_subscriber subscr;
struct timeval age_tv;
int rc;
uint32_t age;
switch (query->id.type) {
case OSMO_MSLOOKUP_ID_IMSI:
rc = db_subscr_get_by_imsi(g_hlr->dbc, query->id.imsi, &subscr);
break;
case OSMO_MSLOOKUP_ID_MSISDN:
rc = db_subscr_get_by_msisdn(g_hlr->dbc, query->id.msisdn, &subscr);
break;
default:
LOGP(DDGSM, LOGL_ERROR, "Unknown mslookup ID type: %d\n", query->id.type);
return false;
}
if (rc) {
LOGP(DDGSM, LOGL_DEBUG, "%s: does not exist in local HLR\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
return false;
}
if (!subscr.vlr_number[0]) {
LOGP(DDGSM, LOGL_DEBUG, "%s: not attached (vlr_number unset)\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
}
if (subscr.vlr_via_proxy.len) {
/* The MSC is behind a proxy, the subscriber is not attached to a local MSC but a remote one. That
* remote proxy should instead respond to the service lookup request. */
LOGP(DDGSM, LOGL_DEBUG, "%s: last attach is not at local MSC, but via proxy %s\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
global_title_name(&subscr.vlr_via_proxy));
return false;
}
age_tv = (struct timeval){ .tv_sec = subscr.last_lu_seen };
age = timestamp_age(&age_tv);
if (age > g_hlr->mslookup.server.max_age) {
LOGP(DDGSM, LOGL_ERROR, "%s: last attach was here, but too long ago: %us > %us\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
age, g_hlr->mslookup.server.max_age);
return false;
}
*lu_age = age;
global_title_set_str(local_msc_name, subscr.vlr_number);
LOGP(DDGSM, LOGL_DEBUG, "%s: attached %u seconds ago at local MSC %s\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
age, global_title_name(local_msc_name));
return true;
}
/* Determine whether the subscriber with the given ID has routed a Location Updating via this HLR as first hop. Return
* true if it is attached at a local MSC, and we are serving as proxy for a remote home HLR.
*/
static bool subscriber_has_done_lu_here_proxy(const struct osmo_mslookup_query *query,
uint32_t *lu_age,
struct global_title *local_msc_name)
{
const struct proxy_subscr *subscr;
uint32_t age;
/* See the local HLR record. If the subscriber is "at home" in this HLR and is also currently located here, we
* will find a valid location updating and no vlr_via_proxy entry. */
subscr = proxy_subscr_get_by_imsi(g_hlr->proxy, query->id.imsi);
if (!subscr) {
LOGP(DDGSM, LOGL_DEBUG, "%s: does not exist in GSUP proxy\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
return false;
}
/* We only need to care about CS LU, since only CS services need D-GSM routing. */
age = timestamp_age(&subscr->cs.last_lu);
if (age > g_hlr->mslookup.server.max_age) {
LOGP(DDGSM, LOGL_ERROR, "%s: last attach was here (proxy), but too long ago: %us > %us\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
age, g_hlr->mslookup.server.max_age);
return false;
}
*lu_age = age;
*local_msc_name = subscr->cs.vlr_name;
LOGP(DDGSM, LOGL_DEBUG, "%s: attached %u seconds ago at local MSC %s; proxying for remote HLR "
OSMO_SOCKADDR_STR_FMT "\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
age, global_title_name(local_msc_name),
OSMO_SOCKADDR_STR_FMT_ARGS(&subscr->remote_hlr_addr));
return true;
}
static bool subscriber_has_done_lu_here(const struct osmo_mslookup_query *query,
uint32_t *lu_age_p,
struct global_title *local_msc_name)
{
uint32_t lu_age = 0;
struct global_title msc_name = {};
uint32_t proxy_lu_age = 0;
struct global_title proxy_msc_name = {};
/* First ask the local HLR db, but if the local proxy record indicates a more recent LU, use that instead.
* For all usual cases, only one of these will reflect a LU, even if a subscriber had more than one home HLR:
* - if the subscriber is known here, we will never proxy.
* - if the subscriber is not known here, this local HLR db will never record a LU.
* However, if a subscriber was being proxied to a remote home HLR, and if then the subscriber was also added to
* the local HLR database, there might occur a situation where both reflect a LU. So, to be safe against all
* situations, compare the two entries.
*/
if (!subscriber_has_done_lu_here_hlr(query, &lu_age, &msc_name))
lu_age = 0;
if (!subscriber_has_done_lu_here_proxy(query, &proxy_lu_age, &proxy_msc_name))
proxy_lu_age = 0;
if (lu_age && proxy_lu_age) {
LOGP(DDGSM, LOGL_DEBUG,
"%s: a LU is on record both in the local HLR (age %us) and the GSUP proxy (age %us)\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
lu_age, proxy_lu_age);
}
/* If proxy has a younger lu, replace. */
if (proxy_lu_age && (!lu_age || (proxy_lu_age < lu_age))) {
lu_age = proxy_lu_age;
msc_name = proxy_msc_name;
}
if (!lu_age || !msc_name.len) {
LOGP(DDGSM, LOGL_DEBUG, "%s: not attached here\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
return false;
}
LOGP(DDGSM, LOGL_DEBUG, "%s: attached here, at MSC %s\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
global_title_name(&msc_name));
*lu_age_p = lu_age;
*local_msc_name = msc_name;
return true;
}
/* A remote entity is asking us whether we are providing the given service for the given subscriber. */
void osmo_mslookup_server_rx(const struct osmo_mslookup_query *query,
struct osmo_mslookup_result *result)
{
const struct dgsm_service_host *service_host;
uint32_t age;
struct global_title msc_name;
/* A request for a home HLR: answer exactly if this is the subscriber's home HLR, i.e. the IMSI is listed in the
* HLR database. */
if (strcmp(query->service, OSMO_MSLOOKUP_SERVICE_HLR_GSUP) == 0)
return mslookup_server_rx_hlr_gsup(query, result);
/* All other service types: answer when the subscriber has done a LU that is either listed in the local HLR or
* in the GSUP proxy database: i.e. if the subscriber has done a Location Updating at an MSC belonging to this
* HLR. Respond with whichever services are configured in the osmo-hlr.cfg. */
if (!subscriber_has_done_lu_here(query, &age, &msc_name)) {
*result = not_found;
return;
}
/* We've detected a LU here. The MSC where the LU happened is stored in msc_unit_name, and the LU age is stored
* in 'age'. Figure out the address configured for that MSC and service name. */
service_host = dgsm_config_service_get(&msc_name, query->service);
if (!service_host) {
/* Find such service set globally (no MSC unit name) */
service_host = dgsm_config_service_get(&dgsm_config_msc_wildcard, query->service);
}
if (!service_host) {
LOGP(DDGSM, LOGL_ERROR,
"%s: subscriber found, but no service %s configured, cannot service lookup request\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
osmo_quote_str_c(OTC_SELECT, query->service, -1));
*result = not_found;
return;
}
set_result(result, service_host, age);
}

7
src/mslookup_server.h Normal file
View File

@@ -0,0 +1,7 @@
#pragma once
struct osmo_mslookup_query;
struct osmo_mslookup_result;
void osmo_mslookup_server_rx(const struct osmo_mslookup_query *query,
struct osmo_mslookup_result *result);

166
src/mslookup_server_mdns.c Normal file
View File

@@ -0,0 +1,166 @@
#include <stdlib.h>
#include <unistd.h>
#include <osmocom/mslookup/mslookup.h>
#include <osmocom/mslookup/mdns.h>
#include "logging.h"
#include "mslookup_server.h"
#include "mslookup_server_mdns.h"
static void osmo_mslookup_server_mdns_tx(struct osmo_mslookup_server_mdns *server,
const struct osmo_mdns_request *req,
const struct osmo_mslookup_query *query,
const struct osmo_mslookup_result *result)
{
const char *errmsg = NULL;
struct msgb *msg;
struct osmo_mdns_answer ans;
struct osmo_mdns_record *rec_age;
struct osmo_mdns_record rec_ip_v4 = {};
struct osmo_mdns_record *rec_ip_v4_port;
struct osmo_mdns_record rec_ip_v6 = {};
struct osmo_mdns_record *rec_ip_v6_port;
uint32_t ip_v4;
struct in6_addr ip_v6;
void *ctx = talloc_named_const(server, 0, __func__);
LOGP(DDGSM, LOGL_DEBUG, "%s: sending mDNS response\n",
osmo_mslookup_result_name_c(OTC_SELECT, query, result));
osmo_mdns_answer_init(&ans);
ans.id = req->id;
ans.domain = req->domain;
rec_age = osmo_mdns_encode_txt_record(ctx, "age", "%u", result->age);
llist_add_tail(&rec_age->list, &ans.records);
if (osmo_sockaddr_str_is_nonzero(&result->host_v4)) {
if (osmo_sockaddr_str_to_32n(&result->host_v4, &ip_v4)) {
errmsg = "Error encoding IPv4 address";
goto clean_and_exit;
}
rec_ip_v4.type = OSMO_MSLOOKUP_MDNS_RECORD_TYPE_A;
rec_ip_v4.data = (void*)&ip_v4;
rec_ip_v4.length = sizeof(ip_v4);
llist_add_tail(&rec_ip_v4.list, &ans.records);
rec_ip_v4_port = osmo_mdns_encode_txt_record(ctx, "port", "%u", result->host_v4.port);
if (!rec_ip_v4_port) {
errmsg = "Error encoding IPv4 port";
goto clean_and_exit;
}
llist_add_tail(&rec_ip_v4_port->list, &ans.records);
}
if (osmo_sockaddr_str_is_nonzero(&result->host_v6)) {
if (osmo_sockaddr_str_to_in6_addr(&result->host_v6, &ip_v6)) {
errmsg = "Error encoding IPv6 address";
goto clean_and_exit;
}
rec_ip_v6.type = OSMO_MSLOOKUP_MDNS_RECORD_TYPE_AAAA;
rec_ip_v6.data = (void*)&ip_v6;
rec_ip_v6.length = sizeof(ip_v6);
llist_add_tail(&rec_ip_v6.list, &ans.records);
rec_ip_v6_port = osmo_mdns_encode_txt_record(ctx, "port", "%u", result->host_v6.port);
if (!rec_ip_v6_port) {
errmsg = "Error encoding IPv6 port";
goto clean_and_exit;
}
llist_add_tail(&rec_ip_v6_port->list, &ans.records);
}
msg = msgb_alloc(1024, __func__);
if (osmo_mdns_encode_answer(ctx, msg, &ans)) {
errmsg = "Error encoding DNS answer packet";
goto clean_and_exit;
}
if (osmo_mdns_sock_send(server->sock, msg))
errmsg = "Error sending DNS answer";
clean_and_exit:
if (errmsg)
LOGP(DDGSM, LOGL_ERROR, "%s: DNS: %s\n", osmo_mslookup_result_name_c(ctx, query, result), errmsg);
talloc_free(ctx);
}
static void osmo_mslookup_server_mdns_handle_request(struct osmo_mslookup_server_mdns *server,
const struct osmo_mdns_request *req)
{
struct osmo_mslookup_query query;
struct osmo_mslookup_result result;
if (osmo_mslookup_query_from_domain_str(&query, req->domain)) {
LOGP(DDGSM, LOGL_ERROR, "mDNS mslookup server: unable to parse request domain string: %s\n",
osmo_quote_str_c(OTC_SELECT, req->domain, -1));
return;
}
osmo_mslookup_server_rx(&query, &result);
/* Error logging already happens in osmo_mslookup_server_rx() */
if (result.rc != OSMO_MSLOOKUP_RC_OK)
return;
osmo_mslookup_server_mdns_tx(server, req, &query, &result);
}
static int osmo_mslookup_server_mdns_rx(struct osmo_fd *osmo_fd, unsigned int what)
{
struct osmo_mslookup_server_mdns *server = osmo_fd->data;
struct osmo_mdns_request *req;
int n;
uint8_t buffer[1024];
void *ctx;
/* Parse the message and print it */
n = read(osmo_fd->fd, buffer, sizeof(buffer));
if (n < 0)
return n;
ctx = talloc_named_const(server, 0, __func__);
req = osmo_mdns_decode_request(ctx, buffer, n);
if (!req) {
LOGP(DDGSM, LOGL_DEBUG, "mDNS rx: ignoring: not a request\n");
talloc_free(ctx);
return -1;
}
LOGP(DDGSM, LOGL_DEBUG, "mDNS rx request: %s\n", osmo_quote_str_c(OTC_SELECT, req->domain, -1));
osmo_mslookup_server_mdns_handle_request(server, req);
talloc_free(ctx);
return n;
}
struct osmo_mslookup_server_mdns *osmo_mslookup_server_mdns_start(void *ctx, const struct osmo_sockaddr_str *bind_addr)
{
struct osmo_mslookup_server_mdns *server = talloc_zero(ctx, struct osmo_mslookup_server_mdns);
OSMO_ASSERT(server);
*server = (struct osmo_mslookup_server_mdns){
.bind_addr = *bind_addr,
};
server->sock = osmo_mdns_sock_init(server,
bind_addr->ip, bind_addr->port, true,
osmo_mslookup_server_mdns_rx,
server, 0);
if (!server->sock) {
LOGP(DDGSM, LOGL_ERROR,
"mslookup mDNS server: error initializing multicast bind on " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(bind_addr));
talloc_free(server);
return NULL;
}
return server;
}
void osmo_mslookup_server_mdns_stop(struct osmo_mslookup_server_mdns *server)
{
if (!server)
return;
osmo_mdns_sock_cleanup(server->sock);
talloc_free(server);
}

View File

@@ -0,0 +1,14 @@
#pragma once
#include <stdbool.h>
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/mslookup/mdns_sock.h>
struct osmo_mslookup_server_mdns {
struct osmo_mslookup_server *mslookup;
struct osmo_sockaddr_str bind_addr;
struct osmo_mdns_sock *sock;
};
struct osmo_mslookup_server_mdns *osmo_mslookup_server_mdns_start(void *ctx, const struct osmo_sockaddr_str *bind_addr);
void osmo_mslookup_server_mdns_stop(struct osmo_mslookup_server_mdns *server);

173
src/proxy.c Normal file
View File

@@ -0,0 +1,173 @@
#include <sys/time.h>
#include <string.h>
#include <talloc.h>
#include <errno.h>
#include <osmocom/core/timer.h>
#include "logging.h"
#include "proxy.h"
/* Why have a separate struct to add an llist_head entry?
* This is to keep the option open to store the proxy data in the database instead, without any visible effect outside
* of proxy.c. */
struct proxy_subscr_listentry {
struct llist_head entry;
struct timeval last_update;
struct proxy_subscr data;
};
/* Central implementation to set a timestamp to the current time, in case we want to modify this in the future. */
void timestamp_update(struct timeval *tv)
{
osmo_gettimeofday(tv, NULL);
}
time_t timestamp_age(const struct timeval *last_update)
{
struct timeval age;
struct timeval now;
timestamp_update(&now);
timersub(&now, last_update, &age);
return age.tv_sec;
}
static bool proxy_subscr_matches_imsi(const struct proxy_subscr *proxy_subscr, const char *imsi)
{
if (!proxy_subscr || !imsi)
return false;
return strcmp(proxy_subscr->imsi, imsi) == 0;
}
static bool proxy_subscr_matches_msisdn(const struct proxy_subscr *proxy_subscr, const char *msisdn)
{
if (!proxy_subscr || !msisdn)
return false;
return strcmp(proxy_subscr->msisdn, msisdn) == 0;
}
static struct proxy_subscr_listentry *_proxy_get_by_imsi(struct proxy *proxy, const char *imsi)
{
struct proxy_subscr_listentry *e;
if (!proxy)
return NULL;
llist_for_each_entry(e, &proxy->subscr_list, entry) {
if (proxy_subscr_matches_imsi(&e->data, imsi))
return e;
}
return NULL;
}
static struct proxy_subscr_listentry *_proxy_get_by_msisdn(struct proxy *proxy, const char *msisdn)
{
struct proxy_subscr_listentry *e;
if (!proxy)
return NULL;
llist_for_each_entry(e, &proxy->subscr_list, entry) {
if (proxy_subscr_matches_msisdn(&e->data, msisdn))
return e;
}
return NULL;
}
const struct proxy_subscr *proxy_subscr_get_by_imsi(struct proxy *proxy, const char *imsi)
{
struct proxy_subscr_listentry *e = _proxy_get_by_imsi(proxy, imsi);
if (!e)
return NULL;
return &e->data;
}
const struct proxy_subscr *proxy_subscr_get_by_msisdn(struct proxy *proxy, const char *msisdn)
{
struct proxy_subscr_listentry *e = _proxy_get_by_msisdn(proxy, msisdn);
if (!e)
return NULL;
return &e->data;
}
void proxy_subscrs_get_by_remote_hlr(struct proxy *proxy, const struct osmo_sockaddr_str *remote_hlr_addr,
bool (*yield)(struct proxy *proxy, const struct proxy_subscr *subscr, void *data),
void *data)
{
struct proxy_subscr_listentry *e;
if (!proxy)
return;
llist_for_each_entry(e, &proxy->subscr_list, entry) {
if (!osmo_sockaddr_str_ip_cmp(remote_hlr_addr, &e->data.remote_hlr_addr)) {
if (!yield(proxy, &e->data, data))
return;
}
}
}
int proxy_subscr_update(struct proxy *proxy, const struct proxy_subscr *proxy_subscr)
{
struct proxy_subscr_listentry *e = _proxy_get_by_imsi(proxy, proxy_subscr->imsi);
if (!e) {
/* Does not exist yet */
e = talloc_zero(proxy, struct proxy_subscr_listentry);
llist_add(&e->entry, &proxy->subscr_list);
}
e->data = *proxy_subscr;
timestamp_update(&e->last_update);
return 0;
}
int _proxy_subscr_del(struct proxy_subscr_listentry *e)
{
llist_del(&e->entry);
return 0;
}
int proxy_subscr_del(struct proxy *proxy, const char *imsi)
{
struct proxy_subscr_listentry *e = _proxy_get_by_imsi(proxy, imsi);
if (!e)
return -ENOENT;
return _proxy_subscr_del(e);
}
/* Discard stale proxy entries. */
static void proxy_cleanup(void *proxy_v)
{
struct proxy *proxy = proxy_v;
struct proxy_subscr_listentry *e, *n;
llist_for_each_entry_safe(e, n, &proxy->subscr_list, entry) {
if (timestamp_age(&e->last_update) <= proxy->fresh_time)
continue;
_proxy_subscr_del(e);
}
if (proxy->gc_period)
osmo_timer_schedule(&proxy->gc_timer, proxy->gc_period, 0);
else
LOGP(DDGSM, LOGL_NOTICE, "Proxy cleanup is switched off (gc_period == 0)\n");
}
void proxy_set_gc_period(struct proxy *proxy, uint32_t gc_period)
{
proxy->gc_period = gc_period;
proxy_cleanup(proxy);
}
struct proxy *proxy_init(void *ctx)
{
struct proxy *proxy = talloc_zero(ctx, struct proxy);
*proxy = (struct proxy){
.fresh_time = 60*60,
.gc_period = 60,
};
INIT_LLIST_HEAD(&proxy->subscr_list);
osmo_timer_setup(&proxy->gc_timer, proxy_cleanup, proxy);
/* Invoke to trigger the first timer schedule */
proxy_set_gc_period(proxy, proxy->gc_period);
return proxy;
}
void proxy_del(struct proxy *proxy)
{
osmo_timer_del(&proxy->gc_timer);
talloc_free(proxy);
}

56
src/proxy.h Normal file
View File

@@ -0,0 +1,56 @@
#pragma once
#include <time.h>
#include <osmocom/gsm/protocol/gsm_23_003.h>
#include <osmocom/core/sockaddr_str.h>
#include "global_title.h"
void timestamp_update(struct timeval *timestamp);
time_t timestamp_age(const struct timeval *timestamp);
struct proxy {
struct llist_head subscr_list;
/* How long to keep proxy entries without a refresh, in seconds. */
uint32_t fresh_time;
/* How often to garbage collect the proxy cache, period in seconds.
* To change this and take effect immediately, rather use proxy_set_gc_period(). */
uint32_t gc_period;
struct osmo_timer_list gc_timer;
};
struct proxy_subscr_domain_state {
struct global_title vlr_name;
struct timeval last_lu;
#if 0
/* not needed unless we ever want a system with multiple proxy hops: */
/* Set if this is a middle proxy, i.e. a proxy behind another proxy.
* That is mostly to know whether the MS is attached at a local MSC/SGSN or further away. */
struct global_title vlr_via_proxy;
#endif
};
struct proxy_subscr {
char imsi[GSM23003_IMSI_MAX_DIGITS+1];
char msisdn[GSM23003_MSISDN_MAX_DIGITS+1];
struct osmo_sockaddr_str remote_hlr_addr;
struct proxy_subscr_domain_state cs, ps;
};
struct proxy *proxy_init(void *ctx);
void proxy_del(struct proxy *proxy);
void proxy_set_gc_period(struct proxy *proxy, uint32_t gc_period);
/* The API to access / modify proxy entries keeps the implementation opaque, to make sure that we can easily move proxy
* storage to SQLite db. */
const struct proxy_subscr *proxy_subscr_get_by_imsi(struct proxy *proxy, const char *imsi);
const struct proxy_subscr *proxy_subscr_get_by_msisdn(struct proxy *proxy, const char *msisdn);
void proxy_subscrs_get_by_remote_hlr(struct proxy *proxy, const struct osmo_sockaddr_str *remote_hlr_addr,
bool (*yield)(struct proxy *proxy, const struct proxy_subscr *subscr, void *data),
void *data);
const struct proxy_subscr *proxy_subscr_get_by_imsi(struct proxy *proxy, const char *imsi);
int proxy_subscr_update(struct proxy *proxy, const struct proxy_subscr *proxy_subscr);
int proxy_subscr_del(struct proxy *proxy, const char *imsi);

193
src/remote_hlr.c Normal file
View File

@@ -0,0 +1,193 @@
#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
#include <osmocom/gsm/gsup.h>
#include <osmocom/abis/ipa.h>
#include <osmocom/gsupclient/gsup_client.h>
#include "logging.h"
#include "hlr.h"
#include "gsup_server.h"
#include "gsup_router.h"
#include "dgsm.h"
#include "remote_hlr.h"
#include "proxy.h"
static LLIST_HEAD(remote_hlrs);
void remote_hlr_err_reply(struct osmo_gsup_client *gsupc, const struct osmo_gsup_message *gsup_orig,
enum gsm48_gmm_cause cause)
{
struct osmo_gsup_message gsup_reply;
/* No need to answer if we couldn't parse an ERROR message type, only REQUESTs need an error reply. */
if (!OSMO_GSUP_IS_MSGT_REQUEST(gsup_orig->message_type))
return;
gsup_reply = (struct osmo_gsup_message){
.cause = cause,
.message_type = OSMO_GSUP_TO_MSGT_ERROR(gsup_orig->message_type),
.message_class = gsup_orig->message_class,
/* RP-Message-Reference is mandatory for SM Service */
.sm_rp_mr = gsup_orig->sm_rp_mr,
};
OSMO_STRLCPY_ARRAY(gsup_reply.imsi, gsup_orig->imsi);
/* For SS/USSD, it's important to keep both session state and ID IEs */
if (gsup_orig->session_state != OSMO_GSUP_SESSION_STATE_NONE) {
gsup_reply.session_state = OSMO_GSUP_SESSION_STATE_END;
gsup_reply.session_id = gsup_orig->session_id;
}
if (osmo_gsup_client_enc_send(gsupc, &gsup_reply))
LOGP(DLGSUP, LOGL_ERROR, "Failed to send Error reply (imsi=%s)\n",
osmo_quote_str(gsup_orig->imsi, -1));
}
/* We are receiving back a GSUP message from a remote HLR to go back to a local MSC.
* The local MSC shall be indicated by gsup.destination_name. */
static int remote_hlr_rx(struct osmo_gsup_client *gsupc, struct msgb *msg)
{
struct osmo_gsup_message gsup;
struct osmo_gsup_conn *vlr_conn;
const struct proxy_subscr *proxy_subscr;
struct global_title destination;
struct msgb *gsup_copy;
int rc;
rc = osmo_gsup_decode(msgb_l2(msg), msgb_l2len(msg), &gsup);
if (rc < 0) {
LOG_GSUPC(gsupc, LOGL_ERROR, "Failed to decode GSUP message: '%s' (%d) [ %s]\n",
get_value_string(gsm48_gmm_cause_names, -rc), -rc, osmo_hexdump(msg->data, msg->len));
return rc;
}
if (!gsup.imsi[0]) {
LOG_GSUPC_MSG(gsupc, &gsup, LOGL_ERROR, "Failed to decode GSUP message: missing IMSI\n");
remote_hlr_err_reply(gsupc, &gsup, GMM_CAUSE_INV_MAND_INFO);
return -GMM_CAUSE_INV_MAND_INFO;
}
if (!gsup.destination_name || !gsup.destination_name_len
|| global_title_set(&destination, gsup.destination_name, gsup.destination_name_len)) {
LOG_GSUPC_MSG(gsupc, &gsup, LOGL_ERROR, "no valid Destination Name IE, cannot route to VLR.\n");
remote_hlr_err_reply(gsupc, &gsup, GMM_CAUSE_INV_MAND_INFO);
return -GMM_CAUSE_INV_MAND_INFO;
}
proxy_subscr = proxy_subscr_get_by_imsi(g_hlr->proxy, gsup.imsi);
if (!proxy_subscr) {
LOG_GSUPC_MSG(gsupc, &gsup, LOGL_ERROR, "Cannot route, no GSUP proxy record for this IMSI\n");
remote_hlr_err_reply(gsupc, &gsup, GMM_CAUSE_IMSI_UNKNOWN);
return -GMM_CAUSE_IMSI_UNKNOWN;
}
/* Route to MSC that we're proxying for */
vlr_conn = gsup_route_find_gt(g_hlr->gs, &destination);
if (!vlr_conn) {
LOG_GSUPC_MSG(gsupc, &gsup, LOGL_ERROR, "Destination VLR unreachable: %s\n",
global_title_name(&destination));
remote_hlr_err_reply(gsupc, &gsup, GMM_CAUSE_MSC_TEMP_NOTREACH);
return -GMM_CAUSE_MSC_TEMP_NOTREACH;
}
/* The outgoing message needs to be a separate msgb, because osmo_gsup_conn_send() takes ownership of it, an the
* gsup_client also does a msgb_free() after dispatching to this callback.
* We also need to strip the IPA header and have headroom. Just re-encode. */
gsup_copy = osmo_gsup_msgb_alloc("GSUP proxy to VLR");
if (osmo_gsup_encode(gsup_copy, &gsup)) {
LOG_GSUPC_MSG(gsupc, &gsup, LOGL_ERROR, "Failed to re-encode GSUP message, cannot forward\n");
remote_hlr_err_reply(gsupc, &gsup, GMM_CAUSE_MSC_TEMP_NOTREACH);
return -GMM_CAUSE_MSC_TEMP_NOTREACH;
}
return osmo_gsup_conn_send(vlr_conn, gsup_copy);
}
static bool remote_hlr_up_down(struct osmo_gsup_client *gsupc, bool up)
{
struct remote_hlr *remote_hlr = gsupc->data;
if (!up) {
LOGP(DDGSM, LOGL_ERROR,
"link to remote HLR is down, removing GSUP client: " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr));
remote_hlr_destroy(remote_hlr);
return false;
}
dgsm_remote_hlr_up(remote_hlr);
return true;
}
struct remote_hlr *remote_hlr_get(const struct osmo_sockaddr_str *addr, bool create)
{
struct remote_hlr *rh;
llist_for_each_entry(rh, &remote_hlrs, entry) {
if (!osmo_sockaddr_str_ip_cmp(&rh->addr, addr))
return rh;
}
if (!create)
return NULL;
/* Doesn't exist yet, create a GSUP client to remote HLR. */
rh = talloc_zero(dgsm_ctx, struct remote_hlr);
OSMO_ASSERT(rh);
*rh = (struct remote_hlr){
.addr = *addr,
.gsupc = osmo_gsup_client_create3(rh, &g_hlr->gsup_unit_name,
addr->ip, addr->port,
NULL,
remote_hlr_rx,
remote_hlr_up_down,
rh),
};
if (!rh->gsupc) {
LOGP(DDGSM, LOGL_ERROR,
"Failed to establish connection to remote HLR " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(addr));
talloc_free(rh);
return NULL;
}
rh->gsupc->data = rh;
llist_add(&rh->entry, &remote_hlrs);
return rh;
}
void remote_hlr_destroy(struct remote_hlr *remote_hlr)
{
osmo_gsup_client_destroy(remote_hlr->gsupc);
remote_hlr->gsupc = NULL;
llist_del(&remote_hlr->entry);
talloc_free(remote_hlr);
}
/* This function takes ownership of the msg, do not free it after passing to this function. */
int remote_hlr_msgb_send(struct remote_hlr *remote_hlr, struct msgb *msg)
{
int rc = osmo_gsup_client_send(remote_hlr->gsupc, msg);
if (rc) {
LOGP(DDGSM, LOGL_ERROR, "Failed to send GSUP message to " OSMO_SOCKADDR_STR_FMT "\n",
OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr));
}
return rc;
}
void remote_hlr_gsup_forward(struct remote_hlr *remote_hlr, struct osmo_gsup_req *req)
{
int rc;
struct msgb *msg = osmo_gsup_msgb_alloc("GSUP proxy to remote HLR");
/* To forward to a remote HLR, we need to indicate the source MSC's name in the Source Name IE to make sure the
* reply can be routed back. Store the sender MSC in gsup->source_name -- the remote HLR is required to return
* this as gsup->destination_name so that the reply gets routed to the original MSC. */
struct osmo_gsup_message forward = req->gsup;
forward.source_name = req->source_name.val;
forward.source_name_len = req->source_name.len;
rc = osmo_gsup_encode(msg, &forward);
if (rc) {
osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Failed to encode GSUP message for forwarding\n");
return;
}
remote_hlr_msgb_send(remote_hlr, msg);
osmo_gsup_req_free(req);
}

27
src/remote_hlr.h Normal file
View File

@@ -0,0 +1,27 @@
#pragma once
#include <stdbool.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/sockaddr_str.h>
struct osmo_gsup_client;
struct osmo_gsup_message;
struct msgb;
#define LOG_GSUPC(gsupc, level, fmt, args...) \
LOGP(DDGSM, level, "HLR Proxy: GSUP from %s:%u: " fmt, (gsupc)->link->addr, (gsupc)->link->port, ##args)
#define LOG_GSUPC_MSG(gsupc, gsup_msg, level, fmt, args...) \
LOG_GSUPC(gsupc, level, "%s: " fmt, osmo_gsup_message_type_name((gsup_msg)->message_type), ##args)
/* GSUP client link for proxying to a remote HLR. */
struct remote_hlr {
struct llist_head entry;
struct osmo_sockaddr_str addr;
struct osmo_gsup_client *gsupc;
};
struct remote_hlr *remote_hlr_get(const struct osmo_sockaddr_str *addr, bool create);
void remote_hlr_destroy(struct remote_hlr *remote_hlr);
int remote_hlr_msgb_send(struct remote_hlr *remote_hlr, struct msgb *msg);
void remote_hlr_gsup_forward(struct remote_hlr *remote_hlr, struct osmo_gsup_req *req);

View File

@@ -2,8 +2,8 @@ SUBDIRS = \
auc \
gsup_server \
db \
gsup \
db_upgrade \
mslookup_manual_test \
$(NULL)
# The `:;' works around a Bash 3.2 bug when the output is not writeable.

View File

@@ -30,6 +30,7 @@ db_test_LDADD = \
$(top_builddir)/src/db_auc.o \
$(top_builddir)/src/db_hlr.o \
$(top_builddir)/src/db.o \
$(top_builddir)/src/global_title.o \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOGSM_LIBS) \
$(LIBOSMOABIS_LIBS) \

View File

@@ -145,6 +145,8 @@ void dump_subscr(struct hlr_subscriber *subscr)
#define Ps(name) \
if (*subscr->name) \
Pfo(name, "'%s'", subscr)
#define Pgt(name) \
Pfv(name, "%s", global_title_name(&subscr->name))
#define Pd(name) \
Pfv(name, "%"PRId64, (int64_t)subscr->name)
#define Pd_nonzero(name) \
@@ -235,6 +237,14 @@ static const char *imsi2 = "123456789000002";
static const char *short_imsi = "123456";
static const char *unknown_imsi = "999999999";
static int db_subscr_lu_str(struct db_context *dbc, int64_t subscr_id,
const char *vlr_or_sgsn_number, bool is_ps)
{
struct global_title vlr_nr;
global_title_set_str(&vlr_nr, vlr_or_sgsn_number);
return db_subscr_lu(dbc, subscr_id, &vlr_nr, is_ps, NULL);
}
static void test_subscr_create_update_sel_delete()
{
int64_t id0, id1, id2, id_short;
@@ -386,39 +396,39 @@ static void test_subscr_create_update_sel_delete()
comment("Record LU for PS and CS (SGSN and VLR names)");
ASSERT_RC(db_subscr_lu(dbc, id0, "5952", true), 0);
ASSERT_RC(db_subscr_lu_str(dbc, id0, "5952", true), 0);
ASSERT_SEL(id, id0, 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "712", false), 0);
ASSERT_RC(db_subscr_lu_str(dbc, id0, "712", false), 0);
ASSERT_SEL(id, id0, 0);
comment("Record LU for PS and CS (SGSN and VLR names) *again*");
ASSERT_RC(db_subscr_lu(dbc, id0, "111", true), 0);
ASSERT_RC(db_subscr_lu_str(dbc, id0, "111", true), 0);
ASSERT_SEL(id, id0, 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "111", true), 0);
ASSERT_RC(db_subscr_lu_str(dbc, id0, "111", true), 0);
ASSERT_SEL(id, id0, 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "222", false), 0);
ASSERT_RC(db_subscr_lu_str(dbc, id0, "222", false), 0);
ASSERT_SEL(id, id0, 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "222", false), 0);
ASSERT_RC(db_subscr_lu_str(dbc, id0, "222", false), 0);
ASSERT_SEL(id, id0, 0);
comment("Unset LU info for PS and CS (SGSN and VLR names)");
ASSERT_RC(db_subscr_lu(dbc, id0, "", true), 0);
ASSERT_RC(db_subscr_lu_str(dbc, id0, "", true), 0);
ASSERT_SEL(id, id0, 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "", false), 0);
ASSERT_RC(db_subscr_lu_str(dbc, id0, "", false), 0);
ASSERT_SEL(id, id0, 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "111", true), 0);
ASSERT_RC(db_subscr_lu(dbc, id0, "222", false), 0);
ASSERT_RC(db_subscr_lu_str(dbc, id0, "111", true), 0);
ASSERT_RC(db_subscr_lu_str(dbc, id0, "222", false), 0);
ASSERT_SEL(id, id0, 0);
ASSERT_RC(db_subscr_lu(dbc, id0, NULL, true), 0);
ASSERT_RC(db_subscr_lu_str(dbc, id0, NULL, true), 0);
ASSERT_SEL(id, id0, 0);
ASSERT_RC(db_subscr_lu(dbc, id0, NULL, false), 0);
ASSERT_RC(db_subscr_lu_str(dbc, id0, NULL, false), 0);
ASSERT_SEL(id, id0, 0);
comment("Record LU for non-existent ID");
ASSERT_RC(db_subscr_lu(dbc, 99999, "5952", true), -ENOENT);
ASSERT_RC(db_subscr_lu(dbc, 99999, "712", false), -ENOENT);
ASSERT_RC(db_subscr_lu_str(dbc, 99999, "5952", true), -ENOENT);
ASSERT_RC(db_subscr_lu_str(dbc, 99999, "712", false), -ENOENT);
ASSERT_SEL(id, 99999, -ENOENT);
comment("Purge and un-purge PS and CS");

View File

@@ -435,7 +435,7 @@ DAUC Cannot disable CS: no such subscriber: IMSI='foobar'
--- Record LU for PS and CS (SGSN and VLR names)
db_subscr_lu(dbc, id0, "5952", true) --> 0
db_subscr_lu_str(dbc, id0, "5952", true) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -445,7 +445,7 @@ struct hlr_subscriber {
.sgsn_number = '5952',
}
db_subscr_lu(dbc, id0, "712", false) --> 0
db_subscr_lu_str(dbc, id0, "712", false) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -459,7 +459,7 @@ struct hlr_subscriber {
--- Record LU for PS and CS (SGSN and VLR names) *again*
db_subscr_lu(dbc, id0, "111", true) --> 0
db_subscr_lu_str(dbc, id0, "111", true) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -470,7 +470,7 @@ struct hlr_subscriber {
.sgsn_number = '111',
}
db_subscr_lu(dbc, id0, "111", true) --> 0
db_subscr_lu_str(dbc, id0, "111", true) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -481,7 +481,7 @@ struct hlr_subscriber {
.sgsn_number = '111',
}
db_subscr_lu(dbc, id0, "222", false) --> 0
db_subscr_lu_str(dbc, id0, "222", false) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -492,7 +492,7 @@ struct hlr_subscriber {
.sgsn_number = '111',
}
db_subscr_lu(dbc, id0, "222", false) --> 0
db_subscr_lu_str(dbc, id0, "222", false) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -506,7 +506,7 @@ struct hlr_subscriber {
--- Unset LU info for PS and CS (SGSN and VLR names)
db_subscr_lu(dbc, id0, "", true) --> 0
db_subscr_lu_str(dbc, id0, "", true) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -516,7 +516,7 @@ struct hlr_subscriber {
.vlr_number = '222',
}
db_subscr_lu(dbc, id0, "", false) --> 0
db_subscr_lu_str(dbc, id0, "", false) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -525,9 +525,9 @@ struct hlr_subscriber {
.msisdn = '543210123456789',
}
db_subscr_lu(dbc, id0, "111", true) --> 0
db_subscr_lu_str(dbc, id0, "111", true) --> 0
db_subscr_lu(dbc, id0, "222", false) --> 0
db_subscr_lu_str(dbc, id0, "222", false) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -538,7 +538,7 @@ struct hlr_subscriber {
.sgsn_number = '111',
}
db_subscr_lu(dbc, id0, NULL, true) --> 0
db_subscr_lu_str(dbc, id0, NULL, true) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -548,7 +548,7 @@ struct hlr_subscriber {
.vlr_number = '222',
}
db_subscr_lu(dbc, id0, NULL, false) --> 0
db_subscr_lu_str(dbc, id0, NULL, false) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -560,10 +560,10 @@ struct hlr_subscriber {
--- Record LU for non-existent ID
db_subscr_lu(dbc, 99999, "5952", true) --> -ENOENT
db_subscr_lu_str(dbc, 99999, "5952", true) --> -ENOENT
DAUC Cannot update SGSN number for subscriber ID=99999: no such subscriber
db_subscr_lu(dbc, 99999, "712", false) --> -ENOENT
db_subscr_lu_str(dbc, 99999, "712", false) --> -ENOENT
DAUC Cannot update VLR number for subscriber ID=99999: no such subscriber
db_subscr_get_by_id(dbc, 99999, &g_subscr) --> -ENOENT

View File

@@ -80,7 +80,10 @@ rc = 0
DMAIN hlr starting
DDB using database: <PATH>test.db
DDB Database <PATH>test.db' has HLR DB schema version 0
DDB Database <PATH>test.db' has been upgraded to HLR DB schema version 1
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
DMAIN Cmdline option --db-check: Database was opened successfully, quitting.
Resulting db:
@@ -117,7 +120,6 @@ Table: subscriber
name|type|notnull|dflt_value|pk
ggsn_number|VARCHAR(15)|0||0
gmlc_number|VARCHAR(15)|0||0
hlr_number|VARCHAR(15)|0||0
id|INTEGER|0||1
imei|VARCHAR(14)|0||0
imeisv|VARCHAR|0||0
@@ -126,6 +128,7 @@ last_lu_seen|TIMESTAMP|0|NULL|0
lmsi|INTEGER|0||0
ms_purged_cs|BOOLEAN|1|0|0
ms_purged_ps|BOOLEAN|1|0|0
msc_number|VARCHAR(15)|0||0
msisdn|VARCHAR(15)|0||0
nam_cs|BOOLEAN|1|1|0
nam_ps|BOOLEAN|1|1|0
@@ -133,17 +136,19 @@ periodic_lu_tmr|INTEGER|0||0
periodic_rau_tau_tmr|INTEGER|0||0
sgsn_address|VARCHAR|0||0
sgsn_number|VARCHAR(15)|0||0
sgsn_via_proxy|VARCHAR|0||0
smsc_number|VARCHAR(15)|0||0
vlr_number|VARCHAR(15)|0||0
vlr_via_proxy|VARCHAR|0||0
Table subscriber contents:
ggsn_number|gmlc_number|hlr_number|id|imei|imeisv|imsi|last_lu_seen|lmsi|ms_purged_cs|ms_purged_ps|msisdn|nam_cs|nam_ps|periodic_lu_tmr|periodic_rau_tau_tmr|sgsn_address|sgsn_number|smsc_number|vlr_number
|||1|||123456789012345|||0|0|098765432109876|1|1||||||MSC-1
|||2|||111111111|||1|0||1|1||||||
|||3|||222222222|||0|1|22222|1|1||||||
|||4|||333333|||0|0|3|0|1||||||
|||5|||444444444444444|||0|0|4444|1|0||||||
|||6|||5555555|||0|0|55555555555555|0|0||||||
ggsn_number|gmlc_number|id|imei|imeisv|imsi|last_lu_seen|lmsi|ms_purged_cs|ms_purged_ps|msc_number|msisdn|nam_cs|nam_ps|periodic_lu_tmr|periodic_rau_tau_tmr|sgsn_address|sgsn_number|sgsn_via_proxy|smsc_number|vlr_number|vlr_via_proxy
||1|||123456789012345|||0|0||098765432109876|1|1|||||||MSC-1|
||2|||111111111|||1|0|||1|1||||||||
||3|||222222222|||0|1||22222|1|1||||||||
||4|||333333|||0|0||3|0|1||||||||
||5|||444444444444444|||0|0||4444|1|0||||||||
||6|||5555555|||0|0||55555555555555|0|0||||||||
Table: subscriber_apn
name|type|notnull|dflt_value|pk
@@ -164,5 +169,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 2
DDB Database <PATH>test.db' has HLR DB schema version 4
DMAIN Cmdline option --db-check: Database was opened successfully, quitting.

View File

@@ -11,21 +11,21 @@ cfg="$srcdir/osmo-hlr.cfg"
dump_sorted_schema(){
db_file="$1"
tables="$(sqlite3 "$db_file" "SELECT name FROM sqlite_master WHERE type = 'table' order by name")"
tables="$(sqlite3 -batch -noheader "$db_file" "SELECT name FROM sqlite_master WHERE type = 'table' order by name")"
for table in $tables; do
echo
echo "Table: $table"
sqlite3 -header "$db_file" "SELECT name,type,\"notnull\",dflt_value,pk FROM PRAGMA_TABLE_INFO('$table') order by name;"
sqlite3 -batch -header "$db_file" "SELECT name,type,\"notnull\",dflt_value,pk FROM PRAGMA_TABLE_INFO('$table') order by name;"
echo
echo "Table $table contents:"
columns="$(sqlite3 "$db_file" "SELECT name FROM PRAGMA_TABLE_INFO('$table') order by name;")"
sqlite3 -header "$db_file" "SELECT $(echo $columns | sed 's/ /,/g') from $table;"
columns="$(sqlite3 -batch -noheader "$db_file" "SELECT name FROM PRAGMA_TABLE_INFO('$table') order by name;")"
sqlite3 -batch -header "$db_file" "SELECT $(echo $columns | sed 's/ /,/g') from $table;"
done
}
rm -f "$db"
echo "Creating db in schema version 0"
sqlite3 "$db" < "$srcdir/hlr_db_v0.sql"
sqlite3 -batch "$db" < "$srcdir/hlr_db_v0.sql"
echo
echo "Version 0 db:"
@@ -61,7 +61,7 @@ if [ -n "$do_equivalence_test" ]; then
-n OsmoHLR -p 4258 \
-r "$osmo_hlr -c $cfg -l $mint_db" \
"$srcdir/create_subscribers.vty"
sqlite3 "$mint_db" < "$srcdir/create_subscribers_step2.sql"
sqlite3 -batch "$mint_db" < "$srcdir/create_subscribers_step2.sql"
set +x
test_dump="$builddir/test.dump"

View File

@@ -1,91 +0,0 @@
/* (C) 2018 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 <string.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/application.h>
#include <osmocom/gsm/gsup.h>
#include "logging.h"
#include "luop.h"
struct osmo_gsup_server;
/* override osmo_gsup_addr_send() to not actually send anything. */
int osmo_gsup_addr_send(struct osmo_gsup_server *gs,
const uint8_t *addr, size_t addrlen,
struct msgb *msg)
{
LOGP(DMAIN, LOGL_DEBUG, "%s\n", msgb_hexdump(msg));
msgb_free(msg);
return 0;
}
int db_subscr_get_by_imsi(struct db_context *dbc, const char *imsi,
struct hlr_subscriber *subscr)
{
return 0;
}
/* Verify that the internally allocated msgb is large enough */
void test_gsup_tx_insert_subscr_data()
{
struct lu_operation luop = {
.state = LU_S_LU_RECEIVED,
.subscr = {
.imsi = "123456789012345",
.msisdn = "987654321098765",
.nam_cs = true,
.nam_ps = true,
},
.is_ps = true,
};
lu_op_tx_insert_subscr_data(&luop);
}
const struct log_info_cat default_categories[] = {
[DMAIN] = {
.name = "DMAIN",
.description = "Main Program",
.enabled = 1, .loglevel = LOGL_DEBUG,
},
};
static struct log_info info = {
.cat = default_categories,
.num_cat = ARRAY_SIZE(default_categories),
};
int main(int argc, char **argv)
{
void *ctx = talloc_named_const(NULL, 0, "gsup_test");
osmo_init_logging2(ctx, &info);
log_set_print_filename(osmo_stderr_target, 0);
log_set_print_timestamp(osmo_stderr_target, 0);
log_set_use_color(osmo_stderr_target, 0);
log_set_print_category(osmo_stderr_target, 1);
test_gsup_tx_insert_subscr_data();
printf("Done.\n");
return EXIT_SUCCESS;
}

View File

@@ -1,2 +0,0 @@
DMAIN 10 01 08 21 43 65 87 09 21 43 f5 08 09 08 89 67 45 23 01 89 67 f5 05 07 10 01 01 12 02 01 2a 28 01 01
DMAIN LU OP state change: LU RECEIVED -> ISD SENT

View File

@@ -1 +0,0 @@
Done.

View File

@@ -31,6 +31,7 @@ gsup_server_test_SOURCES = \
gsup_server_test_LDADD = \
$(top_srcdir)/src/gsup_server.c \
$(top_srcdir)/src/gsup_router.c \
$(top_srcdir)/src/global_title.c \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOGSM_LIBS) \
$(LIBOSMOABIS_LIBS) \

View File

@@ -1,6 +1,7 @@
AM_CPPFLAGS = \
$(all_includes) \
-I$(top_srcdir)/src \
-I$(top_srcdir)/include \
$(NULL)
AM_CFLAGS = \
@@ -16,27 +17,25 @@ AM_LDFLAGS = \
$(NULL)
EXTRA_DIST = \
gsup_test.ok \
gsup_test.err \
run.sh \
osmo-hlr-1.cfg \
osmo-hlr-2.cfg \
$(NULL)
noinst_PROGRAMS = \
gsup_test \
fake_msc \
$(NULL)
gsup_test_SOURCES = \
gsup_test.c \
fake_msc_SOURCES = \
fake_msc.c \
$(NULL)
gsup_test_LDADD = \
$(top_srcdir)/src/luop.c \
$(top_srcdir)/src/gsup_server.c \
$(top_srcdir)/src/gsup_router.c \
fake_msc_LDADD = \
$(top_builddir)/src/gsupclient/libosmo-gsup-client.la \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOGSM_LIBS) \
$(LIBOSMOABIS_LIBS) \
$(NULL)
.PHONY: update_exp
update_exp:
$(builddir)/gsup_test >"$(srcdir)/gsup_test.ok" 2>"$(srcdir)/gsup_test.err"
run:
$(srcdir)/run.sh $(srcdir) $(builddir)

View File

@@ -0,0 +1,86 @@
#include <osmocom/core/logging.h>
#include <osmocom/core/select.h>
#include <osmocom/core/application.h>
#include <osmocom/gsupclient/gsup_client.h>
void *ctx;
int gsup_client_read_cb(struct osmo_gsup_client *gsupc, struct msgb *msg)
{
struct osmo_gsup_message gsup;
if (osmo_gsup_decode(msgb_l2(msg), msgb_l2len(msg), &gsup)) {
printf("fake_msc: GSUP rx, but failed to decode\n");
return 0;
}
printf("fake_msc: GSUP rx %s %s (destination_name=%s)\n",
gsup.imsi, osmo_gsup_message_type_name(gsup.message_type),
osmo_quote_str((const char*)gsup.destination_name, gsup.destination_name_len));
return 0;
}
struct osmo_gsup_client *gsupc;
struct osmo_timer_list do_stuff_timer;
static void gsup_send(const struct osmo_gsup_message *gsup)
{
printf("fake_msc: GSUP tx %s %s\n", gsup->imsi, osmo_gsup_message_type_name(gsup->message_type));
osmo_gsup_client_enc_send(gsupc, gsup);
}
void do_stuff(void *data)
{
static int i = 0;
int seq = 0;
if (i == seq++) {
struct osmo_gsup_message gsup = {
.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST,
.imsi = "222222",
.cn_domain = OSMO_GSUP_CN_DOMAIN_CS,
};
gsup_send(&gsup);
}
seq += 3;
if (i == seq++) {
struct osmo_gsup_message gsup = {
.message_type = OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST,
.imsi = "222222",
.cn_domain = OSMO_GSUP_CN_DOMAIN_CS,
};
gsup_send(&gsup);
}
seq += 60;
if (i == seq++) {
exit(0);
}
i++;
osmo_timer_schedule(&do_stuff_timer, 1, 0);
}
int main()
{
ctx = talloc_named_const(NULL, 0, "main");
osmo_init_logging2(ctx, NULL);
log_set_print_filename(osmo_stderr_target, 0);
log_set_print_level(osmo_stderr_target, 0);
log_set_print_category(osmo_stderr_target, 0);
log_set_print_category_hex(osmo_stderr_target, 0);
log_set_use_color(osmo_stderr_target, 0);
log_set_category_filter(osmo_stderr_target, DLMSLOOKUP, true, LOGL_DEBUG);
struct ipaccess_unit gsup_client_name = {
.unit_name = "fake-msc-1",
.serno = "fake-msc-1",
};
gsupc = osmo_gsup_client_create2(ctx, &gsup_client_name, "127.0.0.1", OSMO_GSUP_PORT, gsup_client_read_cb,
NULL);
osmo_timer_setup(&do_stuff_timer, do_stuff, NULL);
osmo_timer_schedule(&do_stuff_timer, 1, 0);
for (;;) {
osmo_select_main_ctx(0);
}
}

View File

@@ -0,0 +1,40 @@
hlr
gsup
bind ip 127.0.0.1
ipa-name testHLR-1
ussd route prefix *0# internal own-msisdn
ussd route prefix *1# internal own-imsi
ussd route prefix *#100# internal own-msisdn
ussd route prefix *#101# internal own-imsi
store-imei
line vty
bind 127.0.0.1
ctrl
bind 127.0.0.1
mslookup
mdns
log stderr
logging filter all 1
logging color 1
logging print level 1
logging print category 1
logging print category-hex 0
logging print file basename last
logging print extended-timestamp 1
logging level set-all debug
logging level linp error
log gsmtap 127.0.0.1
logging filter all 1
logging color 1
logging print level 1
logging print category 1
logging print category-hex 0
logging print file basename last
logging print extended-timestamp 1
logging level set-all debug
logging level linp error

View File

@@ -0,0 +1,40 @@
hlr
gsup
bind ip 127.0.0.2
ipa-name testHLR-2
ussd route prefix *0# internal own-msisdn
ussd route prefix *1# internal own-imsi
ussd route prefix *#100# internal own-msisdn
ussd route prefix *#101# internal own-imsi
store-imei
line vty
bind 127.0.0.2
ctrl
bind 127.0.0.2
mslookup
mdns
log stderr
logging filter all 1
logging color 1
logging print level 1
logging print category 1
logging print category-hex 0
logging print file basename last
logging timestamp 1
logging level set-all debug
logging level linp error
log gsmtap 127.0.0.1
logging filter all 1
logging color 1
logging print level 1
logging print category 1
logging print category-hex 0
logging print file basename last
logging print extended-timestamp 1
logging level set-all debug
logging level linp error

View File

@@ -0,0 +1,22 @@
#!/bin/sh
srcdir="${1:-.}"
builddir="${2:-.}"
cd "$builddir"
osmo-hlr -c "$srcdir/osmo-hlr-1.cfg" -l hlr1.db &
sleep 1
osmo-hlr -c "$srcdir/osmo-hlr-2.cfg" -l hlr2.db &
sleep 1
osmo_interact_vty.py -H 127.0.0.1 -p 4258 -c 'enable; subscriber imsi 111111 create; subscriber imsi 111111 update msisdn 1'
osmo_interact_vty.py -H 127.0.0.2 -p 4258 -c 'enable; subscriber imsi 222222 create; subscriber imsi 222222 update msisdn 2'
sleep 1
./fake_msc &
echo enter to exit
read enter_to_exit
kill %1 %2 %3
killall osmo-hlr
killall fake_msc

View File

@@ -52,6 +52,7 @@ OsmoHLR(config)# list
end
...
hlr
mslookup
OsmoHLR(config)# hlr
OsmoHLR(config-hlr)# list
@@ -113,3 +114,41 @@ hlr
ussd route prefix *#100# internal own-msisdn
ussd route prefix *#101# internal own-imsi
end
OsmoHLR# configure terminal
OsmoHLR(config)# mslookup
OsmoHLR(config-mslookup)# list
...
mdns
no mdns
server
no server
client
no client
OsmoHLR(config-mslookup)# server
OsmoHLR(config-mslookup-server)# list
...
mdns bind IP <1-65535>
no mdns
service NAME at IP <1-65535>
no service NAME
no service NAME at IP <1-65535>
msc .UNIT_NAME
OsmoHLR(config-mslookup-server)# msc MSC-1
OsmoHLR(config-mslookup-server-msc)# list
...
service NAME at IP <1-65535>
no service NAME
no service NAME at IP <1-65535>
OsmoHLR(config-mslookup-server-msc)# exit
OsmoHLR(config-mslookup-server)# exit
OsmoHLR(config-mslookup)# client
OsmoHLR(config-mslookup-client)# list
...
timeout <1-100000>
mdns to IP <1-65535>
no mdns

View File

@@ -15,13 +15,6 @@ cat $abs_srcdir/auc/auc_ts_55_205_test_sets.err > experr
AT_CHECK([$abs_top_builddir/tests/auc/auc_ts_55_205_test_sets], [], [expout], [experr])
AT_CLEANUP
AT_SETUP([gsup])
AT_KEYWORDS([gsup])
cat $abs_srcdir/gsup/gsup_test.ok > expout
cat $abs_srcdir/gsup/gsup_test.err > experr
AT_CHECK([$abs_top_builddir/tests/gsup/gsup_test], [], [expout], [experr])
AT_CLEANUP
AT_SETUP([gsup_server])
AT_KEYWORDS([gsup_server])
cat $abs_srcdir/gsup_server/gsup_server_test.ok > expout