diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 4 | ||||
-rw-r--r-- | src/db_hlr.c | 57 | ||||
-rw-r--r-- | src/gsup_router.c | 21 | ||||
-rw-r--r-- | src/gsup_send.c | 41 | ||||
-rw-r--r-- | src/gsup_server.c | 80 | ||||
-rw-r--r-- | src/gsupclient/Makefile.am | 6 | ||||
-rw-r--r-- | src/gsupclient/gsup_req.c | 410 | ||||
-rw-r--r-- | src/gsupclient/ipa_name.c | 97 | ||||
-rw-r--r-- | src/hlr.c | 492 | ||||
-rw-r--r-- | src/hlr_ussd.c | 199 | ||||
-rw-r--r-- | src/logging.c | 6 | ||||
-rw-r--r-- | src/lu_fsm.c | 308 | ||||
-rw-r--r-- | src/luop.c | 258 |
13 files changed, 1235 insertions, 744 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index f858ff0..bfbe775 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -41,7 +41,6 @@ osmo_hlr_SOURCES = \ auc.c \ ctrl.c \ db.c \ - luop.c \ db_auc.c \ db_hlr.c \ gsup_router.c \ @@ -53,9 +52,11 @@ osmo_hlr_SOURCES = \ hlr_vty_subscr.c \ gsup_send.c \ hlr_ussd.c \ + lu_fsm.c \ $(NULL) osmo_hlr_LDADD = \ + $(top_builddir)/src/gsupclient/libosmo-gsup-client.la \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOGSM_LIBS) \ $(LIBOSMOVTY_LIBS) \ @@ -71,6 +72,7 @@ osmo_hlr_db_tool_SOURCES = \ logging.c \ rand_urandom.c \ dbd_decode_binary.c \ + $(srcdir)/gsupclient/ipa_name.c \ $(NULL) osmo_hlr_db_tool_LDADD = \ diff --git a/src/db_hlr.c b/src/db_hlr.c index b3e3887..fdac75f 100644 --- a/src/db_hlr.c +++ b/src/db_hlr.c @@ -28,6 +28,7 @@ #include <time.h> #include <osmocom/core/utils.h> +#include <osmocom/core/timer.h> #include <osmocom/crypt/auth.h> #include <osmocom/gsm/gsm23003.h> @@ -36,8 +37,7 @@ #include <osmocom/hlr/logging.h> #include <osmocom/hlr/hlr.h> #include <osmocom/hlr/db.h> -#include <osmocom/hlr/gsup_server.h> -#include <osmocom/hlr/luop.h> +#include <osmocom/gsupclient/ipa_name.h> #define LOGHLR(imsi, level, fmt, args ...) LOGP(DAUC, level, "IMSI='%s': " fmt, imsi, ## args) @@ -734,7 +734,8 @@ out: * -EIO on database errors. */ int db_subscr_lu(struct db_context *dbc, int64_t subscr_id, - const char *vlr_or_sgsn_number, bool is_ps) + const struct osmo_ipa_name *vlr_name, bool is_ps, + const struct osmo_ipa_name *via_proxy) { sqlite3_stmt *stmt; int rc, ret = 0; @@ -746,7 +747,7 @@ int db_subscr_lu(struct db_context *dbc, int64_t subscr_id, if (!db_bind_int64(stmt, "$subscriber_id", subscr_id)) return -EIO; - if (!db_bind_text(stmt, "$number", vlr_or_sgsn_number)) + if (!db_bind_text(stmt, "$number", (char*)vlr_name->val)) return -EIO; /* execute the statement */ @@ -873,51 +874,3 @@ out: return ret; } - -/*! Update nam_cs/nam_ps in the db and trigger notifications to GSUP clients. - * \param[in,out] hlr Global hlr context. - * \param[in] subscr Subscriber from a fresh db_subscr_get_by_*() call. - * \param[in] nam_val True to enable CS/PS, false to disable. - * \param[in] is_ps True to enable/disable PS, false for CS. - * \returns 0 on success, ENOEXEC if there is no need to change, a negative - * value on error. - */ -int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps) -{ - int rc; - struct lu_operation *luop; - struct osmo_gsup_conn *co; - bool is_val = is_ps? subscr->nam_ps : subscr->nam_cs; - - if (is_val == nam_val) { - LOGHLR(subscr->imsi, LOGL_DEBUG, "Already has the requested value when asked to %s %s\n", - nam_val ? "enable" : "disable", is_ps ? "PS" : "CS"); - return ENOEXEC; - } - - rc = db_subscr_nam(hlr->dbc, subscr->imsi, nam_val, is_ps); - if (rc) - return rc > 0? -rc : rc; - - /* If we're disabling, send a notice out to the GSUP client that is - * responsible. Otherwise no need. */ - if (nam_val) - return 0; - - /* 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); - if (!luop) { - LOGHLR(subscr->imsi, LOGL_ERROR, - "Cannot notify GSUP client, cannot allocate lu_operation," - " for %s:%u\n", - co && co->conn && co->conn->server? co->conn->server->addr : "unset", - co && co->conn && co->conn->server? co->conn->server->port : 0); - continue; - } - luop->subscr = *subscr; - lu_op_tx_del_subscr_data(luop); - lu_op_free(luop); - } - return 0; -} diff --git a/src/gsup_router.c b/src/gsup_router.c index adf3af7..ba71fe4 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_by_ipa_name(struct osmo_gsup_server *gs, const struct osmo_ipa_name *ipa_name) +{ + return gsup_route_find(gs, ipa_name->val, ipa_name->len); +} + /*! Find a GSUP connection's route (to read the IPA address from the route). * \param[in] conn GSUP connection * \return GSUP route @@ -67,10 +72,15 @@ struct gsup_route *gsup_route_find_by_conn(const struct osmo_gsup_conn *conn) int gsup_route_add(struct osmo_gsup_conn *conn, const uint8_t *addr, size_t addrlen) { struct gsup_route *gr; + struct osmo_gsup_conn *exists_on_conn; /* Check if we already have a route for this address */ - if (gsup_route_find(conn->server, addr, addrlen)) - return -EEXIST; + exists_on_conn = gsup_route_find(conn->server, addr, addrlen); + if (exists_on_conn) { + if (exists_on_conn != conn) + return -EEXIST; + return 0; + } /* allocate new route and populate it */ gr = talloc_zero(conn->server, struct gsup_route); @@ -86,6 +96,11 @@ int gsup_route_add(struct osmo_gsup_conn *conn, const uint8_t *addr, size_t addr return 0; } +int gsup_route_add_ipa_name(struct osmo_gsup_conn *conn, const struct osmo_ipa_name *ipa_name) +{ + return gsup_route_add(conn, ipa_name->val, ipa_name->len); +} + /* delete all routes for the given connection */ int gsup_route_del_conn(struct osmo_gsup_conn *conn) { @@ -95,7 +110,7 @@ int gsup_route_del_conn(struct osmo_gsup_conn *conn) llist_for_each_entry_safe(gr, gr2, &conn->server->routes, list) { if (gr->conn == conn) { LOGP(DMAIN, LOGL_INFO, "Removing GSUP route for %s (GSUP disconnect)\n", - gr->addr); + osmo_quote_str_c(OTC_SELECT, (char*)gr->addr, talloc_total_size(gr->addr))); llist_del(&gr->list); talloc_free(gr); num_deleted++; diff --git a/src/gsup_send.c b/src/gsup_send.c index 29aeaa5..99fae93 100644 --- a/src/gsup_send.c +++ b/src/gsup_send.c @@ -42,7 +42,8 @@ int osmo_gsup_addr_send(struct osmo_gsup_server *gs, conn = gsup_route_find(gs, addr, addrlen); if (!conn) { - DEBUGP(DLGSUP, "Cannot find route for addr %s\n", osmo_quote_str((const char*)addr, addrlen)); + LOGP(DLGSUP, LOGL_ERROR, + "Cannot find route for addr %s\n", osmo_quote_str((const char*)addr, addrlen)); msgb_free(msg); return -ENODEV; } @@ -50,3 +51,41 @@ int osmo_gsup_addr_send(struct osmo_gsup_server *gs, return osmo_gsup_conn_send(conn, msg); } +/*! Send a msgb to a given address using routing. + * \param[in] gs gsup server + * \param[in] ipa_name IPA unit name of the client (SGSN, MSC/VLR, proxy). + * \param[in] msg message buffer + */ +int osmo_gsup_send_to_ipa_name(struct osmo_gsup_server *gs, const struct osmo_ipa_name *ipa_name, struct msgb *msg) +{ + if (ipa_name->val[ipa_name->len - 1]) { + /* Is not nul terminated. But for legacy reasons we (still) require that. */ + if (ipa_name->len >= sizeof(ipa_name->val)) { + LOGP(DLGSUP, LOGL_ERROR, "IPA unit name is too long: %s\n", + osmo_ipa_name_to_str(ipa_name)); + return -EINVAL; + } + struct osmo_ipa_name ipa_name2 = *ipa_name; + ipa_name2.val[ipa_name->len] = '\0'; + ipa_name2.len++; + return osmo_gsup_addr_send(gs, ipa_name2.val, ipa_name2.len, msg); + } + return osmo_gsup_addr_send(gs, ipa_name->val, ipa_name->len, msg); +} + +int osmo_gsup_enc_send_to_ipa_name(struct osmo_gsup_server *gs, const struct osmo_ipa_name *ipa_name, + const struct osmo_gsup_message *gsup) +{ + struct msgb *msg = osmo_gsup_msgb_alloc("GSUP Tx"); + int rc; + rc = osmo_gsup_encode(msg, gsup); + if (rc) { + LOGP(DLGSUP, LOGL_ERROR, "IMSI-%s: Cannot encode GSUP: %s\n", + gsup->imsi, osmo_gsup_message_type_name(gsup->message_type)); + msgb_free(msg); + return -EINVAL; + } + + LOGP(DLGSUP, LOGL_DEBUG, "IMSI-%s: Tx: %s\n", gsup->imsi, osmo_gsup_message_type_name(gsup->message_type)); + return osmo_gsup_send_to_ipa_name(gs, ipa_name, msg); +} diff --git a/src/gsup_server.c b/src/gsup_server.c index ed1b285..ba2d456 100644 --- a/src/gsup_server.c +++ b/src/gsup_server.c @@ -26,10 +26,15 @@ #include <osmocom/abis/ipaccess.h> #include <osmocom/gsm/gsm48_ie.h> #include <osmocom/gsm/apn.h> +#include <osmocom/gsm/gsm23003.h> #include <osmocom/hlr/gsup_server.h> #include <osmocom/hlr/gsup_router.h> +#define LOG_GSUP_CONN(conn, level, fmt, args...) \ + LOGP(DLGSUP, level, "GSUP peer %s: " fmt, \ + (conn) ? osmo_ipa_name_to_str(&(conn)->peer_name) : "NULL", ##args) + struct msgb *osmo_gsup_msgb_alloc(const char *label) { struct msgb *msg = msgb_alloc_headroom(1024+16, 16, label); @@ -57,6 +62,57 @@ int osmo_gsup_conn_send(struct osmo_gsup_conn *conn, struct msgb *msg) return 0; } +static void gsup_server_send_req_response(struct osmo_gsup_req *req, struct osmo_gsup_message *response) +{ + struct osmo_gsup_server *server = req->cb_data; + struct osmo_gsup_conn *conn; + struct msgb *msg = osmo_gsup_msgb_alloc("GSUP Tx"); + int rc; + + conn = gsup_route_find_by_ipa_name(server, &req->source_name); + if (!conn) { + LOG_GSUP_REQ(req, LOGL_ERROR, "GSUP client that sent this request was disconnected, cannot respond\n"); + msgb_free(msg); + return; + } + + rc = osmo_gsup_encode(msg, response); + if (rc) { + LOG_GSUP_REQ(req, LOGL_ERROR, "Unable to encode: {%s}\n", + osmo_gsup_message_to_str_c(OTC_SELECT, response)); + msgb_free(msg); + return; + } + + rc = osmo_gsup_conn_send(conn, msg); + if (rc) + LOG_GSUP_CONN(conn, LOGL_ERROR, "Unable to send: %s\n", osmo_gsup_message_to_str_c(OTC_SELECT, response)); +} + +struct osmo_gsup_req *osmo_gsup_conn_rx(struct osmo_gsup_conn *conn, struct msgb *msg) +{ + struct osmo_gsup_req *req = osmo_gsup_req_new(conn->server, &conn->peer_name, msg, gsup_server_send_req_response, + conn->server, NULL); + if (!req) + return NULL; + + if (req->via_proxy.len) { + /* The source of the GSUP message is not the immediate GSUP peer, but that peer is our proxy for that + * source. Add it to the routes for this conn (so we can route responses back). */ + if (gsup_route_add_ipa_name(conn, &req->source_name)) { + LOG_GSUP_REQ(req, LOGL_ERROR, + "GSUP message received from %s via peer %s, but there already exists a" + " different route to this source, message is not routable\n", + osmo_ipa_name_to_str(&req->source_name), + osmo_ipa_name_to_str(&conn->peer_name)); + osmo_gsup_req_respond_msgt(req, OSMO_GSUP_MSGT_ROUTING_ERROR, true); + return NULL; + } + } + + return req; +} + static int osmo_gsup_conn_oap_handle(struct osmo_gsup_conn *conn, struct msgb *msg_rx) { @@ -202,10 +258,18 @@ static int osmo_gsup_server_ccm_cb(struct ipa_server_conn *conn, return -EINVAL; } - gsup_route_add(clnt, addr, addr_len); + osmo_ipa_name_set(&clnt->peer_name, addr, addr_len); + gsup_route_add_ipa_name(clnt, &clnt->peer_name); return 0; } +static void osmo_gsup_conn_free(struct osmo_gsup_conn *conn) +{ + gsup_route_del_conn(conn); + llist_del(&conn->list); + talloc_free(conn); +} + static int osmo_gsup_server_closed_cb(struct ipa_server_conn *conn) { struct osmo_gsup_conn *clnt = (struct osmo_gsup_conn *)conn->data; @@ -213,10 +277,7 @@ static int osmo_gsup_server_closed_cb(struct ipa_server_conn *conn) LOGP(DLGSUP, LOGL_INFO, "Lost GSUP client %s:%d\n", conn->addr, conn->port); - gsup_route_del_conn(clnt); - llist_del(&clnt->list); - talloc_free(clnt); - + osmo_gsup_conn_free(clnt); return 0; } @@ -298,8 +359,7 @@ failed: struct osmo_gsup_server * osmo_gsup_server_create(void *ctx, const char *ip_addr, uint16_t tcp_port, - osmo_gsup_read_cb_t read_cb, - struct llist_head *lu_op_lst, void *priv) + osmo_gsup_read_cb_t read_cb, void *priv) { struct osmo_gsup_server *gsups; int rc; @@ -325,8 +385,6 @@ osmo_gsup_server_create(void *ctx, const char *ip_addr, uint16_t tcp_port, if (rc < 0) goto failed; - gsups->luop = lu_op_lst; - return gsups; failed: @@ -390,8 +448,10 @@ int osmo_gsup_create_insert_subscriber_data_msg(struct osmo_gsup_message *gsup, int len; OSMO_ASSERT(gsup); + *gsup = (struct osmo_gsup_message){ + .message_type = OSMO_GSUP_MSGT_INSERT_DATA_REQUEST, + }; - gsup->message_type = OSMO_GSUP_MSGT_INSERT_DATA_REQUEST; osmo_strlcpy(gsup->imsi, imsi, sizeof(gsup->imsi)); if (msisdn_enc_size < OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN) diff --git a/src/gsupclient/Makefile.am b/src/gsupclient/Makefile.am index 4a449ec..38b1582 100644 --- a/src/gsupclient/Makefile.am +++ b/src/gsupclient/Makefile.am @@ -8,7 +8,11 @@ AM_CFLAGS = -Wall $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)/incl lib_LTLIBRARIES = libosmo-gsup-client.la -libosmo_gsup_client_la_SOURCES = gsup_client.c +libosmo_gsup_client_la_SOURCES = \ + ipa_name.c \ + gsup_client.c \ + gsup_req.c \ + $(NULL) libosmo_gsup_client_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined libosmo_gsup_client_la_LIBADD = $(TALLOC_LIBS) $(LIBOSMOCORE_LIBS) $(LIBOSMOABIS_LIBS) diff --git a/src/gsupclient/gsup_req.c b/src/gsupclient/gsup_req.c new file mode 100644 index 0000000..4a2ff23 --- /dev/null +++ b/src/gsupclient/gsup_req.c @@ -0,0 +1,410 @@ +/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <errno.h> +#include <inttypes.h> + +#include <osmocom/core/logging.h> +#include <osmocom/gsm/gsm23003.h> + +#include <osmocom/gsupclient/gsup_req.h> + +/*! Create a new osmo_gsup_req record, decode GSUP and add to a provided list of requests. + * + * Rationales: + * + * - osmo_gsup_req makes it easy to handle GSUP requests asynchronously. Before this, a GSUP message struct would be + * valid only within a read callback function, and would not survive asynchronous handling, because the struct often + * points directly into the received msgb. An osmo_gsup_req takes ownership of the msgb and ensures that the data + * remains valid, so that it can easily be queued for later handling. + * - osmo_gsup_req unifies the composition of response messages to ensure that all IEs that identify it to belong to + * the initial request are preserved / derived, like the source_name, destination_name, session_id, etc (see + * osmo_gsup_make_response() for details). + * - Deallocation of an osmo_gsup_req is implicit upon sending a response. The idea is that msgb memory leaks are a + * recurring source of bugs. By enforcing a request-response relation with implicit deallocation, osmo_gsup_req aims + * to help avoid most such memory leaks implicitly. + * + * The typical GSUP message sequence is: + * -> rx request, + * <- tx response. + * + * With osmo_gsup_req we can easily expand to: + * -> rx request, + * ... wait asynchronously, + * <- tx response. + * + * Only few GSUP conversations go beyond a 1:1 request-response match. But some have a session (e.g. USSD) or more + * negotiation may happen before the initial request is completed (e.g. Update Location with interleaved Insert + * Subscriber Data), so osmo_gsup_req also allows passing non-final responses. + * The final_response flag allows for: + * -> rx request, + * ... wait async, + * <- tx intermediate message to same peer (final_response = false, req remains open), + * ... wait async, + * -> rx intermediate response, + * ... wait async, + * <- tx final response (final_response = true, req is deallocated). + * + * This function takes ownership of the msgb, which will, on success, be owned by the returned osmo_gsup_req instance + * until osmo_gsup_req_free(). If a decoding error occurs, send an error response immediately, and return NULL. + * + * The original CNI entity that sent the message is found in req->source_name. If the message was passed on by an + * intermediate CNI peer, then req->via_proxy is set to the immediate peer, and it is the responsibility of the caller + * to add req->source_name to the GSUP routes that are serviced by req->via_proxy (usually not relevant for clients with + * a single GSUP conn). + * Examples: + * + * "msc" ---> here + * source_name = "msc" + * via_proxy = <empty> + * + * "msc" ---> "proxy-HLR" ---> here (e.g. home HLR) + * source_name = "msc" + * via_proxy = "proxy-HLR" + * + * "msc" ---> "proxy-HLR" ---> "home-HLR" ---> here (e.g. EUSE) + * source_name = "msc" + * via_proxy = "home-HLR" + * + * An osmo_gsup_req must be concluded (and deallocated) by calling one of the osmo_gsup_req_respond* functions. + * + * Note: osmo_gsup_req API makes use of OTC_SELECT to allocate volatile buffers for logging. Use of + * osmo_select_main_ctx() is mandatory when using osmo_gsup_req. + * + * \param[in] ctx Talloc context for allocation of the new request. + * \param[in] from_peer The IPA unit name of the immediate GSUP peer from which this msgb was received. + * \param[in] msg The message buffer containing the received GSUP message, where msgb_l2() shall point to the GSUP + * message start. The caller no longer owns the msgb when it is passed to this function: on error, the + * msgb is freed immediately, and on success, the msgb is owned by the returned osmo_gsup_req. + * \param[in] send_response_cb User specific method to send a GSUP response message, invoked upon + * osmo_gsup_req_respond*() functions. Typically this invokes encoding and transmitting the + * GSUP message over a network socket. See for example gsup_server_send_req_response(). + * \param[inout] cb_data Context data to be used freely by the caller. + * \param[inout] add_to_list List to which to append this request, or NULL for no list. + * \return a newly allocated osmo_gsup_req, or NULL on error. If NULL is returned, an error response has already been + * dispatched to the send_response_cb. + */ +struct osmo_gsup_req *osmo_gsup_req_new(void *ctx, const struct osmo_ipa_name *from_peer, struct msgb *msg, + osmo_gsup_req_send_response_t send_response_cb, void *cb_data, + struct llist_head *add_to_list) +{ + static unsigned int next_req_nr = 1; + struct osmo_gsup_req *req; + int rc; + + if (!msgb_l2(msg) || !msgb_l2len(msg)) { + LOGP(DLGSUP, LOGL_ERROR, "Rx GSUP from %s: missing or empty L2 data\n", + osmo_ipa_name_to_str(from_peer)); + msgb_free(msg); + return NULL; + } + + req = talloc_zero(ctx, struct osmo_gsup_req); + OSMO_ASSERT(req); + /* Note: req->gsup is declared const, so that the incoming message cannot be modified by handlers. */ + req->nr = next_req_nr++; + req->msg = msg; + req->send_response_cb = send_response_cb; + req->cb_data = cb_data; + if (from_peer) + req->source_name = *from_peer; + rc = osmo_gsup_decode(msgb_l2(req->msg), msgb_l2len(req->msg), (struct osmo_gsup_message*)&req->gsup); + if (rc < 0) { + LOGP(DLGSUP, LOGL_ERROR, "Rx GSUP from %s: cannot decode (rc=%d)\n", osmo_ipa_name_to_str(from_peer), rc); + osmo_gsup_req_free(req); + return NULL; + } + + LOG_GSUP_REQ(req, LOGL_DEBUG, "new request: {%s}\n", osmo_gsup_message_to_str_c(OTC_SELECT, &req->gsup)); + + if (req->gsup.source_name_len) { + if (osmo_ipa_name_set(&req->source_name, req->gsup.source_name, req->gsup.source_name_len)) { + LOGP(DLGSUP, LOGL_ERROR, + "Rx GSUP from %s: failed to decode source_name, message is not routable\n", + osmo_ipa_name_to_str(from_peer)); + osmo_gsup_req_respond_msgt(req, OSMO_GSUP_MSGT_ROUTING_ERROR, true); + return NULL; + } + + /* The source of the GSUP message is not the immediate GSUP peer; the peer is our proxy for that source. + */ + if (osmo_ipa_name_cmp(&req->source_name, from_peer)) + req->via_proxy = *from_peer; + } + + if (!osmo_imsi_str_valid(req->gsup.imsi)) { + osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO, "invalid IMSI: %s", + osmo_quote_str(req->gsup.imsi, -1)); + return NULL; + } + + if (add_to_list) + llist_add_tail(&req->entry, add_to_list); + return req; +} + +/*! Free an osmo_gsup_req and its msgb -- this is usually implicit in osmo_gsup_req_resond_*(), it should not be + * necessary to call this directly. */ +void osmo_gsup_req_free(struct osmo_gsup_req *req) +{ + LOG_GSUP_REQ(req, LOGL_DEBUG, "free\n"); + if (req->msg) + msgb_free(req->msg); + if (req->entry.prev) + llist_del(&req->entry); + talloc_free(req); +} + +/*! Send a response to a GSUP request. + * + * Ensure that the response message contains all GSUP IEs that identify it as a response for the request req, by calling + * osmo_gsup_make_response(). + * + * The final complete response message is passed to req->send_response_cb() to take care of the transmission. + * + * \param req Request as previously initialized by osmo_gsup_req_new(). + * \param response Buffer to compose the response, possibly with some pre-configured IEs. + * Any missing IEs are added via osmo_gsup_make_response(). + * Must not be NULL. Does not need to remain valid memory beyond the function call, + * i.e. this can just be a local variable in the calling function. + * \param error True when the response message indicates an error response (error message type). + * \param final_response True when the request is concluded by this response, which deallocates the req. + * False when the request should remain open after this response. + * For most plain request->response GSUP messages, this should be True. + * \param file Source file for logging as in __FILE__, added by osmo_gsup_req_respond() macro. + * \param line Source line for logging as in __LINE__, added by osmo_gsup_req_respond() macro. + */ +int _osmo_gsup_req_respond(struct osmo_gsup_req *req, struct osmo_gsup_message *response, + bool error, bool final_response, const char *file, int line) +{ + int rc; + + rc = osmo_gsup_make_response(response, &req->gsup, error, final_response); + if (rc) { + LOG_GSUP_REQ_SRC(req, LOGL_ERROR, file, line, "Invalid response (rc=%d): {%s}\n", + rc, osmo_gsup_message_to_str_c(OTC_SELECT, response)); + rc = -EINVAL; + goto exit_cleanup; + } + + if (!req->send_response_cb) { + LOG_GSUP_REQ_SRC(req, LOGL_ERROR, file, line, "No send_response_cb set, cannot send: {%s}\n", + osmo_gsup_message_to_str_c(OTC_SELECT, response)); + rc = -EINVAL; + goto exit_cleanup; + } + + LOG_GSUP_REQ_SRC(req, LOGL_DEBUG, file, line, "Tx response: {%s}\n", + osmo_gsup_message_to_str_c(OTC_SELECT, response)); + req->send_response_cb(req, response); + +exit_cleanup: + if (final_response) + osmo_gsup_req_free(req); + return rc; +} + +/*! Shorthand for _osmo_gsup_req_respond() with no additional IEs and a fixed message type. + * Set the message type in a local osmo_gsup_message and feed it to _osmo_gsup_req_respond(). + * That will ensure to add all IEs that identify it as a response to req. + * + * \param req Request as previously initialized by osmo_gsup_req_new(). + * \param message_type The GSUP message type discriminator to respond with. + * \param final_response True when the request is concluded by this response, which deallocates the req. + * False when the request should remain open after this response. + * For most plain request->response GSUP messages, this should be True. + * \param file Source file for logging as in __FILE__, added by osmo_gsup_req_respond_msgt() macro. + * \param line Source line for logging as in __LINE__, added by osmo_gsup_req_respond_msgt() macro. + */ +int _osmo_gsup_req_respond_msgt(struct osmo_gsup_req *req, enum osmo_gsup_message_type message_type, + bool final_response, const char *file, int line) +{ + struct osmo_gsup_message response = { + .message_type = message_type, + }; + return _osmo_gsup_req_respond(req, &response, OSMO_GSUP_IS_MSGT_ERROR(message_type), final_response, + file, line); +} + +/*! Shorthand for _osmo_gsup_req_respond() with an error cause IEs and using the req's matched error message type. + * Set the error cause in a local osmo_gsup_message and feed it to _osmo_gsup_req_respond(). + * That will ensure to add all IEs that identify it as a response to req. + * + * Responding with an error always implies a final response: req is implicitly deallocated. + * + * \param req Request as previously initialized by osmo_gsup_req_new(). + * \param cause The error cause to include in a OSMO_GSUP_CAUSE_IE. + * \param file Source file for logging as in __FILE__, added by osmo_gsup_req_respond_err() macro. + * \param line Source line for logging as in __LINE__, added by osmo_gsup_req_respond_err() macro. + */ +void _osmo_gsup_req_respond_err(struct osmo_gsup_req *req, enum gsm48_gmm_cause cause, + const char *file, int line) +{ + struct osmo_gsup_message response = { + .cause = cause, + }; + + /* No need to answer if we couldn't parse an ERROR message type, only REQUESTs need an error reply. */ + if (!OSMO_GSUP_IS_MSGT_REQUEST(req->gsup.message_type)) { + osmo_gsup_req_free(req); + return; + } + + osmo_gsup_req_respond(req, &response, true, true); +} + +/*! This function is implicitly called by the osmo_gsup_req API, if at all possible rather use osmo_gsup_req_respond(). + * This function is non-static mostly to allow unit testing. + * + * Set fields, if still unset, that need to be copied from a received message over to its response message, to ensure + * the response can be routed back to the requesting peer even via GSUP proxies. + * + * Note: after calling this function, fields in the reply may reference the same memory as rx and are not deep-copied, + * as is the usual way we are handling decoded GSUP messages. + * + * These fields are set in the reply message, iff they are still unset: + * - Set reply->message_type to the rx's matching RESULT code (or ERROR code if error == true). + * - IMSI, + * - Set reply->destination_name to rx->source_name (for proxy routing), + * - sm_rp_mr (for SMS), + * - session_id (for SS/USSD), + * - if rx->session_state is not NONE, set tx->session_state depending on the final_response argument: + * If false, set to OSMO_GSUP_SESSION_STATE_CONTINUE, else OSMO_GSUP_SESSION_STATE_END. + * + * If values in reply are already set, they will not be overwritten. The return code is an optional way of finding out + * whether all values that were already set in 'reply' are indeed matching the 'rx' values that would have been set. + * + * \param[in] rx Received GSUP message that is being replied to. + * \param[inout] reply The message that should be the response to rx, either empty or with some values already set up. + * \return 0 if the resulting message is a valid response for rx, nonzero otherwise. A nonzero rc has no effect on the + * values set in the reply message: all unset fields are first updated, and then the rc is determined. + * The rc is intended to merely warn if the reply message already contained data that is incompatible with rx, + * e.g. a mismatching IMSI. + */ +int osmo_gsup_make_response(struct osmo_gsup_message *reply, + const struct osmo_gsup_message *rx, bool error, bool final_response) +{ + int rc = 0; + + if (!reply->message_type) { + if (error) + reply->message_type = OSMO_GSUP_TO_MSGT_ERROR(rx->message_type); + else + reply->message_type = OSMO_GSUP_TO_MSGT_RESULT(rx->message_type); + } + + if (*reply->imsi == '\0') + OSMO_STRLCPY_ARRAY(reply->imsi, rx->imsi); + + if (reply->message_class == OSMO_GSUP_MESSAGE_CLASS_UNSET) + reply->message_class = rx->message_class; + + if (!reply->destination_name || !reply->destination_name_len) { + reply->destination_name = rx->source_name; + reply->destination_name_len = rx->source_name_len; + } + + /* RP-Message-Reference is mandatory for SM Service */ + if (!reply->sm_rp_mr) + reply->sm_rp_mr = rx->sm_rp_mr; + + /* For SS/USSD, it's important to keep both session state and ID IEs */ + if (!reply->session_id) + reply->session_id = rx->session_id; + if (rx->session_state != OSMO_GSUP_SESSION_STATE_NONE + && reply->session_state == OSMO_GSUP_SESSION_STATE_NONE) { + if (final_response || rx->session_state == OSMO_GSUP_SESSION_STATE_END) + reply->session_state = OSMO_GSUP_SESSION_STATE_END; + else + reply->session_state = OSMO_GSUP_SESSION_STATE_CONTINUE; + } + + if (strcmp(reply->imsi, rx->imsi)) + rc |= 1 << 0; + if (reply->message_class != rx->message_class) + rc |= 1 << 1; + if (rx->sm_rp_mr && (!reply->sm_rp_mr || *rx->sm_rp_mr != *reply->sm_rp_mr)) + rc |= 1 << 2; + if (reply->session_id != rx->session_id) + rc |= 1 << 3; + return rc; +} + +/*! Print the most important value of a GSUP message to a string buffer in human readable form. + * \param[out] buf The buffer to write to. + * \param[out] buflen sizeof(buf). + * \param[in] msg GSUP message to print. + */ +size_t osmo_gsup_message_to_str_buf(char *buf, size_t buflen, const struct osmo_gsup_message *msg) +{ + struct osmo_strbuf sb = { .buf = buf, .len = buflen }; + if (!msg) { + OSMO_STRBUF_PRINTF(sb, "NULL"); + return sb.chars_needed; + } + + if (msg->message_class) + OSMO_STRBUF_PRINTF(sb, "%s ", osmo_gsup_message_class_name(msg->message_class)); + + OSMO_STRBUF_PRINTF(sb, "%s:", osmo_gsup_message_type_name(msg->message_type)); + + OSMO_STRBUF_PRINTF(sb, " imsi="); + OSMO_STRBUF_APPEND(sb, osmo_quote_cstr_buf, msg->imsi, strnlen(msg->imsi, sizeof(msg->imsi))); + + if (msg->cause) + OSMO_STRBUF_PRINTF(sb, " cause=%s", get_value_string(gsm48_gmm_cause_names, msg->cause)); + + switch (msg->cn_domain) { + case OSMO_GSUP_CN_DOMAIN_CS: + OSMO_STRBUF_PRINTF(sb, " cn_domain=CS"); + break; + case OSMO_GSUP_CN_DOMAIN_PS: + OSMO_STRBUF_PRINTF(sb, " cn_domain=PS"); + break; + default: + if (msg->cn_domain) + OSMO_STRBUF_PRINTF(sb, " cn_domain=?(%d)", msg->cn_domain); + break; + } + + if (msg->source_name_len) { + OSMO_STRBUF_PRINTF(sb, " source_name="); + OSMO_STRBUF_APPEND(sb, osmo_quote_cstr_buf, (char*)msg->source_name, msg->source_name_len); + } + + if (msg->destination_name_len) { + OSMO_STRBUF_PRINTF(sb, " destination_name="); + OSMO_STRBUF_APPEND(sb, osmo_quote_cstr_buf, (char*)msg->destination_name, msg->destination_name_len); + } + + if (msg->session_id) + OSMO_STRBUF_PRINTF(sb, " session_id=%" PRIu32, msg->session_id); + if (msg->session_state) + OSMO_STRBUF_PRINTF(sb, " session_state=%s", osmo_gsup_session_state_name(msg->session_state)); + + if (msg->sm_rp_mr) + OSMO_STRBUF_PRINTF(sb, " sm_rp_mr=%" PRIu8, *msg->sm_rp_mr); + + return sb.chars_needed; +} + +/*! Same as osmo_gsup_message_to_str_buf() but returns a talloc allocated string. */ +char *osmo_gsup_message_to_str_c(void *ctx, const struct osmo_gsup_message *msg) +{ + OSMO_NAME_C_IMPL(ctx, 64, "ERROR", osmo_gsup_message_to_str_buf, msg) +} diff --git a/src/gsupclient/ipa_name.c b/src/gsupclient/ipa_name.c new file mode 100644 index 0000000..2db069f --- /dev/null +++ b/src/gsupclient/ipa_name.c @@ -0,0 +1,97 @@ +/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <errno.h> +#include <string.h> +#include <osmocom/core/utils.h> +#include <osmocom/gsupclient/ipa_name.h> + +int osmo_ipa_name_set(struct osmo_ipa_name *ipa_name, const uint8_t *val, size_t len) +{ + if (!val || !len) { + *ipa_name = (struct osmo_ipa_name){}; + return 0; + } + if (len > sizeof(ipa_name->val)) + return -ENOSPC; + ipa_name->len = len; + memcpy(ipa_name->val, val, len); + return 0; +} + +int osmo_ipa_name_set_str(struct osmo_ipa_name *ipa_name, const char *str_fmt, ...) +{ + va_list ap; + if (!str_fmt) + return osmo_ipa_name_set(ipa_name, NULL, 0); + + va_start(ap, str_fmt); + vsnprintf((char*)(ipa_name->val), sizeof(ipa_name->val), str_fmt, ap); + va_end(ap); + ipa_name->len = strlen((char*)(ipa_name->val))+1; + return 0; +} + +int osmo_ipa_name_cmp(const struct osmo_ipa_name *a, const struct osmo_ipa_name *b) +{ + int cmp; + if (a == b) + return 0; + if (!a) + return -1; + if (!b) + return 1; + if (!a->len && !b->len) + return 0; + if (!a->len && b->len) + return -1; + if (!b->len && a->len) + return 1; + + if (a->len == b->len) + return memcmp(a->val, b->val, a->len); + else if (a->len < b->len) { + cmp = memcmp(a->val, b->val, a->len); + if (!cmp) + cmp = -1; + return cmp; + } else { + /* a->len > b->len */ + cmp = memcmp(a->val, b->val, b->len); + if (!cmp) + cmp = 1; + return cmp; + } +} + +/* Call osmo_ipa_name_to_str_c with OTC_SELECT. */ +const char *osmo_ipa_name_to_str(const struct osmo_ipa_name *ipa_name) +{ + return osmo_ipa_name_to_str_c(OTC_SELECT, ipa_name); +} + +/* Return an unquoted string, not including the terminating zero. Used for writing VTY config. */ +const char *osmo_ipa_name_to_str_c(void *ctx, const struct osmo_ipa_name *ipa_name) +{ + size_t len = ipa_name->len; + if (!len) + return talloc_strdup(ctx, ""); + if (ipa_name->val[len-1] == '\0') + len--; + return osmo_escape_str_c(ctx, (char*)ipa_name->val, len); +} @@ -35,8 +35,9 @@ #include <osmocom/gsm/apn.h> #include <osmocom/gsm/gsm48_ie.h> #include <osmocom/gsm/gsm_utils.h> -#include <osmocom/gsm/protocol/gsm_23_003.h> +#include <osmocom/gsm/gsm23003.h> +#include <osmocom/gsupclient/ipa_name.h> #include <osmocom/hlr/db.h> #include <osmocom/hlr/hlr.h> #include <osmocom/hlr/ctrl.h> @@ -44,14 +45,20 @@ #include <osmocom/hlr/gsup_server.h> #include <osmocom/hlr/gsup_router.h> #include <osmocom/hlr/rand.h> -#include <osmocom/hlr/luop.h> #include <osmocom/hlr/hlr_vty.h> #include <osmocom/hlr/hlr_ussd.h> +#include <osmocom/hlr/lu_fsm.h> struct hlr *g_hlr; static void *hlr_ctx = NULL; static int quit = 0; +struct osmo_tdef g_hlr_tdefs[] = { + /* 4222 is also the OSMO_GSUP_PORT */ + { .T = -4222, .default_val = 30, .desc = "GSUP Update Location timeout" }, + {} +}; + /* Trigger 'Insert Subscriber Data' messages to all connected GSUP clients. * * \param[in] subscr A subscriber we have new data to send for. @@ -69,6 +76,8 @@ osmo_hlr_subscriber_update_notify(struct hlr_subscriber *subscr) return; } + /* FIXME: send only to current vlr_number and sgsn_number */ + llist_for_each_entry(co, &g_hlr->gs->clients, list) { struct osmo_gsup_message gsup = { }; uint8_t msisdn_enc[OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN]; @@ -222,145 +231,102 @@ static int subscr_create_on_demand(const char *imsi) return 0; } +/*! Update nam_cs/nam_ps in the db and trigger notifications to GSUP clients. + * \param[in,out] hlr Global hlr context. + * \param[in] subscr Subscriber from a fresh db_subscr_get_by_*() call. + * \param[in] nam_val True to enable CS/PS, false to disable. + * \param[in] is_ps True to enable/disable PS, false for CS. + * \returns 0 on success, ENOEXEC if there is no need to change, a negative + * value on error. + */ +int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps) +{ + int rc; + bool is_val = is_ps? subscr->nam_ps : subscr->nam_cs; + struct osmo_ipa_name vlr_name; + struct osmo_gsup_message gsup_del_data = { + .message_type = OSMO_GSUP_MSGT_DELETE_DATA_REQUEST, + }; + OSMO_STRLCPY_ARRAY(gsup_del_data.imsi, subscr->imsi); + + if (is_val == nam_val) { + LOGP(DAUC, LOGL_DEBUG, "IMSI-%s: Already has the requested value when asked to %s %s\n", + subscr->imsi, nam_val ? "enable" : "disable", is_ps ? "PS" : "CS"); + return ENOEXEC; + } + + rc = db_subscr_nam(hlr->dbc, subscr->imsi, nam_val, is_ps); + if (rc) + return rc > 0? -rc : rc; + + /* If we're disabling, send a notice out to the GSUP client that is + * responsible. Otherwise no need. */ + if (nam_val) + return 0; + + if (subscr->vlr_number && osmo_ipa_name_set_str(&vlr_name, subscr->vlr_number)) + osmo_gsup_enc_send_to_ipa_name(g_hlr->gs, &vlr_name, &gsup_del_data); + if (subscr->sgsn_number && osmo_ipa_name_set_str(&vlr_name, subscr->sgsn_number)) + osmo_gsup_enc_send_to_ipa_name(g_hlr->gs, &vlr_name, &gsup_del_data); + return 0; +} + /*********************************************************************** * Send Auth Info handling ***********************************************************************/ /* process an incoming SAI request */ -static int rx_send_auth_info(struct osmo_gsup_conn *conn, - const struct osmo_gsup_message *gsup, - struct db_context *dbc) +static int rx_send_auth_info(unsigned int auc_3g_ind, struct osmo_gsup_req *req) { - struct osmo_gsup_message gsup_out; - struct msgb *msg_out; + struct osmo_gsup_message gsup_out = { + .message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT, + }; bool separation_bit = false; int num_auth_vectors = OSMO_GSUP_MAX_NUM_AUTH_INFO; int rc; - subscr_create_on_demand(gsup->imsi); + subscr_create_on_demand(req->gsup.imsi); - /* initialize return message structure */ - memset(&gsup_out, 0, sizeof(gsup_out)); - memcpy(&gsup_out.imsi, &gsup->imsi, sizeof(gsup_out.imsi)); - - if (gsup->current_rat_type == OSMO_RAT_EUTRAN_SGS) + if (req->gsup.current_rat_type == OSMO_RAT_EUTRAN_SGS) separation_bit = true; - if (gsup->num_auth_vectors > 0 && - gsup->num_auth_vectors <= OSMO_GSUP_MAX_NUM_AUTH_INFO) - num_auth_vectors = gsup->num_auth_vectors; + if (req->gsup.num_auth_vectors > 0 && + req->gsup.num_auth_vectors <= OSMO_GSUP_MAX_NUM_AUTH_INFO) + num_auth_vectors = req->gsup.num_auth_vectors; - rc = db_get_auc(dbc, gsup->imsi, conn->auc_3g_ind, + rc = db_get_auc(g_hlr->dbc, req->gsup.imsi, auc_3g_ind, gsup_out.auth_vectors, num_auth_vectors, - gsup->rand, gsup->auts, separation_bit); + req->gsup.rand, req->gsup.auts, separation_bit); + if (rc <= 0) { - gsup_out.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR; switch (rc) { case 0: /* 0 means "0 tuples generated", which shouldn't happen. * Treat the same as "no auth data". */ case -ENOKEY: - LOGP(DAUC, LOGL_NOTICE, "%s: IMSI known, but has no auth data;" - " Returning slightly inaccurate cause 'IMSI Unknown' via GSUP\n", - gsup->imsi); - gsup_out.cause = GMM_CAUSE_IMSI_UNKNOWN; - break; + osmo_gsup_req_respond_err(req, GMM_CAUSE_IMSI_UNKNOWN, + "IMSI known, but has no auth data;" + " Returning slightly inaccurate cause 'IMSI Unknown' via GSUP"); + return rc; case -ENOENT: - LOGP(DAUC, LOGL_NOTICE, "%s: IMSI not known\n", gsup->imsi); - gsup_out.cause = GMM_CAUSE_IMSI_UNKNOWN; - break; + osmo_gsup_req_respond_err(req, GMM_CAUSE_IMSI_UNKNOWN, "IMSI unknown"); + return rc; default: - LOGP(DAUC, LOGL_ERROR, "%s: failure to look up IMSI in db\n", gsup->imsi); - gsup_out.cause = GMM_CAUSE_NET_FAIL; - break; + osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "failure to look up IMSI in db"); + return rc; } - } else { - gsup_out.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT; - gsup_out.num_auth_vectors = rc; } + gsup_out.num_auth_vectors = rc; - msg_out = osmo_gsup_msgb_alloc("GSUP AUC response"); - osmo_gsup_encode(msg_out, &gsup_out); - return osmo_gsup_conn_send(conn, msg_out); -} - -/*********************************************************************** - * LU Operation State / Structure - ***********************************************************************/ - -static LLIST_HEAD(g_lu_ops); - -/*! Receive Cancel Location Result from old VLR/SGSN */ -void lu_op_rx_cancel_old_ack(struct lu_operation *luop, - const struct osmo_gsup_message *gsup) -{ - OSMO_ASSERT(luop->state == LU_S_CANCEL_SENT); - /* FIXME: Check for spoofing */ - - osmo_timer_del(&luop->timer); - - /* FIXME */ - - lu_op_tx_insert_subscr_data(luop); -} - -/*! Receive Insert Subscriber Data Result from new VLR/SGSN */ -static void lu_op_rx_insert_subscr_data_ack(struct lu_operation *luop, - const struct osmo_gsup_message *gsup) -{ - OSMO_ASSERT(luop->state == LU_S_ISD_SENT); - /* FIXME: Check for spoofing */ - - osmo_timer_del(&luop->timer); - - /* Subscriber_Present_HLR */ - /* CS only: Check_SS_required? -> MAP-FW-CHECK_SS_IND.req */ - - /* Send final ACK towards inquiring VLR/SGSN */ - lu_op_tx_ack(luop); -} - -/*! Receive GSUP message for given \ref lu_operation */ -void lu_op_rx_gsup(struct lu_operation *luop, - const struct osmo_gsup_message *gsup) -{ - switch (gsup->message_type) { - case OSMO_GSUP_MSGT_INSERT_DATA_ERROR: - /* FIXME */ - break; - case OSMO_GSUP_MSGT_INSERT_DATA_RESULT: - lu_op_rx_insert_subscr_data_ack(luop, gsup); - break; - case OSMO_GSUP_MSGT_LOCATION_CANCEL_ERROR: - /* FIXME */ - break; - case OSMO_GSUP_MSGT_LOCATION_CANCEL_RESULT: - lu_op_rx_cancel_old_ack(luop, gsup); - break; - default: - LOGP(DMAIN, LOGL_ERROR, "Unhandled GSUP msg_type 0x%02x\n", - gsup->message_type); - break; - } + osmo_gsup_req_respond(req, &gsup_out, false, true); + return 0; } -/*! Receive Update Location Request, creates new \ref lu_operation */ -static int rx_upd_loc_req(struct osmo_gsup_conn *conn, - const struct osmo_gsup_message *gsup) +/*! Receive Update Location Request, creates new lu_operation */ +static int rx_upd_loc_req(struct osmo_gsup_conn *conn, struct osmo_gsup_req *req) { - struct hlr_subscriber *subscr; - struct lu_operation *luop = lu_op_alloc_conn(conn); - if (!luop) { - LOGP(DMAIN, LOGL_ERROR, "LU REQ from conn without addr?\n"); - return -EINVAL; - } - - subscr = &luop->subscr; - - lu_op_statechg(luop, LU_S_LU_RECEIVED); - - switch (gsup->cn_domain) { + switch (req->gsup.cn_domain) { case OSMO_GSUP_CN_DOMAIN_CS: conn->supports_cs = true; break; @@ -371,143 +337,64 @@ static int rx_upd_loc_req(struct osmo_gsup_conn *conn, * a request, the PS Domain is assumed." */ case OSMO_GSUP_CN_DOMAIN_PS: conn->supports_ps = true; - luop->is_ps = true; break; } - llist_add(&luop->list, &g_lu_ops); - subscr_create_on_demand(gsup->imsi); - - /* Roughly follwing "Process Update_Location_HLR" of TS 09.02 */ - - /* check if subscriber is known at all */ - if (!lu_op_fill_subscr(luop, g_hlr->dbc, gsup->imsi)) { - /* Send Error back: Subscriber Unknown in HLR */ - osmo_strlcpy(luop->subscr.imsi, gsup->imsi, sizeof(luop->subscr.imsi)); - lu_op_tx_error(luop, GMM_CAUSE_IMSI_UNKNOWN); - return 0; - } - - /* Check if subscriber is generally permitted on CS or PS - * service (as requested) */ - if (!luop->is_ps && !luop->subscr.nam_cs) { - lu_op_tx_error(luop, GMM_CAUSE_PLMN_NOTALLOWED); - return 0; - } else if (luop->is_ps && !luop->subscr.nam_ps) { - lu_op_tx_error(luop, GMM_CAUSE_GPRS_NOTALLOWED); - return 0; - } - - /* TODO: Set subscriber tracing = deactive in VLR/SGSN */ - -#if 0 - /* Cancel in old VLR/SGSN, if new VLR/SGSN differs from old (FIXME: OS#4491) */ - if (luop->is_ps == false && - strcmp(subscr->vlr_number, vlr_number)) { - lu_op_tx_cancel_old(luop); - } else if (luop->is_ps == true && - strcmp(subscr->sgsn_number, sgsn_number)) { - lu_op_tx_cancel_old(luop); - } else -#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", - osmo_quote_str((const char*)luop->peer, -1)); - if (db_subscr_lu(g_hlr->dbc, subscr->id, (const char *)luop->peer, 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"); - - /* TODO: Subscriber allowed to roam in PLMN? */ - /* TODO: Update RoutingInfo */ - /* TODO: Reset Flag MS Purged (cs/ps) */ - /* TODO: Control_Tracing_HLR / Control_Tracing_HLR_with_SGSN */ - lu_op_tx_insert_subscr_data(luop); + subscr_create_on_demand(req->gsup.imsi); + lu_rx_gsup(req); return 0; } -static int rx_purge_ms_req(struct osmo_gsup_conn *conn, - const struct osmo_gsup_message *gsup) +static int rx_purge_ms_req(struct osmo_gsup_req *req) { - struct osmo_gsup_message gsup_reply = {0}; - struct msgb *msg_out; - bool is_ps = false; + bool is_ps = (req->gsup.cn_domain != OSMO_GSUP_CN_DOMAIN_CS); int rc; - LOGP(DAUC, LOGL_INFO, "%s: Purge MS (%s)\n", gsup->imsi, - is_ps ? "PS" : "CS"); - - memcpy(gsup_reply.imsi, gsup->imsi, sizeof(gsup_reply.imsi)); - - if (gsup->cn_domain == OSMO_GSUP_CN_DOMAIN_PS) - is_ps = true; + LOG_GSUP_REQ_CAT(req, DAUC, LOGL_INFO, "Purge MS (%s)\n", is_ps ? "PS" : "CS"); /* FIXME: check if the VLR that sends the purge is the same that * we have on record. Only update if yes */ /* Perform the actual update of the DB */ - rc = db_subscr_purge(g_hlr->dbc, gsup->imsi, true, is_ps); + rc = db_subscr_purge(g_hlr->dbc, req->gsup.imsi, true, is_ps); if (rc == 0) - gsup_reply.message_type = OSMO_GSUP_MSGT_PURGE_MS_RESULT; - else if (rc == -ENOENT) { - gsup_reply.message_type = OSMO_GSUP_MSGT_PURGE_MS_ERROR; - gsup_reply.cause = GMM_CAUSE_IMSI_UNKNOWN; - } else { - gsup_reply.message_type = OSMO_GSUP_MSGT_PURGE_MS_ERROR; - gsup_reply.cause = GMM_CAUSE_NET_FAIL; - } - - msg_out = osmo_gsup_msgb_alloc("GSUP AUC response"); - osmo_gsup_encode(msg_out, &gsup_reply); - return osmo_gsup_conn_send(conn, msg_out); -} - -static int gsup_send_err_reply(struct osmo_gsup_conn *conn, const char *imsi, - enum osmo_gsup_message_type type_in, uint8_t err_cause) -{ - int type_err = OSMO_GSUP_TO_MSGT_ERROR(type_in); - struct osmo_gsup_message gsup_reply = {0}; - struct msgb *msg_out; - - OSMO_STRLCPY_ARRAY(gsup_reply.imsi, imsi); - gsup_reply.message_type = type_err; - gsup_reply.cause = err_cause; - msg_out = osmo_gsup_msgb_alloc("GSUP ERR response"); - osmo_gsup_encode(msg_out, &gsup_reply); - LOGP(DMAIN, LOGL_NOTICE, "Tx %s\n", osmo_gsup_message_type_name(type_err)); - return osmo_gsup_conn_send(conn, msg_out); + osmo_gsup_req_respond_msgt(req, OSMO_GSUP_MSGT_PURGE_MS_RESULT, true); + else if (rc == -ENOENT) + osmo_gsup_req_respond_err(req, GMM_CAUSE_IMSI_UNKNOWN, "IMSI unknown"); + else + osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "db error"); + return rc; } -static int rx_check_imei_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup) +static int rx_check_imei_req(struct osmo_gsup_req *req) { - struct osmo_gsup_message gsup_reply = {0}; - struct msgb *msg_out; + struct osmo_gsup_message gsup_reply; char imei[GSM23003_IMEI_NUM_DIGITS_NO_CHK+1] = {0}; + const struct osmo_gsup_message *gsup = &req->gsup; int rc; /* Require IMEI */ if (!gsup->imei_enc) { - LOGP(DMAIN, LOGL_ERROR, "%s: missing IMEI\n", gsup->imsi); - gsup_send_err_reply(conn, gsup->imsi, gsup->message_type, GMM_CAUSE_INV_MAND_INFO); + osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO, "missing IMEI"); return -1; } /* Decode IMEI (fails if IMEI is too long) */ rc = gsm48_decode_bcd_number2(imei, sizeof(imei), gsup->imei_enc, gsup->imei_enc_len, 0); if (rc < 0) { - LOGP(DMAIN, LOGL_ERROR, "%s: failed to decode IMEI (rc: %i)\n", gsup->imsi, rc); - gsup_send_err_reply(conn, gsup->imsi, gsup->message_type, GMM_CAUSE_INV_MAND_INFO); + osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO, + "failed to decode IMEI %s (rc: %d)", + osmo_hexdump_c(OTC_SELECT, gsup->imei_enc, gsup->imei_enc_len), + rc); return -1; } /* Check if IMEI is too short */ - if (strlen(imei) != GSM23003_IMEI_NUM_DIGITS_NO_CHK) { - LOGP(DMAIN, LOGL_ERROR, "%s: wrong encoded IMEI length (IMEI: '%s', %lu, %i)\n", gsup->imsi, imei, - strlen(imei), GSM23003_IMEI_NUM_DIGITS_NO_CHK); - gsup_send_err_reply(conn, gsup->imsi, gsup->message_type, GMM_CAUSE_INV_MAND_INFO); + if (!osmo_imei_str_valid(imei, false)) { + osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO, + "invalid IMEI: %s", osmo_quote_str_c(OTC_SELECT, imei, -1)); return -1; } @@ -517,7 +404,7 @@ static int rx_check_imei_req(struct osmo_gsup_conn *conn, const struct osmo_gsup if (g_hlr->store_imei) { LOGP(DAUC, LOGL_DEBUG, "IMSI='%s': storing IMEI = %s\n", gsup->imsi, imei); if (db_subscr_update_imei_by_imsi(g_hlr->dbc, gsup->imsi, imei) < 0) { - gsup_send_err_reply(conn, gsup->imsi, gsup->message_type, GMM_CAUSE_INV_MAND_INFO); + osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO, "Failed to store IMEI in HLR db"); return -1; } } else { @@ -525,18 +412,17 @@ static int rx_check_imei_req(struct osmo_gsup_conn *conn, const struct osmo_gsup LOGP(DMAIN, LOGL_INFO, "IMSI='%s': has IMEI = %s (consider setting 'store-imei')\n", gsup->imsi, imei); struct hlr_subscriber subscr; if (db_subscr_get_by_imsi(g_hlr->dbc, gsup->imsi, &subscr) < 0) { - gsup_send_err_reply(conn, gsup->imsi, gsup->message_type, GMM_CAUSE_INV_MAND_INFO); + osmo_gsup_req_respond_err(req, GMM_CAUSE_INV_MAND_INFO, "IMSI unknown"); return -1; } } /* Accept all IMEIs */ - gsup_reply.imei_result = OSMO_GSUP_IMEI_RESULT_ACK; - gsup_reply.message_type = OSMO_GSUP_MSGT_CHECK_IMEI_RESULT; - msg_out = osmo_gsup_msgb_alloc("GSUP Check_IMEI response"); - memcpy(gsup_reply.imsi, gsup->imsi, sizeof(gsup_reply.imsi)); - osmo_gsup_encode(msg_out, &gsup_reply); - return osmo_gsup_conn_send(conn, msg_out); + gsup_reply = (struct osmo_gsup_message){ + .message_type = OSMO_GSUP_MSGT_CHECK_IMEI_RESULT, + .imei_result = OSMO_GSUP_IMEI_RESULT_ACK, + }; + return osmo_gsup_req_respond(req, &gsup_reply, false, true); } static char namebuf[255]; @@ -549,151 +435,112 @@ static char namebuf[255]; osmo_quote_str_buf2(namebuf, sizeof(namebuf), (const char *)(gsup)->destination_name, (gsup)->destination_name_len), \ ## args) -static int read_cb_forward(struct osmo_gsup_conn *conn, struct msgb *msg, const struct osmo_gsup_message *gsup) +static int read_cb_forward(struct osmo_gsup_req *req) { int ret = -EINVAL; - struct osmo_gsup_message *gsup_err; - - /* FIXME: it would be better if the msgb never were deallocated immediately by osmo_gsup_addr_send(), which a - * select-loop volatile talloc context could facilitate. Then we would still be able to access gsup-> members - * (pointing into the msgb) even after sending failed, and we wouldn't need to copy this data before sending: */ - /* Prepare error message (before IEs get deallocated) */ - gsup_err = talloc_zero(hlr_ctx, struct osmo_gsup_message); - OSMO_STRLCPY_ARRAY(gsup_err->imsi, gsup->imsi); - gsup_err->message_class = gsup->message_class; - gsup_err->destination_name = talloc_memdup(gsup_err, gsup->destination_name, gsup->destination_name_len); - gsup_err->destination_name_len = gsup->destination_name_len; - gsup_err->message_type = gsup->message_type; - gsup_err->session_state = gsup->session_state; - gsup_err->session_id = gsup->session_id; - gsup_err->source_name = talloc_memdup(gsup_err, gsup->source_name, gsup->source_name_len); - gsup_err->source_name_len = gsup->source_name_len; + const struct osmo_gsup_message *gsup = &req->gsup; + struct osmo_gsup_message gsup_err; + struct msgb *forward_msg; + struct osmo_ipa_name destination_name; /* Check for routing IEs */ - if (!gsup->source_name || !gsup->source_name_len || !gsup->destination_name || !gsup->destination_name_len) { - LOGP_GSUP_FWD(gsup, LOGL_ERROR, "missing routing IEs\n"); - goto end; + if (!req->gsup.source_name || !req->gsup.source_name_len + || !req->gsup.destination_name || !req->gsup.destination_name_len) { + LOGP_GSUP_FWD(&req->gsup, LOGL_ERROR, "missing routing IEs\n"); + goto routing_error; } - /* Verify source name (e.g. "MSC-00-00-00-00-00-00") */ - if (gsup_route_find(conn->server, gsup->source_name, gsup->source_name_len) != conn) { - LOGP_GSUP_FWD(gsup, LOGL_ERROR, "mismatching source name\n"); - goto end; + if (osmo_ipa_name_set(&destination_name, req->gsup.destination_name, req->gsup.destination_name_len)) { + LOGP_GSUP_FWD(&req->gsup, LOGL_ERROR, "invalid destination name\n"); + goto routing_error; } - /* Forward message without re-encoding (so we don't remove unknown IEs) */ - LOGP_GSUP_FWD(gsup, LOGL_INFO, "checks passed, forwarding\n"); - - /* Remove incoming IPA header to be able to prepend an outgoing IPA header */ - msgb_pull_to_l2(msg); - ret = osmo_gsup_addr_send(g_hlr->gs, gsup->destination_name, gsup->destination_name_len, msg); - /* AT THIS POINT, THE msg MAY BE DEALLOCATED and the data like gsup->imsi, gsup->source_name etc may all be - * invalid and cause segfaults. */ - msg = NULL; - gsup = NULL; - if (ret == -ENODEV) - LOGP_GSUP_FWD(gsup_err, LOGL_ERROR, "destination not connected\n"); - else if (ret) - LOGP_GSUP_FWD(gsup_err, LOGL_ERROR, "unknown error %i\n", ret); - -end: - /* Send error back to source */ + LOG_GSUP_REQ(req, LOGL_INFO, "Forwarding to %s\n", osmo_ipa_name_to_str(&destination_name)); + + /* Forward message without re-encoding (so we don't remove unknown IEs). + * Copy GSUP part to forward, removing incoming IPA header to be able to prepend an outgoing IPA header */ + forward_msg = osmo_gsup_msgb_alloc("GSUP forward"); + forward_msg->l2h = msgb_put(forward_msg, msgb_l2len(req->msg)); + memcpy(forward_msg->l2h, msgb_l2(req->msg), msgb_l2len(req->msg)); + ret = osmo_gsup_send_to_ipa_name(g_hlr->gs, &destination_name, forward_msg); if (ret) { - struct msgb *msg_err = osmo_gsup_msgb_alloc("GSUP forward ERR response"); - gsup_err->message_type = OSMO_GSUP_MSGT_E_ROUTING_ERROR; - osmo_gsup_encode(msg_err, gsup_err); - LOGP_GSUP_FWD(gsup_err, LOGL_NOTICE, "Tx %s\n", osmo_gsup_message_type_name(gsup_err->message_type)); - osmo_gsup_conn_send(conn, msg_err); + LOGP_GSUP_FWD(gsup, LOGL_ERROR, "%s (rc=%d)\n", + ret == -ENODEV ? "destination not connected" : "unknown error", + ret); + goto routing_error; } - talloc_free(gsup_err); - if (msg) - msgb_free(msg); - return ret; + osmo_gsup_req_free(req); + return 0; + +routing_error: + gsup_err = (struct osmo_gsup_message){ + .message_type = OSMO_GSUP_MSGT_ROUTING_ERROR, + .source_name = gsup->destination_name, + .source_name_len = gsup->destination_name_len, + }; + osmo_gsup_req_respond(req, &gsup_err, true, true); + return -1; } static int read_cb(struct osmo_gsup_conn *conn, struct msgb *msg) { - static struct osmo_gsup_message gsup; - int rc; - - if (!msgb_l2(msg) || !msgb_l2len(msg)) { - LOGP(DMAIN, LOGL_ERROR, "missing or empty L2 data\n"); - msgb_free(msg); + struct osmo_gsup_req *req = osmo_gsup_conn_rx(conn, msg); + if (!req) return -EINVAL; - } - - rc = osmo_gsup_decode(msgb_l2(msg), msgb_l2len(msg), &gsup); - if (rc < 0) { - LOGP(DMAIN, LOGL_ERROR, "error in GSUP decode: %d\n", rc); - msgb_free(msg); - return rc; - } - /* 3GPP TS 23.003 Section 2.2 clearly states that an IMSI with less than 5 - * digits is impossible. Even 5 digits is a highly theoretical case */ - if (strlen(gsup.imsi) < 5) { /* TODO: move this check to libosmogsm/gsup.c? */ - LOGP(DMAIN, LOGL_ERROR, "IMSI too short: %s\n", osmo_quote_str(gsup.imsi, -1)); - gsup_send_err_reply(conn, gsup.imsi, gsup.message_type, GMM_CAUSE_INV_MAND_INFO); - msgb_free(msg); - return -EINVAL; + /* If the GSUP recipient is other than this HLR, forward. */ + if (req->gsup.destination_name_len) { + struct osmo_ipa_name destination_name; + struct osmo_ipa_name my_name; + osmo_ipa_name_set_str(&my_name, g_hlr->gsup_unit_name.serno); + if (!osmo_ipa_name_set(&destination_name, req->gsup.destination_name, req->gsup.destination_name_len) + && osmo_ipa_name_cmp(&destination_name, &my_name)) { + return read_cb_forward(req); + } } - if (gsup.destination_name_len) - return read_cb_forward(conn, msg, &gsup); - - switch (gsup.message_type) { + switch (req->gsup.message_type) { /* requests sent to us */ case OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST: - rx_send_auth_info(conn, &gsup, g_hlr->dbc); + rx_send_auth_info(conn->auc_3g_ind, req); break; case OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST: - rx_upd_loc_req(conn, &gsup); + rx_upd_loc_req(conn, req); break; case OSMO_GSUP_MSGT_PURGE_MS_REQUEST: - rx_purge_ms_req(conn, &gsup); + rx_purge_ms_req(req); break; /* responses to requests sent by us */ case OSMO_GSUP_MSGT_DELETE_DATA_ERROR: - LOGP(DMAIN, LOGL_ERROR, "Error while deleting subscriber data " - "for IMSI %s\n", gsup.imsi); + LOG_GSUP_REQ(req, LOGL_ERROR, "Peer responds with: Error while deleting subscriber data\n"); + osmo_gsup_req_free(req); break; case OSMO_GSUP_MSGT_DELETE_DATA_RESULT: - LOGP(DMAIN, LOGL_ERROR, "Deleting subscriber data for IMSI %s\n", - gsup.imsi); + LOG_GSUP_REQ(req, LOGL_DEBUG, "Peer responds with: Subscriber data deleted\n"); + osmo_gsup_req_free(req); break; case OSMO_GSUP_MSGT_PROC_SS_REQUEST: case OSMO_GSUP_MSGT_PROC_SS_RESULT: - rx_proc_ss_req(conn, &gsup); + rx_proc_ss_req(req); break; case OSMO_GSUP_MSGT_PROC_SS_ERROR: - rx_proc_ss_error(conn, &gsup); + rx_proc_ss_error(req); break; case OSMO_GSUP_MSGT_INSERT_DATA_ERROR: case OSMO_GSUP_MSGT_INSERT_DATA_RESULT: case OSMO_GSUP_MSGT_LOCATION_CANCEL_ERROR: case OSMO_GSUP_MSGT_LOCATION_CANCEL_RESULT: - { - struct lu_operation *luop = lu_op_by_imsi(gsup.imsi, - &g_lu_ops); - if (!luop) { - LOGP(DMAIN, LOGL_ERROR, "GSUP message %s for " - "unknown IMSI %s\n", - osmo_gsup_message_type_name(gsup.message_type), - gsup.imsi); - break; - } - lu_op_rx_gsup(luop, &gsup); - } + lu_rx_gsup(req); break; case OSMO_GSUP_MSGT_CHECK_IMEI_REQUEST: - rx_check_imei_req(conn, &gsup); + rx_check_imei_req(req); break; default: LOGP(DMAIN, LOGL_DEBUG, "Unhandled GSUP message type %s\n", - osmo_gsup_message_type_name(gsup.message_type)); + osmo_gsup_message_type_name(req->gsup.message_type)); + osmo_gsup_req_free(req); break; } - msgb_free(msg); return 0; } @@ -908,7 +755,7 @@ int main(int argc, char **argv) g_hlr->gs = osmo_gsup_server_create(hlr_ctx, g_hlr->gsup_bind_addr, OSMO_GSUP_PORT, - read_cb, &g_lu_ops, g_hlr); + read_cb, g_hlr); if (!g_hlr->gs) { LOGP(DMAIN, LOGL_FATAL, "Error starting GSUP server\n"); exit(1); @@ -931,7 +778,8 @@ int main(int argc, char **argv) } while (!quit) - osmo_select_main(0); + osmo_select_main_ctx(0); + osmo_gsup_server_destroy(g_hlr->gs); db_close(g_hlr->dbc); diff --git a/src/hlr_ussd.c b/src/hlr_ussd.c index 8cdc15c..aa7614e 100644 --- a/src/hlr_ussd.c +++ b/src/hlr_ussd.c @@ -170,12 +170,14 @@ struct ss_session { /* subscriber's vlr_number * MO USSD: originating MSC's vlr_number * MT USSD: looked up once per session and cached here */ - uint8_t *vlr_number; - size_t vlr_number_len; + struct osmo_ipa_name vlr_name; /* we don't keep a pointer to the osmo_gsup_{route,conn} towards the MSC/VLR here, * as this might change during inter-VLR hand-over, and we simply look-up the serving MSC/VLR * every time we receive an USSD component from the EUSE */ + + struct osmo_gsup_req *initial_req_from_ms; + struct osmo_gsup_req *initial_req_from_euse; }; struct ss_session *ss_session_find(struct hlr *hlr, const char *imsi, uint32_t session_id) @@ -191,6 +193,10 @@ struct ss_session *ss_session_find(struct hlr *hlr, const char *imsi, uint32_t s void ss_session_free(struct ss_session *ss) { osmo_timer_del(&ss->timeout); + if (ss->initial_req_from_ms) + osmo_gsup_req_free(ss->initial_req_from_ms); + if (ss->initial_req_from_euse) + osmo_gsup_req_free(ss->initial_req_from_euse); llist_del(&ss->list); talloc_free(ss); } @@ -230,32 +236,46 @@ struct ss_session *ss_session_alloc(struct hlr *hlr, const char *imsi, uint32_t ***********************************************************************/ /* Resolve the target MSC by ss->imsi and send GSUP message. */ -static int ss_gsup_send(struct ss_session *ss, struct osmo_gsup_server *gs, struct msgb *msg) +static int ss_gsup_send_to_ms(struct ss_session *ss, struct osmo_gsup_server *gs, struct osmo_gsup_message *gsup) { struct hlr_subscriber subscr = {}; + struct msgb *msg; int rc; + if (ss->initial_req_from_ms) { + /* Use non-final osmo_gsup_req_respond() to not deallocate the ss->initial_req_from_ms */ + osmo_gsup_req_respond(ss->initial_req_from_ms, gsup, false, false); + return 0; + } + + msg = osmo_gsup_msgb_alloc("GSUP USSD FW"); + rc = osmo_gsup_encode(msg, gsup); + if (rc) { + LOGPSS(ss, LOGL_ERROR, "Failed to encode GSUP message\n"); + msgb_free(msg); + return rc; + } + /* Use vlr_number as looked up by the caller, or look up now. */ - if (!ss->vlr_number) { + if (!ss->vlr_name.len) { rc = db_subscr_get_by_imsi(g_hlr->dbc, ss->imsi, &subscr); if (rc < 0) { LOGPSS(ss, LOGL_ERROR, "Cannot find subscriber, cannot route GSUP message\n"); msgb_free(msg); return -EINVAL; } - ss->vlr_number = (uint8_t *)talloc_strdup(ss, subscr.vlr_number); - ss->vlr_number_len = strlen(subscr.vlr_number) + 1; + osmo_ipa_name_set_str(&ss->vlr_name, subscr.vlr_number); } /* Check for empty string (all vlr_number strings end in "\0", because otherwise gsup_route_find() fails) */ - if (ss->vlr_number_len == 1) { + if (ss->vlr_name.len <= 1) { LOGPSS(ss, LOGL_ERROR, "Cannot send GSUP message, no VLR number stored for subscriber\n"); msgb_free(msg); return -EINVAL; } - LOGPSS(ss, LOGL_DEBUG, "Tx SS/USSD to VLR %s\n", osmo_quote_str((char *)ss->vlr_number, ss->vlr_number_len)); - return osmo_gsup_addr_send(gs, ss->vlr_number, ss->vlr_number_len, msg); + LOGPSS(ss, LOGL_DEBUG, "Tx SS/USSD to VLR %s\n", osmo_ipa_name_to_str(&ss->vlr_name)); + return osmo_gsup_send_to_ipa_name(gs, &ss->vlr_name, msg); } static int ss_tx_to_ms(struct ss_session *ss, enum osmo_gsup_message_type gsup_msg_type, @@ -263,7 +283,7 @@ static int ss_tx_to_ms(struct ss_session *ss, enum osmo_gsup_message_type gsup_m { struct osmo_gsup_message resp = {0}; - struct msgb *resp_msg; + int rc; resp.message_type = gsup_msg_type; OSMO_STRLCPY_ARRAY(resp.imsi, ss->imsi); @@ -277,12 +297,10 @@ static int ss_tx_to_ms(struct ss_session *ss, enum osmo_gsup_message_type gsup_m resp.ss_info_len = msgb_length(ss_msg); } - resp_msg = msgb_alloc_headroom(4000, 64, __func__); - OSMO_ASSERT(resp_msg); - osmo_gsup_encode(resp_msg, &resp); - msgb_free(ss_msg); + rc = ss_gsup_send_to_ms(ss, g_hlr->gs, &resp); - return ss_gsup_send(ss, g_hlr->gs, resp_msg); + msgb_free(ss_msg); + return rc; } #if 0 @@ -297,7 +315,7 @@ static int ss_tx_reject(struct ss_session *ss, int invoke_id, uint8_t problem_ta } #endif -static int ss_tx_error(struct ss_session *ss, uint8_t invoke_id, uint8_t error_code) +static int ss_tx_to_ms_error(struct ss_session *ss, uint8_t invoke_id, uint8_t error_code) { struct msgb *msg = gsm0480_gen_return_error(invoke_id, error_code); LOGPSS(ss, LOGL_NOTICE, "Tx ReturnError(%u, 0x%02x)\n", invoke_id, error_code); @@ -305,7 +323,7 @@ static int ss_tx_error(struct ss_session *ss, uint8_t invoke_id, uint8_t error_c return ss_tx_to_ms(ss, OSMO_GSUP_MSGT_PROC_SS_RESULT, true, msg); } -static int ss_tx_ussd_7bit(struct ss_session *ss, bool final, uint8_t invoke_id, const char *text) +static int ss_tx_to_ms_ussd_7bit(struct ss_session *ss, bool final, uint8_t invoke_id, const char *text) { struct msgb *msg = gsm0480_gen_ussd_resp_7bit(invoke_id, text); LOGPSS(ss, LOGL_INFO, "Tx USSD '%s'\n", text); @@ -319,7 +337,7 @@ static int ss_tx_ussd_7bit(struct ss_session *ss, bool final, uint8_t invoke_id, #include <osmocom/hlr/db.h> -static int handle_ussd_own_msisdn(struct osmo_gsup_conn *conn, struct ss_session *ss, +static int handle_ussd_own_msisdn(struct ss_session *ss, const struct osmo_gsup_message *gsup, const struct ss_request *req) { struct hlr_subscriber subscr; @@ -333,25 +351,25 @@ static int handle_ussd_own_msisdn(struct osmo_gsup_conn *conn, struct ss_session snprintf(buf, sizeof(buf), "You have no MSISDN!"); else snprintf(buf, sizeof(buf), "Your extension is %s", subscr.msisdn); - ss_tx_ussd_7bit(ss, true, req->invoke_id, buf); + ss_tx_to_ms_ussd_7bit(ss, true, req->invoke_id, buf); break; case -ENOENT: - ss_tx_error(ss, req->invoke_id, GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER); + ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_UNKNOWN_SUBSCRIBER); break; case -EIO: default: - ss_tx_error(ss, req->invoke_id, GSM0480_ERR_CODE_SYSTEM_FAILURE); + ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_SYSTEM_FAILURE); break; } return 0; } -static int handle_ussd_own_imsi(struct osmo_gsup_conn *conn, struct ss_session *ss, +static int handle_ussd_own_imsi(struct ss_session *ss, const struct osmo_gsup_message *gsup, const struct ss_request *req) { char buf[GSM0480_USSD_7BIT_STRING_LEN+1]; snprintf(buf, sizeof(buf), "Your IMSI is %s", ss->imsi); - ss_tx_ussd_7bit(ss, true, req->invoke_id, buf); + ss_tx_to_ms_ussd_7bit(ss, true, req->invoke_id, buf); return 0; } @@ -398,37 +416,26 @@ static bool ss_op_is_ussd(uint8_t opcode) } /* is this GSUP connection an EUSE (true) or not (false)? */ -static bool conn_is_euse(struct osmo_gsup_conn *conn) +static bool peer_name_is_euse(const struct osmo_ipa_name *peer_name) { - int rc; - uint8_t *addr; - - rc = osmo_gsup_conn_ccm_get(conn, &addr, IPAC_IDTAG_SERNR); - if (rc <= 5) + if (peer_name->len <= 5) return false; - if (!strncmp((char *)addr, "EUSE-", 5)) + if (!strncmp((char *)(peer_name->val), "EUSE-", 5)) return true; else return false; } -static struct hlr_euse *euse_by_conn(struct osmo_gsup_conn *conn) +static struct hlr_euse *euse_by_name(const struct osmo_ipa_name *peer_name) { - int rc; - char *addr; - struct hlr *hlr = conn->server->priv; - - rc = osmo_gsup_conn_ccm_get(conn, (uint8_t **) &addr, IPAC_IDTAG_SERNR); - if (rc <= 5) - return NULL; - if (strncmp(addr, "EUSE-", 5)) + if (!peer_name_is_euse(peer_name)) return NULL; - return euse_find(hlr, addr+5); + return euse_find(g_hlr, (const char*)(peer_name->val)+5); } -static int handle_ss(struct ss_session *ss, const struct osmo_gsup_message *gsup, - const struct ss_request *req) +static int handle_ss(struct ss_session *ss, bool is_euse_originated, const struct osmo_gsup_message *gsup, + const struct ss_request *req) { uint8_t comp_type = gsup->ss_info[0]; @@ -441,17 +448,16 @@ static int handle_ss(struct ss_session *ss, const struct osmo_gsup_message *gsup * we don't handle "structured" SS requests at all. */ LOGPSS(ss, LOGL_NOTICE, "Structured SS requests are not supported, rejecting...\n"); - ss_tx_error(ss, req->invoke_id, GSM0480_ERR_CODE_FACILITY_NOT_SUPPORTED); + ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_FACILITY_NOT_SUPPORTED); return -ENOTSUP; } /* Handle a USSD GSUP message for a given SS Session received from VLR or EUSE */ -static int handle_ussd(struct osmo_gsup_conn *conn, struct ss_session *ss, - const struct osmo_gsup_message *gsup, const struct ss_request *req) +static int handle_ussd(struct ss_session *ss, bool is_euse_originated, const struct osmo_gsup_message *gsup, + const struct ss_request *req) { uint8_t comp_type = gsup->ss_info[0]; struct msgb *msg_out; - bool is_euse_originated = conn_is_euse(conn); LOGPSS(ss, LOGL_INFO, "USSD CompType=%s, OpCode=%s '%s'\n", gsm0480_comp_type_name(comp_type), gsm0480_op_code_name(req->opcode), @@ -459,26 +465,27 @@ static int handle_ussd(struct osmo_gsup_conn *conn, struct ss_session *ss, if ((ss->is_external && !ss->u.euse) || !ss->u.iuse) { LOGPSS(ss, LOGL_NOTICE, "USSD for unknown code '%s'\n", req->ussd_text); - ss_tx_error(ss, req->invoke_id, GSM0480_ERR_CODE_SS_NOT_AVAILABLE); + ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_SS_NOT_AVAILABLE); return 0; } if (is_euse_originated) { - msg_out = osmo_gsup_msgb_alloc("GSUP USSD FW"); /* Received from EUSE, Forward to VLR */ - osmo_gsup_encode(msg_out, gsup); - ss_gsup_send(ss, conn->server, msg_out); + /* Need a non-const osmo_gsup_message, because sending might modify some (routing related?) parts. */ + struct osmo_gsup_message forward = *gsup; + ss_gsup_send_to_ms(ss, g_hlr->gs, &forward); } else { /* Received from VLR (MS) */ if (ss->is_external) { /* Forward to EUSE */ - char addr[128]; - strcpy(addr, "EUSE-"); - osmo_strlcpy(addr+5, ss->u.euse->name, sizeof(addr)-5); - conn = gsup_route_find(conn->server, (uint8_t *)addr, strlen(addr)+1); + struct osmo_ipa_name euse_name; + struct osmo_gsup_conn *conn; + osmo_ipa_name_set_str(&euse_name, "EUSE-%s", ss->u.euse->name); + conn = gsup_route_find_by_ipa_name(g_hlr->gs, &euse_name); if (!conn) { - LOGPSS(ss, LOGL_ERROR, "Cannot find conn for EUSE %s\n", addr); - ss_tx_error(ss, req->invoke_id, GSM0480_ERR_CODE_SYSTEM_FAILURE); + LOGPSS(ss, LOGL_ERROR, "Cannot find conn for EUSE %s\n", + osmo_ipa_name_to_str(&euse_name)); + ss_tx_to_ms_error(ss, req->invoke_id, GSM0480_ERR_CODE_SYSTEM_FAILURE); } else { msg_out = osmo_gsup_msgb_alloc("GSUP USSD FW"); osmo_gsup_encode(msg_out, gsup); @@ -486,7 +493,7 @@ static int handle_ussd(struct osmo_gsup_conn *conn, struct ss_session *ss, } } else { /* Handle internally */ - ss->u.iuse->handle_ussd(conn, ss, gsup, req); + ss->u.iuse->handle_ussd(ss, gsup, req); /* Release session immediately */ ss_session_free(ss); } @@ -498,12 +505,16 @@ static int handle_ussd(struct osmo_gsup_conn *conn, struct ss_session *ss, /* this function is called for any SS_REQ/SS_RESP messages from both the MSC/VLR side as well * as from the EUSE side */ -int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup) +void rx_proc_ss_req(struct osmo_gsup_req *gsup_req) { - struct hlr *hlr = conn->server->priv; + struct hlr *hlr = g_hlr; struct ss_session *ss; struct ss_request req = {0}; - struct gsup_route *gsup_rt; + const struct osmo_gsup_message *gsup = &gsup_req->gsup; + /* Remember whether this function should free the incoming gsup_req: if it is placed as ss->initial_req_from_*, + * do not free it here. If not, free it here. */ + struct osmo_gsup_req *free_gsup_req = gsup_req; + bool is_euse_originated = peer_name_is_euse(&gsup_req->source_name); LOGP(DSS, LOGL_DEBUG, "%s/0x%08x: Process SS (%s)\n", gsup->imsi, gsup->session_id, osmo_gsup_session_state_name(gsup->session_state)); @@ -514,14 +525,15 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message * LOGP(DSS, LOGL_ERROR, "%s/0x%082x: Unable to parse SS request: %s\n", gsup->imsi, gsup->session_id, osmo_hexdump(gsup->ss_info, gsup->ss_info_len)); - /* FIXME: Send a Reject component? */ - goto out_err; + osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_INV_MAND_INFO, "error parsing SS request"); + return; } } else if (gsup->session_state != OSMO_GSUP_SESSION_STATE_END) { LOGP(DSS, LOGL_ERROR, "%s/0x%082x: Missing SS payload for '%s'\n", gsup->imsi, gsup->session_id, osmo_gsup_session_state_name(gsup->session_state)); - goto out_err; + osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_INV_MAND_INFO, "missing SS payload"); + return; } switch (gsup->session_state) { @@ -530,32 +542,29 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message * if (ss_session_find(hlr, gsup->imsi, gsup->session_id)) { LOGP(DSS, LOGL_ERROR, "%s/0x%08x: BEGIN with non-unique session ID!\n", gsup->imsi, gsup->session_id); - goto out_err; + osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_INV_MAND_INFO, "BEGIN with non-unique session ID"); + return; } ss = ss_session_alloc(hlr, gsup->imsi, gsup->session_id); if (!ss) { LOGP(DSS, LOGL_ERROR, "%s/0x%08x: Unable to allocate SS session\n", gsup->imsi, gsup->session_id); - goto out_err; + osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_NET_FAIL, "Unable to allocate SS session"); + return; } /* Get IPA name from VLR conn and save as ss->vlr_number */ - if (!conn_is_euse(conn)) { - gsup_rt = gsup_route_find_by_conn(conn); - if (gsup_rt) { - ss->vlr_number = (uint8_t *)talloc_strdup(ss, (const char *)gsup_rt->addr); - ss->vlr_number_len = strlen((const char *)gsup_rt->addr) + 1; - LOGPSS(ss, LOGL_DEBUG, "Destination IPA name retrieved from GSUP route: %s\n", - osmo_quote_str((const char *)ss->vlr_number, ss->vlr_number_len)); - } else { - LOGPSS(ss, LOGL_NOTICE, "Could not find GSUP route, therefore can't set the destination" - " IPA name. We'll try to look it up later, but this should not" - " have happened.\n"); - } + if (!is_euse_originated) { + ss->initial_req_from_ms = gsup_req; + free_gsup_req = NULL; + ss->vlr_name = gsup_req->source_name; + } else { + ss->initial_req_from_euse = gsup_req; + free_gsup_req = NULL; } if (ss_op_is_ussd(req.opcode)) { - if (conn_is_euse(conn)) { + if (is_euse_originated) { /* EUSE->VLR: MT USSD. EUSE is known ('conn'), VLR is to be resolved */ - ss->u.euse = euse_by_conn(conn); + ss->u.euse = euse_by_name(&gsup_req->source_name); } else { /* VLR->EUSE: MO USSD. VLR is known ('conn'), EUSE is to be resolved */ struct hlr_ussd_route *rt; @@ -576,10 +585,10 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message * } } /* dispatch unstructured SS to routing */ - handle_ussd(conn, ss, gsup, &req); + handle_ussd(ss, is_euse_originated, &gsup_req->gsup, &req); } else { /* dispatch non-call SS to internal code */ - handle_ss(ss, gsup, &req); + handle_ss(ss, is_euse_originated, &gsup_req->gsup, &req); } break; case OSMO_GSUP_SESSION_STATE_CONTINUE: @@ -587,7 +596,8 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message * if (!ss) { LOGP(DSS, LOGL_ERROR, "%s/0x%08x: CONTINUE for unknown SS session\n", gsup->imsi, gsup->session_id); - goto out_err; + osmo_gsup_req_respond_err(gsup_req, GMM_CAUSE_INV_MAND_INFO, "CONTINUE for unknown SS session"); + return; } /* Reschedule self-destruction timer */ @@ -596,10 +606,10 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message * if (ss_op_is_ussd(req.opcode)) { /* dispatch unstructured SS to routing */ - handle_ussd(conn, ss, gsup, &req); + handle_ussd(ss, is_euse_originated, &gsup_req->gsup, &req); } else { /* dispatch non-call SS to internal code */ - handle_ss(ss, gsup, &req); + handle_ss(ss, is_euse_originated, &gsup_req->gsup, &req); } break; case OSMO_GSUP_SESSION_STATE_END: @@ -607,17 +617,17 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message * if (!ss) { LOGP(DSS, LOGL_ERROR, "%s/0x%08x: END for unknown SS session\n", gsup->imsi, gsup->session_id); - goto out_err; + return; } /* SS payload is optional for END */ if (gsup->ss_info && gsup->ss_info_len) { if (ss_op_is_ussd(req.opcode)) { /* dispatch unstructured SS to routing */ - handle_ussd(conn, ss, gsup, &req); + handle_ussd(ss, is_euse_originated, &gsup_req->gsup, &req); } else { /* dispatch non-call SS to internal code */ - handle_ss(ss, gsup, &req); + handle_ss(ss, is_euse_originated, &gsup_req->gsup, &req); } } @@ -626,18 +636,15 @@ int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message * default: LOGP(DSS, LOGL_ERROR, "%s/0x%08x: Unknown SS State %d\n", gsup->imsi, gsup->session_id, gsup->session_state); - goto out_err; + break; } - return 0; - -out_err: - return 0; + if (free_gsup_req) + osmo_gsup_req_free(free_gsup_req); } -int rx_proc_ss_error(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup) +void rx_proc_ss_error(struct osmo_gsup_req *req) { - LOGP(DSS, LOGL_NOTICE, "%s/0x%08x: Process SS ERROR (%s)\n", gsup->imsi, gsup->session_id, - osmo_gsup_session_state_name(gsup->session_state)); - return 0; + LOGP(DSS, LOGL_NOTICE, "%s/0x%08x: Process SS ERROR (%s)\n", req->gsup.imsi, req->gsup.session_id, + osmo_gsup_session_state_name(req->gsup.session_state)); } diff --git a/src/logging.c b/src/logging.c index d0b79cf..15ef596 100644 --- a/src/logging.c +++ b/src/logging.c @@ -31,6 +31,12 @@ const struct log_info_cat hlr_log_info_cat[] = { .color = "\033[1;35m", .enabled = 1, .loglevel = LOGL_NOTICE, }, + [DLU] = { + .name = "DLU", + .description = "Location Updating", + .color = "\033[1;33m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, }; const struct log_info hlr_log_info = { diff --git a/src/lu_fsm.c b/src/lu_fsm.c new file mode 100644 index 0000000..bded4ef --- /dev/null +++ b/src/lu_fsm.c @@ -0,0 +1,308 @@ +/* Roughly following "Process Update_Location_HLR" of TS 09.02 */ + +/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <osmocom/core/utils.h> +#include <osmocom/core/tdef.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/fsm.h> +#include <osmocom/gsm/apn.h> +#include <osmocom/gsm/gsm48_ie.h> + +#include <osmocom/gsupclient/ipa_name.h> +#include <osmocom/gsupclient/gsup_req.h> +#include <osmocom/hlr/logging.h> +#include <osmocom/hlr/hlr.h> +#include <osmocom/hlr/gsup_server.h> + +#include <osmocom/hlr/db.h> + +#define LOG_LU(lu, level, fmt, args...) \ + LOGPFSML((lu)? (lu)->fi : NULL, level, fmt, ##args) + +#define LOG_LU_REQ(lu, req, level, fmt, args...) \ + LOGPFSML((lu)? (lu)->fi : NULL, level, "%s:" fmt, \ + osmo_gsup_message_type_name((req)->gsup.message_type), ##args) + +struct lu { + struct llist_head entry; + struct osmo_fsm_inst *fi; + + struct osmo_gsup_req *update_location_req; + + /* Subscriber state at time of initial Update Location Request */ + struct hlr_subscriber subscr; + bool is_ps; + + /* VLR requesting the LU. */ + struct osmo_ipa_name vlr_name; + + /* If the LU request was received via a proxy and not immediately from a local VLR, this indicates the closest + * peer that forwarded the GSUP message. */ + struct osmo_ipa_name via_proxy; +}; +LLIST_HEAD(g_all_lu); + +enum lu_fsm_event { + LU_EV_RX_GSUP, +}; + +enum lu_fsm_state { + LU_ST_UNVALIDATED, + LU_ST_WAIT_INSERT_DATA_RESULT, + LU_ST_WAIT_LOCATION_CANCEL_RESULT, +}; + +static const struct value_string lu_fsm_event_names[] = { + OSMO_VALUE_STRING(LU_EV_RX_GSUP), + {} +}; + +static struct osmo_tdef_state_timeout lu_fsm_timeouts[32] = { + [LU_ST_WAIT_INSERT_DATA_RESULT] = { .T = -4222 }, + [LU_ST_WAIT_LOCATION_CANCEL_RESULT] = { .T = -4222 }, +}; + +#define lu_state_chg(lu, state) \ + osmo_tdef_fsm_inst_state_chg((lu)->fi, state, lu_fsm_timeouts, g_hlr_tdefs, 5) + +static void lu_success(struct lu *lu) +{ + if (!lu->update_location_req) + LOG_LU(lu, LOGL_ERROR, "No request for this LU\n"); + else + osmo_gsup_req_respond_msgt(lu->update_location_req, OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT, true); + lu->update_location_req = NULL; + osmo_fsm_inst_term(lu->fi, OSMO_FSM_TERM_REGULAR, NULL); +} + +#define lu_failure(LU, CAUSE, log_msg, args...) do { \ + if (!(LU)->update_location_req) \ + LOG_LU(LU, LOGL_ERROR, "No request for this LU\n"); \ + else \ + osmo_gsup_req_respond_err((LU)->update_location_req, CAUSE, log_msg, ##args); \ + (LU)->update_location_req = NULL; \ + osmo_fsm_inst_term((LU)->fi, OSMO_FSM_TERM_REGULAR, NULL); \ + } while(0) + +static struct osmo_fsm lu_fsm; + +static void lu_start(struct osmo_gsup_req *update_location_req) +{ + struct osmo_fsm_inst *fi; + struct lu *lu; + + OSMO_ASSERT(update_location_req); + OSMO_ASSERT(update_location_req->gsup.message_type == OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST); + + fi = osmo_fsm_inst_alloc(&lu_fsm, g_hlr, NULL, LOGL_DEBUG, update_location_req->gsup.imsi); + OSMO_ASSERT(fi); + + lu = talloc(fi, struct lu); + OSMO_ASSERT(lu); + fi->priv = lu; + *lu = (struct lu){ + .fi = fi, + .update_location_req = update_location_req, + .vlr_name = update_location_req->source_name, + .via_proxy = update_location_req->via_proxy, + /* According to GSUP specs, OSMO_GSUP_CN_DOMAIN_PS is the default. */ + .is_ps = (update_location_req->gsup.cn_domain != OSMO_GSUP_CN_DOMAIN_CS), + }; + llist_add(&lu->entry, &g_all_lu); + + osmo_fsm_inst_update_id_f_sanitize(fi, '_', "%s:IMSI-%s", lu->is_ps ? "PS" : "CS", update_location_req->gsup.imsi); + + if (!lu->vlr_name.len) { + lu_failure(lu, GMM_CAUSE_NET_FAIL, "LU without a VLR"); + return; + } + + if (db_subscr_get_by_imsi(g_hlr->dbc, update_location_req->gsup.imsi, &lu->subscr) < 0) { + lu_failure(lu, GMM_CAUSE_IMSI_UNKNOWN, "Subscriber does not exist"); + return; + } + + /* Check if subscriber is generally permitted on CS or PS + * service (as requested) */ + if (!lu->is_ps && !lu->subscr.nam_cs) { + lu_failure(lu, GMM_CAUSE_PLMN_NOTALLOWED, "nam_cs == false"); + return; + } + if (lu->is_ps && !lu->subscr.nam_ps) { + lu_failure(lu, GMM_CAUSE_GPRS_NOTALLOWED, "nam_ps == false"); + return; + } + + /* TODO: Set subscriber tracing = deactive in VLR/SGSN */ + +#if 0 + /* Cancel in old VLR/SGSN, if new VLR/SGSN differs from old (FIXME: OS#4491) */ + if (!lu->is_ps && strcmp(subscr->vlr_number, vlr_number)) { + lu_op_tx_cancel_old(lu); + } else if (lu->is_ps && strcmp(subscr->sgsn_number, sgsn_number)) { + lu_op_tx_cancel_old(lu); + } +#endif + + /* Store the VLR / SGSN number with the subscriber, so we know where it was last seen. */ + if (lu->via_proxy.len) { + LOG_GSUP_REQ(update_location_req, LOGL_DEBUG, "storing %s = %s, via proxy %s\n", + lu->is_ps ? "SGSN number" : "VLR number", + osmo_ipa_name_to_str(&lu->vlr_name), + osmo_ipa_name_to_str(&lu->via_proxy)); + } else { + LOG_GSUP_REQ(update_location_req, LOGL_DEBUG, "storing %s = %s\n", + lu->is_ps ? "SGSN number" : "VLR number", + osmo_ipa_name_to_str(&lu->vlr_name)); + } + + if (db_subscr_lu(g_hlr->dbc, lu->subscr.id, &lu->vlr_name, lu->is_ps, &lu->via_proxy)) { + lu_failure(lu, GMM_CAUSE_NET_FAIL, "Cannot update %s in the database", + lu->is_ps ? "SGSN number" : "VLR number"); + return; + } + + /* TODO: Subscriber allowed to roam in PLMN? */ + /* TODO: Update RoutingInfo */ + /* TODO: Reset Flag MS Purged (cs/ps) */ + /* TODO: Control_Tracing_HLR / Control_Tracing_HLR_with_SGSN */ + + lu_state_chg(lu, LU_ST_WAIT_INSERT_DATA_RESULT); +} + +void lu_rx_gsup(struct osmo_gsup_req *req) +{ + struct lu *lu; + if (req->gsup.message_type == OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST) + return lu_start(req); + + llist_for_each_entry(lu, &g_all_lu, entry) { + if (strcmp(lu->subscr.imsi, req->gsup.imsi)) + continue; + if (osmo_fsm_inst_dispatch(lu->fi, LU_EV_RX_GSUP, req)) { + LOG_LU_REQ(lu, req, LOGL_ERROR, "Cannot receive GSUP messages in this state\n"); + osmo_gsup_req_respond_err(req, GMM_CAUSE_MSGT_INCOMP_P_STATE, + "LU does not accept GSUP rx"); + } + return; + } + osmo_gsup_req_respond_err(req, GMM_CAUSE_MSGT_INCOMP_P_STATE, "No Location Updating in progress for this IMSI"); +} + +static int lu_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + struct lu *lu = fi->priv; + lu_failure(lu, GSM_CAUSE_NET_FAIL, "Timeout"); + return 0; +} + +static void lu_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) +{ + struct lu *lu = fi->priv; + if (lu->update_location_req) + osmo_gsup_req_respond_err(lu->update_location_req, GSM_CAUSE_NET_FAIL, "LU aborted"); + lu->update_location_req = NULL; + llist_del(&lu->entry); +} + +static void lu_fsm_wait_insert_data_result_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + /* Transmit Insert Data Request to the VLR */ + struct lu *lu = fi->priv; + struct hlr_subscriber *subscr = &lu->subscr; + struct osmo_gsup_message gsup; + uint8_t msisdn_enc[OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN]; + uint8_t apn[APN_MAXLEN]; + + if (osmo_gsup_create_insert_subscriber_data_msg(&gsup, subscr->imsi, + subscr->msisdn, msisdn_enc, sizeof(msisdn_enc), + apn, sizeof(apn), + lu->is_ps? OSMO_GSUP_CN_DOMAIN_PS : OSMO_GSUP_CN_DOMAIN_CS)) { + lu_failure(lu, GMM_CAUSE_NET_FAIL, "cannot encode Insert Subscriber Data message"); + return; + } + + if (osmo_gsup_req_respond(lu->update_location_req, &gsup, false, false)) + lu_failure(lu, GMM_CAUSE_NET_FAIL, "cannot send %s", osmo_gsup_message_type_name(gsup.message_type)); +} + +void lu_fsm_wait_insert_data_result(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct lu *lu = fi->priv; + struct osmo_gsup_req *req; + + switch (event) { + case LU_EV_RX_GSUP: + req = data; + break; + default: + OSMO_ASSERT(false); + } + + switch (req->gsup.message_type) { + case OSMO_GSUP_MSGT_INSERT_DATA_RESULT: + osmo_gsup_req_free(req); + lu_success(lu); + break; + + case OSMO_GSUP_MSGT_INSERT_DATA_ERROR: + lu_failure(lu, GMM_CAUSE_NET_FAIL, "Rx %s", osmo_gsup_message_type_name(req->gsup.message_type)); + break; + + default: + osmo_gsup_req_respond_err(req, GMM_CAUSE_MSGT_INCOMP_P_STATE, "unexpected message type in this state"); + break; + } +} + +#define S(x) (1 << (x)) + +static const struct osmo_fsm_state lu_fsm_states[] = { + [LU_ST_UNVALIDATED] = { + .name = "UNVALIDATED", + .out_state_mask = 0 + | S(LU_ST_WAIT_INSERT_DATA_RESULT) + , + }, + [LU_ST_WAIT_INSERT_DATA_RESULT] = { + .name = "WAIT_INSERT_DATA_RESULT", + .in_event_mask = 0 + | S(LU_EV_RX_GSUP) + , + .onenter = lu_fsm_wait_insert_data_result_onenter, + .action = lu_fsm_wait_insert_data_result, + }, +}; + +static struct osmo_fsm lu_fsm = { + .name = "lu", + .states = lu_fsm_states, + .num_states = ARRAY_SIZE(lu_fsm_states), + .log_subsys = DLU, + .event_names = lu_fsm_event_names, + .timer_cb = lu_fsm_timer_cb, + .cleanup = lu_fsm_cleanup, +}; + +static __attribute__((constructor)) void lu_fsm_init() +{ + OSMO_ASSERT(osmo_fsm_register(&lu_fsm) == 0); +} diff --git a/src/luop.c b/src/luop.c deleted file mode 100644 index e63ba91..0000000 --- a/src/luop.c +++ /dev/null @@ -1,258 +0,0 @@ -/* OsmoHLR TX/RX lu operations */ - -/* (C) 2017 sysmocom s.f.m.c. GmbH <info@sysmocom.de> - * All Rights Reserved - * - * Author: Harald Welte <laforge@gnumonks.org> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - */ - -#include <stdbool.h> -#include <string.h> -#include <errno.h> - -#include <osmocom/core/logging.h> -#include <osmocom/gsm/gsup.h> -#include <osmocom/gsm/apn.h> - -#include <osmocom/hlr/gsup_server.h> -#include <osmocom/hlr/gsup_router.h> -#include <osmocom/hlr/logging.h> -#include <osmocom/hlr/luop.h> - -const struct value_string lu_state_names[] = { - { LU_S_NULL, "NULL" }, - { LU_S_LU_RECEIVED, "LU RECEIVED" }, - { LU_S_CANCEL_SENT, "CANCEL SENT" }, - { LU_S_CANCEL_ACK_RECEIVED, "CANCEL-ACK RECEIVED" }, - { LU_S_ISD_SENT, "ISD SENT" }, - { LU_S_ISD_ACK_RECEIVED, "ISD-ACK RECEIVED" }, - { LU_S_COMPLETE, "COMPLETE" }, - { 0, NULL } -}; - -/* Transmit a given GSUP message for the given LU operation */ -static void _luop_tx_gsup(struct lu_operation *luop, - const struct osmo_gsup_message *gsup) -{ - struct msgb *msg_out; - - msg_out = osmo_gsup_msgb_alloc("GSUP LUOP"); - osmo_gsup_encode(msg_out, gsup); - - osmo_gsup_addr_send(luop->gsup_server, luop->peer, - talloc_total_size(luop->peer), - msg_out); -} - -static inline void fill_gsup_msg(struct osmo_gsup_message *out, - const struct lu_operation *lu, - enum osmo_gsup_message_type mt) -{ - memset(out, 0, sizeof(struct osmo_gsup_message)); - if (lu) - osmo_strlcpy(out->imsi, lu->subscr.imsi, - GSM23003_IMSI_MAX_DIGITS + 1); - out->message_type = mt; -} - -/* timer call-back in case LU operation doesn't receive an response */ -static void lu_op_timer_cb(void *data) -{ - struct lu_operation *luop = data; - - DEBUGP(DMAIN, "LU OP timer expired in state %s\n", - get_value_string(lu_state_names, luop->state)); - - switch (luop->state) { - case LU_S_CANCEL_SENT: - break; - case LU_S_ISD_SENT: - break; - default: - break; - } - - lu_op_tx_error(luop, GMM_CAUSE_NET_FAIL); -} - -bool lu_op_fill_subscr(struct lu_operation *luop, struct db_context *dbc, - const char *imsi) -{ - struct hlr_subscriber *subscr = &luop->subscr; - - if (db_subscr_get_by_imsi(dbc, imsi, subscr) < 0) - return false; - - return true; -} - -struct lu_operation *lu_op_alloc(struct osmo_gsup_server *srv) -{ - struct lu_operation *luop; - - luop = talloc_zero(srv, struct lu_operation); - OSMO_ASSERT(luop); - luop->gsup_server = srv; - osmo_timer_setup(&luop->timer, lu_op_timer_cb, luop); - - return luop; -} - -void lu_op_free(struct lu_operation *luop) -{ - /* Only attempt to remove when it was ever added to a list. */ - if (luop->list.next) - llist_del(&luop->list); - - /* Delete timer just in case it is still pending. */ - osmo_timer_del(&luop->timer); - - talloc_free(luop); -} - -struct lu_operation *lu_op_alloc_conn(struct osmo_gsup_conn *conn) -{ - uint8_t *peer_addr; - struct lu_operation *luop = lu_op_alloc(conn->server); - int rc = osmo_gsup_conn_ccm_get(conn, &peer_addr, IPAC_IDTAG_SERNR); - if (rc < 0) { - lu_op_free(luop); - return NULL; - } - - luop->peer = talloc_memdup(luop, peer_addr, rc); - - 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) -{ - struct lu_operation *luop; - - llist_for_each_entry(luop, lst, list) { - if (!strcmp(imsi, luop->subscr.imsi)) - return luop; - } - return NULL; -} - -void lu_op_statechg(struct lu_operation *luop, enum lu_state new_state) -{ - enum lu_state old_state = luop->state; - - DEBUGP(DMAIN, "LU OP state change: %s -> ", - get_value_string(lu_state_names, old_state)); - DEBUGPC(DMAIN, "%s\n", - get_value_string(lu_state_names, new_state)); - - luop->state = new_state; -} - -/*! Transmit UPD_LOC_ERROR and destroy lu_operation */ -void lu_op_tx_error(struct lu_operation *luop, enum gsm48_gmm_cause cause) -{ - struct osmo_gsup_message gsup; - - DEBUGP(DMAIN, "%s: LU OP Tx Error (cause %s)\n", - luop->subscr.imsi, get_value_string(gsm48_gmm_cause_names, - cause)); - - fill_gsup_msg(&gsup, luop, OSMO_GSUP_MSGT_UPDATE_LOCATION_ERROR); - gsup.cause = cause; - - _luop_tx_gsup(luop, &gsup); - - lu_op_free(luop); -} - -/*! Transmit UPD_LOC_RESULT and destroy lu_operation */ -void lu_op_tx_ack(struct lu_operation *luop) -{ - struct osmo_gsup_message gsup; - - fill_gsup_msg(&gsup, luop, OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT); - //FIXME gsup.hlr_enc; - - _luop_tx_gsup(luop, &gsup); - - lu_op_free(luop); -} - -/*! Send Cancel Location to old VLR/SGSN (FIXME: OS#4491) */ -void lu_op_tx_cancel_old(struct lu_operation *luop) -{ - struct osmo_gsup_message gsup; - - OSMO_ASSERT(luop->state == LU_S_LU_RECEIVED); - - fill_gsup_msg(&gsup, NULL, OSMO_GSUP_MSGT_LOCATION_CANCEL_REQUEST); - //gsup.cause = FIXME; - //gsup.cancel_type = FIXME; - - _luop_tx_gsup(luop, &gsup); - - lu_op_statechg(luop, LU_S_CANCEL_SENT); - osmo_timer_schedule(&luop->timer, CANCEL_TIMEOUT_SECS, 0); -} - -/*! Transmit Insert Subscriber Data to new VLR/SGSN */ -void lu_op_tx_insert_subscr_data(struct lu_operation *luop) -{ - struct hlr_subscriber *subscr = &luop->subscr; - struct osmo_gsup_message gsup = { }; - uint8_t msisdn_enc[OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN]; - uint8_t apn[APN_MAXLEN]; - enum osmo_gsup_cn_domain cn_domain; - - OSMO_ASSERT(luop->state == LU_S_LU_RECEIVED || - luop->state == LU_S_CANCEL_ACK_RECEIVED); - - if (luop->is_ps) - cn_domain = OSMO_GSUP_CN_DOMAIN_PS; - else - cn_domain = OSMO_GSUP_CN_DOMAIN_CS; - - if (osmo_gsup_create_insert_subscriber_data_msg(&gsup, subscr->imsi, subscr->msisdn, msisdn_enc, - sizeof(msisdn_enc), apn, sizeof(apn), cn_domain) != 0) { - LOGP(DMAIN, LOGL_ERROR, - "IMSI='%s': Cannot notify GSUP client; could not create gsup message " - "for %s\n", subscr->imsi, luop->peer); - return; - } - - /* Send ISD to new VLR/SGSN */ - _luop_tx_gsup(luop, &gsup); - - lu_op_statechg(luop, LU_S_ISD_SENT); - osmo_timer_schedule(&luop->timer, ISD_TIMEOUT_SECS, 0); -} - -/*! Transmit Delete Subscriber Data to new VLR/SGSN. - * The luop is not freed. */ -void lu_op_tx_del_subscr_data(struct lu_operation *luop) -{ - struct osmo_gsup_message gsup; - - 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); -} |