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
This commit is contained in:
Harald Welte 2018-07-29 16:14:48 +02:00
parent edc1fc0c25
commit 08d5204f0c
6 changed files with 248 additions and 86 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

@ -568,7 +568,9 @@ int main(int argc, char **argv)
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

@ -43,6 +43,9 @@ struct hlr {
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;
};

View File

@ -27,6 +27,7 @@
#include <osmocom/gsm/protocol/gsm_04_80.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include "hlr.h"
#include "hlr_ussd.h"
@ -58,7 +59,6 @@ struct hlr_euse *euse_alloc(struct hlr *hlr, const char *name)
euse = talloc_zero(hlr, struct hlr_euse);
euse->name = talloc_strdup(euse, name);
euse->hlr = hlr;
INIT_LLIST_HEAD(&euse->routes);
llist_add_tail(&euse->list, &hlr->euse_list);
return euse;
@ -71,54 +71,68 @@ void euse_del(struct hlr_euse *euse)
}
struct hlr_euse_route *euse_route_find(struct hlr_euse *euse, const char *prefix)
struct hlr_ussd_route *ussd_route_find_prefix(struct hlr *hlr, const char *prefix)
{
struct hlr_euse_route *rt;
struct hlr_ussd_route *rt;
llist_for_each_entry(rt, &euse->routes, list) {
llist_for_each_entry(rt, &hlr->ussd_routes, list) {
if (!strcmp(rt->prefix, prefix))
return rt;
}
return NULL;
}
struct hlr_euse_route *euse_route_prefix_alloc(struct hlr_euse *euse, 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_euse_route *rt;
struct hlr_ussd_route *rt;
if (euse_route_find(euse, prefix))
if (ussd_route_find_prefix(hlr, prefix))
return NULL;
rt = talloc_zero(euse, struct hlr_euse_route);
rt = talloc_zero(hlr, struct hlr_ussd_route);
rt->prefix = talloc_strdup(rt, prefix);
rt->euse = euse;
llist_add_tail(&rt->list, &euse->routes);
rt->u.iuse = iuse;
llist_add_tail(&rt->list, &hlr->ussd_routes);
return rt;
}
void euse_route_del(struct hlr_euse_route *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);
}
struct hlr_euse *ussd_euse_find_7bit_gsm(struct hlr *hlr, const char *ussd_code)
static struct hlr_ussd_route *ussd_route_lookup_7bit(struct hlr *hlr, const char *ussd_code)
{
struct hlr_euse *euse;
llist_for_each_entry(euse, &hlr->euse_list, list) {
struct hlr_euse_route *rt;
llist_for_each_entry(rt, &euse->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->euse->name, rt->prefix, ussd_code);
return rt->euse;
}
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/EUSE for USSD Code '%s'\n", ussd_code);
LOGP(DSS, LOGL_DEBUG, "Could not find Route for USSD Code '%s'\n", ussd_code);
return NULL;
}
@ -141,8 +155,15 @@ struct ss_session {
/* time-out when we will delete the session */
struct osmo_timer_list timeout;
/* external USSD Entity responsible for this session */
struct hlr_euse *euse;
/* 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 */
@ -247,6 +268,79 @@ 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)
{
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;
}
/***********************************************************************
@ -307,6 +401,7 @@ static int handle_ss(struct ss_session *ss, const struct osmo_gsup_message *gsup
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)
{
@ -318,8 +413,7 @@ static int handle_ussd(struct osmo_gsup_conn *conn, struct ss_session *ss,
gsm0480_comp_type_name(comp_type), gsm0480_op_code_name(req->opcode),
req->ussd_text);
if (!ss->euse) {
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;
@ -333,19 +427,25 @@ static int handle_ussd(struct osmo_gsup_conn *conn, struct ss_session *ss,
/* 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, Forward to EUSE */
char addr[128];
strcpy(addr, "EUSE-");
osmo_strlcpy(addr+5, ss->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);
/* 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 {
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);
/* Handle internally */
ss->u.iuse->handle_ussd(conn, ss, gsup, req);
}
}
@ -392,10 +492,20 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *
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->euse = euse_by_conn(conn);
ss->u.euse = euse_by_conn(conn);
} else {
/* VLR->EUSE: MO USSD. VLR is known ('conn'), EUSE is to be resolved */
ss->euse = ussd_euse_find_7bit_gsm(hlr, (const char *) req.ussd_text);
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);

View File

@ -5,11 +5,15 @@
struct osmo_gsup_conn;
struct hlr_euse_route {
/* hlr_euse.routes */
struct hlr_ussd_route {
/* g_hlr.routes */
struct llist_head list;
struct hlr_euse *euse;
const char *prefix;
bool is_external;
union {
struct hlr_euse *euse;
const struct hlr_iuse *iuse;
} u;
};
struct hlr_euse {
@ -20,21 +24,34 @@ struct hlr_euse {
const char *name;
/* human-readable description */
const char *description;
/* list of hlr_euse_route */
struct llist_head routes;
/* 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);
struct hlr_euse_route *euse_route_find(struct hlr_euse *euse, const char *prefix);
struct hlr_euse_route *euse_route_prefix_alloc(struct hlr_euse *euse, const char *prefix);
void euse_route_del(struct hlr_euse_route *rt);
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

@ -32,6 +32,7 @@
#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"
@ -122,47 +123,77 @@ DEFUN(cfg_hlr_gsup_bind_ip,
}
/***********************************************************************
* External USSD Entity
* USSD Entity
***********************************************************************/
#include "hlr_ussd.h"
DEFUN(cfg_euse_route_pfx, cfg_euse_route_pfx_cmd,
"route prefix PREFIX",
"")
{
struct hlr_euse *euse = vty->index;
struct hlr_euse_route *rt = euse_route_find(euse, argv[0]);
#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;
}
euse_route_prefix_alloc(euse, argv[0]);
ussd_route_prefix_alloc_int(g_hlr, argv[0], iuse);
return CMD_SUCCESS;
}
DEFUN(cfg_euse_no_route_pfx, cfg_euse_no_route_pfx_cmd,
"no route prefix PREFIX",
NO_STR "")
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 = vty->index;
struct hlr_euse_route *rt = euse_route_find(euse, argv[0]);
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;
}
euse_route_del(rt);
ussd_route_del(rt);
return CMD_SUCCESS;
}
DEFUN(cfg_euse_defaultroute, cfg_euse_defaultroute_cmd,
"default-route",
"Set this EUSE as default-route for all USSD to unknown destinations\n")
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 = vty->index;
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",
@ -173,16 +204,10 @@ DEFUN(cfg_euse_defaultroute, cfg_euse_defaultroute_cmd,
return CMD_SUCCESS;
}
DEFUN(cfg_euse_no_defaultroute, cfg_euse_no_defaultroute_cmd,
"no default-route",
NO_STR "Remove this EUSE as default-route for all USSD to unknown destinations\n")
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")
{
struct hlr_euse *euse = vty->index;
if (g_hlr->euse_default != euse) {
vty_out(vty, "%% Current EUSE is no default route, cannot delete it%s", VTY_NEWLINE);
return CMD_WARNING;
}
g_hlr->euse_default = NULL;
return CMD_SUCCESS;
@ -235,13 +260,8 @@ DEFUN(cfg_no_euse, cfg_no_euse_cmd,
static void dump_one_euse(struct vty *vty, struct hlr_euse *euse)
{
struct hlr_euse_route *er;
vty_out(vty, " euse %s%s", euse->name, VTY_NEWLINE);
llist_for_each_entry(er, &euse->routes, list)
vty_out(vty, " route prefix %s%s", er->prefix, VTY_NEWLINE);
if (g_hlr->euse_default == euse)
vty_out(vty, " default-route%s", VTY_NEWLINE);
}
@ -249,10 +269,18 @@ static void dump_one_euse(struct vty *vty, struct hlr_euse *euse)
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;
}
@ -313,10 +341,11 @@ void hlr_vty_init(struct hlr *hlr, const struct log_info *cat)
install_element(HLR_NODE, &cfg_euse_cmd);
install_element(HLR_NODE, &cfg_no_euse_cmd);
install_node(&euse_node, config_write_euse);
install_element(EUSE_NODE, &cfg_euse_route_pfx_cmd);
install_element(EUSE_NODE, &cfg_euse_no_route_pfx_cmd);
install_element(EUSE_NODE, &cfg_euse_defaultroute_cmd);
install_element(EUSE_NODE, &cfg_euse_no_defaultroute_cmd);
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);
}