diff options
author | Neels Hofmeyr <neels@hofmeyr.de> | 2019-11-07 04:22:07 +0100 |
---|---|---|
committer | Neels Hofmeyr <neels@hofmeyr.de> | 2019-11-11 05:31:59 +0100 |
commit | 2975bcbc2febb5c284b0db750e7f40b95dcf7998 (patch) | |
tree | c8086e2b6800e0a4be3ad81c6ebfa3eb53312b45 | |
parent | 749e42902dc42eeeda5a056220f093329f9b51e4 (diff) |
hlr mslookup and gsup proxy works for the first time
Change-Id: I546ca786308fed557703321bed2935e0adcbe0ee
-rw-r--r-- | configure.ac | 1 | ||||
-rw-r--r-- | include/osmocom/gsupclient/gsup_client.h | 12 | ||||
-rw-r--r-- | src/db_hlr.c | 2 | ||||
-rw-r--r-- | src/dgsm.c | 310 | ||||
-rw-r--r-- | src/dgsm.h | 7 | ||||
-rw-r--r-- | src/dgsm_vty.c | 4 | ||||
-rw-r--r-- | src/gsup_router.c | 5 | ||||
-rw-r--r-- | src/gsup_router.h | 1 | ||||
-rw-r--r-- | src/gsup_server.c | 11 | ||||
-rw-r--r-- | src/gsupclient/gsup_client.c | 75 | ||||
-rw-r--r-- | src/hlr.c | 23 | ||||
-rw-r--r-- | src/hlr.h | 1 | ||||
-rw-r--r-- | src/hlr_ussd.c | 8 | ||||
-rw-r--r-- | src/hlr_vty.c | 20 | ||||
-rw-r--r-- | src/luop.c | 11 | ||||
-rw-r--r-- | src/luop.h | 2 | ||||
-rw-r--r-- | src/mslookup_server.c | 2 | ||||
-rw-r--r-- | src/mslookup_server_mdns.c | 2 | ||||
-rw-r--r-- | src/proxy.c | 13 | ||||
-rw-r--r-- | src/proxy.h | 10 | ||||
-rw-r--r-- | src/remote_hlr.c | 101 | ||||
-rw-r--r-- | src/remote_hlr.h | 2 | ||||
-rw-r--r-- | tests/Makefile.am | 1 | ||||
-rw-r--r-- | tests/mslookup_manual_test/Makefile.am | 40 | ||||
-rw-r--r-- | tests/mslookup_manual_test/fake_msc.c | 80 | ||||
-rw-r--r-- | tests/mslookup_manual_test/osmo-hlr-1.cfg | 40 | ||||
-rw-r--r-- | tests/mslookup_manual_test/osmo-hlr-2.cfg | 40 | ||||
-rwxr-xr-x | tests/mslookup_manual_test/run.sh | 22 |
28 files changed, 739 insertions, 107 deletions
diff --git a/configure.ac b/configure.ac index 5089958..c0cf28b 100644 --- a/configure.ac +++ b/configure.ac @@ -188,4 +188,5 @@ AC_OUTPUT( tests/gsup/Makefile tests/db/Makefile tests/db_upgrade/Makefile + tests/mslookup_manual_test/Makefile ) diff --git a/include/osmocom/gsupclient/gsup_client.h b/include/osmocom/gsupclient/gsup_client.h index 154e3e0..deae4d4 100644 --- a/include/osmocom/gsupclient/gsup_client.h +++ b/include/osmocom/gsupclient/gsup_client.h @@ -39,6 +39,8 @@ struct osmo_gsup_client; /* Expects message in msg->l2h */ typedef int (*osmo_gsup_client_read_cb_t)(struct osmo_gsup_client *gsupc, struct msgb *msg); +typedef bool (*osmo_gsup_client_up_down_cb_t)(struct osmo_gsup_client *gsupc, bool up); + struct osmo_gsup_client { const char *unit_name; /* same as ipa_dev->unit_name, for backwards compat */ @@ -54,8 +56,18 @@ struct osmo_gsup_client { int got_ipa_pong; struct ipaccess_unit *ipa_dev; /* identification information sent to IPA server */ + + osmo_gsup_client_up_down_cb_t up_down_cb; }; +struct osmo_gsup_client *osmo_gsup_client_create3(void *talloc_ctx, + struct ipaccess_unit *ipa_dev, + const char *ip_addr, + unsigned int tcp_port, + struct osmo_oap_client_config *oapc_config, + osmo_gsup_client_read_cb_t read_cb, + osmo_gsup_client_up_down_cb_t up_down_cb, + void *data); struct osmo_gsup_client *osmo_gsup_client_create2(void *talloc_ctx, struct ipaccess_unit *ipa_dev, const char *ip_addr, diff --git a/src/db_hlr.c b/src/db_hlr.c index 8d606f8..161c0bd 100644 --- a/src/db_hlr.c +++ b/src/db_hlr.c @@ -906,7 +906,7 @@ int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, /* FIXME: only send to single SGSN where latest update for IMSI came from */ llist_for_each_entry(co, &hlr->gs->clients, list) { - luop = lu_op_alloc_conn(co); + luop = lu_op_alloc_conn(co, NULL); if (!luop) { LOGHLR(subscr->imsi, LOGL_ERROR, "Cannot notify GSUP client, cannot allocate lu_operation," @@ -13,9 +13,6 @@ #include "mslookup_server_mdns.h" #include "global_title.h" -#define LOG_DGSM(imsi, level, fmt, args...) \ - LOGP(DDGSM, level, "(IMSI-%s) " fmt, imsi, ##args) - void *dgsm_ctx = NULL; const struct global_title dgsm_config_msc_wildcard = {}; @@ -174,16 +171,102 @@ static void defer_gsup_message(const struct osmo_gsup_message *gsup) /* Since osmo_gsup_message has a lot of dangling external pointers, the only way to defer the message is to * store it encoded. */ m->gsup = osmo_gsup_msgb_alloc("GSUP proxy defer"); - osmo_gsup_encode(m->gsup, gsup); + if (osmo_gsup_encode(m->gsup, gsup)) { + LOGP(DDGSM, LOGL_ERROR, "GSUP proxy defer: unable to encode GSUP message: %s %s\n", + osmo_gsup_message_type_name(gsup->message_type), osmo_quote_str(gsup->imsi, -1)); + msgb_free(m->gsup); + talloc_free(m); + return; + } llist_add_tail(&m->entry, &pending_gsup_messages); } -void dgsm_send_to_remote_hlr(const struct proxy_subscr *ps, const struct osmo_gsup_message *gsup) +/* Unable to resolve remote HLR for this IMSI, Answer with error back to the sender. */ +static void defer_gsup_message_err(struct pending_gsup_message *m) +{ + struct osmo_gsup_message gsup; + struct osmo_gsup_conn *conn; + + printf("whaha %p %p\n", m, m->gsup); + if (osmo_gsup_decode(m->gsup->data, m->gsup->len, &gsup)) { + LOGP(DDGSM, LOGL_ERROR, "Unable to decode deferred GSUP message for %s\n", m->imsi); + msgb_free(m->gsup); + m->gsup = NULL; + return; + } + + conn = gsup_route_find(g_hlr->gs, gsup.source_name, gsup.source_name_len); + if (!conn) { + LOGP(DDGSM, LOGL_ERROR, "Unable to send error reply for %s %s, source_name is not known: %s\n", + osmo_gsup_message_type_name(gsup.message_type), gsup.imsi, + osmo_quote_str_c(OTC_SELECT, (char*)gsup.source_name, gsup.source_name_len)); + return; + } + osmo_gsup_conn_send_err_reply(conn, &gsup, GMM_CAUSE_IMSI_UNKNOWN); + msgb_free(m->gsup); + m->gsup = NULL; +} + +/* Forward spooled message for this IMSI to remote HLR. */ +static void defer_gsup_message_send(struct pending_gsup_message *m, struct remote_hlr *remote_hlr) +{ + /* To be able to log what we are forwarding, the spooled message needs to be decoded. Do that only if debug + * logging is enabled and decoding succeeds. If decoding fails, don't change anything about the code path, so + * that logging doesn't affect how osmo-hlr works. */ + if (log_check_level(DDGSM, LOGL_DEBUG)) { + struct osmo_gsup_message gsup; + if (osmo_gsup_decode(m->gsup->data, m->gsup->len, &gsup)) + LOGP(DDGSM, LOGL_ERROR, "Unable to decode deferred GSUP message for %s\n", m->imsi); + else + LOGP(DDGSM, LOGL_DEBUG, + "Forwarding deferred GSUP: %s %s from %s to " OSMO_SOCKADDR_STR_FMT "\n", + osmo_gsup_message_type_name(gsup.message_type), gsup.imsi, + osmo_quote_str_c(OTC_SELECT, (char*)gsup.source_name, gsup.source_name_len), + OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr)); + } + + /* If sending fails, still discard. */ + if (!remote_hlr->gsupc || !remote_hlr->gsupc->is_connected) { + LOGP(DDGSM, LOGL_ERROR, "GSUP link to remote HLR is not connected: " OSMO_SOCKADDR_STR_FMT "\n", + OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr)); + defer_gsup_message_err(m); + return; + } + + remote_hlr_msgb_send(remote_hlr, m->gsup); +} + +/* Result of looking for remote HLR. If it failed, pass remote_hlr as NULL. */ +static void defer_gsup_message_pop(const char *imsi, struct remote_hlr *remote_hlr) +{ + struct pending_gsup_message *m, *n; + + if (remote_hlr) + LOG_DGSM(imsi, LOGL_DEBUG, "Sending spooled GSUP messages to remote HLR at " OSMO_SOCKADDR_STR_FMT "\n", + OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr)); + else + LOG_DGSM(imsi, LOGL_ERROR, "No remote HLR found, dropping spooled GSUP messages\n"); + + llist_for_each_entry_safe(m, n, &pending_gsup_messages, entry) { + if (strcmp(m->imsi, imsi)) + continue; + + if (!remote_hlr) + defer_gsup_message_err(m); + else + defer_gsup_message_send(m, remote_hlr); + + llist_del(&m->entry); + talloc_free(m); + } +} + +void dgsm_send_to_remote_hlr(const struct proxy_subscr *proxy_subscr, const struct osmo_gsup_message *gsup) { struct remote_hlr *remote_hlr; - if (!osmo_sockaddr_str_is_nonzero(&ps->remote_hlr)) { + if (!osmo_sockaddr_str_is_nonzero(&proxy_subscr->remote_hlr_addr)) { /* We don't know the remote target yet. Still waiting for an MS lookup response. */ LOG_DGSM(gsup->imsi, LOGL_DEBUG, "GSUP Proxy: deferring until remote proxy is known: %s\n", osmo_gsup_message_type_name(gsup->message_type)); @@ -192,67 +275,216 @@ void dgsm_send_to_remote_hlr(const struct proxy_subscr *ps, const struct osmo_gs } LOG_DGSM(gsup->imsi, LOGL_DEBUG, "GSUP Proxy: forwarding to " OSMO_SOCKADDR_STR_FMT ": %s\n", - OSMO_SOCKADDR_STR_FMT_ARGS(&ps->remote_hlr), osmo_gsup_message_type_name(gsup->message_type)); + OSMO_SOCKADDR_STR_FMT_ARGS(&proxy_subscr->remote_hlr_addr), + osmo_gsup_message_type_name(gsup->message_type)); - remote_hlr = remote_hlr_get(&ps->remote_hlr, true); + remote_hlr = remote_hlr_get(&proxy_subscr->remote_hlr_addr, true); if (!remote_hlr) { LOG_DGSM(gsup->imsi, LOGL_ERROR, "Failed to establish connection to remote HLR " OSMO_SOCKADDR_STR_FMT ", discarding GSUP: %s\n", - OSMO_SOCKADDR_STR_FMT_ARGS(&ps->remote_hlr), osmo_gsup_message_type_name(gsup->message_type)); + OSMO_SOCKADDR_STR_FMT_ARGS(&proxy_subscr->remote_hlr_addr), + osmo_gsup_message_type_name(gsup->message_type)); + return; + } + + if (!remote_hlr->gsupc || !remote_hlr->gsupc->is_connected) { + /* GSUP link is still busy establishing... */ + LOG_DGSM(gsup->imsi, LOGL_DEBUG, "GSUP Proxy: deferring until link to remote proxy is up: %s\n", + osmo_gsup_message_type_name(gsup->message_type)); + defer_gsup_message(gsup); return; } remote_hlr_gsup_send(remote_hlr, gsup); } +static void resolve_hlr_result_cb(struct osmo_mslookup_client *client, + uint32_t request_handle, + const struct osmo_mslookup_query *query, + const struct osmo_mslookup_result *result) +{ + struct proxy *proxy = g_hlr->gsup_proxy.cs; + const struct proxy_subscr *proxy_subscr; + struct proxy_subscr proxy_subscr_new; + struct remote_hlr *remote_hlr; + + /* A remote HLR is answering back, indicating that it is the home HLR for a given IMSI. + * There should be a mostly empty proxy entry for that IMSI. + * Add the remote address data in the proxy. */ + if (query->id.type != OSMO_MSLOOKUP_ID_IMSI) { + LOGP(DDGSM, LOGL_ERROR, "Expected IMSI ID type in mslookup query+result: %s\n", + osmo_mslookup_result_name_c(OTC_SELECT, query, result)); + return; + } + + proxy_subscr = proxy_subscr_get_by_imsi(proxy, query->id.imsi); + if (!proxy_subscr) { + LOG_DGSM(query->id.imsi, LOGL_ERROR, "No proxy entry for mslookup result: %s\n", + osmo_mslookup_result_name_c(OTC_SELECT, query, result)); + defer_gsup_message_pop(query->id.imsi, NULL); + return; + } + + if (result->rc != OSMO_MSLOOKUP_RC_OK) { + LOG_DGSM(query->id.imsi, LOGL_ERROR, "Failed to resolve remote HLR: %s\n", + osmo_mslookup_result_name_c(OTC_SELECT, query, result)); + defer_gsup_message_pop(query->id.imsi, NULL); + proxy_subscr_del(proxy, proxy_subscr->imsi); + return; + } + + /* Store the address. Make a copy to modify. */ + proxy_subscr_new = *proxy_subscr; + proxy_subscr = &proxy_subscr_new; + if (osmo_sockaddr_str_is_nonzero(&result->host_v4)) + proxy_subscr_new.remote_hlr_addr = result->host_v4; + else if (osmo_sockaddr_str_is_nonzero(&result->host_v6)) + proxy_subscr_new.remote_hlr_addr = result->host_v6; + else { + LOG_DGSM(query->id.imsi, LOGL_ERROR, "Invalid address for remote HLR: %s\n", + osmo_mslookup_result_name_c(OTC_SELECT, query, result)); + defer_gsup_message_pop(query->id.imsi, NULL); + proxy_subscr_del(proxy, proxy_subscr->imsi); + return; + } + + if (proxy_subscr_update(proxy, proxy_subscr)) { + LOG_DGSM(query->id.imsi, LOGL_ERROR, "Failed to store proxy entry for remote HLR: %s\n", + osmo_mslookup_result_name_c(OTC_SELECT, query, result)); + defer_gsup_message_pop(query->id.imsi, NULL); + proxy_subscr_del(proxy, proxy_subscr->imsi); + return; + } + LOG_DGSM(proxy_subscr->imsi, LOGL_DEBUG, "Stored remote hlr address for this IMSI: " OSMO_SOCKADDR_STR_FMT "\n", + OSMO_SOCKADDR_STR_FMT_ARGS(&proxy_subscr->remote_hlr_addr)); + + remote_hlr = remote_hlr_get(&proxy_subscr->remote_hlr_addr, true); + if (!remote_hlr) { + defer_gsup_message_pop(query->id.imsi, NULL); + proxy_subscr_del(proxy, proxy_subscr->imsi); + return; + } + + if (!remote_hlr->gsupc || !remote_hlr->gsupc->is_connected) { + LOG_DGSM(query->id.imsi, LOGL_DEBUG, "Resolved remote HLR, waiting for link-up: %s\n", + osmo_mslookup_result_name_c(OTC_SELECT, query, result)); + return; + } + + LOG_DGSM(query->id.imsi, LOGL_DEBUG, "Resolved remote HLR, sending spooled GSUP messages: %s\n", + osmo_mslookup_result_name_c(OTC_SELECT, query, result)); + defer_gsup_message_pop(query->id.imsi, remote_hlr); +} + +static bool remote_hlr_up_yield(struct proxy *proxy, const struct proxy_subscr *proxy_subscr, void *data) +{ + struct remote_hlr *remote_hlr = data; + defer_gsup_message_pop(proxy_subscr->imsi, remote_hlr); + return true; +} + +void dgsm_remote_hlr_up(struct remote_hlr *remote_hlr) +{ + LOGP(DDGSM, LOGL_NOTICE, "link to remote HLR is up, sending spooled GSUP messages: " OSMO_SOCKADDR_STR_FMT "\n", + OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr)); + /* Send all spooled GSUP messaged for IMSIs that are waiting for this link to establish. */ + proxy_subscrs_get_by_remote_hlr(g_hlr->gsup_proxy.cs, &remote_hlr->addr, remote_hlr_up_yield, remote_hlr); +} + /* Return true when the message has been handled by D-GSM. */ bool dgsm_check_forward_gsup_msg(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup) { const struct proxy_subscr *proxy_subscr; - struct proxy_subscr ps_new; - struct gsup_route *r; + struct proxy_subscr proxy_subscr_new; + struct gsup_route *route; struct osmo_gsup_message gsup_copy; - struct proxy *proxy = g_hlr->gsup_proxy.cs; - if (gsup->cn_domain == OSMO_GSUP_CN_DOMAIN_PS) + struct proxy *proxy; + struct osmo_mslookup_query query; + struct osmo_mslookup_query_handling handling; + uint32_t request_handle; + + switch (gsup->cn_domain) { + case OSMO_GSUP_CN_DOMAIN_CS: + proxy = g_hlr->gsup_proxy.cs; + break; + case OSMO_GSUP_CN_DOMAIN_PS: proxy = g_hlr->gsup_proxy.ps; + break; + default: + LOG_DGSM(gsup->imsi, LOGL_ERROR, "Unknown cn_domain: %d\n", gsup->cn_domain); + osmo_gsup_conn_send_err_reply(conn, gsup, GMM_CAUSE_INV_MAND_INFO); + return -GMM_CAUSE_INV_MAND_INFO; + } - proxy_subscr = proxy_subscr_get_by_imsi(proxy, gsup->imsi); - if (proxy_subscr) - goto yes_we_are_proxying; + /* To forward to a remote HLR, we need to indicate the source MSC's name to make sure the reply can be routed + * back. Store the sender MSC in gsup->source_name -- the remote HLR is required to return this as + * gsup->destination_name so that the reply gets routed to the original MSC. */ + route = gsup_route_find_by_conn(conn); + if (!route) { + /* The conn has not sent its IPA unit name yet, and hence we won't be able to proxy responses back from + * a remote HLR. Send GSUP error and indicate that this message has been handled. */ + osmo_gsup_conn_send_err_reply(conn, gsup, GMM_CAUSE_NET_FAIL); + return true; + } - /* No proxy entry exists. If the IMSI is known in the local HLR, then we won't proxy. */ + /* If the IMSI is known in the local HLR, then we won't proxy. */ if (db_subscr_exists_by_imsi(g_hlr->dbc, gsup->imsi) == 0) return false; + /* Are we already forwarding this IMSI to a remote HLR? */ + proxy_subscr = proxy_subscr_get_by_imsi(proxy, gsup->imsi); + if (proxy_subscr) + goto yes_we_are_proxying; + /* The IMSI is not known locally, so we want to proxy to a remote HLR, but no proxy entry exists yet. We need to * look up the subscriber in remote HLRs via D-GSM mslookup, forward GSUP and reply once a result is back from * there. Defer message and kick off MS lookup. */ + /* Kick off an mslookup for the remote HLR. */ + if (!g_hlr->mslookup.client.client) { + LOGP(DDGSM, LOGL_DEBUG, "mslookup client not running, cannot query remote home HLR for %s\n", + gsup->imsi); + return false; + } + + query = (struct osmo_mslookup_query){ + .id = { + .type = OSMO_MSLOOKUP_ID_IMSI, + } + }; + OSMO_STRLCPY_ARRAY(query.id.imsi, gsup->imsi); + OSMO_STRLCPY_ARRAY(query.service, OSMO_MSLOOKUP_SERVICE_HLR_GSUP); + handling = (struct osmo_mslookup_query_handling){ + .result_timeout_milliseconds = g_hlr->mslookup.client.result_timeout_milliseconds, + .result_cb = resolve_hlr_result_cb, + }; + request_handle = osmo_mslookup_client_request(g_hlr->mslookup.client.client, &query, &handling); + if (!request_handle) { + LOG_DGSM(gsup->imsi, LOGL_ERROR, "Error dispatching mslookup query for home HLR: %s\n", + osmo_mslookup_result_name_c(OTC_SELECT, &query, NULL)); + proxy_subscr_del(proxy, gsup->imsi); + return false; + } + /* Add a proxy entry without a remote address to indicate that we are busy querying for a remote HLR. */ - ps_new = (struct proxy_subscr){}; - OSMO_STRLCPY_ARRAY(ps_new.imsi, gsup->imsi); - proxy_subscr_update(proxy, &ps_new); - proxy_subscr = &ps_new; + proxy_subscr_new = (struct proxy_subscr){}; + OSMO_STRLCPY_ARRAY(proxy_subscr_new.imsi, gsup->imsi); + global_title_set(&proxy_subscr_new.vlr_name, route->addr, talloc_total_size(route->addr)); + proxy_subscr = &proxy_subscr_new; + proxy_subscr_update(proxy, proxy_subscr); yes_we_are_proxying: OSMO_ASSERT(proxy_subscr); - /* To forward to a remote HLR, we need to indicate the source MSC's name to make sure the reply can be routed - * back. Store the sender MSC in gsup->source_name -- the remote HLR is required to return this as - * gsup->destination_name so that the reply gets routed to the original MSC. */ - r = gsup_route_find_by_conn(conn); - if (!r) { - /* The conn has not sent its IPA unit name yet, and hence we won't be able to proxy responses back from - * a remote HLR. Send GSUP error and indicate that this message has been handled. */ - osmo_gsup_conn_send_err_reply(conn, gsup, GMM_CAUSE_NET_FAIL); - return true; - } - - /* Be aware that osmo_gsup_message has a lot of external pointer references, so this is not a deep copy. */ + /* Be aware that osmo_gsup_message has a lot of external pointer references, so this is not a deep copy. + * dgsm_send_to_remote_hlr() may need to defer the GSUP message until the remote HLR has answered, in which case + * it will take care to deep copy (encode into a msgb), i.e. it is fine to pass a shallow copy. */ gsup_copy = *gsup; - gsup_copy.source_name = r->addr; - gsup_copy.source_name_len = talloc_total_size(r->addr); + gsup_copy.source_name = route->addr; + gsup_copy.source_name_len = talloc_total_size(route->addr); + /* If the remote HLR is already known, directly forward the GSUP message; otherwise, spool the GSUP message + * until the remote HLR will respond / until timeout aborts. */ dgsm_send_to_remote_hlr(proxy_subscr, &gsup_copy); return true; } @@ -263,7 +495,15 @@ void dgsm_init(void *ctx) dgsm_ctx = talloc_named_const(ctx, 0, "dgsm"); dgsm_pending_messages_ctx = talloc_named_const(dgsm_ctx, 0, "dgsm_pending_messages"); INIT_LLIST_HEAD(&g_hlr->mslookup.vty.server.msc_configs); + g_hlr->mslookup.server.max_age = 60 * 60; + + g_hlr->mslookup.client.result_timeout_milliseconds = 2000; + + g_hlr->gsup_proxy.gsup_client_name.unit_name = "HLR"; + g_hlr->gsup_proxy.gsup_client_name.serno = "unnamed-HLR"; + g_hlr->gsup_proxy.gsup_client_name.swversion = PACKAGE_NAME "-" PACKAGE_VERSION; + osmo_sockaddr_str_from_str(&g_hlr->mslookup.vty.server.mdns.bind_addr, OSMO_MSLOOKUP_MDNS_IP4, OSMO_MSLOOKUP_MDNS_PORT); osmo_sockaddr_str_from_str(&g_hlr->mslookup.vty.client.mdns.query_addr, @@ -4,7 +4,11 @@ #include "gsup_server.h" #include "global_title.h" +#define LOG_DGSM(imsi, level, fmt, args...) \ + LOGP(DDGSM, level, "(IMSI-%s) " fmt, imsi, ##args) + struct vty; +struct remote_hlr; extern void *dgsm_ctx; @@ -67,4 +71,5 @@ void dgsm_start(void *ctx); bool dgsm_check_forward_gsup_msg(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup); void dgsm_vty_init(); -void dgsm_vty_go_parent_action(struct vty *vty); + +void dgsm_remote_hlr_up(struct remote_hlr *remote_hlr); diff --git a/src/dgsm_vty.c b/src/dgsm_vty.c index c3da81e..4335c44 100644 --- a/src/dgsm_vty.c +++ b/src/dgsm_vty.c @@ -353,7 +353,3 @@ void dgsm_vty_init() install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_no_mdns_cmd); } - -void dgsm_vty_go_parent_action(struct vty *vty) -{ -} diff --git a/src/gsup_router.c b/src/gsup_router.c index df978ba..7794320 100644 --- a/src/gsup_router.c +++ b/src/gsup_router.c @@ -47,6 +47,11 @@ struct osmo_gsup_conn *gsup_route_find(struct osmo_gsup_server *gs, return NULL; } +struct osmo_gsup_conn *gsup_route_find_gt(struct osmo_gsup_server *gs, const struct global_title *gt) +{ + return gsup_route_find(gs, gt->val, gt->len); +} + /*! Find a GSUP connection's route (to read the IPA address from the route). * \param[in] conn GSUP connection * \return GSUP route diff --git a/src/gsup_router.h b/src/gsup_router.h index 339ab66..0eb3ba9 100644 --- a/src/gsup_router.h +++ b/src/gsup_router.h @@ -13,6 +13,7 @@ struct gsup_route { struct osmo_gsup_conn *gsup_route_find(struct osmo_gsup_server *gs, const uint8_t *addr, size_t addrlen); +struct osmo_gsup_conn *gsup_route_find_gt(struct osmo_gsup_server *gs, const struct global_title *gt); struct gsup_route *gsup_route_find_by_conn(const struct osmo_gsup_conn *conn); diff --git a/src/gsup_server.c b/src/gsup_server.c index a22ee14..f10034b 100644 --- a/src/gsup_server.c +++ b/src/gsup_server.c @@ -82,14 +82,10 @@ void osmo_gsup_conn_send_err_reply(struct osmo_gsup_conn *conn, const struct osm /* RP-Message-Reference is mandatory for SM Service */ .sm_rp_mr = gsup_orig->sm_rp_mr, }; + osmo_gsup_set_reply(gsup_orig, &gsup_reply); - OSMO_STRLCPY_ARRAY(gsup_reply.imsi, gsup_orig->imsi); - - /* For SS/USSD, it's important to keep both session state and ID IEs */ - if (gsup_orig->session_state != OSMO_GSUP_SESSION_STATE_NONE) { + if (gsup_orig->session_state != OSMO_GSUP_SESSION_STATE_NONE) gsup_reply.session_state = OSMO_GSUP_SESSION_STATE_END; - gsup_reply.session_id = gsup_orig->session_id; - } msg_out = osmo_gsup_msgb_alloc("GSUP ERR response"); rc = osmo_gsup_encode(msg_out, &gsup_reply); @@ -100,6 +96,9 @@ void osmo_gsup_conn_send_err_reply(struct osmo_gsup_conn *conn, const struct osm return; } + LOGP(DLGSUP, LOGL_DEBUG, "%s: GSUP tx %s\n", + osmo_quote_str(gsup_orig->imsi, -1), osmo_gsup_message_type_name(gsup_reply.message_type)); + rc = osmo_gsup_conn_send(conn, msg_out); if (rc) LOGP(DLGSUP, LOGL_ERROR, "%s: Unable to send error response %s (rc=%d)\n", diff --git a/src/gsupclient/gsup_client.c b/src/gsupclient/gsup_client.c index 814d5a2..4216c2c 100644 --- a/src/gsupclient/gsup_client.c +++ b/src/gsupclient/gsup_client.c @@ -97,7 +97,14 @@ static void connect_timer_cb(void *gsupc_) if (gsupc->is_connected) return; + if (gsupc->up_down_cb) { + /* When the up_down_cb() returns false, the user asks us not to retry connecting. */ + if (!gsupc->up_down_cb(gsupc, false)) + return; + } + gsup_client_connect(gsupc); + } static void client_send(struct osmo_gsup_client *gsupc, int proto_ext, @@ -139,9 +146,18 @@ static void gsup_client_updown_cb(struct ipa_client_conn *link, int up) gsup_client_oap_register(gsupc); osmo_timer_del(&gsupc->connect_timer); + + if (gsupc->up_down_cb) + gsupc->up_down_cb(gsupc, true); } else { osmo_timer_del(&gsupc->ping_timer); + if (gsupc->up_down_cb) { + /* When the up_down_cb() returns false, the user asks us not to retry connecting. */ + if (!gsupc->up_down_cb(gsupc, false)) + return; + } + osmo_timer_schedule(&gsupc->connect_timer, OSMO_GSUP_CLIENT_RECONNECT_INTERVAL, 0); } @@ -258,33 +274,29 @@ static void start_test_procedure(struct osmo_gsup_client *gsupc) gsup_client_send_ping(gsupc); } -/*! - * Create a gsup client connecting to the specified IP address and TCP port. - * Use the provided ipaccess unit as the client-side identifier; ipa_dev should - * be allocated in talloc_ctx talloc_ctx as well. - * \param[in] talloc_ctx talloc context. - * \param[in] ipa_dev IP access unit which contains client identification information; must be allocated - * in talloc_ctx as well to ensure it lives throughout the lifetime of the connection. - * \param[in] ip_addr GSUP server IP address. - * \param[in] tcp_port GSUP server TCP port. - * \param[in] read_cb callback for reading from the GSUP connection. - * \param[in] oapc_config OPA client configuration. - * \returns a GSUP client connection or NULL on failure. - */ -struct osmo_gsup_client *osmo_gsup_client_create2(void *talloc_ctx, +struct osmo_gsup_client *osmo_gsup_client_create3(void *talloc_ctx, struct ipaccess_unit *ipa_dev, const char *ip_addr, unsigned int tcp_port, + struct osmo_oap_client_config *oapc_config, osmo_gsup_client_read_cb_t read_cb, - struct osmo_oap_client_config *oapc_config) + osmo_gsup_client_up_down_cb_t up_down_cb, + void *data) { struct osmo_gsup_client *gsupc; int rc; + OSMO_ASSERT(ipa_dev->unit_name); + gsupc = talloc_zero(talloc_ctx, struct osmo_gsup_client); OSMO_ASSERT(gsupc); - gsupc->unit_name = (const char *)ipa_dev->unit_name; /* API backwards compat */ - gsupc->ipa_dev = ipa_dev; + *gsupc = (struct osmo_gsup_client){ + .unit_name = (const char *)ipa_dev->unit_name, /* API backwards compat */ + .ipa_dev = ipa_dev, + .read_cb = read_cb, + .up_down_cb = up_down_cb, + .data = data, + }; /* a NULL oapc_config will mark oap_state disabled. */ rc = osmo_oap_client_init(oapc_config, &gsupc->oap_state); @@ -303,14 +315,9 @@ struct osmo_gsup_client *osmo_gsup_client_create2(void *talloc_ctx, goto failed; osmo_timer_setup(&gsupc->connect_timer, connect_timer_cb, gsupc); - rc = gsup_client_connect(gsupc); - if (rc < 0) goto failed; - - gsupc->read_cb = read_cb; - return gsupc; failed: @@ -318,6 +325,30 @@ failed: return NULL; } +/*! + * Create a gsup client connecting to the specified IP address and TCP port. + * Use the provided ipaccess unit as the client-side identifier; ipa_dev should + * be allocated in talloc_ctx talloc_ctx as well. + * \param[in] talloc_ctx talloc context. + * \param[in] ipa_dev IP access unit which contains client identification information; must be allocated + * in talloc_ctx as well to ensure it lives throughout the lifetime of the connection. + * \param[in] ip_addr GSUP server IP address. + * \param[in] tcp_port GSUP server TCP port. + * \param[in] read_cb callback for reading from the GSUP connection. + * \param[in] oapc_config OPA client configuration. + * \returns a GSUP client connection or NULL on failure. + */ +struct osmo_gsup_client *osmo_gsup_client_create2(void *talloc_ctx, + struct ipaccess_unit *ipa_dev, + const char *ip_addr, + unsigned int tcp_port, + osmo_gsup_client_read_cb_t read_cb, + struct osmo_oap_client_config *oapc_config) +{ + return osmo_gsup_client_create3(talloc_ctx, ipa_dev, ip_addr, tcp_port, oapc_config, + read_cb, NULL, NULL); +} + /** * Like osmo_gsup_client_create2() except it expects a unit name instead * of a full-blown ipacess_unit as the client-side identifier. @@ -234,15 +234,14 @@ static int rx_send_auth_info(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup, struct db_context *dbc) { - struct osmo_gsup_message gsup_out; + struct osmo_gsup_message gsup_out = {}; struct msgb *msg_out; int rc; subscr_create_on_demand(gsup->imsi); /* initialize return message structure */ - memset(&gsup_out, 0, sizeof(gsup_out)); - memcpy(&gsup_out.imsi, &gsup->imsi, sizeof(gsup_out.imsi)); + osmo_gsup_set_reply(gsup, &gsup_out); rc = db_get_auc(dbc, gsup->imsi, conn->auc_3g_ind, gsup_out.auth_vectors, @@ -344,7 +343,7 @@ static int rx_upd_loc_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup) { struct hlr_subscriber *subscr; - struct lu_operation *luop = lu_op_alloc_conn(conn); + struct lu_operation *luop = lu_op_alloc_conn(conn, gsup); if (!luop) { LOGP(DMAIN, LOGL_ERROR, "LU REQ from conn without addr?\n"); return -EINVAL; @@ -406,9 +405,15 @@ static int rx_upd_loc_req(struct osmo_gsup_conn *conn, #endif /* Store the VLR / SGSN number with the subscriber, so we know where it was last seen. */ - LOGP(DAUC, LOGL_DEBUG, "IMSI='%s': storing %s = %s\n", - subscr->imsi, luop->is_ps ? "SGSN number" : "VLR number", - global_title_name(&luop->peer)); + if (global_title_cmp(&luop->vlr_number, &luop->peer)) + LOGP(DAUC, LOGL_DEBUG, "IMSI='%s': storing %s = %s, via proxy %s\n", + subscr->imsi, luop->is_ps ? "SGSN number" : "VLR number", + global_title_name(&luop->vlr_number), global_title_name(&luop->peer)); + else + LOGP(DAUC, LOGL_DEBUG, "IMSI='%s': storing %s = %s\n", + subscr->imsi, luop->is_ps ? "SGSN number" : "VLR number", + global_title_name(&luop->vlr_number)); + if (db_subscr_lu(g_hlr->dbc, subscr->id, &luop->peer, &luop->vlr_number, luop->is_ps)) LOGP(DAUC, LOGL_ERROR, "IMSI='%s': Cannot update %s in the database\n", subscr->imsi, luop->is_ps ? "SGSN number" : "VLR number"); @@ -425,12 +430,12 @@ static int rx_upd_loc_req(struct osmo_gsup_conn *conn, static int rx_purge_ms_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup) { - struct osmo_gsup_message gsup_reply = {0}; + struct osmo_gsup_message gsup_reply = {}; struct msgb *msg_out; bool is_ps = false; int rc; - memcpy(gsup_reply.imsi, gsup->imsi, sizeof(gsup_reply.imsi)); + osmo_gsup_set_reply(gsup, &gsup_reply); if (gsup->cn_domain == OSMO_GSUP_CN_DOMAIN_PS) is_ps = true; @@ -76,6 +76,7 @@ struct hlr { } server; struct { + unsigned int result_timeout_milliseconds; struct osmo_mslookup_client *client; struct osmo_mslookup_client_method *mdns; } client; diff --git a/src/hlr_ussd.c b/src/hlr_ussd.c index be0fee0..e2dbde1 100644 --- a/src/hlr_ussd.c +++ b/src/hlr_ussd.c @@ -262,16 +262,18 @@ static int ss_tx_to_ms(struct ss_session *ss, enum osmo_gsup_message_type gsup_m bool final, struct msgb *ss_msg) { - struct osmo_gsup_message resp = {0}; + struct osmo_gsup_message resp = { + .message_type = gsup_msg_type, + .cn_domain = OSMO_GSUP_CN_DOMAIN_CS, + .session_id = ss->session_id, + }; 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); diff --git a/src/hlr_vty.c b/src/hlr_vty.c index 0c359f8..314278f 100644 --- a/src/hlr_vty.c +++ b/src/hlr_vty.c @@ -147,6 +147,24 @@ DEFUN(cfg_hlr_gsup_bind_ip, return CMD_SUCCESS; } +DEFUN(cfg_hlr_gsup_ipa_name, + cfg_hlr_gsup_ipa_name_cmd, + "ipa-name NAME", + "Set the IPA name of this HLR, for proxying to remote HLRs\n" + "A globally unique name for this HLR. For example: PLMN + redundancy server number: HLR-901-70-0. " + "This name is used for GSUP routing and must be set if multiple HLRs interconnect (e.g. mslookup " + "for Distributed GSM).\n") +{ + if (vty->type != VTY_FILE) { + vty_out(vty, "gsup/ipa-name: The GSUP IPA name cannot be changed at run-time; " + "It can only be set in the configuraton file.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + g_hlr->gsup_proxy.gsup_client_name.serno = talloc_strdup(g_hlr, argv[0]); + return CMD_SUCCESS; +} + /*********************************************************************** * USSD Entity ***********************************************************************/ @@ -397,7 +415,6 @@ DEFUN(cfg_no_subscr_create_on_demand, cfg_no_subscr_create_on_demand_cmd, int hlr_vty_go_parent(struct vty *vty) { - dgsm_vty_go_parent_action(vty); switch (vty->node) { case GSUP_NODE: case EUSE_NODE: @@ -457,6 +474,7 @@ void hlr_vty_init(void) install_node(&gsup_node, config_write_hlr_gsup); install_element(GSUP_NODE, &cfg_hlr_gsup_bind_ip_cmd); + install_element(GSUP_NODE, &cfg_hlr_gsup_ipa_name_cmd); install_element(HLR_NODE, &cfg_database_cmd); @@ -66,6 +66,7 @@ static inline void fill_gsup_msg(struct osmo_gsup_message *out, osmo_strlcpy(out->imsi, lu->subscr.imsi, GSM23003_IMSI_MAX_DIGITS + 1); out->message_type = mt; + out->cn_domain = lu->is_ps ? OSMO_GSUP_CN_DOMAIN_PS : OSMO_GSUP_CN_DOMAIN_CS; } /* timer call-back in case LU operation doesn't receive an response */ @@ -123,7 +124,7 @@ void lu_op_free(struct lu_operation *luop) talloc_free(luop); } -struct lu_operation *lu_op_alloc_conn(struct osmo_gsup_conn *conn) +struct lu_operation *lu_op_alloc_conn(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup) { uint8_t *peer_addr; struct lu_operation *luop = lu_op_alloc(conn->server); @@ -140,10 +141,14 @@ struct lu_operation *lu_op_alloc_conn(struct osmo_gsup_conn *conn) return NULL; } + if (gsup && gsup->source_name_len) + global_title_set(&luop->vlr_number, gsup->source_name, gsup->source_name_len); + else + luop->vlr_number = luop->peer; + return luop; } -/* FIXME: this doesn't seem to work at all */ struct lu_operation *lu_op_by_imsi(const char *imsi, const struct llist_head *lst) { @@ -255,8 +260,6 @@ void lu_op_tx_del_subscr_data(struct lu_operation *luop) fill_gsup_msg(&gsup, luop, OSMO_GSUP_MSGT_DELETE_DATA_REQUEST); - gsup.cn_domain = OSMO_GSUP_CN_DOMAIN_PS; - /* Send ISD to new VLR/SGSN */ _luop_tx_gsup(luop, &gsup); } @@ -67,7 +67,7 @@ struct lu_operation { struct lu_operation *lu_op_alloc(struct osmo_gsup_server *srv); -struct lu_operation *lu_op_alloc_conn(struct osmo_gsup_conn *conn); +struct lu_operation *lu_op_alloc_conn(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup); void lu_op_statechg(struct lu_operation *luop, enum lu_state new_state); bool lu_op_fill_subscr(struct lu_operation *luop, struct db_context *dbc, const char *imsi); diff --git a/src/mslookup_server.c b/src/mslookup_server.c index 4255a41..219a20c 100644 --- a/src/mslookup_server.c +++ b/src/mslookup_server.c @@ -196,7 +196,7 @@ static bool subscriber_has_done_lu_here_proxy(const struct osmo_mslookup_query * OSMO_SOCKADDR_STR_FMT "\n", osmo_mslookup_result_name_c(OTC_SELECT, query, NULL), age, global_title_name(local_msc_name), - OSMO_SOCKADDR_STR_FMT_ARGS(&subscr->remote_hlr)); + OSMO_SOCKADDR_STR_FMT_ARGS(&subscr->remote_hlr_addr)); return true; } diff --git a/src/mslookup_server_mdns.c b/src/mslookup_server_mdns.c index c91cd5c..6b705da 100644 --- a/src/mslookup_server_mdns.c +++ b/src/mslookup_server_mdns.c @@ -36,7 +36,7 @@ static void osmo_mslookup_server_mdns_tx(struct osmo_mslookup_server_mdns *serve llist_add_tail(&rec_age->list, &ans.records); if (osmo_sockaddr_str_is_nonzero(&result->host_v4)) { - if (osmo_sockaddr_str_to_32(&result->host_v4, &ip_v4)) { + if (osmo_sockaddr_str_to_32n(&result->host_v4, &ip_v4)) { errmsg = "Error encoding IPv4 address"; goto clean_and_exit; } diff --git a/src/proxy.c b/src/proxy.c index a93c004..6724269 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -83,6 +83,19 @@ const struct proxy_subscr *proxy_subscr_get_by_msisdn(struct proxy *proxy, const return &e->data; } +void proxy_subscrs_get_by_remote_hlr(struct proxy *proxy, const struct osmo_sockaddr_str *remote_hlr_addr, + bool (*yield)(struct proxy *proxy, const struct proxy_subscr *subscr, void *data), + void *data) +{ + struct proxy_subscr_listentry *e; + llist_for_each_entry(e, &proxy->subscr_list, entry) { + if (!osmo_sockaddr_str_ip_cmp(remote_hlr_addr, &e->data.remote_hlr_addr)) { + if (!yield(proxy, &e->data, data)) + return; + } + } +} + int proxy_subscr_update(struct proxy *proxy, const struct proxy_subscr *proxy_subscr) { struct proxy_subscr_listentry *e = _proxy_get_by_imsi(proxy, proxy_subscr->imsi); diff --git a/src/proxy.h b/src/proxy.h index 4d332cf..0a99b0d 100644 --- a/src/proxy.h +++ b/src/proxy.h @@ -13,6 +13,7 @@ struct proxy { /* How long to keep proxy entries without a refresh, in seconds. */ uint32_t fresh_time; + /* How often to garbage collect the proxy cache, period in seconds. * To change this and take effect immediately, rather use proxy_set_gc_period(). */ uint32_t gc_period; @@ -28,14 +29,21 @@ struct proxy_subscr { struct global_title vlr_via_proxy; #endif struct global_title vlr_name; - struct osmo_sockaddr_str remote_hlr; + struct osmo_sockaddr_str remote_hlr_addr; struct timeval last_lu; }; struct proxy *proxy_init(void *ctx); void proxy_del(struct proxy *proxy); void proxy_set_gc_period(struct proxy *proxy, uint32_t gc_period); + +/* The API to access / modify proxy entries keeps the implementation opaque, to make sure that we can easily move proxy + * storage to SQLite db. */ const struct proxy_subscr *proxy_subscr_get_by_imsi(struct proxy *proxy, const char *imsi); const struct proxy_subscr *proxy_subscr_get_by_msisdn(struct proxy *proxy, const char *msisdn); +void proxy_subscrs_get_by_remote_hlr(struct proxy *proxy, const struct osmo_sockaddr_str *remote_hlr_addr, + bool (*yield)(struct proxy *proxy, const struct proxy_subscr *subscr, void *data), + void *data); +const struct proxy_subscr *proxy_subscr_get_by_imsi(struct proxy *proxy, const char *imsi); int proxy_subscr_update(struct proxy *proxy, const struct proxy_subscr *proxy_subscr); int proxy_subscr_del(struct proxy *proxy, const char *imsi); diff --git a/src/remote_hlr.c b/src/remote_hlr.c index 600b037..37ca913 100644 --- a/src/remote_hlr.c +++ b/src/remote_hlr.c @@ -8,6 +8,7 @@ #include "gsup_router.h" #include "dgsm.h" #include "remote_hlr.h" +#include "proxy.h" static LLIST_HEAD(remote_hlrs); @@ -49,10 +50,15 @@ void remote_hlr_err_reply(struct osmo_gsup_client *gsupc, const struct osmo_gsup osmo_quote_str(gsup_orig->imsi, -1)); } +/* We are receiving back a GSUP message from a remote HLR to go back to a local MSC. + * The local MSC shall be indicated by gsup.destination_name. */ static int remote_hlr_rx(struct osmo_gsup_client *gsupc, struct msgb *msg) { struct osmo_gsup_message gsup; - struct osmo_gsup_conn *msc_conn; + struct osmo_gsup_conn *vlr_conn; + struct proxy *proxy; + const struct proxy_subscr *proxy_subscr; + struct msgb *gsup_copy; int rc; rc = osmo_gsup_decode(msgb_l2(msg), msgb_l2len(msg), &gsup); @@ -68,26 +74,66 @@ static int remote_hlr_rx(struct osmo_gsup_client *gsupc, struct msgb *msg) return -GMM_CAUSE_INV_MAND_INFO; } - /* Since this is a proxy link to a remote osmo-msc, we are acting on behalf of a local MSC, and need to know the - * routing name of that local MSC. We have sent it to the remote HLR as source_name, and we're required to get - * it back as destination_name. */ - if (!gsup.destination_name || !gsup.destination_name_len) { - LOG_GSUP_MSG(gsupc, &gsup, LOGL_ERROR, "message lacks Destination Name IE, cannot route to MSC.\n"); + switch (gsup.cn_domain) { + case OSMO_GSUP_CN_DOMAIN_CS: + proxy = g_hlr->gsup_proxy.cs; + break; + case OSMO_GSUP_CN_DOMAIN_PS: + proxy = g_hlr->gsup_proxy.ps; + break; + default: + LOG_GSUP_MSG(gsupc, &gsup, LOGL_ERROR, "Unknown cn_domain: %d\n", gsup.cn_domain); remote_hlr_err_reply(gsupc, &gsup, GMM_CAUSE_INV_MAND_INFO); return -GMM_CAUSE_INV_MAND_INFO; } + if (!proxy) { + LOG_GSUP_MSG(gsupc, &gsup, LOGL_ERROR, "Cannot route, there is no GSUP proxy set up\n"); + remote_hlr_err_reply(gsupc, &gsup, GMM_CAUSE_NET_FAIL); + return -GMM_CAUSE_NET_FAIL; + } + + proxy_subscr = proxy_subscr_get_by_imsi(proxy, gsup.imsi); + if (!proxy_subscr) { + LOG_GSUP_MSG(gsupc, &gsup, LOGL_ERROR, "Cannot route, no GSUP proxy record for this IMSI\n"); + remote_hlr_err_reply(gsupc, &gsup, GMM_CAUSE_IMSI_UNKNOWN); + return -GMM_CAUSE_IMSI_UNKNOWN; + } + /* Route to MSC that we're proxying for */ - msc_conn = gsup_route_find(g_hlr->gs, gsup.destination_name, gsup.destination_name_len); - if (!msc_conn) { - LOG_GSUP_MSG(gsupc, &gsup, LOGL_ERROR, "Destination MSC unreachable: %s\n", - osmo_quote_str((char*)gsup.destination_name, gsup.destination_name_len)); + vlr_conn = gsup_route_find_gt(g_hlr->gs, &proxy_subscr->vlr_name); + if (!vlr_conn) { + LOG_GSUP_MSG(gsupc, &gsup, LOGL_ERROR, "Destination VLR unreachable: %s\n", + global_title_name(&proxy_subscr->vlr_name)); remote_hlr_err_reply(gsupc, &gsup, GMM_CAUSE_MSC_TEMP_NOTREACH); return -GMM_CAUSE_MSC_TEMP_NOTREACH; } - /* The outgoing message needs to be a separate msgb, because osmo_gsup_conn_send() takes ownership of it. */ - return osmo_gsup_conn_send(msc_conn, msgb_copy(msg, "GSUP proxy to MSC")); + /* The outgoing message needs to be a separate msgb, because osmo_gsup_conn_send() takes ownership of it, an the + * gsup_client also does a msgb_free() after dispatching to this callback. + * We also need to strip the IPA header and have headroom. Just re-encode. */ + gsup_copy = osmo_gsup_msgb_alloc("GSUP proxy to VLR"); + if (osmo_gsup_encode(gsup_copy, &gsup)) { + LOG_GSUP_MSG(gsupc, &gsup, LOGL_ERROR, "Failed to re-encode GSUP message, cannot forward\n"); + remote_hlr_err_reply(gsupc, &gsup, GMM_CAUSE_MSC_TEMP_NOTREACH); + return -GMM_CAUSE_MSC_TEMP_NOTREACH; + } + return osmo_gsup_conn_send(vlr_conn, gsup_copy); +} + +static bool remote_hlr_up_down(struct osmo_gsup_client *gsupc, bool up) +{ + struct remote_hlr *remote_hlr = gsupc->data; + if (!up) { + LOGP(DDGSM, LOGL_ERROR, + "link to remote HLR is down, removing GSUP client: " OSMO_SOCKADDR_STR_FMT "\n", + OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr)); + remote_hlr_destroy(remote_hlr); + return false; + } + + dgsm_remote_hlr_up(remote_hlr); + return true; } struct remote_hlr *remote_hlr_get(const struct osmo_sockaddr_str *addr, bool create) @@ -95,7 +141,7 @@ struct remote_hlr *remote_hlr_get(const struct osmo_sockaddr_str *addr, bool cre struct remote_hlr *rh; llist_for_each_entry(rh, &remote_hlrs, entry) { - if (!osmo_sockaddr_str_cmp(&rh->addr, addr)) + if (!osmo_sockaddr_str_ip_cmp(&rh->addr, addr)) return rh; } @@ -104,20 +150,36 @@ struct remote_hlr *remote_hlr_get(const struct osmo_sockaddr_str *addr, bool cre /* Doesn't exist yet, create a GSUP client to remote HLR. */ rh = talloc_zero(dgsm_ctx, struct remote_hlr); + OSMO_ASSERT(rh); *rh = (struct remote_hlr){ .addr = *addr, - .gsupc = osmo_gsup_client_create2(rh, &g_hlr->gsup_proxy.gsup_client_name, + .gsupc = osmo_gsup_client_create3(rh, &g_hlr->gsup_proxy.gsup_client_name, addr->ip, addr->port, + NULL, remote_hlr_rx, - NULL), + remote_hlr_up_down, + rh), }; if (!rh->gsupc) { + LOGP(DDGSM, LOGL_ERROR, + "Failed to establish connection to remote HLR " OSMO_SOCKADDR_STR_FMT "\n", + OSMO_SOCKADDR_STR_FMT_ARGS(addr)); talloc_free(rh); return NULL; } + rh->gsupc->data = rh; + llist_add(&rh->entry, &remote_hlrs); return rh; } +void remote_hlr_destroy(struct remote_hlr *remote_hlr) +{ + osmo_gsup_client_destroy(remote_hlr->gsupc); + remote_hlr->gsupc = NULL; + llist_del(&remote_hlr->entry); + talloc_free(remote_hlr); +} + /* This function takes ownership of the msg, do not free it after passing to this function. */ int remote_hlr_msgb_send(struct remote_hlr *remote_hlr, struct msgb *msg) { @@ -131,8 +193,13 @@ int remote_hlr_msgb_send(struct remote_hlr *remote_hlr, struct msgb *msg) int remote_hlr_gsup_send(struct remote_hlr *remote_hlr, const struct osmo_gsup_message *gsup) { + int rc; struct msgb *msg = osmo_gsup_msgb_alloc("GSUP proxy to remote HLR"); - osmo_gsup_encode(msg, gsup); + rc = osmo_gsup_encode(msg, gsup); + if (rc) { + LOG_DGSM(gsup->imsi, LOGL_ERROR, "Failed to encode GSUP message: %s\n", + osmo_gsup_message_type_name(gsup->message_type)); + return rc; + } return remote_hlr_msgb_send(remote_hlr, msg); } - diff --git a/src/remote_hlr.h b/src/remote_hlr.h index 331ef35..5f2c84d 100644 --- a/src/remote_hlr.h +++ b/src/remote_hlr.h @@ -8,6 +8,7 @@ struct osmo_gsup_client; struct osmo_gsup_message; struct msgb; +/* GSUP client link for proxying to a remote HLR. */ struct remote_hlr { struct llist_head entry; struct osmo_sockaddr_str addr; @@ -15,5 +16,6 @@ struct remote_hlr { }; struct remote_hlr *remote_hlr_get(const struct osmo_sockaddr_str *addr, bool create); +void remote_hlr_destroy(struct remote_hlr *remote_hlr); int remote_hlr_msgb_send(struct remote_hlr *remote_hlr, struct msgb *msg); int remote_hlr_gsup_send(struct remote_hlr *remote_hlr, const struct osmo_gsup_message *gsup); diff --git a/tests/Makefile.am b/tests/Makefile.am index 357fbac..e580678 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -4,6 +4,7 @@ SUBDIRS = \ db \ gsup \ db_upgrade \ + mslookup_manual_test \ $(NULL) # The `:;' works around a Bash 3.2 bug when the output is not writeable. diff --git a/tests/mslookup_manual_test/Makefile.am b/tests/mslookup_manual_test/Makefile.am new file mode 100644 index 0000000..20c3059 --- /dev/null +++ b/tests/mslookup_manual_test/Makefile.am @@ -0,0 +1,40 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/src \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + -ggdb3 \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMOABIS_CFLAGS) \ + $(NULL) + +AM_LDFLAGS = \ + -no-install \ + $(NULL) + +EXTRA_DIST = \ + run.sh \ + osmo-hlr-1.cfg \ + osmo-hlr-2.cfg \ + $(NULL) + +noinst_PROGRAMS = \ + fake_msc \ + $(NULL) + +fake_msc_SOURCES = \ + fake_msc.c \ + $(NULL) + +fake_msc_LDADD = \ + $(top_builddir)/src/gsupclient/libosmo-gsup-client.la \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOABIS_LIBS) \ + $(NULL) + +run: + $(srcdir)/run.sh $(srcdir) $(builddir) diff --git a/tests/mslookup_manual_test/fake_msc.c b/tests/mslookup_manual_test/fake_msc.c new file mode 100644 index 0000000..36a40b7 --- /dev/null +++ b/tests/mslookup_manual_test/fake_msc.c @@ -0,0 +1,80 @@ +#include <osmocom/core/logging.h> +#include <osmocom/core/select.h> +#include <osmocom/core/application.h> +#include <osmocom/gsupclient/gsup_client.h> + +void *ctx; + +int gsup_client_read_cb(struct osmo_gsup_client *gsupc, struct msgb *msg) +{ + struct osmo_gsup_message gsup; + if (osmo_gsup_decode(msgb_l2(msg), msgb_l2len(msg), &gsup)) { + printf("fake_msc: GSUP rx, but failed to decode\n"); + return 0; + } + printf("fake_msc: GSUP rx %s %s (destination_name=%s)\n", + gsup.imsi, osmo_gsup_message_type_name(gsup.message_type), + osmo_quote_str((const char*)gsup.destination_name, gsup.destination_name_len)); + return 0; +} + +struct osmo_gsup_client *gsupc; +struct osmo_timer_list do_stuff_timer; + +void do_stuff(void *data) +{ + static int i = 0; + int seq = 0; + if (i == seq++) { + struct osmo_gsup_message gsup = { + .message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST, + .imsi = "222222", + .cn_domain = OSMO_GSUP_CN_DOMAIN_CS, + }; + osmo_gsup_client_enc_send(gsupc, &gsup); + } + + seq += 3; + if (i == seq++) { + struct osmo_gsup_message gsup = { + .message_type = OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST, + .imsi = "222222", + .cn_domain = OSMO_GSUP_CN_DOMAIN_CS, + }; + osmo_gsup_client_enc_send(gsupc, &gsup); + } + + seq += 10; + if (i == seq++) { + exit(0); + } + + i++; + osmo_timer_schedule(&do_stuff_timer, 1, 0); +} + +int main() +{ + ctx = talloc_named_const(NULL, 0, "main"); + osmo_init_logging2(ctx, NULL); + + log_set_print_filename(osmo_stderr_target, 0); + log_set_print_level(osmo_stderr_target, 0); + log_set_print_category(osmo_stderr_target, 0); + log_set_print_category_hex(osmo_stderr_target, 0); + log_set_use_color(osmo_stderr_target, 0); + log_set_category_filter(osmo_stderr_target, DLMSLOOKUP, true, LOGL_DEBUG); + + struct ipaccess_unit gsup_client_name = { + .unit_name = "fake-msc-1", + .serno = "fake-msc-1", + }; + gsupc = osmo_gsup_client_create2(ctx, &gsup_client_name, "127.0.0.1", OSMO_GSUP_PORT, gsup_client_read_cb, + NULL); + + osmo_timer_setup(&do_stuff_timer, do_stuff, NULL); + osmo_timer_schedule(&do_stuff_timer, 1, 0); + for (;;) { + osmo_select_main_ctx(0); + } +} diff --git a/tests/mslookup_manual_test/osmo-hlr-1.cfg b/tests/mslookup_manual_test/osmo-hlr-1.cfg new file mode 100644 index 0000000..2f33e3e --- /dev/null +++ b/tests/mslookup_manual_test/osmo-hlr-1.cfg @@ -0,0 +1,40 @@ +hlr + gsup + bind ip 127.0.0.1 + ipa-name testHLR-1 + ussd route prefix *0# internal own-msisdn + ussd route prefix *1# internal own-imsi + ussd route prefix *#100# internal own-msisdn + ussd route prefix *#101# internal own-imsi + store-imei + +line vty + bind 127.0.0.1 +ctrl + bind 127.0.0.1 + +mslookup + mdns + +log stderr + logging filter all 1 + logging color 1 + logging print level 1 + logging print category 1 + logging print category-hex 0 + logging print file basename last + logging print extended-timestamp 1 + logging level set-all debug + logging level linp error + +log gsmtap 127.0.0.1 + logging filter all 1 + logging color 1 + logging print level 1 + logging print category 1 + logging print category-hex 0 + logging print file basename last + logging print extended-timestamp 1 + logging level set-all debug + logging level linp error + diff --git a/tests/mslookup_manual_test/osmo-hlr-2.cfg b/tests/mslookup_manual_test/osmo-hlr-2.cfg new file mode 100644 index 0000000..06ddd4f --- /dev/null +++ b/tests/mslookup_manual_test/osmo-hlr-2.cfg @@ -0,0 +1,40 @@ +hlr + gsup + bind ip 127.0.0.2 + ipa-name testHLR-2 + ussd route prefix *0# internal own-msisdn + ussd route prefix *1# internal own-imsi + ussd route prefix *#100# internal own-msisdn + ussd route prefix *#101# internal own-imsi + store-imei + +line vty + bind 127.0.0.2 +ctrl + bind 127.0.0.2 + +mslookup + mdns + +log stderr + logging filter all 1 + logging color 1 + logging print level 1 + logging print category 1 + logging print category-hex 0 + logging print file basename last + logging timestamp 1 + logging level set-all debug + logging level linp error + +log gsmtap 127.0.0.1 + logging filter all 1 + logging color 1 + logging print level 1 + logging print category 1 + logging print category-hex 0 + logging print file basename last + logging print extended-timestamp 1 + logging level set-all debug + logging level linp error + diff --git a/tests/mslookup_manual_test/run.sh b/tests/mslookup_manual_test/run.sh new file mode 100755 index 0000000..ea4dabf --- /dev/null +++ b/tests/mslookup_manual_test/run.sh @@ -0,0 +1,22 @@ +#!/bin/sh +srcdir="${1:-.}" +builddir="${2:-.}" + +cd "$builddir" + +osmo-hlr -c "$srcdir/osmo-hlr-1.cfg" -l hlr1.db & +sleep 1 +osmo-hlr -c "$srcdir/osmo-hlr-2.cfg" -l hlr2.db & + +sleep 1 +osmo_interact_vty.py -H 127.0.0.1 -p 4258 -c 'enable; subscriber imsi 111111 create; subscriber imsi 111111 update msisdn 1' +osmo_interact_vty.py -H 127.0.0.2 -p 4258 -c 'enable; subscriber imsi 222222 create; subscriber imsi 222222 update msisdn 2' +sleep 1 + +./fake_msc & + +echo enter to exit +read enter_to_exit +kill %1 %2 %3 +killall osmo-hlr +killall fake_msc |