aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--configure.ac1
-rw-r--r--include/Makefile.am2
-rw-r--r--include/osmocom/gsupclient/gsup_req.h119
-rw-r--r--include/osmocom/gsupclient/ipa_name.h38
-rw-r--r--include/osmocom/hlr/Makefile.am2
-rw-r--r--include/osmocom/hlr/db.h18
-rw-r--r--include/osmocom/hlr/gsup_router.h7
-rw-r--r--include/osmocom/hlr/gsup_server.h11
-rw-r--r--include/osmocom/hlr/hlr.h8
-rw-r--r--include/osmocom/hlr/hlr_ussd.h7
-rw-r--r--include/osmocom/hlr/logging.h1
-rw-r--r--include/osmocom/hlr/lu_fsm.h22
-rw-r--r--include/osmocom/hlr/luop.h81
-rw-r--r--src/Makefile.am4
-rw-r--r--src/db_hlr.c57
-rw-r--r--src/gsup_router.c21
-rw-r--r--src/gsup_send.c41
-rw-r--r--src/gsup_server.c80
-rw-r--r--src/gsupclient/Makefile.am6
-rw-r--r--src/gsupclient/gsup_req.c410
-rw-r--r--src/gsupclient/ipa_name.c97
-rw-r--r--src/hlr.c492
-rw-r--r--src/hlr_ussd.c199
-rw-r--r--src/logging.c6
-rw-r--r--src/lu_fsm.c308
-rw-r--r--src/luop.c258
-rw-r--r--tests/Makefile.am1
-rw-r--r--tests/db/Makefile.am1
-rw-r--r--tests/db/db_test.c39
-rw-r--r--tests/db/db_test.err28
-rw-r--r--tests/gsup/Makefile.am36
-rw-r--r--tests/gsup/gsup_test.c113
-rw-r--r--tests/gsup/gsup_test.err0
-rw-r--r--tests/gsup/gsup_test.ok32
-rw-r--r--tests/gsup_server/Makefile.am2
-rw-r--r--tests/test_nodes.vty2
-rw-r--r--tests/testsuite.at7
37 files changed, 1692 insertions, 865 deletions
diff --git a/configure.ac b/configure.ac
index 73ff752..0e03ff0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -204,6 +204,7 @@ AC_OUTPUT(
tests/auc/Makefile
tests/auc/gen_ts_55_205_test_sets/Makefile
tests/gsup_server/Makefile
+ tests/gsup/Makefile
tests/db/Makefile
tests/db_upgrade/Makefile
tests/mslookup/Makefile
diff --git a/include/Makefile.am b/include/Makefile.am
index 9827950..aeeb03f 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -1,7 +1,9 @@
SUBDIRS = osmocom
nobase_include_HEADERS = \
+ osmocom/gsupclient/ipa_name.h \
osmocom/gsupclient/gsup_client.h \
+ osmocom/gsupclient/gsup_req.h \
osmocom/mslookup/mdns.h \
osmocom/mslookup/mdns_sock.h \
osmocom/mslookup/mslookup_client_fake.h \
diff --git a/include/osmocom/gsupclient/gsup_req.h b/include/osmocom/gsupclient/gsup_req.h
new file mode 100644
index 0000000..c61483b
--- /dev/null
+++ b/include/osmocom/gsupclient/gsup_req.h
@@ -0,0 +1,119 @@
+/* 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/>.
+ */
+
+#pragma once
+
+#include <osmocom/gsm/gsup.h>
+#include <osmocom/gsupclient/ipa_name.h>
+
+struct osmo_gsup_req;
+
+#define LOG_GSUP_REQ_CAT_SRC(req, subsys, level, file, line, fmt, args...) \
+ LOGPSRC(subsys, level, file, line, "GSUP %u: %s: IMSI-%s %s: " fmt, \
+ (req) ? (req)->nr : 0, \
+ (req) ? osmo_ipa_name_to_str(&(req)->source_name) : "NULL", \
+ (req) ? (req)->gsup.imsi : "NULL", \
+ (req) ? osmo_gsup_message_type_name((req)->gsup.message_type) : "NULL", \
+ ##args)
+#define LOG_GSUP_REQ_CAT(req, subsys, level, fmt, args...) \
+ LOG_GSUP_REQ_CAT_SRC(req, subsys, level, __FILE__, __LINE__, fmt, ##args)
+
+#define LOG_GSUP_REQ_SRC(req, level, file, line, fmt, args...) \
+ LOG_GSUP_REQ_CAT_SRC(req, DLGSUP, level, file, line, fmt, ##args)
+
+#define LOG_GSUP_REQ(req, level, fmt, args...) \
+ LOG_GSUP_REQ_SRC(req, level, __FILE__, __LINE__, fmt, ##args)
+
+typedef void (*osmo_gsup_req_send_response_t)(struct osmo_gsup_req *req, struct osmo_gsup_message *response);
+
+/* Keep track of an incoming request, to route back a response when it is ready.
+ * Particularly, a GSUP response to a request must contain various bits of information that need to be copied from the
+ * request for proxy/routing to work and for session states to remain valid. That is the main reason why (almost) all
+ * GSUP request/response should go through an osmo_gsup_req, even if it is handled synchronously.
+ */
+struct osmo_gsup_req {
+ /* The incoming GSUP message in decoded form. */
+ const struct osmo_gsup_message gsup;
+
+ /* Decoding result code. If decoding failed, this will be != 0. */
+ int decode_rc;
+
+ /* The ultimate source of this message: the source_name form the GSUP message, or, if not present, then the
+ * immediate GSUP peer. GSUP messages going via a proxy reflect the initial source in the source_name.
+ * This source_name is implicitly added to the routes for the conn the message was received on. */
+ struct osmo_ipa_name source_name;
+
+ /* If the source_name is not an immediate GSUP peer, this is set to the closest intermediate peer between here
+ * and source_name. */
+ struct osmo_ipa_name via_proxy;
+
+ /* Identify this request by number, for logging. */
+ unsigned int nr;
+
+ /* osmo_gsup_req can be used by both gsup_server and gsup_client. The individual method of actually sending a
+ * GSUP message is provided by this callback. */
+ osmo_gsup_req_send_response_t send_response_cb;
+
+ /* User supplied data pointer, may be used to provide context to send_response_cb(). */
+ void *cb_data;
+
+ /* List entry that can be used to keep a list of osmo_gsup_req instances; not used directly by osmo_gsup_req.c,
+ * it is up to using implementations to keep a list. If this is non-NULL, osmo_gsup_req_free() calls
+ * llist_del() on this. */
+ struct llist_head entry;
+
+ /* A decoded GSUP message still points into the received msgb. For a decoded osmo_gsup_message to remain valid,
+ * we also need to keep the msgb. */
+ struct msgb *msg;
+};
+
+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);
+void osmo_gsup_req_free(struct osmo_gsup_req *req);
+
+/*! See _osmo_gsup_req_respond() for details.
+ * Call _osmo_gsup_req_respond(), passing the caller's source file and line for logging. */
+#define osmo_gsup_req_respond(REQ, RESPONSE, ERROR, FINAL_RESPONSE) \
+ _osmo_gsup_req_respond(REQ, RESPONSE, ERROR, FINAL_RESPONSE, __FILE__, __LINE__)
+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);
+
+/*! See _osmo_gsup_req_respond_msgt() for details.
+ * Call _osmo_gsup_req_respond_msgt(), passing the caller's source file and line for logging. */
+#define osmo_gsup_req_respond_msgt(REQ, MESSAGE_TYPE, FINAL_RESPONSE) \
+ _osmo_gsup_req_respond_msgt(REQ, MESSAGE_TYPE, FINAL_RESPONSE, __FILE__, __LINE__)
+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);
+
+/*! See _osmo_gsup_req_respond_err() for details.
+ * Log an error message, and call _osmo_gsup_req_respond_err(), passing the caller's source file and line for logging.
+ */
+#define osmo_gsup_req_respond_err(REQ, CAUSE, FMT, args...) do { \
+ LOG_GSUP_REQ(REQ, LOGL_ERROR, "%s: " FMT "\n", \
+ get_value_string(gsm48_gmm_cause_names, CAUSE), ##args); \
+ _osmo_gsup_req_respond_err(REQ, CAUSE, __FILE__, __LINE__); \
+ } while(0)
+void _osmo_gsup_req_respond_err(struct osmo_gsup_req *req, enum gsm48_gmm_cause cause,
+ const char *file, int line);
+
+int osmo_gsup_make_response(struct osmo_gsup_message *reply,
+ const struct osmo_gsup_message *rx, bool error, bool final_response);
+
+size_t osmo_gsup_message_to_str_buf(char *buf, size_t bufsize, const struct osmo_gsup_message *msg);
+char *osmo_gsup_message_to_str_c(void *ctx, const struct osmo_gsup_message *msg);
diff --git a/include/osmocom/gsupclient/ipa_name.h b/include/osmocom/gsupclient/ipa_name.h
new file mode 100644
index 0000000..73cd1fb
--- /dev/null
+++ b/include/osmocom/gsupclient/ipa_name.h
@@ -0,0 +1,38 @@
+/* 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/>.
+ */
+
+#pragma once
+#include <unistd.h>
+#include <stdint.h>
+
+/*! IPA Name: Arbitrary length blob, not necessarily zero-terminated.
+ * In osmo-hlr, struct hlr_subscriber is mostly used as static reference and cannot serve as talloc context, which is
+ * why this is also implemented as a fixed-maximum-size buffer instead of a talloc'd arbitrary sized buffer.
+ * NOTE: The length of val may be extended in the future if it becomes necessary.
+ * At the time of writing, this holds IPA unit name strings of very limited length.
+ */
+struct osmo_ipa_name {
+ size_t len;
+ uint8_t val[128];
+};
+
+int osmo_ipa_name_set(struct osmo_ipa_name *ipa_name, const uint8_t *val, size_t len);
+int osmo_ipa_name_set_str(struct osmo_ipa_name *ipa_name, const char *str_fmt, ...);
+int osmo_ipa_name_cmp(const struct osmo_ipa_name *a, const struct osmo_ipa_name *b);
+const char *osmo_ipa_name_to_str_c(void *ctx, const struct osmo_ipa_name *ipa_name);
+const char *osmo_ipa_name_to_str(const struct osmo_ipa_name *ipa_name);
diff --git a/include/osmocom/hlr/Makefile.am b/include/osmocom/hlr/Makefile.am
index 77a8764..532fa5d 100644
--- a/include/osmocom/hlr/Makefile.am
+++ b/include/osmocom/hlr/Makefile.am
@@ -9,6 +9,6 @@ noinst_HEADERS = \
hlr_vty.h \
hlr_vty_subscr.h \
logging.h \
- luop.h \
+ lu_fsm.h \
rand.h \
$(NULL)
diff --git a/include/osmocom/hlr/db.h b/include/osmocom/hlr/db.h
index c927099..5c627be 100644
--- a/include/osmocom/hlr/db.h
+++ b/include/osmocom/hlr/db.h
@@ -3,6 +3,8 @@
#include <stdbool.h>
#include <sqlite3.h>
+#include <osmocom/gsupclient/ipa_name.h>
+
struct hlr;
enum stmt_idx {
@@ -151,13 +153,12 @@ int db_subscr_get_by_id(struct db_context *dbc, int64_t id,
int db_subscr_get_by_imei(struct db_context *dbc, const char *imei, struct hlr_subscriber *subscr);
int db_subscr_nam(struct db_context *dbc, const char *imsi, bool nam_val, bool is_ps);
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);
int db_subscr_purge(struct db_context *dbc, const char *by_imsi,
bool purge_val, bool is_ps);
-int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps);
-
/*! Call sqlite3_column_text() and copy result to a char[].
* \param[out] buf A char[] used as sizeof() arg(!) and osmo_strlcpy() target.
* \param[in] stmt An sqlite3_stmt*.
@@ -168,3 +169,14 @@ int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val,
const char *_txt = (const char *) sqlite3_column_text(stmt, idx); \
osmo_strlcpy(buf, _txt, sizeof(buf)); \
} while (0)
+
+/*! Call sqlite3_column_text() and copy result to a struct osmo_ipa_name.
+ * \param[out] ipa_name A struct osmo_ipa_name* to write to.
+ * \param[in] stmt An sqlite3_stmt*.
+ * \param[in] idx Index in stmt's returned columns.
+ */
+#define copy_sqlite3_text_to_ipa_name(ipa_name, stmt, idx) \
+ do { \
+ const char *_txt = (const char *) sqlite3_column_text(stmt, idx); \
+ osmo_ipa_name_set_str(ipa_name, _txt); \
+ } while (0)
diff --git a/include/osmocom/hlr/gsup_router.h b/include/osmocom/hlr/gsup_router.h
index 0fc10d0..ee12a2b 100644
--- a/include/osmocom/hlr/gsup_router.h
+++ b/include/osmocom/hlr/gsup_router.h
@@ -3,6 +3,8 @@
#include <stdint.h>
#include <osmocom/hlr/gsup_server.h>
+struct osmo_ipa_name;
+
struct gsup_route {
struct llist_head list;
@@ -12,10 +14,12 @@ struct gsup_route {
struct osmo_gsup_conn *gsup_route_find(struct osmo_gsup_server *gs,
const uint8_t *addr, size_t addrlen);
+struct osmo_gsup_conn *gsup_route_find_by_ipa_name(struct osmo_gsup_server *gs, const struct osmo_ipa_name *ipa_name);
struct gsup_route *gsup_route_find_by_conn(const struct osmo_gsup_conn *conn);
/* add a new route for the given address to the given conn */
+int gsup_route_add_ipa_name(struct osmo_gsup_conn *conn, const struct osmo_ipa_name *ipa_name);
int gsup_route_add(struct osmo_gsup_conn *conn, const uint8_t *addr, size_t addrlen);
/* delete all routes for the given connection */
@@ -24,3 +28,6 @@ int gsup_route_del_conn(struct osmo_gsup_conn *conn);
int osmo_gsup_addr_send(struct osmo_gsup_server *gs,
const uint8_t *addr, size_t addrlen,
struct msgb *msg);
+int osmo_gsup_send_to_ipa_name(struct osmo_gsup_server *gs, const struct osmo_ipa_name *ipa_name, struct msgb *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);
diff --git a/include/osmocom/hlr/gsup_server.h b/include/osmocom/hlr/gsup_server.h
index 14f5013..149971a 100644
--- a/include/osmocom/hlr/gsup_server.h
+++ b/include/osmocom/hlr/gsup_server.h
@@ -5,6 +5,8 @@
#include <osmocom/abis/ipa.h>
#include <osmocom/abis/ipaccess.h>
#include <osmocom/gsm/gsup.h>
+#include <osmocom/gsupclient/ipa_name.h>
+#include <osmocom/gsupclient/gsup_req.h>
#ifndef OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN
#define OSMO_GSUP_MAX_CALLED_PARTY_BCD_LEN 43 /* TS 24.008 10.5.4.7 */
@@ -22,9 +24,6 @@ struct osmo_gsup_server {
/* list of osmo_gsup_conn */
struct llist_head clients;
- /* lu_operations list */
- struct llist_head *luop;
-
struct ipa_server_link *link;
osmo_gsup_read_cb_t read_cb;
struct llist_head routes;
@@ -45,10 +44,15 @@ struct osmo_gsup_conn {
/* Set when Location Update is received: */
bool supports_cs; /* client supports OSMO_GSUP_CN_DOMAIN_CS */
bool supports_ps; /* client supports OSMO_GSUP_CN_DOMAIN_PS */
+
+ /* The IPA unit name received on this link. Routes with more unit names serviced by this link may exist in
+ * osmo_gsup_server->routes, but this is the name the immediate peer identified as in the IPA handshake. */
+ struct osmo_ipa_name peer_name;
};
struct msgb *osmo_gsup_msgb_alloc(const char *label);
+struct osmo_gsup_req *osmo_gsup_conn_rx(struct osmo_gsup_conn *conn, struct msgb *msg);
int osmo_gsup_conn_send(struct osmo_gsup_conn *conn, struct msgb *msg);
int osmo_gsup_conn_ccm_get(const struct osmo_gsup_conn *clnt, uint8_t **addr,
uint8_t tag);
@@ -57,7 +61,6 @@ 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);
void osmo_gsup_server_destroy(struct osmo_gsup_server *gsups);
diff --git a/include/osmocom/hlr/hlr.h b/include/osmocom/hlr/hlr.h
index 0564518..5885600 100644
--- a/include/osmocom/hlr/hlr.h
+++ b/include/osmocom/hlr/hlr.h
@@ -24,10 +24,16 @@
#include <stdbool.h>
#include <osmocom/core/linuxlist.h>
+#include <osmocom/gsm/ipa.h>
+#include <osmocom/core/tdef.h>
#define HLR_DEFAULT_DB_FILE_PATH "hlr.db"
struct hlr_euse;
+struct osmo_gsup_conn;
+enum osmo_gsup_message_type;
+
+extern struct osmo_tdef g_hlr_tdefs[];
struct hlr {
/* GSUP server pointer */
@@ -43,6 +49,7 @@ struct hlr {
/* Local bind addr */
char *gsup_bind_addr;
+ struct ipaccess_unit gsup_unit_name;
struct llist_head euse_list;
struct hlr_euse *euse_default;
@@ -67,3 +74,4 @@ extern struct hlr *g_hlr;
struct hlr_subscriber;
void osmo_hlr_subscriber_update_notify(struct hlr_subscriber *subscr);
+int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps);
diff --git a/include/osmocom/hlr/hlr_ussd.h b/include/osmocom/hlr/hlr_ussd.h
index 08e810e..8b2e837 100644
--- a/include/osmocom/hlr/hlr_ussd.h
+++ b/include/osmocom/hlr/hlr_ussd.h
@@ -46,8 +46,8 @@ struct hlr_ussd_route *ussd_route_prefix_alloc_ext(struct hlr *hlr, const char *
struct hlr_euse *euse);
void ussd_route_del(struct hlr_ussd_route *rt);
-int rx_proc_ss_req(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup);
-int rx_proc_ss_error(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup);
+void rx_proc_ss_req(struct osmo_gsup_req *req);
+void rx_proc_ss_error(struct osmo_gsup_req *req);
struct ss_session;
struct ss_request;
@@ -56,6 +56,5 @@ struct ss_request;
struct hlr_iuse {
const char *name;
/* call-back to be called for any incoming USSD messages for this IUSE */
- int (*handle_ussd)(struct osmo_gsup_conn *conn, struct ss_session *ss,
- const struct osmo_gsup_message *gsup, const struct ss_request *req);
+ int (*handle_ussd)(struct ss_session *ss, const struct osmo_gsup_message *gsup, const struct ss_request *req);
};
diff --git a/include/osmocom/hlr/logging.h b/include/osmocom/hlr/logging.h
index 83f1acd..4e0a25c 100644
--- a/include/osmocom/hlr/logging.h
+++ b/include/osmocom/hlr/logging.h
@@ -9,6 +9,7 @@ enum {
DAUC,
DSS,
DMSLOOKUP,
+ DLU,
};
extern const struct log_info hlr_log_info;
diff --git a/include/osmocom/hlr/lu_fsm.h b/include/osmocom/hlr/lu_fsm.h
new file mode 100644
index 0000000..2440185
--- /dev/null
+++ b/include/osmocom/hlr/lu_fsm.h
@@ -0,0 +1,22 @@
+/* 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/>.
+ *
+ */
+
+#pragma once
+
+void lu_rx_gsup(struct osmo_gsup_req *req);
diff --git a/include/osmocom/hlr/luop.h b/include/osmocom/hlr/luop.h
deleted file mode 100644
index 77a1dec..0000000
--- a/include/osmocom/hlr/luop.h
+++ /dev/null
@@ -1,81 +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/>.
- *
- */
-
-#pragma once
-
-#include <stdbool.h>
-
-#include <osmocom/core/timer.h>
-#include <osmocom/gsm/gsup.h>
-
-#include <osmocom/hlr/db.h>
-#include <osmocom/hlr/gsup_server.h>
-
-#define CANCEL_TIMEOUT_SECS 30
-#define ISD_TIMEOUT_SECS 30
-
-enum lu_state {
- LU_S_NULL,
- LU_S_LU_RECEIVED,
- LU_S_CANCEL_SENT,
- LU_S_CANCEL_ACK_RECEIVED,
- LU_S_ISD_SENT,
- LU_S_ISD_ACK_RECEIVED,
- LU_S_COMPLETE,
-};
-
-extern const struct value_string lu_state_names[];
-
-struct lu_operation {
- /*! entry in global list of location update operations */
- struct llist_head list;
- /*! to which gsup_server do we belong */
- struct osmo_gsup_server *gsup_server;
- /*! state of the location update */
- enum lu_state state;
- /*! CS (false) or PS (true) Location Update? */
- bool is_ps;
- /*! currently running timer */
- struct osmo_timer_list timer;
-
- /*! subscriber related to this operation */
- struct hlr_subscriber subscr;
- /*! peer VLR/SGSN starting the request */
- uint8_t *peer;
-};
-
-
-struct lu_operation *lu_op_alloc(struct osmo_gsup_server *srv);
-struct lu_operation *lu_op_alloc_conn(struct osmo_gsup_conn *conn);
-void lu_op_statechg(struct lu_operation *luop, enum lu_state new_state);
-bool lu_op_fill_subscr(struct lu_operation *luop, struct db_context *dbc,
- const char *imsi);
-struct lu_operation *lu_op_by_imsi(const char *imsi,
- const struct llist_head *lst);
-
-void lu_op_tx_error(struct lu_operation *luop, enum gsm48_gmm_cause cause);
-void lu_op_tx_ack(struct lu_operation *luop);
-void lu_op_tx_cancel_old(struct lu_operation *luop);
-void lu_op_tx_insert_subscr_data(struct lu_operation *luop);
-void lu_op_tx_del_subscr_data(struct lu_operation *luop);
-
-void lu_op_free(struct lu_operation *luop);
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);
+}
diff --git a/src/hlr.c b/src/hlr.c
index ac6afc3..47b3211 100644
--- a/src/hlr.c
+++ b/src/hlr.c
@@ -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);
-}
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 776f8a9..9015494 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -2,6 +2,7 @@ SUBDIRS = \
auc \
gsup_server \
db \
+ gsup \
db_upgrade \
mslookup \
$(NULL)
diff --git a/tests/db/Makefile.am b/tests/db/Makefile.am
index f13824d..0194957 100644
--- a/tests/db/Makefile.am
+++ b/tests/db/Makefile.am
@@ -30,6 +30,7 @@ db_test_LDADD = \
$(top_builddir)/src/db_auc.o \
$(top_builddir)/src/db_hlr.o \
$(top_builddir)/src/db.o \
+ $(top_builddir)/src/ipa_name.o \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOGSM_LIBS) \
$(LIBOSMOABIS_LIBS) \
diff --git a/tests/db/db_test.c b/tests/db/db_test.c
index b9b263d..c53baed 100644
--- a/tests/db/db_test.c
+++ b/tests/db/db_test.c
@@ -27,6 +27,7 @@
#include <osmocom/core/utils.h>
#include <osmocom/core/logging.h>
+#include <osmocom/gsupclient/ipa_name.h>
#include <osmocom/hlr/db.h>
#include <osmocom/hlr/logging.h>
@@ -145,6 +146,8 @@ void dump_subscr(struct hlr_subscriber *subscr)
#define Ps(name) \
if (*subscr->name) \
Pfo(name, "'%s'", subscr)
+#define Pgt(name) \
+ Pfv(name, "%s", osmo_ipa_name_to_str(&subscr->name))
#define Pd(name) \
Pfv(name, "%"PRId64, (int64_t)subscr->name)
#define Pd_nonzero(name) \
@@ -235,6 +238,14 @@ static const char *imsi2 = "123456789000002";
static const char *short_imsi = "123456";
static const char *unknown_imsi = "999999999";
+static int db_subscr_lu_str(struct db_context *dbc, int64_t subscr_id,
+ const char *vlr_or_sgsn_number, bool is_ps)
+{
+ struct osmo_ipa_name vlr_nr;
+ osmo_ipa_name_set_str(&vlr_nr, vlr_or_sgsn_number);
+ return db_subscr_lu(dbc, subscr_id, &vlr_nr, is_ps, NULL);
+}
+
static void test_subscr_create_update_sel_delete()
{
int64_t id0, id1, id2, id_short;
@@ -386,39 +397,39 @@ static void test_subscr_create_update_sel_delete()
comment("Record LU for PS and CS (SGSN and VLR names)");
- ASSERT_RC(db_subscr_lu(dbc, id0, "5952", true), 0);
+ ASSERT_RC(db_subscr_lu_str(dbc, id0, "5952", true), 0);
ASSERT_SEL(id, id0, 0);
- ASSERT_RC(db_subscr_lu(dbc, id0, "712", false), 0);
+ ASSERT_RC(db_subscr_lu_str(dbc, id0, "712", false), 0);
ASSERT_SEL(id, id0, 0);
comment("Record LU for PS and CS (SGSN and VLR names) *again*");
- ASSERT_RC(db_subscr_lu(dbc, id0, "111", true), 0);
+ ASSERT_RC(db_subscr_lu_str(dbc, id0, "111", true), 0);
ASSERT_SEL(id, id0, 0);
- ASSERT_RC(db_subscr_lu(dbc, id0, "111", true), 0);
+ ASSERT_RC(db_subscr_lu_str(dbc, id0, "111", true), 0);
ASSERT_SEL(id, id0, 0);
- ASSERT_RC(db_subscr_lu(dbc, id0, "222", false), 0);
+ ASSERT_RC(db_subscr_lu_str(dbc, id0, "222", false), 0);
ASSERT_SEL(id, id0, 0);
- ASSERT_RC(db_subscr_lu(dbc, id0, "222", false), 0);
+ ASSERT_RC(db_subscr_lu_str(dbc, id0, "222", false), 0);
ASSERT_SEL(id, id0, 0);
comment("Unset LU info for PS and CS (SGSN and VLR names)");
- ASSERT_RC(db_subscr_lu(dbc, id0, "", true), 0);
+ ASSERT_RC(db_subscr_lu_str(dbc, id0, "", true), 0);
ASSERT_SEL(id, id0, 0);
- ASSERT_RC(db_subscr_lu(dbc, id0, "", false), 0);
+ ASSERT_RC(db_subscr_lu_str(dbc, id0, "", false), 0);
ASSERT_SEL(id, id0, 0);
- ASSERT_RC(db_subscr_lu(dbc, id0, "111", true), 0);
- ASSERT_RC(db_subscr_lu(dbc, id0, "222", false), 0);
+ ASSERT_RC(db_subscr_lu_str(dbc, id0, "111", true), 0);
+ ASSERT_RC(db_subscr_lu_str(dbc, id0, "222", false), 0);
ASSERT_SEL(id, id0, 0);
- ASSERT_RC(db_subscr_lu(dbc, id0, NULL, true), 0);
+ ASSERT_RC(db_subscr_lu_str(dbc, id0, NULL, true), 0);
ASSERT_SEL(id, id0, 0);
- ASSERT_RC(db_subscr_lu(dbc, id0, NULL, false), 0);
+ ASSERT_RC(db_subscr_lu_str(dbc, id0, NULL, false), 0);
ASSERT_SEL(id, id0, 0);
comment("Record LU for non-existent ID");
- ASSERT_RC(db_subscr_lu(dbc, 99999, "5952", true), -ENOENT);
- ASSERT_RC(db_subscr_lu(dbc, 99999, "712", false), -ENOENT);
+ ASSERT_RC(db_subscr_lu_str(dbc, 99999, "5952", true), -ENOENT);
+ ASSERT_RC(db_subscr_lu_str(dbc, 99999, "712", false), -ENOENT);
ASSERT_SEL(id, 99999, -ENOENT);
comment("Purge and un-purge PS and CS");
diff --git a/tests/db/db_test.err b/tests/db/db_test.err
index 6423550..e4d43c9 100644
--- a/tests/db/db_test.err
+++ b/tests/db/db_test.err
@@ -435,7 +435,7 @@ DAUC Cannot disable CS: no such subscriber: IMSI='foobar'
--- Record LU for PS and CS (SGSN and VLR names)
-db_subscr_lu(dbc, id0, "5952", true) --> 0
+db_subscr_lu_str(dbc, id0, "5952", true) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -445,7 +445,7 @@ struct hlr_subscriber {
.sgsn_number = '5952',
}
-db_subscr_lu(dbc, id0, "712", false) --> 0
+db_subscr_lu_str(dbc, id0, "712", false) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -459,7 +459,7 @@ struct hlr_subscriber {
--- Record LU for PS and CS (SGSN and VLR names) *again*
-db_subscr_lu(dbc, id0, "111", true) --> 0
+db_subscr_lu_str(dbc, id0, "111", true) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -470,7 +470,7 @@ struct hlr_subscriber {
.sgsn_number = '111',
}
-db_subscr_lu(dbc, id0, "111", true) --> 0
+db_subscr_lu_str(dbc, id0, "111", true) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -481,7 +481,7 @@ struct hlr_subscriber {
.sgsn_number = '111',
}
-db_subscr_lu(dbc, id0, "222", false) --> 0
+db_subscr_lu_str(dbc, id0, "222", false) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -492,7 +492,7 @@ struct hlr_subscriber {
.sgsn_number = '111',
}
-db_subscr_lu(dbc, id0, "222", false) --> 0
+db_subscr_lu_str(dbc, id0, "222", false) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -506,7 +506,7 @@ struct hlr_subscriber {
--- Unset LU info for PS and CS (SGSN and VLR names)
-db_subscr_lu(dbc, id0, "", true) --> 0
+db_subscr_lu_str(dbc, id0, "", true) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -516,7 +516,7 @@ struct hlr_subscriber {
.vlr_number = '222',
}
-db_subscr_lu(dbc, id0, "", false) --> 0
+db_subscr_lu_str(dbc, id0, "", false) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -525,9 +525,9 @@ struct hlr_subscriber {
.msisdn = '543210123456789',
}
-db_subscr_lu(dbc, id0, "111", true) --> 0
+db_subscr_lu_str(dbc, id0, "111", true) --> 0
-db_subscr_lu(dbc, id0, "222", false) --> 0
+db_subscr_lu_str(dbc, id0, "222", false) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -538,7 +538,7 @@ struct hlr_subscriber {
.sgsn_number = '111',
}
-db_subscr_lu(dbc, id0, NULL, true) --> 0
+db_subscr_lu_str(dbc, id0, NULL, true) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -548,7 +548,7 @@ struct hlr_subscriber {
.vlr_number = '222',
}
-db_subscr_lu(dbc, id0, NULL, false) --> 0
+db_subscr_lu_str(dbc, id0, NULL, false) --> 0
db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
struct hlr_subscriber {
@@ -560,10 +560,10 @@ struct hlr_subscriber {
--- Record LU for non-existent ID
-db_subscr_lu(dbc, 99999, "5952", true) --> -ENOENT
+db_subscr_lu_str(dbc, 99999, "5952", true) --> -ENOENT
DAUC Cannot update SGSN number for subscriber ID=99999: no such subscriber
-db_subscr_lu(dbc, 99999, "712", false) --> -ENOENT
+db_subscr_lu_str(dbc, 99999, "712", false) --> -ENOENT
DAUC Cannot update VLR number for subscriber ID=99999: no such subscriber
db_subscr_get_by_id(dbc, 99999, &g_subscr) --> -ENOENT
diff --git a/tests/gsup/Makefile.am b/tests/gsup/Makefile.am
new file mode 100644
index 0000000..5dbb180
--- /dev/null
+++ b/tests/gsup/Makefile.am
@@ -0,0 +1,36 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ $(NULL)
+
+AM_CFLAGS = \
+ -I$(top_srcdir)/include \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(NULL)
+
+AM_LDFLAGS = \
+ -no-install \
+ $(NULL)
+
+EXTRA_DIST = \
+ gsup_test.ok \
+ gsup_test.err \
+ $(NULL)
+
+noinst_PROGRAMS = \
+ gsup_test \
+ $(NULL)
+
+gsup_test_SOURCES = \
+ gsup_test.c \
+ $(NULL)
+
+gsup_test_LDADD = \
+ $(top_builddir)/src/gsupclient/libosmo-gsup-client.la \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(NULL)
+
+.PHONY: update_exp
+update_exp:
+ $(builddir)/gsup_test >"$(srcdir)/gsup_test.ok" 2>"$(srcdir)/gsup_test.err"
diff --git a/tests/gsup/gsup_test.c b/tests/gsup/gsup_test.c
new file mode 100644
index 0000000..1a7bb76
--- /dev/null
+++ b/tests/gsup/gsup_test.c
@@ -0,0 +1,113 @@
+/* (C) 2018 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 <string.h>
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/application.h>
+#include <osmocom/gsupclient/gsup_req.h>
+
+void *ctx = NULL;
+
+static void test_gsup_make_response(void)
+{
+ char *source_name = "incoming-source-name";
+ char *destination_name = "preset-destination-name";
+ uint8_t sm_rp_mr = 23;
+ uint8_t other_sm_rp_mr = 17;
+ struct osmo_gsup_message rx = {
+ .message_type = OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST,
+ .imsi = "1234567",
+ .message_class = OSMO_GSUP_MESSAGE_CLASS_SUBSCRIBER_MANAGEMENT,
+ .source_name = (uint8_t*)source_name,
+ .source_name_len = strlen(source_name) + 1,
+ .sm_rp_mr = &sm_rp_mr,
+ .session_id = 42,
+ .session_state = OSMO_GSUP_SESSION_STATE_BEGIN,
+ };
+ struct osmo_gsup_message nonempty = {
+ .message_type = OSMO_GSUP_MSGT_ROUTING_ERROR,
+ .imsi = "987654321",
+ .message_class = OSMO_GSUP_MESSAGE_CLASS_INTER_MSC,
+ .destination_name = (uint8_t*)destination_name,
+ .destination_name_len = strlen(destination_name) + 1,
+ .sm_rp_mr = &other_sm_rp_mr,
+ .session_id = 11,
+ .session_state = OSMO_GSUP_SESSION_STATE_END,
+ };
+ void *name_ctx = talloc_named_const(ctx, 0, __func__);
+ int error;
+ int final;
+ char *nonempty_str;
+ int rc;
+
+ printf("\n%s()\n", __func__);
+ printf("rx = %s\n", osmo_gsup_message_to_str_c(name_ctx, &rx));
+
+ printf("\nwriting to an empty struct osmo_gsup_message should populate values as needed:\n");
+ for (error = 0; error <= 1; error++) {
+ for (final = 0; final <= 1; final++) {
+ struct osmo_gsup_message target = {};
+ printf("- args (error=%d, final=%d)\n", error, final);
+ rc = osmo_gsup_make_response(&target, &rx, error, final);
+ printf(" %s\n", osmo_gsup_message_to_str_c(name_ctx, &target));
+ printf(" rc = %d\n", rc);
+ }
+ }
+
+ printf("\nwriting to an already populated struct osmo_gsup_message, should have no effect:\n");
+ nonempty_str = osmo_gsup_message_to_str_c(name_ctx, &nonempty);
+ for (error = 0; error <= 1; error++) {
+ for (final = 0; final <= 1; final++) {
+ struct osmo_gsup_message target = nonempty;
+ char *result;
+ printf("- args (error=%d, final=%d)\n", error, final);
+ rc = osmo_gsup_make_response(&target, &rx, error, final);
+ result = osmo_gsup_message_to_str_c(name_ctx, &target);
+ printf(" %s\n", result);
+ if (strcmp(result, nonempty_str))
+ printf(" ERROR: expected: %s\n", nonempty_str);
+ printf(" rc = %d\n", rc);
+ }
+ }
+}
+
+const struct log_info_cat default_categories[] = {
+};
+
+static struct log_info info = {
+ .cat = default_categories,
+ .num_cat = ARRAY_SIZE(default_categories),
+};
+
+int main(int argc, char **argv)
+{
+ ctx = talloc_named_const(NULL, 0, "gsup_test");
+ osmo_init_logging2(ctx, &info);
+ log_set_print_filename(osmo_stderr_target, 0);
+ log_set_print_timestamp(osmo_stderr_target, 0);
+ log_set_use_color(osmo_stderr_target, 0);
+ log_set_print_category(osmo_stderr_target, 1);
+
+ test_gsup_make_response();
+
+ printf("Done.\n");
+ return EXIT_SUCCESS;
+}
diff --git a/tests/gsup/gsup_test.err b/tests/gsup/gsup_test.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/gsup/gsup_test.err
diff --git a/tests/gsup/gsup_test.ok b/tests/gsup/gsup_test.ok
new file mode 100644
index 0000000..0899633
--- /dev/null
+++ b/tests/gsup/gsup_test.ok
@@ -0,0 +1,32 @@
+
+test_gsup_make_response()
+rx = Subscriber-Management OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST: imsi="1234567" source_name="incoming-source-name\0" session_id=42 session_state=BEGIN sm_rp_mr=23
+
+writing to an empty struct osmo_gsup_message should populate values as needed:
+- args (error=0, final=0)
+ Subscriber-Management OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT: imsi="1234567" destination_name="incoming-source-name\0" session_id=42 session_state=CONTINUE sm_rp_mr=23
+ rc = 0
+- args (error=0, final=1)
+ Subscriber-Management OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT: imsi="1234567" destination_name="incoming-source-name\0" session_id=42 session_state=END sm_rp_mr=23
+ rc = 0
+- args (error=1, final=0)
+ Subscriber-Management OSMO_GSUP_MSGT_UPDATE_LOCATION_ERROR: imsi="1234567" destination_name="incoming-source-name\0" session_id=42 session_state=CONTINUE sm_rp_mr=23
+ rc = 0
+- args (error=1, final=1)
+ Subscriber-Management OSMO_GSUP_MSGT_UPDATE_LOCATION_ERROR: imsi="1234567" destination_name="incoming-source-name\0" session_id=42 session_state=END sm_rp_mr=23
+ rc = 0
+
+writing to an already populated struct osmo_gsup_message, should have no effect:
+- args (error=0, final=0)
+ Inter-MSC OSMO_GSUP_MSGT_ROUTING_ERROR: imsi="987654321" destination_name="preset-destination-name\0" session_id=11 session_state=END sm_rp_mr=17
+ rc = 15
+- args (error=0, final=1)
+ Inter-MSC OSMO_GSUP_MSGT_ROUTING_ERROR: imsi="987654321" destination_name="preset-destination-name\0" session_id=11 session_state=END sm_rp_mr=17
+ rc = 15
+- args (error=1, final=0)
+ Inter-MSC OSMO_GSUP_MSGT_ROUTING_ERROR: imsi="987654321" destination_name="preset-destination-name\0" session_id=11 session_state=END sm_rp_mr=17
+ rc = 15
+- args (error=1, final=1)
+ Inter-MSC OSMO_GSUP_MSGT_ROUTING_ERROR: imsi="987654321" destination_name="preset-destination-name\0" session_id=11 session_state=END sm_rp_mr=17
+ rc = 15
+Done.
diff --git a/tests/gsup_server/Makefile.am b/tests/gsup_server/Makefile.am
index e64ac4a..48fda88 100644
--- a/tests/gsup_server/Makefile.am
+++ b/tests/gsup_server/Makefile.am
@@ -31,6 +31,8 @@ gsup_server_test_SOURCES = \
gsup_server_test_LDADD = \
$(top_srcdir)/src/gsup_server.c \
$(top_srcdir)/src/gsup_router.c \
+ $(top_srcdir)/src/gsupclient/ipa_name.c \
+ $(top_srcdir)/src/gsupclient/gsup_req.c \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOGSM_LIBS) \
$(LIBOSMOABIS_LIBS) \
diff --git a/tests/test_nodes.vty b/tests/test_nodes.vty
index a752c93..3f31b0d 100644
--- a/tests/test_nodes.vty
+++ b/tests/test_nodes.vty
@@ -148,6 +148,8 @@ log stderr
logging level db notice
logging level auc notice
logging level ss info
+ logging level mslookup notice
+ logging level lu notice
...
hlr
store-imei
diff --git a/tests/testsuite.at b/tests/testsuite.at
index d30b5e9..956ef87 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -15,6 +15,13 @@ cat $abs_srcdir/auc/auc_ts_55_205_test_sets.err > experr
AT_CHECK([$abs_top_builddir/tests/auc/auc_ts_55_205_test_sets], [], [expout], [experr])
AT_CLEANUP
+AT_SETUP([gsup])
+AT_KEYWORDS([gsup])
+cat $abs_srcdir/gsup/gsup_test.ok > expout
+cat $abs_srcdir/gsup/gsup_test.err > experr
+AT_CHECK([$abs_top_builddir/tests/gsup/gsup_test], [], [expout], [experr])
+AT_CLEANUP
+
AT_SETUP([gsup_server])
AT_KEYWORDS([gsup_server])
cat $abs_srcdir/gsup_server/gsup_server_test.ok > expout