diff options
author | Harald Welte <laforge@gnumonks.org> | 2018-06-16 20:21:10 +0200 |
---|---|---|
committer | Harald Welte <laforge@gnumonks.org> | 2018-07-30 14:53:13 +0000 |
commit | bb77939a86260fcd0c6e2c15b5c6f06df8a0b9fe (patch) | |
tree | bb05e9a1d093fd38dc589fc6dd7b89b288c7aab1 | |
parent | 4956ae1f7074aca8b9c45dfd29bd6e3bc36c65ae (diff) |
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
-rw-r--r-- | src/hlr.c | 9 | ||||
-rw-r--r-- | src/hlr.h | 2 | ||||
-rw-r--r-- | src/hlr_ussd.c | 303 | ||||
-rw-r--r-- | src/hlr_ussd.h | 10 |
4 files changed, 323 insertions, 1 deletions
@@ -42,6 +42,7 @@ #include "rand.h" #include "luop.h" #include "hlr_vty.h" +#include "hlr_ussd.h" struct hlr *g_hlr; static int quit = 0; @@ -402,6 +403,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: @@ -560,6 +568,7 @@ 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->ss_sessions); rc = osmo_init_logging2(hlr_ctx, &hlr_log_info); if (rc < 0) { @@ -43,6 +43,8 @@ struct hlr { struct llist_head euse_list; struct hlr_euse *euse_default; + + struct llist_head ss_sessions; }; extern struct hlr *g_hlr; diff --git a/src/hlr_ussd.c b/src/hlr_ussd.c index 785eafd..41fba34 100644 --- a/src/hlr_ussd.c +++ b/src/hlr_ussd.c @@ -1,4 +1,4 @@ -/* OsmoHLR VTY implementation */ +/* OsmoHLR SS/USSD implementation */ /* (C) 2018 Harald Welte <laforge@gnumonks.org> * @@ -21,11 +21,22 @@ #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 "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) { @@ -91,3 +102,293 @@ void euse_route_del(struct hlr_euse_route *rt) llist_del(&rt->list); talloc_free(rt); } + +struct hlr_euse *ussd_euse_find_7bit_gsm(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(DMAIN, LOGL_DEBUG, "Found EUSE %s (prefix %s) for USSD Code '%s'\n", + rt->euse->name, rt->prefix, ussd_code); + return rt->euse; + } + } + } + + LOGP(DMAIN, LOGL_DEBUG, "Could not find Route/EUSE for USSD Code '%s'\n", ussd_code); + return NULL; +} + +/*********************************************************************** + * handling functions for individual GSUP messages + ***********************************************************************/ + +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; + + /* external USSD Entity responsible for this session */ + struct hlr_euse *euse; + /* 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; + + LOGP(DMAIN, LOGL_NOTICE, "%s/0x%08x: SS Session Timeout, destroying\n", ss->imsi, ss->session_id); + /* 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 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]; + + LOGP(DMAIN, LOGL_INFO, "%s: SS CompType=%s, OpCode=%s\n", gsup->imsi, + gsm0480_comp_type_name(comp_type), gsm0480_op_code_name(req->opcode)); + /* FIXME */ + return 0; +} + +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); + + LOGP(DMAIN, LOGL_INFO, "%s: USSD CompType=%s, OpCode=%s '%s'\n", gsup->imsi, + gsm0480_comp_type_name(comp_type), gsm0480_op_code_name(req->opcode), + req->ussd_text); + + msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP USSD FW"); + OSMO_ASSERT(msg_out); + + if (!ss->euse) { + LOGP(DMAIN, LOGL_NOTICE, "%s: USSD for unknown code '%s'\n", gsup->imsi, req->ussd_text); + /* FIXME: send proper error */ + return 0; + } + + if (is_euse_originated) { + /* 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, 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) { + LOGP(DMAIN, LOGL_ERROR, "Cannot find conn for EUSE %s\n", addr); + /* FIXME: send proper error */ + return -1; + } + osmo_gsup_encode(msg_out, gsup); + osmo_gsup_conn_send(conn, msg_out); + } + + 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(DMAIN, LOGL_INFO, "%s: Process SS (0x%08x, %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(DMAIN, LOGL_ERROR, "%s: Unable to parse SS request for 0x%08x: %s\n", + gsup->imsi, gsup->session_id, + osmo_hexdump(gsup->ss_info, gsup->ss_info_len)); + 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(DMAIN, LOGL_ERROR, "%s/0x%08x: BEGIN with non-uinque session ID!\n", + gsup->imsi, gsup->session_id); + goto out_err; + } + ss = ss_session_alloc(hlr, gsup->imsi, gsup->session_id); + if (!ss) { + LOGP(DMAIN, LOGL_ERROR, "%s: Unable to allocate SS session for 0x%08x\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->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); + } + /* 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(DMAIN, LOGL_ERROR, "%s: CONTINUE for unknwon SS session 0x%08x\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(DMAIN, LOGL_ERROR, "%s: END for unknwon SS session 0x%08x\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(DMAIN, LOGL_ERROR, "%s: Unknown SS State %d\n", gsup->imsi, 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(DMAIN, LOGL_NOTICE, "%s: Process SS ERROR (0x%08x, %s)\n", gsup->imsi, gsup->session_id, + osmo_gsup_session_state_name(gsup->session_state)); + return 0; +} diff --git a/src/hlr_ussd.h b/src/hlr_ussd.h index 05d2099..433a7f2 100644 --- a/src/hlr_ussd.h +++ b/src/hlr_ussd.h @@ -1,5 +1,9 @@ #include <stdint.h> #include <osmocom/core/linuxlist.h> +#include <osmocom/gsm/gsup.h> +#include "gsup_server.h" + +struct osmo_gsup_conn; struct hlr_euse_route { /* hlr_euse.routes */ @@ -18,6 +22,9 @@ struct hlr_euse { const char *description; /* list of hlr_euse_route */ struct llist_head routes; + + /* GSUP connection to the EUSE, if any */ + struct osmo_gsup_conn *conn; }; @@ -28,3 +35,6 @@ 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); + +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); |