<p>Harald Welte <strong>merged</strong> this change.</p><p><a href="https://gerrit.osmocom.org/10251">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  Harald Welte: Looks good to me, approved
  Jenkins Builder: Verified

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">USSD: Add support for internal USSD handlers<br><br>There are some requests that are best served inside the HLR, as it<br>has access to subscriber information such as MSISDN and IMSI.<br><br>This unfortunately required quite some restructuring of the USSD<br>related structures including the VTY syntax for adding routes.<br><br>The default config file has been updated to replicate the *#100#<br>built-in behavior of old OsmoNITB.<br><br>Closes: OS#2566<br>Change-Id: I1d09fab810a6bb9ab02904de72dbc9e8a414f9f9<br>---<br>M doc/examples/osmo-hlr.cfg<br>M src/hlr.c<br>M src/hlr.h<br>M src/hlr_ussd.c<br>M src/hlr_ussd.h<br>M src/hlr_vty.c<br>M tests/test_nodes.vty<br>7 files changed, 257 insertions(+), 89 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/doc/examples/osmo-hlr.cfg b/doc/examples/osmo-hlr.cfg</span><br><span>index ebc22ec..35d942d 100644</span><br><span>--- a/doc/examples/osmo-hlr.cfg</span><br><span>+++ b/doc/examples/osmo-hlr.cfg</span><br><span>@@ -21,3 +21,4 @@</span><br><span> hlr</span><br><span>  gsup</span><br><span>   bind ip 127.0.0.1</span><br><span style="color: hsl(120, 100%, 40%);">+ ussd route prefix *#100# internal own-msisdn</span><br><span>diff --git a/src/hlr.c b/src/hlr.c</span><br><span>index bcf620d..df48a99 100644</span><br><span>--- a/src/hlr.c</span><br><span>+++ b/src/hlr.c</span><br><span>@@ -568,7 +568,9 @@</span><br><span> </span><br><span>  g_hlr = talloc_zero(hlr_ctx, struct hlr);</span><br><span>    INIT_LLIST_HEAD(&g_hlr->euse_list);</span><br><span style="color: hsl(120, 100%, 40%);">+    INIT_LLIST_HEAD(&g_hlr->iuse_list);</span><br><span>   INIT_LLIST_HEAD(&g_hlr->ss_sessions);</span><br><span style="color: hsl(120, 100%, 40%);">+  INIT_LLIST_HEAD(&g_hlr->ussd_routes);</span><br><span> </span><br><span>     rc = osmo_init_logging2(hlr_ctx, &hlr_log_info);</span><br><span>         if (rc < 0) {</span><br><span>diff --git a/src/hlr.h b/src/hlr.h</span><br><span>index 7112352..315c3dd 100644</span><br><span>--- a/src/hlr.h</span><br><span>+++ b/src/hlr.h</span><br><span>@@ -43,6 +43,9 @@</span><br><span> </span><br><span>    struct llist_head euse_list;</span><br><span>         struct hlr_euse *euse_default;</span><br><span style="color: hsl(120, 100%, 40%);">+        struct llist_head iuse_list;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        struct llist_head ussd_routes;</span><br><span> </span><br><span>   struct llist_head ss_sessions;</span><br><span> };</span><br><span>diff --git a/src/hlr_ussd.c b/src/hlr_ussd.c</span><br><span>index d23debf..f9399d2 100644</span><br><span>--- a/src/hlr_ussd.c</span><br><span>+++ b/src/hlr_ussd.c</span><br><span>@@ -27,6 +27,7 @@</span><br><span> #include <osmocom/gsm/protocol/gsm_04_80.h></span><br><span> #include <stdint.h></span><br><span> #include <string.h></span><br><span style="color: hsl(120, 100%, 40%);">+#include <errno.h></span><br><span> </span><br><span> #include "hlr.h"</span><br><span> #include "hlr_ussd.h"</span><br><span>@@ -58,7 +59,6 @@</span><br><span>      euse = talloc_zero(hlr, struct hlr_euse);</span><br><span>    euse->name = talloc_strdup(euse, name);</span><br><span>   euse->hlr = hlr;</span><br><span style="color: hsl(0, 100%, 40%);">-     INIT_LLIST_HEAD(&euse->routes);</span><br><span>       llist_add_tail(&euse->list, &hlr->euse_list);</span><br><span> </span><br><span>      return euse;</span><br><span>@@ -71,54 +71,68 @@</span><br><span> }</span><br><span> </span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-struct hlr_euse_route *euse_route_find(struct hlr_euse *euse, const char *prefix)</span><br><span style="color: hsl(120, 100%, 40%);">+struct hlr_ussd_route *ussd_route_find_prefix(struct hlr *hlr, const char *prefix)</span><br><span> {</span><br><span style="color: hsl(0, 100%, 40%);">-       struct hlr_euse_route *rt;</span><br><span style="color: hsl(120, 100%, 40%);">+    struct hlr_ussd_route *rt;</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-  llist_for_each_entry(rt, &euse->routes, list) {</span><br><span style="color: hsl(120, 100%, 40%);">+        llist_for_each_entry(rt, &hlr->ussd_routes, list) {</span><br><span>           if (!strcmp(rt->prefix, prefix))</span><br><span>                  return rt;</span><br><span>   }</span><br><span>    return NULL;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-struct hlr_euse_route *euse_route_prefix_alloc(struct hlr_euse *euse, const char *prefix)</span><br><span style="color: hsl(120, 100%, 40%);">+struct hlr_ussd_route *ussd_route_prefix_alloc_int(struct hlr *hlr, const char *prefix,</span><br><span style="color: hsl(120, 100%, 40%);">+                                                  const struct hlr_iuse *iuse)</span><br><span> {</span><br><span style="color: hsl(0, 100%, 40%);">-    struct hlr_euse_route *rt;</span><br><span style="color: hsl(120, 100%, 40%);">+    struct hlr_ussd_route *rt;</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-  if (euse_route_find(euse, prefix))</span><br><span style="color: hsl(120, 100%, 40%);">+    if (ussd_route_find_prefix(hlr, prefix))</span><br><span>             return NULL;</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-        rt = talloc_zero(euse, struct hlr_euse_route);</span><br><span style="color: hsl(120, 100%, 40%);">+        rt = talloc_zero(hlr, struct hlr_ussd_route);</span><br><span>        rt->prefix = talloc_strdup(rt, prefix);</span><br><span style="color: hsl(0, 100%, 40%);">-      rt->euse = euse;</span><br><span style="color: hsl(0, 100%, 40%);">-     llist_add_tail(&rt->list, &euse->routes);</span><br><span style="color: hsl(120, 100%, 40%);">+       rt->u.iuse = iuse;</span><br><span style="color: hsl(120, 100%, 40%);">+ llist_add_tail(&rt->list, &hlr->ussd_routes);</span><br><span> </span><br><span>      return rt;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-void euse_route_del(struct hlr_euse_route *rt)</span><br><span style="color: hsl(120, 100%, 40%);">+struct hlr_ussd_route *ussd_route_prefix_alloc_ext(struct hlr *hlr, const char *prefix,</span><br><span style="color: hsl(120, 100%, 40%);">+                                               struct hlr_euse *euse)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+  struct hlr_ussd_route *rt;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+  if (ussd_route_find_prefix(hlr, prefix))</span><br><span style="color: hsl(120, 100%, 40%);">+              return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        rt = talloc_zero(hlr, struct hlr_ussd_route);</span><br><span style="color: hsl(120, 100%, 40%);">+ rt->prefix = talloc_strdup(rt, prefix);</span><br><span style="color: hsl(120, 100%, 40%);">+    rt->is_external = true;</span><br><span style="color: hsl(120, 100%, 40%);">+    rt->u.euse = euse;</span><br><span style="color: hsl(120, 100%, 40%);">+ llist_add_tail(&rt->list, &hlr->ussd_routes);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ return rt;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+void ussd_route_del(struct hlr_ussd_route *rt)</span><br><span> {</span><br><span>         llist_del(&rt->list);</span><br><span>         talloc_free(rt);</span><br><span> }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-struct hlr_euse *ussd_euse_find_7bit_gsm(struct hlr *hlr, const char *ussd_code)</span><br><span style="color: hsl(120, 100%, 40%);">+static struct hlr_ussd_route *ussd_route_lookup_7bit(struct hlr *hlr, const char *ussd_code)</span><br><span> {</span><br><span style="color: hsl(0, 100%, 40%);">-    struct hlr_euse *euse;</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-  llist_for_each_entry(euse, &hlr->euse_list, list) {</span><br><span style="color: hsl(0, 100%, 40%);">-              struct hlr_euse_route *rt;</span><br><span style="color: hsl(0, 100%, 40%);">-              llist_for_each_entry(rt, &euse->routes, list) {</span><br><span style="color: hsl(0, 100%, 40%);">-                  if (!strncmp(ussd_code, rt->prefix, strlen(rt->prefix))) {</span><br><span style="color: hsl(0, 100%, 40%);">-                                LOGP(DSS, LOGL_DEBUG, "Found EUSE %s (prefix %s) for USSD Code '%s'\n",</span><br><span style="color: hsl(0, 100%, 40%);">-                                       rt->euse->name, rt->prefix, ussd_code);</span><br><span style="color: hsl(0, 100%, 40%);">-                                return rt->euse;</span><br><span style="color: hsl(0, 100%, 40%);">-                     }</span><br><span style="color: hsl(120, 100%, 40%);">+     struct hlr_ussd_route *rt;</span><br><span style="color: hsl(120, 100%, 40%);">+    llist_for_each_entry(rt, &hlr->ussd_routes, list) {</span><br><span style="color: hsl(120, 100%, 40%);">+            if (!strncmp(ussd_code, rt->prefix, strlen(rt->prefix))) {</span><br><span style="color: hsl(120, 100%, 40%);">+                      LOGP(DSS, LOGL_DEBUG, "Found EUSE %s (prefix %s) for USSD Code '%s'\n",</span><br><span style="color: hsl(120, 100%, 40%);">+                             rt->u.euse->name, rt->prefix, ussd_code);</span><br><span style="color: hsl(120, 100%, 40%);">+                    return rt;</span><br><span>           }</span><br><span>    }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-   LOGP(DSS, LOGL_DEBUG, "Could not find Route/EUSE for USSD Code '%s'\n", ussd_code);</span><br><span style="color: hsl(120, 100%, 40%);">+ LOGP(DSS, LOGL_DEBUG, "Could not find Route for USSD Code '%s'\n", ussd_code);</span><br><span>     return NULL;</span><br><span> }</span><br><span> </span><br><span>@@ -141,8 +155,15 @@</span><br><span>         /* time-out when we will delete the session */</span><br><span>       struct osmo_timer_list timeout;</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-     /* external USSD Entity responsible for this session */</span><br><span style="color: hsl(0, 100%, 40%);">- struct hlr_euse *euse;</span><br><span style="color: hsl(120, 100%, 40%);">+        /* is this USSD for an external handler (EUSE): true */</span><br><span style="color: hsl(120, 100%, 40%);">+       bool is_external;</span><br><span style="color: hsl(120, 100%, 40%);">+     union {</span><br><span style="color: hsl(120, 100%, 40%);">+               /* external USSD Entity responsible for this session */</span><br><span style="color: hsl(120, 100%, 40%);">+               struct hlr_euse *euse;</span><br><span style="color: hsl(120, 100%, 40%);">+                /* internal USSD Entity responsible for this session */</span><br><span style="color: hsl(120, 100%, 40%);">+               const struct hlr_iuse *iuse;</span><br><span style="color: hsl(120, 100%, 40%);">+  } u;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>       /* we don't keep a pointer to the osmo_gsup_{route,conn} towards the MSC/VLR here,</span><br><span>        * as this might change during inter-VLR hand-over, and we simply look-up the serving MSC/VLR</span><br><span>         * every time we receive an USSD component from the EUSE */</span><br><span>@@ -247,6 +268,79 @@</span><br><span>   return ss_tx_to_ms(ss, OSMO_GSUP_MSGT_PROC_SS_RESULT, true, msg);</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+static int ss_tx_ussd_7bit(struct ss_session *ss, bool final, uint8_t invoke_id, const char *text)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+     struct msgb *msg = gsm0480_gen_ussd_resp_7bit(invoke_id, text);</span><br><span style="color: hsl(120, 100%, 40%);">+       LOGPSS(ss, LOGL_INFO, "Tx USSD '%s'\n", text);</span><br><span style="color: hsl(120, 100%, 40%);">+      OSMO_ASSERT(msg);</span><br><span style="color: hsl(120, 100%, 40%);">+     return ss_tx_to_ms(ss, OSMO_GSUP_MSGT_PROC_SS_RESULT, final, msg);</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/***********************************************************************</span><br><span style="color: hsl(120, 100%, 40%);">+ * Internal USSD Handlers</span><br><span style="color: hsl(120, 100%, 40%);">+ ***********************************************************************/</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+#include "db.h"</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static int handle_ussd_own_msisdn(struct osmo_gsup_conn *conn, struct ss_session *ss,</span><br><span style="color: hsl(120, 100%, 40%);">+                           const struct osmo_gsup_message *gsup, const struct ss_request *req)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+      struct hlr_subscriber subscr;</span><br><span style="color: hsl(120, 100%, 40%);">+ char buf[GSM0480_USSD_7BIT_STRING_LEN+1];</span><br><span style="color: hsl(120, 100%, 40%);">+     int rc;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     rc = db_subscr_get_by_imsi(g_hlr->dbc, ss->imsi, &subscr);</span><br><span style="color: hsl(120, 100%, 40%);">+  switch (rc) {</span><br><span style="color: hsl(120, 100%, 40%);">+ case 0:</span><br><span style="color: hsl(120, 100%, 40%);">+               if (strlen(subscr.msisdn) == 0)</span><br><span style="color: hsl(120, 100%, 40%);">+                       snprintf(buf, sizeof(buf), "You have no MSISDN!");</span><br><span style="color: hsl(120, 100%, 40%);">+          else</span><br><span style="color: hsl(120, 100%, 40%);">+                  snprintf(buf, sizeof(buf), "Your extension is %s\r", subscr.msisdn);</span><br><span style="color: hsl(120, 100%, 40%);">+                ss_tx_ussd_7bit(ss, true, req->invoke_id, buf);</span><br><span style="color: hsl(120, 100%, 40%);">+            break;</span><br><span style="color: hsl(120, 100%, 40%);">+        case -ENOENT:</span><br><span style="color: hsl(120, 100%, 40%);">+         ss_tx_error(ss, true, GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER);</span><br><span style="color: hsl(120, 100%, 40%);">+           break;</span><br><span style="color: hsl(120, 100%, 40%);">+        case -EIO:</span><br><span style="color: hsl(120, 100%, 40%);">+    default:</span><br><span style="color: hsl(120, 100%, 40%);">+              ss_tx_error(ss, true, GSM0480_ERR_CODE_SYSTEM_FAILURE);</span><br><span style="color: hsl(120, 100%, 40%);">+               break;</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+     return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static int handle_ussd_own_imsi(struct osmo_gsup_conn *conn, struct ss_session *ss,</span><br><span style="color: hsl(120, 100%, 40%);">+                           const struct osmo_gsup_message *gsup, const struct ss_request *req)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+        char buf[GSM0480_USSD_7BIT_STRING_LEN+1];</span><br><span style="color: hsl(120, 100%, 40%);">+     snprintf(buf, sizeof(buf), "Your IMSI is %s!\n", ss->imsi);</span><br><span style="color: hsl(120, 100%, 40%);">+      ss_tx_ussd_7bit(ss, true, req->invoke_id, buf);</span><br><span style="color: hsl(120, 100%, 40%);">+    return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static const struct hlr_iuse hlr_iuses[] = {</span><br><span style="color: hsl(120, 100%, 40%);">+        {</span><br><span style="color: hsl(120, 100%, 40%);">+             .name = "own-msisdn",</span><br><span style="color: hsl(120, 100%, 40%);">+               .handle_ussd = handle_ussd_own_msisdn,</span><br><span style="color: hsl(120, 100%, 40%);">+        },</span><br><span style="color: hsl(120, 100%, 40%);">+    {</span><br><span style="color: hsl(120, 100%, 40%);">+             .name = "own-imsi",</span><br><span style="color: hsl(120, 100%, 40%);">+         .handle_ussd = handle_ussd_own_imsi,</span><br><span style="color: hsl(120, 100%, 40%);">+  },</span><br><span style="color: hsl(120, 100%, 40%);">+};</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+const struct hlr_iuse *iuse_find(const char *name)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+       unsigned int i;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     for (i = 0; i < ARRAY_SIZE(hlr_iuses); i++) {</span><br><span style="color: hsl(120, 100%, 40%);">+              const struct hlr_iuse *iuse = &hlr_iuses[i];</span><br><span style="color: hsl(120, 100%, 40%);">+              if (!strcmp(name, iuse->name))</span><br><span style="color: hsl(120, 100%, 40%);">+                     return iuse;</span><br><span style="color: hsl(120, 100%, 40%);">+  }</span><br><span style="color: hsl(120, 100%, 40%);">+     return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span> </span><br><span> </span><br><span> /***********************************************************************</span><br><span>@@ -307,6 +401,7 @@</span><br><span>    return 0;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+/* Handle a USSD GSUP message for a given SS Session received from VLR or EUSE */</span><br><span> static int handle_ussd(struct osmo_gsup_conn *conn, struct ss_session *ss,</span><br><span>                  const struct osmo_gsup_message *gsup, const struct ss_request *req)</span><br><span> {</span><br><span>@@ -318,8 +413,7 @@</span><br><span>               gsm0480_comp_type_name(comp_type), gsm0480_op_code_name(req->opcode),</span><br><span>             req->ussd_text);</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">- if (!ss->euse) {</span><br><span style="color: hsl(120, 100%, 40%);">+   if ((ss->is_external && !ss->u.euse) || !ss->u.iuse) {</span><br><span>              LOGPSS(ss, LOGL_NOTICE, "USSD for unknown code '%s'\n", req->ussd_text);</span><br><span>                ss_tx_error(ss, req->invoke_id, GSM0480_ERR_CODE_SS_NOT_AVAILABLE);</span><br><span>               return 0;</span><br><span>@@ -333,19 +427,25 @@</span><br><span>            /* FIXME: resolve this based on the database vlr_addr */</span><br><span>             osmo_gsup_addr_send(conn->server, (uint8_t *)"MSC-00-00-00-00-00-00", 22, msg_out);</span><br><span>     } else {</span><br><span style="color: hsl(0, 100%, 40%);">-                /* Received from VLR, Forward to EUSE */</span><br><span style="color: hsl(0, 100%, 40%);">-                char addr[128];</span><br><span style="color: hsl(0, 100%, 40%);">-         strcpy(addr, "EUSE-");</span><br><span style="color: hsl(0, 100%, 40%);">-                osmo_strlcpy(addr+5, ss->euse->name, sizeof(addr)-5);</span><br><span style="color: hsl(0, 100%, 40%);">-             conn = gsup_route_find(conn->server, (uint8_t *)addr, strlen(addr)+1);</span><br><span style="color: hsl(0, 100%, 40%);">-               if (!conn) {</span><br><span style="color: hsl(0, 100%, 40%);">-                    LOGPSS(ss, LOGL_ERROR, "Cannot find conn for EUSE %s\n", addr);</span><br><span style="color: hsl(0, 100%, 40%);">-                       ss_tx_error(ss, req->invoke_id, GSM0480_ERR_CODE_SYSTEM_FAILURE);</span><br><span style="color: hsl(120, 100%, 40%);">+          /* Received from VLR (MS) */</span><br><span style="color: hsl(120, 100%, 40%);">+          if (ss->is_external) {</span><br><span style="color: hsl(120, 100%, 40%);">+                     /* Forward to EUSE */</span><br><span style="color: hsl(120, 100%, 40%);">+                 char addr[128];</span><br><span style="color: hsl(120, 100%, 40%);">+                       strcpy(addr, "EUSE-");</span><br><span style="color: hsl(120, 100%, 40%);">+                      osmo_strlcpy(addr+5, ss->u.euse->name, sizeof(addr)-5);</span><br><span style="color: hsl(120, 100%, 40%);">+                 conn = gsup_route_find(conn->server, (uint8_t *)addr, strlen(addr)+1);</span><br><span style="color: hsl(120, 100%, 40%);">+                     if (!conn) {</span><br><span style="color: hsl(120, 100%, 40%);">+                          LOGPSS(ss, LOGL_ERROR, "Cannot find conn for EUSE %s\n", addr);</span><br><span style="color: hsl(120, 100%, 40%);">+                             ss_tx_error(ss, req->invoke_id, GSM0480_ERR_CODE_SYSTEM_FAILURE);</span><br><span style="color: hsl(120, 100%, 40%);">+                  } else {</span><br><span style="color: hsl(120, 100%, 40%);">+                              msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP USSD FW");</span><br><span style="color: hsl(120, 100%, 40%);">+                         OSMO_ASSERT(msg_out);</span><br><span style="color: hsl(120, 100%, 40%);">+                         osmo_gsup_encode(msg_out, gsup);</span><br><span style="color: hsl(120, 100%, 40%);">+                              osmo_gsup_conn_send(conn, msg_out);</span><br><span style="color: hsl(120, 100%, 40%);">+                   }</span><br><span>            } else {</span><br><span style="color: hsl(0, 100%, 40%);">-                        msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP USSD FW");</span><br><span style="color: hsl(0, 100%, 40%);">-                   OSMO_ASSERT(msg_out);</span><br><span style="color: hsl(0, 100%, 40%);">-                   osmo_gsup_encode(msg_out, gsup);</span><br><span style="color: hsl(0, 100%, 40%);">-                        osmo_gsup_conn_send(conn, msg_out);</span><br><span style="color: hsl(120, 100%, 40%);">+                   /* Handle internally */</span><br><span style="color: hsl(120, 100%, 40%);">+                       ss->u.iuse->handle_ussd(conn, ss, gsup, req);</span><br><span>          }</span><br><span>    }</span><br><span> </span><br><span>@@ -392,10 +492,20 @@</span><br><span>                if (ss_op_is_ussd(req.opcode)) {</span><br><span>                     if (conn_is_euse(conn)) {</span><br><span>                            /* EUSE->VLR: MT USSD. EUSE is known ('conn'), VLR is to be resolved */</span><br><span style="color: hsl(0, 100%, 40%);">-                              ss->euse = euse_by_conn(conn);</span><br><span style="color: hsl(120, 100%, 40%);">+                             ss->u.euse = euse_by_conn(conn);</span><br><span>                  } else {</span><br><span>                             /* VLR->EUSE: MO USSD. VLR is known ('conn'), EUSE is to be resolved */</span><br><span style="color: hsl(0, 100%, 40%);">-                              ss->euse = ussd_euse_find_7bit_gsm(hlr, (const char *) req.ussd_text);</span><br><span style="color: hsl(120, 100%, 40%);">+                             struct hlr_ussd_route *rt;</span><br><span style="color: hsl(120, 100%, 40%);">+                            rt = ussd_route_lookup_7bit(hlr, (const char *) req.ussd_text);</span><br><span style="color: hsl(120, 100%, 40%);">+                               if (rt) {</span><br><span style="color: hsl(120, 100%, 40%);">+                                     if (rt->is_external) {</span><br><span style="color: hsl(120, 100%, 40%);">+                                             ss->is_external = true;</span><br><span style="color: hsl(120, 100%, 40%);">+                                            ss->u.euse = rt->u.euse;</span><br><span style="color: hsl(120, 100%, 40%);">+                                        } else if (rt) {</span><br><span style="color: hsl(120, 100%, 40%);">+                                              ss->is_external = false;</span><br><span style="color: hsl(120, 100%, 40%);">+                                           ss->u.iuse = rt->u.iuse;</span><br><span style="color: hsl(120, 100%, 40%);">+                                        }</span><br><span style="color: hsl(120, 100%, 40%);">+                             }</span><br><span>                    }</span><br><span>                    /* dispatch unstructured SS to routing */</span><br><span>                    handle_ussd(conn, ss, gsup, &req);</span><br><span>diff --git a/src/hlr_ussd.h b/src/hlr_ussd.h</span><br><span>index 433a7f2..d1b9fe0 100644</span><br><span>--- a/src/hlr_ussd.h</span><br><span>+++ b/src/hlr_ussd.h</span><br><span>@@ -5,11 +5,15 @@</span><br><span> </span><br><span> struct osmo_gsup_conn;</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-struct hlr_euse_route {</span><br><span style="color: hsl(0, 100%, 40%);">-        /* hlr_euse.routes */</span><br><span style="color: hsl(120, 100%, 40%);">+struct hlr_ussd_route {</span><br><span style="color: hsl(120, 100%, 40%);">+        /* g_hlr.routes */</span><br><span>   struct llist_head list;</span><br><span style="color: hsl(0, 100%, 40%);">- struct hlr_euse *euse;</span><br><span>       const char *prefix;</span><br><span style="color: hsl(120, 100%, 40%);">+   bool is_external;</span><br><span style="color: hsl(120, 100%, 40%);">+     union {</span><br><span style="color: hsl(120, 100%, 40%);">+               struct hlr_euse *euse;</span><br><span style="color: hsl(120, 100%, 40%);">+                const struct hlr_iuse *iuse;</span><br><span style="color: hsl(120, 100%, 40%);">+  } u;</span><br><span> };</span><br><span> </span><br><span> struct hlr_euse {</span><br><span>@@ -20,21 +24,34 @@</span><br><span>    const char *name;</span><br><span>    /* human-readable description */</span><br><span>     const char *description;</span><br><span style="color: hsl(0, 100%, 40%);">-        /* list of hlr_euse_route */</span><br><span style="color: hsl(0, 100%, 40%);">-    struct llist_head routes;</span><br><span> </span><br><span>        /* GSUP connection to the EUSE, if any */</span><br><span>    struct osmo_gsup_conn *conn;</span><br><span> };</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span> struct hlr_euse *euse_find(struct hlr *hlr, const char *name);</span><br><span> struct hlr_euse *euse_alloc(struct hlr *hlr, const char *name);</span><br><span> void euse_del(struct hlr_euse *euse);</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-struct hlr_euse_route *euse_route_find(struct hlr_euse *euse, const char *prefix);</span><br><span style="color: hsl(0, 100%, 40%);">-struct hlr_euse_route *euse_route_prefix_alloc(struct hlr_euse *euse, const char *prefix);</span><br><span style="color: hsl(0, 100%, 40%);">-void euse_route_del(struct hlr_euse_route *rt);</span><br><span style="color: hsl(120, 100%, 40%);">+const struct hlr_iuse *iuse_find(const char *name);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+struct hlr_ussd_route *ussd_route_find_prefix(struct hlr *hlr, const char *prefix);</span><br><span style="color: hsl(120, 100%, 40%);">+struct hlr_ussd_route *ussd_route_prefix_alloc_int(struct hlr *hlr, const char *prefix,</span><br><span style="color: hsl(120, 100%, 40%);">+                                                 const struct hlr_iuse *iuse);</span><br><span style="color: hsl(120, 100%, 40%);">+struct hlr_ussd_route *ussd_route_prefix_alloc_ext(struct hlr *hlr, const char *prefix,</span><br><span style="color: hsl(120, 100%, 40%);">+                                                struct hlr_euse *euse);</span><br><span style="color: hsl(120, 100%, 40%);">+void ussd_route_del(struct hlr_ussd_route *rt);</span><br><span> </span><br><span> int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup);</span><br><span> int rx_proc_ss_error(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+struct ss_session;</span><br><span style="color: hsl(120, 100%, 40%);">+struct ss_request;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/* Internal USSD Handler */</span><br><span style="color: hsl(120, 100%, 40%);">+struct hlr_iuse {</span><br><span style="color: hsl(120, 100%, 40%);">+     const char *name;</span><br><span style="color: hsl(120, 100%, 40%);">+     /* call-back to be called for any incoming USSD messages for this IUSE */</span><br><span style="color: hsl(120, 100%, 40%);">+     int (*handle_ussd)(struct osmo_gsup_conn *conn, struct ss_session *ss,</span><br><span style="color: hsl(120, 100%, 40%);">+                           const struct osmo_gsup_message *gsup, const struct ss_request *req);</span><br><span style="color: hsl(120, 100%, 40%);">+};</span><br><span>diff --git a/src/hlr_vty.c b/src/hlr_vty.c</span><br><span>index 5c359b7..9532a03 100644</span><br><span>--- a/src/hlr_vty.c</span><br><span>+++ b/src/hlr_vty.c</span><br><span>@@ -32,6 +32,7 @@</span><br><span> #include <osmocom/vty/misc.h></span><br><span> #include <osmocom/abis/ipa.h></span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+#include "hlr.h"</span><br><span> #include "hlr_vty.h"</span><br><span> #include "hlr_vty_subscr.h"</span><br><span> #include "gsup_server.h"</span><br><span>@@ -122,47 +123,77 @@</span><br><span> }</span><br><span> </span><br><span> /***********************************************************************</span><br><span style="color: hsl(0, 100%, 40%);">- * External USSD Entity</span><br><span style="color: hsl(120, 100%, 40%);">+ * USSD Entity</span><br><span>  ***********************************************************************/</span><br><span> </span><br><span> #include "hlr_ussd.h"</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-DEFUN(cfg_euse_route_pfx, cfg_euse_route_pfx_cmd,</span><br><span style="color: hsl(0, 100%, 40%);">-    "route prefix PREFIX",</span><br><span style="color: hsl(0, 100%, 40%);">-        "")</span><br><span style="color: hsl(0, 100%, 40%);">-{</span><br><span style="color: hsl(0, 100%, 40%);">-  struct hlr_euse *euse = vty->index;</span><br><span style="color: hsl(0, 100%, 40%);">-  struct hlr_euse_route *rt = euse_route_find(euse, argv[0]);</span><br><span style="color: hsl(120, 100%, 40%);">+#define USSD_STR "USSD Configuration\n"</span><br><span style="color: hsl(120, 100%, 40%);">+#define UROUTE_STR "Routing Configuration\n"</span><br><span style="color: hsl(120, 100%, 40%);">+#define PREFIX_STR "Prefix-Matching Route\n" "USSD Prefix\n"</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+#define INT_CHOICE "(own-msisdn|own-imsi)"</span><br><span style="color: hsl(120, 100%, 40%);">+#define INT_STR "Internal USSD Handler\n" \</span><br><span style="color: hsl(120, 100%, 40%);">+             "Respond with subscribers' own MSISDN\n" \</span><br><span style="color: hsl(120, 100%, 40%);">+              "Respond with subscribers' own IMSI\n"</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+#define EXT_STR "External USSD Handler\n" \</span><br><span style="color: hsl(120, 100%, 40%);">+         "Name of External USSD Handler (IPA CCM ID)\n"</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+DEFUN(cfg_ussd_route_pfx_int, cfg_ussd_route_pfx_int_cmd,</span><br><span style="color: hsl(120, 100%, 40%);">+ "ussd route prefix PREFIX internal " INT_CHOICE,</span><br><span style="color: hsl(120, 100%, 40%);">+    USSD_STR UROUTE_STR PREFIX_STR INT_STR)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+    const struct hlr_iuse *iuse = iuse_find(argv[1]);</span><br><span style="color: hsl(120, 100%, 40%);">+     struct hlr_ussd_route *rt = ussd_route_find_prefix(g_hlr, argv[0]);</span><br><span>  if (rt) {</span><br><span>            vty_out(vty, "%% Cannot add [another?] route for prefix %s%s", argv[0], VTY_NEWLINE);</span><br><span>              return CMD_WARNING;</span><br><span>  }</span><br><span style="color: hsl(0, 100%, 40%);">-       euse_route_prefix_alloc(euse, argv[0]);</span><br><span style="color: hsl(120, 100%, 40%);">+       ussd_route_prefix_alloc_int(g_hlr, argv[0], iuse);</span><br><span> </span><br><span>       return CMD_SUCCESS;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-DEFUN(cfg_euse_no_route_pfx, cfg_euse_no_route_pfx_cmd,</span><br><span style="color: hsl(0, 100%, 40%);">-     "no route prefix PREFIX",</span><br><span style="color: hsl(0, 100%, 40%);">-     NO_STR "")</span><br><span style="color: hsl(120, 100%, 40%);">+DEFUN(cfg_ussd_route_pfx_ext, cfg_ussd_route_pfx_ext_cmd,</span><br><span style="color: hsl(120, 100%, 40%);">+       "ussd route prefix PREFIX external EUSE",</span><br><span style="color: hsl(120, 100%, 40%);">+   USSD_STR UROUTE_STR PREFIX_STR EXT_STR)</span><br><span> {</span><br><span style="color: hsl(0, 100%, 40%);">-    struct hlr_euse *euse = vty->index;</span><br><span style="color: hsl(0, 100%, 40%);">-  struct hlr_euse_route *rt = euse_route_find(euse, argv[0]);</span><br><span style="color: hsl(120, 100%, 40%);">+   struct hlr_euse *euse = euse_find(g_hlr, argv[1]);</span><br><span style="color: hsl(120, 100%, 40%);">+    struct hlr_ussd_route *rt = ussd_route_find_prefix(g_hlr, argv[0]);</span><br><span style="color: hsl(120, 100%, 40%);">+   if (rt) {</span><br><span style="color: hsl(120, 100%, 40%);">+             vty_out(vty, "%% Cannot add [another?] route for prefix %s%s", argv[0], VTY_NEWLINE);</span><br><span style="color: hsl(120, 100%, 40%);">+               return CMD_WARNING;</span><br><span style="color: hsl(120, 100%, 40%);">+   }</span><br><span style="color: hsl(120, 100%, 40%);">+     if (!euse) {</span><br><span style="color: hsl(120, 100%, 40%);">+          vty_out(vty, "%% Cannot find euse '%s'%s", argv[1], VTY_NEWLINE);</span><br><span style="color: hsl(120, 100%, 40%);">+           return CMD_WARNING;</span><br><span style="color: hsl(120, 100%, 40%);">+   }</span><br><span style="color: hsl(120, 100%, 40%);">+     ussd_route_prefix_alloc_ext(g_hlr, argv[0], euse);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+  return CMD_SUCCESS;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+DEFUN(cfg_ussd_no_route_pfx, cfg_ussd_no_route_pfx_cmd,</span><br><span style="color: hsl(120, 100%, 40%);">+     "no ussd route prefix PREFIX",</span><br><span style="color: hsl(120, 100%, 40%);">+      NO_STR USSD_STR UROUTE_STR PREFIX_STR)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+     struct hlr_ussd_route *rt = ussd_route_find_prefix(g_hlr, argv[0]);</span><br><span>  if (!rt) {</span><br><span>           vty_out(vty, "%% Cannot find route for prefix %s%s", argv[0], VTY_NEWLINE);</span><br><span>                return CMD_WARNING;</span><br><span>  }</span><br><span style="color: hsl(0, 100%, 40%);">-       euse_route_del(rt);</span><br><span style="color: hsl(120, 100%, 40%);">+   ussd_route_del(rt);</span><br><span> </span><br><span>      return CMD_SUCCESS;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-DEFUN(cfg_euse_defaultroute, cfg_euse_defaultroute_cmd,</span><br><span style="color: hsl(0, 100%, 40%);">-     "default-route",</span><br><span style="color: hsl(0, 100%, 40%);">-      "Set this EUSE as default-route for all USSD to unknown destinations\n")</span><br><span style="color: hsl(120, 100%, 40%);">+DEFUN(cfg_ussd_defaultroute, cfg_ussd_defaultroute_cmd,</span><br><span style="color: hsl(120, 100%, 40%);">+   "ussd default-route external EUSE",</span><br><span style="color: hsl(120, 100%, 40%);">+ USSD_STR "Configure default-route for all USSD to unknown destinations\n"</span><br><span style="color: hsl(120, 100%, 40%);">+   EXT_STR)</span><br><span> {</span><br><span style="color: hsl(0, 100%, 40%);">-   struct hlr_euse *euse = vty->index;</span><br><span style="color: hsl(120, 100%, 40%);">+        struct hlr_euse *euse = euse_find(g_hlr, argv[0]);</span><br><span> </span><br><span>       if (g_hlr->euse_default != euse) {</span><br><span>                vty_out(vty, "Switching default route from %s to %s%s",</span><br><span>@@ -174,16 +205,10 @@</span><br><span>    return CMD_SUCCESS;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-DEFUN(cfg_euse_no_defaultroute, cfg_euse_no_defaultroute_cmd,</span><br><span style="color: hsl(0, 100%, 40%);">-       "no default-route",</span><br><span style="color: hsl(0, 100%, 40%);">-   NO_STR "Remove this EUSE as default-route for all USSD to unknown destinations\n")</span><br><span style="color: hsl(120, 100%, 40%);">+DEFUN(cfg_ussd_no_defaultroute, cfg_ussd_no_defaultroute_cmd,</span><br><span style="color: hsl(120, 100%, 40%);">+   "no ussd default-route",</span><br><span style="color: hsl(120, 100%, 40%);">+    NO_STR USSD_STR "Remove the default-route for all USSD to unknown destinations\n")</span><br><span> {</span><br><span style="color: hsl(0, 100%, 40%);">-       struct hlr_euse *euse = vty->index;</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-  if (g_hlr->euse_default != euse) {</span><br><span style="color: hsl(0, 100%, 40%);">-           vty_out(vty, "%% Current EUSE is no default route, cannot delete it%s", VTY_NEWLINE);</span><br><span style="color: hsl(0, 100%, 40%);">-         return CMD_WARNING;</span><br><span style="color: hsl(0, 100%, 40%);">-     }</span><br><span>    g_hlr->euse_default = NULL;</span><br><span> </span><br><span>   return CMD_SUCCESS;</span><br><span>@@ -236,24 +261,27 @@</span><br><span> </span><br><span> static void dump_one_euse(struct vty *vty, struct hlr_euse *euse)</span><br><span> {</span><br><span style="color: hsl(0, 100%, 40%);">-       struct hlr_euse_route *er;</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span>   vty_out(vty, " euse %s%s", euse->name, VTY_NEWLINE);</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-       llist_for_each_entry(er, &euse->routes, list)</span><br><span style="color: hsl(0, 100%, 40%);">-            vty_out(vty, "  route prefix %s%s", er->prefix, VTY_NEWLINE);</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-      if (g_hlr->euse_default == euse)</span><br><span style="color: hsl(0, 100%, 40%);">-             vty_out(vty, "  default-route%s", VTY_NEWLINE);</span><br><span> }</span><br><span> </span><br><span> static int config_write_euse(struct vty *vty)</span><br><span> {</span><br><span>     struct hlr_euse *euse;</span><br><span style="color: hsl(120, 100%, 40%);">+        struct hlr_ussd_route *rt;</span><br><span> </span><br><span>       llist_for_each_entry(euse, &g_hlr->euse_list, list)</span><br><span>           dump_one_euse(vty, euse);</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+ llist_for_each_entry(rt, &g_hlr->ussd_routes, list) {</span><br><span style="color: hsl(120, 100%, 40%);">+          vty_out(vty, " ussd route prefix %s %s %s%s", rt->prefix,</span><br><span style="color: hsl(120, 100%, 40%);">+                        rt->is_external ? "external" : "internal",</span><br><span style="color: hsl(120, 100%, 40%);">+                     rt->is_external ? rt->u.euse->name : rt->u.iuse->name,</span><br><span style="color: hsl(120, 100%, 40%);">+                 VTY_NEWLINE);</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   if (g_hlr->euse_default)</span><br><span style="color: hsl(120, 100%, 40%);">+           vty_out(vty, " ussd default-route external %s%s", g_hlr->euse_default->name, VTY_NEWLINE);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>         return 0;</span><br><span> }</span><br><span> </span><br><span>@@ -314,10 +342,11 @@</span><br><span>   install_element(HLR_NODE, &cfg_euse_cmd);</span><br><span>        install_element(HLR_NODE, &cfg_no_euse_cmd);</span><br><span>     install_node(&euse_node, config_write_euse);</span><br><span style="color: hsl(0, 100%, 40%);">-        install_element(EUSE_NODE, &cfg_euse_route_pfx_cmd);</span><br><span style="color: hsl(0, 100%, 40%);">-        install_element(EUSE_NODE, &cfg_euse_no_route_pfx_cmd);</span><br><span style="color: hsl(0, 100%, 40%);">-     install_element(EUSE_NODE, &cfg_euse_defaultroute_cmd);</span><br><span style="color: hsl(0, 100%, 40%);">-     install_element(EUSE_NODE, &cfg_euse_no_defaultroute_cmd);</span><br><span style="color: hsl(120, 100%, 40%);">+        install_element(HLR_NODE, &cfg_ussd_route_pfx_int_cmd);</span><br><span style="color: hsl(120, 100%, 40%);">+   install_element(HLR_NODE, &cfg_ussd_route_pfx_ext_cmd);</span><br><span style="color: hsl(120, 100%, 40%);">+   install_element(HLR_NODE, &cfg_ussd_no_route_pfx_cmd);</span><br><span style="color: hsl(120, 100%, 40%);">+    install_element(HLR_NODE, &cfg_ussd_defaultroute_cmd);</span><br><span style="color: hsl(120, 100%, 40%);">+    install_element(HLR_NODE, &cfg_ussd_no_defaultroute_cmd);</span><br><span> </span><br><span>    hlr_vty_subscriber_init();</span><br><span> }</span><br><span>diff --git a/tests/test_nodes.vty b/tests/test_nodes.vty</span><br><span>index 4badad5..58f5c61 100644</span><br><span>--- a/tests/test_nodes.vty</span><br><span>+++ b/tests/test_nodes.vty</span><br><span>@@ -72,6 +72,11 @@</span><br><span>   gsup</span><br><span>   euse NAME</span><br><span>   no euse NAME</span><br><span style="color: hsl(120, 100%, 40%);">+  ussd route prefix PREFIX internal (own-msisdn|own-imsi)</span><br><span style="color: hsl(120, 100%, 40%);">+  ussd route prefix PREFIX external EUSE</span><br><span style="color: hsl(120, 100%, 40%);">+  no ussd route prefix PREFIX</span><br><span style="color: hsl(120, 100%, 40%);">+  ussd default-route external EUSE</span><br><span style="color: hsl(120, 100%, 40%);">+  no ussd default-route</span><br><span> </span><br><span> OsmoHLR(config-hlr)# gsup</span><br><span> OsmoHLR(config-hlr-gsup)# list</span><br><span>@@ -122,4 +127,5 @@</span><br><span> hlr</span><br><span>  gsup</span><br><span>   bind ip 127.0.0.1</span><br><span style="color: hsl(120, 100%, 40%);">+ ussd route prefix *#100# internal own-msisdn</span><br><span> end</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.osmocom.org/10251">change 10251</a>. To unsubscribe, or for help writing mail filters, visit <a href="https://gerrit.osmocom.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.osmocom.org/10251"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: osmo-hlr </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-MessageType: merged </div>
<div style="display:none"> Gerrit-Change-Id: I1d09fab810a6bb9ab02904de72dbc9e8a414f9f9 </div>
<div style="display:none"> Gerrit-Change-Number: 10251 </div>
<div style="display:none"> Gerrit-PatchSet: 9 </div>
<div style="display:none"> Gerrit-Owner: Harald Welte <laforge@gnumonks.org> </div>
<div style="display:none"> Gerrit-Reviewer: Harald Welte <laforge@gnumonks.org> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins Builder </div>