Compare commits

...

11 Commits

Author SHA1 Message Date
Harald Welte
c3de9a62aa Store identity of VLR/SGSN in UpdateLocation
The HLR must store the least-recently used VLR and SGSN identities in
its database to ensure subsequent mobile-terminated transactions can
be routed accordingly.

Change-Id: Ib2611421f3638eadc361787af801fffe9a34bd8a
Closes: OS#2796
2018-07-30 16:34:33 +02:00
Harald Welte
08d5204f0c USSD: Add support for internal USSD handlers
There are some requests that are best served inside the HLR, as it
has access to subscriber information such as MSISDN and IMSI.

This unfortunately required quite some restructuring of the USSD
related structures including the VTY syntax for adding routes.

The default config file has been updated to replicate the *#100#
built-in behavior of old OsmoNITB.

Closes: OS#2566
Change-Id: I1d09fab810a6bb9ab02904de72dbc9e8a414f9f9
2018-07-30 16:34:26 +02:00
Harald Welte
edc1fc0c25 Add osmo-euse-demo as minimalistic test of a External USSD (EUSE) handler
This is a small program which simply echo's the USSD request message it
gets in a quote back to the sender.  Its purpose is to illustrate how
EUSEs can be implemented using libosmo-gsup-client.

Change-Id: I3fb8554ca329cb609c591058254117006f665e73
2018-07-30 16:33:41 +02:00
Harald Welte
e2e5cefda9 USSD: Add new "DSS" logging category and use it appropriately
Change-Id: I0ac198a49ba70ea40fea18464325f1925797a6e8
2018-07-30 16:33:26 +02:00
Harald Welte
9e59287467 USSD: Further unification of log output; Use LOGPSS when possible
Change-Id: I2c508fe70337d24c4a8b48e0393ad3c979eea0e7
2018-07-30 16:33:26 +02:00
Harald Welte
7057711ca9 USSD: Send ReturnError component if USSD Code unknown / EUSE disconnected
Change-Id: Ieef06cec05dd81f600594465d18804362e0fafd6
2018-07-30 16:33:26 +02:00
Harald Welte
3a190461ee hlr_ussd: Introduce LOGPSS() macro
Change-Id: I1058ef9fd67af2224c991e43bab02bcf21c9f174
2018-07-30 16:33:26 +02:00
Harald Welte
f76578d6a8 USSD: Add basic dispatch + decode of GSUP-encapsulated SS/USSD
We don't want any SS session to run for more than 30s.  The timeout
is currently not refreshed.

If we need more comprehensive timeout handling, using osmo_fsm for SS
sessions might make sense.

Change-Id: I5c9fb6b619402d2a23fea9db99590143d85ac11a
2018-07-30 16:33:24 +02:00
Harald Welte
2c8bfe7278 USSD: Add Core USSD handling + VTY routing config to HLR
Change-Id: I3cfd7cd401ea32b7e92f1124d129099d9f7dc6e6
2018-07-30 16:33:20 +02:00
Harald Welte
0c0c3a1dde hlr: Export + Declare global g_hlr symbol
Change-Id: I6f3e50f071fb2fbbe58413b4760dc2215055a444
2018-07-30 16:33:20 +02:00
Harald Welte
d1bc55c3b6 GSUP: Log GSUP route add/remove
Change-Id: I1768d0b8ee7e2821e40a799c9a1c1d900a7ddc48
2018-07-30 16:33:20 +02:00
13 changed files with 1063 additions and 9 deletions

View File

@@ -17,3 +17,4 @@ ctrl
hlr
gsup
bind ip 127.0.0.1
ussd route prefix *#100# internal own-msisdn

View File

@@ -32,12 +32,14 @@ noinst_HEADERS = \
ctrl.h \
hlr_vty.h \
hlr_vty_subscr.h \
hlr_ussd.h \
db_bootstrap.h \
$(NULL)
bin_PROGRAMS = \
osmo-hlr \
osmo-hlr-db-tool \
osmo-euse-demo \
$(NULL)
osmo_hlr_SOURCES = \
@@ -55,6 +57,7 @@ osmo_hlr_SOURCES = \
hlr_vty.c \
hlr_vty_subscr.c \
gsup_send.c \
hlr_ussd.c \
$(NULL)
osmo_hlr_LDADD = \
@@ -96,6 +99,16 @@ db_test_LDADD = \
$(SQLITE3_LIBS) \
$(NULL)
osmo_euse_demo_SOURCES = \
osmo-euse-demo.c \
$(NULL)
osmo_euse_demo_LDADD = \
$(top_builddir)/src/gsupclient/libosmo-gsup-client.la \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOGSM_LIBS) \
$(NULL)
BOOTSTRAP_SQL = $(top_srcdir)/sql/hlr.sql
db_bootstrap.h: $(BOOTSTRAP_SQL) $(srcdir)/db_bootstrap.sed

View File

@@ -23,6 +23,7 @@
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/talloc.h>
#include "logging.h"
#include "gsup_server.h"
struct gsup_route {
@@ -60,6 +61,8 @@ int gsup_route_add(struct osmo_gsup_conn *conn, const uint8_t *addr, size_t addr
if (!gr)
return -ENOMEM;
LOGP(DMAIN, LOGL_INFO, "Adding GSUP route for %s\n", addr);
gr->addr = talloc_memdup(gr, addr, addrlen);
gr->conn = conn;
llist_add_tail(&gr->list, &conn->server->routes);
@@ -75,6 +78,8 @@ 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);
llist_del(&gr->list);
talloc_free(gr);
num_deleted++;

View File

@@ -42,8 +42,9 @@
#include "rand.h"
#include "luop.h"
#include "hlr_vty.h"
#include "hlr_ussd.h"
static struct hlr *g_hlr;
struct hlr *g_hlr;
static int quit = 0;
/* Trigger 'Insert Subscriber Data' messages to all connected GSUP clients.
@@ -297,10 +298,19 @@ static int rx_upd_loc_req(struct osmo_gsup_conn *conn,
} else
#endif
{
int rc;
uint8_t *addr;
rc = osmo_gsup_conn_ccm_get(conn, &addr, IPAC_IDTAG_SERNR);
if (rc <= 0) {
osmo_strlcpy(luop->subscr.imsi, gsup->imsi, sizeof(luop->subscr.imsi));
lu_op_tx_error(luop, GMM_CAUSE_NET_FAIL);
return 0;
}
/* 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 */
db_subscr_lu(g_hlr->dbc, luop->subscr.id, (char *)addr, luop->is_ps);
lu_op_tx_insert_subscr_data(luop);
}
return 0;
@@ -402,6 +412,13 @@ static int read_cb(struct osmo_gsup_conn *conn, struct msgb *msg)
LOGP(DMAIN, LOGL_ERROR, "Deleting subscriber data for IMSI %s\n",
gsup.imsi);
break;
case OSMO_GSUP_MSGT_PROC_SS_REQUEST:
case OSMO_GSUP_MSGT_PROC_SS_RESULT:
rx_proc_ss_req(conn, &gsup);
break;
case OSMO_GSUP_MSGT_PROC_SS_ERROR:
rx_proc_ss_error(conn, &gsup);
break;
case OSMO_GSUP_MSGT_INSERT_DATA_ERROR:
case OSMO_GSUP_MSGT_INSERT_DATA_RESULT:
case OSMO_GSUP_MSGT_LOCATION_CANCEL_ERROR:
@@ -559,6 +576,10 @@ int main(int argc, char **argv)
vty_info.tall_ctx = hlr_ctx;
g_hlr = talloc_zero(hlr_ctx, struct hlr);
INIT_LLIST_HEAD(&g_hlr->euse_list);
INIT_LLIST_HEAD(&g_hlr->iuse_list);
INIT_LLIST_HEAD(&g_hlr->ss_sessions);
INIT_LLIST_HEAD(&g_hlr->ussd_routes);
rc = osmo_init_logging2(hlr_ctx, &hlr_log_info);
if (rc < 0) {

View File

@@ -23,6 +23,9 @@
#pragma once
#include <stdbool.h>
#include <osmocom/core/linuxlist.h>
struct hlr_euse;
struct hlr {
/* GSUP server pointer */
@@ -37,8 +40,18 @@ struct hlr {
/* Local bind addr */
char *gsup_bind_addr;
struct llist_head euse_list;
struct hlr_euse *euse_default;
struct llist_head iuse_list;
struct llist_head ussd_routes;
struct llist_head ss_sessions;
};
extern struct hlr *g_hlr;
struct hlr_subscriber;
void osmo_hlr_subscriber_update_notify(struct hlr_subscriber *subscr);

565
src/hlr_ussd.c Normal file
View File

@@ -0,0 +1,565 @@
/* OsmoHLR SS/USSD implementation */
/* (C) 2018 Harald Welte <laforge@gnumonks.org>
*
* 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 <osmocom/core/talloc.h>
#include <osmocom/core/timer.h>
#include <osmocom/gsm/gsup.h>
#include <osmocom/gsm/gsm0480.h>
#include <osmocom/gsm/protocol/gsm_04_80.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include "hlr.h"
#include "hlr_ussd.h"
#include "gsup_server.h"
#include "gsup_router.h"
#include "logging.h"
/***********************************************************************
* core data structures expressing config from VTY
***********************************************************************/
struct hlr_euse *euse_find(struct hlr *hlr, const char *name)
{
struct hlr_euse *euse;
llist_for_each_entry(euse, &hlr->euse_list, list) {
if (!strcmp(euse->name, name))
return euse;
}
return NULL;
}
struct hlr_euse *euse_alloc(struct hlr *hlr, const char *name)
{
struct hlr_euse *euse = euse_find(hlr, name);
if (euse)
return NULL;
euse = talloc_zero(hlr, struct hlr_euse);
euse->name = talloc_strdup(euse, name);
euse->hlr = hlr;
llist_add_tail(&euse->list, &hlr->euse_list);
return euse;
}
void euse_del(struct hlr_euse *euse)
{
llist_del(&euse->list);
talloc_free(euse);
}
struct hlr_ussd_route *ussd_route_find_prefix(struct hlr *hlr, const char *prefix)
{
struct hlr_ussd_route *rt;
llist_for_each_entry(rt, &hlr->ussd_routes, list) {
if (!strcmp(rt->prefix, prefix))
return rt;
}
return NULL;
}
struct hlr_ussd_route *ussd_route_prefix_alloc_int(struct hlr *hlr, const char *prefix,
const struct hlr_iuse *iuse)
{
struct hlr_ussd_route *rt;
if (ussd_route_find_prefix(hlr, prefix))
return NULL;
rt = talloc_zero(hlr, struct hlr_ussd_route);
rt->prefix = talloc_strdup(rt, prefix);
rt->u.iuse = iuse;
llist_add_tail(&rt->list, &hlr->ussd_routes);
return rt;
}
struct hlr_ussd_route *ussd_route_prefix_alloc_ext(struct hlr *hlr, const char *prefix,
struct hlr_euse *euse)
{
struct hlr_ussd_route *rt;
if (ussd_route_find_prefix(hlr, prefix))
return NULL;
rt = talloc_zero(hlr, struct hlr_ussd_route);
rt->prefix = talloc_strdup(rt, prefix);
rt->is_external = true;
rt->u.euse = euse;
llist_add_tail(&rt->list, &hlr->ussd_routes);
return rt;
}
void ussd_route_del(struct hlr_ussd_route *rt)
{
llist_del(&rt->list);
talloc_free(rt);
}
static struct hlr_ussd_route *ussd_route_lookup_7bit(struct hlr *hlr, const char *ussd_code)
{
struct hlr_ussd_route *rt;
llist_for_each_entry(rt, &hlr->ussd_routes, list) {
if (!strncmp(ussd_code, rt->prefix, strlen(rt->prefix))) {
LOGP(DSS, LOGL_DEBUG, "Found EUSE %s (prefix %s) for USSD Code '%s'\n",
rt->u.euse->name, rt->prefix, ussd_code);
return rt;
}
}
LOGP(DSS, LOGL_DEBUG, "Could not find Route for USSD Code '%s'\n", ussd_code);
return NULL;
}
/***********************************************************************
* handling functions for individual GSUP messages
***********************************************************************/
#define LOGPSS(ss, lvl, fmt, args...) \
LOGP(DSS, lvl, "%s/0x%08x: " fmt, (ss)->imsi, (ss)->session_id, ## args)
struct ss_session {
/* link us to hlr->ss_sessions */
struct llist_head list;
/* imsi of this session */
char imsi[GSM23003_IMSI_MAX_DIGITS+2];
/* ID of this session (unique per IMSI) */
uint32_t session_id;
/* state of the session */
enum osmo_gsup_session_state state;
/* time-out when we will delete the session */
struct osmo_timer_list timeout;
/* is this USSD for an external handler (EUSE): true */
bool is_external;
union {
/* external USSD Entity responsible for this session */
struct hlr_euse *euse;
/* internal USSD Entity responsible for this session */
const struct hlr_iuse *iuse;
} u;
/* 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 ss_session *ss_session_find(struct hlr *hlr, const char *imsi, uint32_t session_id)
{
struct ss_session *ss;
llist_for_each_entry(ss, &hlr->ss_sessions, list) {
if (!strcmp(ss->imsi, imsi) && ss->session_id == session_id)
return ss;
}
return NULL;
}
void ss_session_free(struct ss_session *ss)
{
osmo_timer_del(&ss->timeout);
llist_del(&ss->list);
talloc_free(ss);
}
static void ss_session_timeout(void *data)
{
struct ss_session *ss = data;
LOGPSS(ss, LOGL_NOTICE, "SS Session Timeout, destroying\n");
/* FIXME: should we send a ReturnError component to the MS? */
ss_session_free(ss);
}
struct ss_session *ss_session_alloc(struct hlr *hlr, const char *imsi, uint32_t session_id)
{
struct ss_session *ss;
OSMO_ASSERT(!ss_session_find(hlr, imsi, session_id));
ss = talloc_zero(hlr, struct ss_session);
OSMO_ASSERT(ss);
OSMO_STRLCPY_ARRAY(ss->imsi, imsi);
ss->session_id = session_id;
osmo_timer_setup(&ss->timeout, ss_session_timeout, ss);
/* NOTE: The timeout is currently global and not refreshed with subsequent messages
* within the SS/USSD session. So 30s after the initial SS message, the session will
* timeout! */
osmo_timer_schedule(&ss->timeout, 30, 0);
llist_add_tail(&ss->list, &hlr->ss_sessions);
return ss;
}
/***********************************************************************
* handling functions for encoding SS messages + wrapping them in GSUP
***********************************************************************/
static int ss_tx_to_ms(struct ss_session *ss, enum osmo_gsup_message_type gsup_msg_type,
bool final, struct msgb *ss_msg)
{
struct osmo_gsup_message resp = {0};
struct msgb *resp_msg;
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 = gsm0480_msgb_alloc_name(__func__);
OSMO_ASSERT(resp_msg);
osmo_gsup_encode(resp_msg, &resp);
msgb_free(ss_msg);
/* FIXME: resolve this based on the database vlr_addr */
return osmo_gsup_addr_send(g_hlr->gs, (uint8_t *)"MSC-00-00-00-00-00-00", 22, resp_msg);
}
#if 0
static int ss_tx_reject(struct ss_session *ss, int invoke_id, uint8_t problem_tag,
uint8_t problem_code)
{
struct msgb *msg = gsm0480_gen_reject(invoke_id, problem_tag, problem_code);
LOGPSS(ss, LOGL_NOTICE, "Tx Reject(%u, 0x%02x, 0x%02x)\n", invoke_id,
problem_tag, problem_code);
OSMO_ASSERT(msg);
return ss_tx_to_ms(ss, OSMO_GSUP_MSGT_PROC_SS_RESULT, true, msg);
}
#endif
static int ss_tx_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);
OSMO_ASSERT(msg);
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)
{
struct msgb *msg = gsm0480_gen_ussd_resp_7bit(invoke_id, text);
LOGPSS(ss, LOGL_INFO, "Tx USSD '%s'\n", text);
OSMO_ASSERT(msg);
return ss_tx_to_ms(ss, OSMO_GSUP_MSGT_PROC_SS_RESULT, final, msg);
}
/***********************************************************************
* Internal USSD Handlers
***********************************************************************/
#include "db.h"
static int handle_ussd_own_msisdn(struct osmo_gsup_conn *conn, struct ss_session *ss,
const struct osmo_gsup_message *gsup, const struct ss_request *req)
{
struct hlr_subscriber subscr;
char buf[GSM0480_USSD_7BIT_STRING_LEN+1];
int rc;
rc = db_subscr_get_by_imsi(g_hlr->dbc, ss->imsi, &subscr);
switch (rc) {
case 0:
if (strlen(subscr.msisdn) == 0)
snprintf(buf, sizeof(buf), "You have no MSISDN!");
else
snprintf(buf, sizeof(buf), "Your extension is %s\r", subscr.msisdn);
ss_tx_ussd_7bit(ss, true, req->invoke_id, buf);
break;
case -ENOENT:
ss_tx_error(ss, true, GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER);
break;
case -EIO:
default:
ss_tx_error(ss, true, GSM0480_ERR_CODE_SYSTEM_FAILURE);
break;
}
return 0;
}
static int handle_ussd_own_imsi(struct osmo_gsup_conn *conn, 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!\n", ss->imsi);
ss_tx_ussd_7bit(ss, true, req->invoke_id, buf);
return 0;
}
static const struct hlr_iuse hlr_iuses[] = {
{
.name = "own-msisdn",
.handle_ussd = handle_ussd_own_msisdn,
},
{
.name = "own-imsi",
.handle_ussd = handle_ussd_own_imsi,
},
};
const struct hlr_iuse *iuse_find(const char *name)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(hlr_iuses); i++) {
const struct hlr_iuse *iuse = &hlr_iuses[i];
if (!strcmp(name, iuse->name))
return iuse;
}
return NULL;
}
/***********************************************************************
* handling functions for individual GSUP messages
***********************************************************************/
static bool ss_op_is_ussd(uint8_t opcode)
{
switch (opcode) {
case GSM0480_OP_CODE_PROCESS_USS_DATA:
case GSM0480_OP_CODE_PROCESS_USS_REQ:
case GSM0480_OP_CODE_USS_REQUEST:
case GSM0480_OP_CODE_USS_NOTIFY:
return true;
default:
return false;
}
}
/* is this GSUP connection an EUSE (true) or not (false)? */
static bool conn_is_euse(struct osmo_gsup_conn *conn)
{
int rc;
uint8_t *addr;
rc = osmo_gsup_conn_ccm_get(conn, &addr, IPAC_IDTAG_SERNR);
if (rc <= 5)
return false;
if (!strncmp((char *)addr, "EUSE-", 5))
return true;
else
return false;
}
static struct hlr_euse *euse_by_conn(struct osmo_gsup_conn *conn)
{
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))
return NULL;
return euse_find(hlr, addr+5);
}
static int handle_ss(struct ss_session *ss, const struct osmo_gsup_message *gsup,
const struct ss_request *req)
{
uint8_t comp_type = gsup->ss_info[0];
LOGPSS(ss, LOGL_INFO, "SS CompType=%s, OpCode=%s\n",
gsm0480_comp_type_name(comp_type), gsm0480_op_code_name(req->opcode));
/* FIXME */
return 0;
}
/* 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)
{
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),
req->ussd_text);
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);
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);
/* FIXME: resolve this based on the database vlr_addr */
osmo_gsup_addr_send(conn->server, (uint8_t *)"MSC-00-00-00-00-00-00", 22, msg_out);
} 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);
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);
} else {
msg_out = msgb_alloc_headroom(1024+16, 16, "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);
}
}
return 0;
}
/* 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)
{
struct hlr *hlr = conn->server->priv;
struct ss_session *ss;
struct ss_request req = {0};
LOGP(DSS, LOGL_DEBUG, "%s/0x%08x: Process SS (%s)\n", gsup->imsi, gsup->session_id,
osmo_gsup_session_state_name(gsup->session_state));
/* decode and find out what kind of SS message it is */
if (gsup->ss_info && gsup->ss_info_len) {
if (gsm0480_parse_facility_ie(gsup->ss_info, gsup->ss_info_len, &req)) {
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;
}
}
switch (gsup->session_state) {
case OSMO_GSUP_SESSION_STATE_BEGIN:
/* Check for overlapping Session ID usage */
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;
}
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;
}
if (ss_op_is_ussd(req.opcode)) {
if (conn_is_euse(conn)) {
/* EUSE->VLR: MT USSD. EUSE is known ('conn'), VLR is to be resolved */
ss->u.euse = euse_by_conn(conn);
} else {
/* VLR->EUSE: MO USSD. VLR is known ('conn'), EUSE is to be resolved */
struct hlr_ussd_route *rt;
rt = ussd_route_lookup_7bit(hlr, (const char *) req.ussd_text);
if (rt) {
if (rt->is_external) {
ss->is_external = true;
ss->u.euse = rt->u.euse;
} else if (rt) {
ss->is_external = false;
ss->u.iuse = rt->u.iuse;
}
}
}
/* dispatch unstructured SS to routing */
handle_ussd(conn, ss, gsup, &req);
} else {
/* dispatch non-call SS to internal code */
handle_ss(ss, gsup, &req);
}
break;
case OSMO_GSUP_SESSION_STATE_CONTINUE:
ss = ss_session_find(hlr, gsup->imsi, gsup->session_id);
if (!ss) {
LOGP(DSS, LOGL_ERROR, "%s/0x%08x: CONTINUE for unknown SS session\n",
gsup->imsi, gsup->session_id);
goto out_err;
}
if (ss_op_is_ussd(req.opcode)) {
/* dispatch unstructured SS to routing */
handle_ussd(conn, ss, gsup, &req);
} else {
/* dispatch non-call SS to internal code */
handle_ss(ss, gsup, &req);
}
break;
case OSMO_GSUP_SESSION_STATE_END:
ss = ss_session_find(hlr, gsup->imsi, gsup->session_id);
if (!ss) {
LOGP(DSS, LOGL_ERROR, "%s/0x%08x: END for unknown SS session\n",
gsup->imsi, gsup->session_id);
goto out_err;
}
if (ss_op_is_ussd(req.opcode)) {
/* dispatch unstructured SS to routing */
handle_ussd(conn, ss, gsup, &req);
} else {
/* dispatch non-call SS to internal code */
handle_ss(ss, gsup, &req);
}
ss_session_free(ss);
break;
default:
LOGP(DSS, LOGL_ERROR, "%s/0x%08x: Unknown SS State %d\n", gsup->imsi,
gsup->session_id, gsup->session_state);
goto out_err;
}
return 0;
out_err:
return 0;
}
int rx_proc_ss_error(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup)
{
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;
}

57
src/hlr_ussd.h Normal file
View File

@@ -0,0 +1,57 @@
#include <stdint.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/gsm/gsup.h>
#include "gsup_server.h"
struct osmo_gsup_conn;
struct hlr_ussd_route {
/* g_hlr.routes */
struct llist_head list;
const char *prefix;
bool is_external;
union {
struct hlr_euse *euse;
const struct hlr_iuse *iuse;
} u;
};
struct hlr_euse {
/* list in the per-hlr list of EUSEs */
struct llist_head list;
struct hlr *hlr;
/* name (must match the IPA ID tag) */
const char *name;
/* human-readable description */
const char *description;
/* GSUP connection to the EUSE, if any */
struct osmo_gsup_conn *conn;
};
struct hlr_euse *euse_find(struct hlr *hlr, const char *name);
struct hlr_euse *euse_alloc(struct hlr *hlr, const char *name);
void euse_del(struct hlr_euse *euse);
const struct hlr_iuse *iuse_find(const char *name);
struct hlr_ussd_route *ussd_route_find_prefix(struct hlr *hlr, const char *prefix);
struct hlr_ussd_route *ussd_route_prefix_alloc_int(struct hlr *hlr, const char *prefix,
const struct hlr_iuse *iuse);
struct hlr_ussd_route *ussd_route_prefix_alloc_ext(struct hlr *hlr, const char *prefix,
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);
struct ss_session;
struct ss_request;
/* Internal USSD Handler */
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);
};

View File

@@ -6,6 +6,10 @@
*
* All Rights Reserved
*
* (C) 2018 Harald Welte <laforge@gnumonks.org>
*
* 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
@@ -28,12 +32,11 @@
#include <osmocom/vty/misc.h>
#include <osmocom/abis/ipa.h>
#include "hlr.h"
#include "hlr_vty.h"
#include "hlr_vty_subscr.h"
#include "gsup_server.h"
static struct hlr *g_hlr = NULL;
struct cmd_node hlr_node = {
HLR_NODE,
"%s(config-hlr)# ",
@@ -119,12 +122,180 @@ DEFUN(cfg_hlr_gsup_bind_ip,
return CMD_SUCCESS;
}
/***********************************************************************
* USSD Entity
***********************************************************************/
#include "hlr_ussd.h"
#define USSD_STR "USSD Configuration\n"
#define UROUTE_STR "Routing Configuration\n"
#define PREFIX_STR "Prefix-Matching Route\n" "USSD Prefix\n"
#define INT_CHOICE "(own-msisdn|own-imsi)"
#define INT_STR "Internal USSD Handler\n" \
"Respond with subscribers' own MSISDN\n" \
"Respond with subscribers' own IMSI\n"
#define EXT_STR "External USSD Handler\n" \
"Name of External USSD Handler (IPA CCM ID)\n"
DEFUN(cfg_ussd_route_pfx_int, cfg_ussd_route_pfx_int_cmd,
"ussd route prefix PREFIX internal " INT_CHOICE,
USSD_STR UROUTE_STR PREFIX_STR INT_STR)
{
const struct hlr_iuse *iuse = iuse_find(argv[1]);
struct hlr_ussd_route *rt = ussd_route_find_prefix(g_hlr, argv[0]);
if (rt) {
vty_out(vty, "%% Cannot add [another?] route for prefix %s%s", argv[0], VTY_NEWLINE);
return CMD_WARNING;
}
ussd_route_prefix_alloc_int(g_hlr, argv[0], iuse);
return CMD_SUCCESS;
}
DEFUN(cfg_ussd_route_pfx_ext, cfg_ussd_route_pfx_ext_cmd,
"ussd route prefix PREFIX external EUSE",
USSD_STR UROUTE_STR PREFIX_STR EXT_STR)
{
struct hlr_euse *euse = euse_find(g_hlr, argv[1]);
struct hlr_ussd_route *rt = ussd_route_find_prefix(g_hlr, argv[0]);
if (rt) {
vty_out(vty, "%% Cannot add [another?] route for prefix %s%s", argv[0], VTY_NEWLINE);
return CMD_WARNING;
}
if (!euse) {
vty_out(vty, "%% Cannot find euse '%s'%s", argv[1], VTY_NEWLINE);
return CMD_WARNING;
}
ussd_route_prefix_alloc_ext(g_hlr, argv[0], euse);
return CMD_SUCCESS;
}
DEFUN(cfg_ussd_no_route_pfx, cfg_ussd_no_route_pfx_cmd,
"no ussd route prefix PREFIX",
NO_STR USSD_STR UROUTE_STR PREFIX_STR)
{
struct hlr_ussd_route *rt = ussd_route_find_prefix(g_hlr, argv[0]);
if (!rt) {
vty_out(vty, "%% Cannot find route for prefix %s%s", argv[0], VTY_NEWLINE);
return CMD_WARNING;
}
ussd_route_del(rt);
return CMD_SUCCESS;
}
DEFUN(cfg_ussd_defaultroute, cfg_ussd_defaultroute_cmd,
"ussd default-route external EUSE",
USSD_STR "Configure default-route for all USSD to unknown destinations\n"
EXT_STR)
{
struct hlr_euse *euse = euse_find(g_hlr, argv[0]);
if (g_hlr->euse_default != euse) {
vty_out(vty, "Switching default route from %s to %s%s",
g_hlr->euse_default->name, euse->name, VTY_NEWLINE);
g_hlr->euse_default = euse;
}
return CMD_SUCCESS;
}
DEFUN(cfg_ussd_no_defaultroute, cfg_ussd_no_defaultroute_cmd,
"no ussd default-route",
NO_STR USSD_STR "Remove the default-route for all USSD to unknown destinations\n")
{
g_hlr->euse_default = NULL;
return CMD_SUCCESS;
}
struct cmd_node euse_node = {
EUSE_NODE,
"%s(config-hlr-euse)# ",
1,
};
DEFUN(cfg_euse, cfg_euse_cmd,
"euse NAME",
"Configure a particular External USSD Entity\n"
"Alphanumeric name of the External USSD Entity\n")
{
struct hlr_euse *euse;
const char *id = argv[0];
euse = euse_find(g_hlr, id);
if (!euse) {
euse = euse_alloc(g_hlr, id);
if (!euse)
return CMD_WARNING;
}
vty->index = euse;
vty->index_sub = &euse->description;
vty->node = EUSE_NODE;
return CMD_SUCCESS;
}
DEFUN(cfg_no_euse, cfg_no_euse_cmd,
"no euse NAME",
NO_STR "Remove a particular External USSD Entity\n"
"Alphanumeric name of the External USSD Entity\n")
{
struct hlr_euse *euse = euse_find(g_hlr, argv[0]);
if (!euse) {
vty_out(vty, "%% Cannot remove non-existant EUSE %s%s", argv[0], VTY_NEWLINE);
return CMD_WARNING;
}
if (g_hlr->euse_default == euse) {
vty_out(vty, "%% Cannot remove EUSE %s, it is the default route%s", argv[0], VTY_NEWLINE);
return CMD_WARNING;
}
euse_del(euse);
return CMD_SUCCESS;
}
static void dump_one_euse(struct vty *vty, struct hlr_euse *euse)
{
vty_out(vty, " euse %s%s", euse->name, VTY_NEWLINE);
if (g_hlr->euse_default == euse)
vty_out(vty, " default-route%s", VTY_NEWLINE);
}
static int config_write_euse(struct vty *vty)
{
struct hlr_euse *euse;
struct hlr_ussd_route *rt;
llist_for_each_entry(euse, &g_hlr->euse_list, list)
dump_one_euse(vty, euse);
llist_for_each_entry(rt, &g_hlr->ussd_routes, list) {
vty_out(vty, " ussd route prefix %s %s %s%s", rt->prefix,
rt->is_external ? "external" : "internal",
rt->is_external ? rt->u.euse->name : rt->u.iuse->name,
VTY_NEWLINE);
}
return 0;
}
/***********************************************************************
* Common Code
***********************************************************************/
int hlr_vty_go_parent(struct vty *vty)
{
switch (vty->node) {
case GSUP_NODE:
case EUSE_NODE:
vty->node = HLR_NODE;
vty->index = NULL;
vty->index_sub = NULL;
break;
default:
case HLR_NODE:
@@ -154,8 +325,6 @@ int hlr_vty_is_config_node(struct vty *vty, int node)
void hlr_vty_init(struct hlr *hlr, const struct log_info *cat)
{
g_hlr = hlr;
logging_vty_add_cmds(cat);
osmo_talloc_vty_add_cmds();
@@ -169,5 +338,14 @@ void hlr_vty_init(struct hlr *hlr, const struct log_info *cat)
install_element(GSUP_NODE, &cfg_hlr_gsup_bind_ip_cmd);
install_element(HLR_NODE, &cfg_euse_cmd);
install_element(HLR_NODE, &cfg_no_euse_cmd);
install_node(&euse_node, config_write_euse);
install_element(HLR_NODE, &cfg_ussd_route_pfx_int_cmd);
install_element(HLR_NODE, &cfg_ussd_route_pfx_ext_cmd);
install_element(HLR_NODE, &cfg_ussd_no_route_pfx_cmd);
install_element(HLR_NODE, &cfg_ussd_defaultroute_cmd);
install_element(HLR_NODE, &cfg_ussd_no_defaultroute_cmd);
hlr_vty_subscriber_init(hlr);
}

View File

@@ -30,6 +30,7 @@
enum hlr_vty_node {
HLR_NODE = _LAST_OSMOVTY_NODE + 1,
GSUP_NODE,
EUSE_NODE,
};
int hlr_vty_is_config_node(struct vty *vty, int node);

View File

@@ -33,8 +33,6 @@ struct vty;
#define hexdump_buf(buf) osmo_hexdump_nospc((void*)buf, sizeof(buf))
static struct hlr *g_hlr = NULL;
static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr)
{
int rc;
@@ -478,8 +476,6 @@ DEFUN(subscriber_aud3g,
void hlr_vty_subscriber_init(struct hlr *hlr)
{
g_hlr = hlr;
install_element_ve(&subscriber_show_cmd);
install_element(ENABLE_NODE, &subscriber_create_cmd);
install_element(ENABLE_NODE, &subscriber_delete_cmd);

View File

@@ -19,6 +19,13 @@ const struct log_info_cat hlr_log_info_cat[] = {
.color = "\033[1;33m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DSS] = {
.name = "DSS",
.description = "Supplementary Services",
.color = "\033[1;34m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
};
const struct log_info hlr_log_info = {

View File

@@ -7,6 +7,7 @@ enum {
DDB,
DGSUP,
DAUC,
DSS,
};
extern const struct log_info hlr_log_info;

196
src/osmo-euse-demo.c Normal file
View File

@@ -0,0 +1,196 @@
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/select.h>
#include <osmocom/core/application.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/logging.h>
#include <osmocom/gsm/gsup.h>
#include <osmocom/gsm/gsm0480.h>
#include <osmocom/gsm/protocol/gsm_04_80.h>
#include <osmocom/gsupclient/gsup_client.h>
#include "logging.h"
static struct osmo_gsup_client *g_gc;
/*! send a SS/USSD response to a given imsi/session.
* \param[in] gsupc GSUP client connection through which to send
* \param[in] imsi IMSI of the subscriber
* \param[in] session_id Unique identifier of SS session for which this response is
* \param[in] gsup_msg_type GSUP message type (OSMO_GSUP_MSGT_PROC_SS_{REQUEST,RESULT,ERROR})
* \param[in] final Is this the final result (true=END) or an intermediate result (false=CONTINUE)
* \param[in] msg Optional binary/BER encoded SS date (for FACILITY IE). Can be NULL. Freed in
* this function call.
*/
static int euse_tx_ss(struct osmo_gsup_client *gsupc, const char *imsi, uint32_t session_id,
enum osmo_gsup_message_type gsup_msg_type, bool final, struct msgb *ss_msg)
{
struct osmo_gsup_message resp = {0};
struct msgb *resp_msg;
switch (gsup_msg_type) {
case OSMO_GSUP_MSGT_PROC_SS_REQUEST:
case OSMO_GSUP_MSGT_PROC_SS_RESULT:
case OSMO_GSUP_MSGT_PROC_SS_ERROR:
break;
default:
msgb_free(ss_msg);
return -EINVAL;
}
resp.message_type = gsup_msg_type;
OSMO_STRLCPY_ARRAY(resp.imsi, imsi);
if (final)
resp.session_state = OSMO_GSUP_SESSION_STATE_END;
else
resp.session_state = OSMO_GSUP_SESSION_STATE_CONTINUE;
resp.session_id = session_id;
if (ss_msg) {
resp.ss_info = msgb_data(ss_msg);
resp.ss_info_len = msgb_length(ss_msg);
}
resp_msg = gsm0480_msgb_alloc_name(__func__);
OSMO_ASSERT(resp_msg);
osmo_gsup_encode(resp_msg, &resp);
msgb_free(ss_msg);
return osmo_gsup_client_send(gsupc, resp_msg);
}
/*! send a SS/USSD reject to a given IMSI/session.
* \param[in] gsupc GSUP client connection through which to send
* \param[in] imsi IMSI of the subscriber
* \param[in] session_id Unique identifier of SS session for which this response is
* \param[in] invoke_id InvokeID of the request
* \param[in] problem_tag Problem code tag (table 3.13)
* \param[in] problem_code Problem code (table 3.14-3.17)
*/
static int euse_tx_ussd_reject(struct osmo_gsup_client *gsupc, const char *imsi, uint32_t session_id,
int invoke_id, uint8_t problem_tag, uint8_t problem_code)
{
struct msgb *msg = gsm0480_gen_reject(invoke_id, problem_tag, problem_code);
LOGP(DMAIN, LOGL_NOTICE, "Tx %s/0x%08x: Reject(%d, 0x%02x, 0x%02x)\n", imsi, session_id,
invoke_id, problem_tag, problem_code);
OSMO_ASSERT(msg);
return euse_tx_ss(gsupc, imsi, session_id, OSMO_GSUP_MSGT_PROC_SS_RESULT, true, msg);
}
/*! send a SS/USSD response in 7-bit GSM default alphabet o a given imsi/session.
* \param[in] gsupc GSUP client connection through which to send
* \param[in] imsi IMSI of the subscriber
* \param[in] session_id Unique identifier of SS session for which this response is
* \param[in] final Is this the final result (true=END) or an intermediate result
* (false=CONTINUE)
* \param[in] invoke_id InvokeID of the request
*/
static int euse_tx_ussd_resp_7bit(struct osmo_gsup_client *gsupc, const char *imsi, uint32_t session_id,
bool final, uint8_t invoke_id, const char *text)
{
struct msgb *ss_msg;
/* encode response; remove L3 header */
ss_msg = gsm0480_gen_ussd_resp_7bit(invoke_id, text);
LOGP(DMAIN, LOGL_DEBUG, "Tx %s/0x%08x: USSD Result(%d, %s, '%s')\n", imsi, session_id,
invoke_id, final ? "END" : "CONTINUE", text);
OSMO_ASSERT(ss_msg);
return euse_tx_ss(gsupc, imsi, session_id, OSMO_GSUP_MSGT_PROC_SS_RESULT, final, ss_msg);
}
static int euse_rx_proc_ss_req(struct osmo_gsup_client *gsupc, const struct osmo_gsup_message *gsup)
{
char buf[GSM0480_USSD_7BIT_STRING_LEN+1];
struct ss_request req = {0};
if (gsup->ss_info && gsup->ss_info_len) {
if (gsm0480_parse_facility_ie(gsup->ss_info, gsup->ss_info_len, &req)) {
return euse_tx_ussd_reject(gsupc, gsup->imsi, gsup->session_id, -1,
GSM_0480_PROBLEM_CODE_TAG_GENERAL,
GSM_0480_GEN_PROB_CODE_BAD_STRUCTURE);
}
}
LOGP(DMAIN, LOGL_INFO, "Rx %s/0x%08x: USSD SessionState=%s, OpCode=%s, '%s'\n", gsup->imsi,
gsup->session_id, osmo_gsup_session_state_name(gsup->session_state),
gsm0480_op_code_name(req.opcode), req.ussd_text);
/* we only handle single-request-response USSD in this demo */
if (gsup->session_state != OSMO_GSUP_SESSION_STATE_BEGIN) {
return euse_tx_ussd_reject(gsupc, gsup->imsi, gsup->session_id, req.invoke_id,
GSM_0480_PROBLEM_CODE_TAG_GENERAL,
GSM_0480_GEN_PROB_CODE_UNRECOGNISED);
}
snprintf(buf, sizeof(buf), "You sent \"%s\"", req.ussd_text);
return euse_tx_ussd_resp_7bit(gsupc, gsup->imsi, gsup->session_id, true, req.invoke_id, buf);
}
static int gsupc_read_cb(struct osmo_gsup_client *gsupc, struct msgb *msg)
{
struct osmo_gsup_message gsup_msg = {0};
int rc;
rc = osmo_gsup_decode(msgb_l2(msg), msgb_l2len(msg), &gsup_msg);
if (rc < 0) {
LOGP(DMAIN, LOGL_ERROR, "Error decoding GSUP: %s\n", msgb_hexdump(msg));
return rc;
}
DEBUGP(DMAIN, "Rx GSUP %s: %s\n", osmo_gsup_message_type_name(gsup_msg.message_type),
msgb_hexdump(msg));
//if (strlen(gsup_msg.imsi) < 5)
//return gsup_send_err_reply(gsupc, gsup.imsi, gsup.message_type, GMM_CAUSE_INV_MAND_INFO);
switch (gsup_msg.message_type) {
case OSMO_GSUP_MSGT_PROC_SS_REQUEST:
case OSMO_GSUP_MSGT_PROC_SS_RESULT:
euse_rx_proc_ss_req(gsupc, &gsup_msg);
break;
case OSMO_GSUP_MSGT_PROC_SS_ERROR:
break;
default:
LOGP(DMAIN, LOGL_DEBUG, "Unhandled GSUP message type %s\n",
osmo_gsup_message_type_name(gsup_msg.message_type));
break;
}
msgb_free(msg);
return 0;
}
static struct log_info_cat default_categories[] = {
[DMAIN] = {
.name = "DMAIN",
.description = "Main Program",
.enabled = 1, .loglevel = LOGL_DEBUG,
},
};
static const struct log_info gsup_log_info = {
.cat = default_categories,
.num_cat = ARRAY_SIZE(default_categories),
};
int main(int argc, char **argv)
{
char *server_host = "127.0.0.1";
uint16_t server_port = OSMO_GSUP_PORT;
void *ctx = talloc_named_const(NULL, 0, "demo-euse");
osmo_init_logging2(ctx, &gsup_log_info);
g_gc = osmo_gsup_client_create(ctx, "EUSE-foobar", server_host, server_port, gsupc_read_cb, NULL);
while (1) {
osmo_select_main(0);
}
exit(0);
}