aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bsc/osmo_bsc_sccp.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/osmo-bsc/osmo_bsc_sccp.c')
-rw-r--r--src/osmo-bsc/osmo_bsc_sccp.c288
1 files changed, 288 insertions, 0 deletions
diff --git a/src/osmo-bsc/osmo_bsc_sccp.c b/src/osmo-bsc/osmo_bsc_sccp.c
new file mode 100644
index 000000000..1abb47394
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_sccp.c
@@ -0,0 +1,288 @@
+/* Interaction with the SCCP subsystem */
+/*
+ * (C) 2009-2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2010 by On-Waves
+ * 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 <openbsc/gsm_data.h>
+#include <openbsc/osmo_bsc.h>
+#include <openbsc/osmo_bsc_grace.h>
+#include <openbsc/osmo_msc_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/signal.h>
+
+#include <osmocore/gsm0808.h>
+#include <osmocore/talloc.h>
+#include <osmocore/protocol/gsm_08_08.h>
+
+#include <osmocom/sccp/sccp.h>
+
+/* SCCP helper */
+#define SCCP_IT_TIMER 60
+
+static LLIST_HEAD(active_connections);
+
+static void free_queued(struct osmo_bsc_sccp_con *conn)
+{
+ struct msgb *msg;
+
+ while (!llist_empty(&conn->sccp_queue)) {
+ /* this is not allowed to fail */
+ msg = msgb_dequeue(&conn->sccp_queue);
+ msgb_free(msg);
+ }
+
+ conn->sccp_queue_size = 0;
+}
+
+static void send_queued(struct osmo_bsc_sccp_con *conn)
+{
+ struct msgb *msg;
+
+ while (!llist_empty(&conn->sccp_queue)) {
+ /* this is not allowed to fail */
+ msg = msgb_dequeue(&conn->sccp_queue);
+ sccp_connection_write(conn->sccp, msg);
+ msgb_free(msg);
+ conn->sccp_queue_size -= 1;
+ }
+}
+
+static void msc_outgoing_sccp_data(struct sccp_connection *conn,
+ struct msgb *msg, unsigned int len)
+{
+ struct osmo_bsc_sccp_con *bsc_con =
+ (struct osmo_bsc_sccp_con *) conn->data_ctx;
+
+ bsc_handle_dt1(bsc_con, msg, len);
+}
+
+static void msc_outgoing_sccp_state(struct sccp_connection *conn, int old_state)
+{
+ struct osmo_bsc_sccp_con *con_data;
+
+ if (conn->connection_state >= SCCP_CONNECTION_STATE_RELEASE_COMPLETE) {
+ con_data = (struct osmo_bsc_sccp_con *) conn->data_ctx;
+ if(con_data->conn) {
+ LOGP(DMSC, LOGL_ERROR,
+ "ERROR: The lchan is still associated\n.");
+ gsm0808_clear(con_data->conn);
+ subscr_con_free(con_data->conn);
+ con_data->conn = NULL;
+ }
+
+ con_data->sccp = NULL;
+ free_queued(con_data);
+ sccp_connection_free(conn);
+ bsc_delete_connection(con_data);
+ } else if (conn->connection_state == SCCP_CONNECTION_STATE_ESTABLISHED) {
+ LOGP(DMSC, LOGL_DEBUG, "Connection established: %p\n", conn);
+ con_data = (struct osmo_bsc_sccp_con *) conn->data_ctx;
+
+ bsc_del_timer(&con_data->sccp_cc_timeout);
+ bsc_schedule_timer(&con_data->sccp_it_timeout, SCCP_IT_TIMER, 0);
+
+ send_queued(con_data);
+ }
+}
+
+static void bsc_sccp_force_free(struct osmo_bsc_sccp_con *data)
+{
+ if (data->conn) {
+ gsm0808_clear(data->conn);
+ subscr_con_free(data->conn);
+ data->conn = NULL;
+ }
+
+ free_queued(data);
+ sccp_connection_force_free(data->sccp);
+ data->sccp = NULL;
+ bsc_delete_connection(data);
+}
+
+static void sccp_it_timeout(void *_data)
+{
+ struct osmo_bsc_sccp_con *data =
+ (struct osmo_bsc_sccp_con *) _data;
+
+ sccp_connection_send_it(data->sccp);
+ bsc_schedule_timer(&data->sccp_it_timeout, SCCP_IT_TIMER, 0);
+}
+
+static void sccp_cc_timeout(void *_data)
+{
+ struct osmo_bsc_sccp_con *data =
+ (struct osmo_bsc_sccp_con *) _data;
+
+ if (data->sccp->connection_state >= SCCP_CONNECTION_STATE_ESTABLISHED)
+ return;
+
+ LOGP(DMSC, LOGL_ERROR, "The connection was never established.\n");
+ bsc_sccp_force_free(data);
+}
+
+static void msc_sccp_write_ipa(struct sccp_connection *conn, struct msgb *msg, void *data)
+{
+ struct gsm_network *net = (struct gsm_network *) data;
+ msc_queue_write(net->msc_data->msc_con, msg, IPAC_PROTO_SCCP);
+}
+
+static int msc_sccp_accept(struct sccp_connection *connection, void *data)
+{
+ LOGP(DMSC, LOGL_DEBUG, "Rejecting incoming SCCP connection.\n");
+ return -1;
+}
+
+static int msc_sccp_read(struct msgb *msgb, unsigned int length, void *data)
+{
+ struct gsm_network *net = (struct gsm_network *) data;
+ return bsc_handle_udt(net, net->msc_data->msc_con, msgb, length);
+}
+
+int bsc_queue_for_msc(struct osmo_bsc_sccp_con *conn, struct msgb *msg)
+{
+ struct sccp_connection *sccp = conn->sccp;
+
+ if (sccp->connection_state > SCCP_CONNECTION_STATE_ESTABLISHED) {
+ LOGP(DMSC, LOGL_ERROR, "Connection closing, dropping packet on: %p\n", sccp);
+ msgb_free(msg);
+ } else if (sccp->connection_state == SCCP_CONNECTION_STATE_ESTABLISHED
+ && conn->sccp_queue_size == 0) {
+ sccp_connection_write(sccp, msg);
+ msgb_free(msg);
+ } else if (conn->sccp_queue_size > 10) {
+ LOGP(DMSC, LOGL_ERROR, "Connection closing, dropping packet on: %p\n", sccp);
+ msgb_free(msg);
+ } else {
+ LOGP(DMSC, LOGL_DEBUG, "Queueing packet on %p. Queue size: %d\n", sccp, conn->sccp_queue_size);
+ conn->sccp_queue_size += 1;
+ msgb_enqueue(&conn->sccp_queue, msg);
+ }
+
+ return 0;
+}
+
+int bsc_create_new_connection(struct gsm_subscriber_connection *conn)
+{
+ struct gsm_network *net;
+ struct osmo_bsc_sccp_con *bsc_con;
+ struct sccp_connection *sccp;
+
+ net = conn->bts->network;
+ if (!net->msc_data->msc_con->is_authenticated) {
+ LOGP(DMSC, LOGL_ERROR, "Not connected to a MSC. Not forwarding data.\n");
+ return -1;
+ }
+
+ if (!bsc_grace_allow_new_connection(net)) {
+ LOGP(DMSC, LOGL_NOTICE, "BSC in grace period. No new connections.\n");
+ return -1;
+ }
+
+ sccp = sccp_connection_socket();
+ if (!sccp) {
+ LOGP(DMSC, LOGL_ERROR, "Failed to allocate memory.\n");
+ return -ENOMEM;
+ }
+
+ bsc_con = talloc_zero(conn->bts, struct osmo_bsc_sccp_con);
+ if (!bsc_con) {
+ LOGP(DMSC, LOGL_ERROR, "Failed to allocate.\n");
+ sccp_connection_free(sccp);
+ return -1;
+ }
+
+ /* callbacks */
+ sccp->state_cb = msc_outgoing_sccp_state;
+ sccp->data_cb = msc_outgoing_sccp_data;
+ sccp->data_ctx = bsc_con;
+
+ /* prepare the timers */
+ bsc_con->sccp_it_timeout.cb = sccp_it_timeout;
+ bsc_con->sccp_it_timeout.data = bsc_con;
+ bsc_con->sccp_cc_timeout.cb = sccp_cc_timeout;
+ bsc_con->sccp_cc_timeout.data = bsc_con;
+
+ INIT_LLIST_HEAD(&bsc_con->sccp_queue);
+
+ bsc_con->sccp = sccp;
+ bsc_con->msc_con = net->msc_data->msc_con;
+ bsc_con->conn = conn;
+ llist_add(&bsc_con->entry, &active_connections);
+ conn->sccp_con = bsc_con;
+ return 0;
+}
+
+int bsc_open_connection(struct osmo_bsc_sccp_con *conn, struct msgb *msg)
+{
+ bsc_schedule_timer(&conn->sccp_cc_timeout, 10, 0);
+ sccp_connection_connect(conn->sccp, &sccp_ssn_bssap, msg);
+ msgb_free(msg);
+ return 0;
+}
+
+int bsc_delete_connection(struct osmo_bsc_sccp_con *sccp)
+{
+ if (!sccp)
+ return 0;
+
+ if (sccp->conn)
+ LOGP(DMSC, LOGL_ERROR, "Should have been cleared.\n");
+
+ llist_del(&sccp->entry);
+ bsc_del_timer(&sccp->sccp_it_timeout);
+ bsc_del_timer(&sccp->sccp_cc_timeout);
+ talloc_free(sccp);
+ return 0;
+}
+
+static void bsc_close_connections(struct bsc_msc_connection *msc_con)
+{
+ struct osmo_bsc_sccp_con *con, *tmp;
+
+ llist_for_each_entry_safe(con, tmp, &active_connections, entry)
+ bsc_sccp_force_free(con);
+}
+
+static int handle_msc_signal(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct osmo_msc_data *data;
+
+ if (subsys != SS_MSC)
+ return 0;
+
+ data = (struct osmo_msc_data *) signal_data;
+ if (signal == S_MSC_LOST)
+ bsc_close_connections(data->msc_con);
+
+ return 0;
+}
+
+int osmo_bsc_sccp_init(struct gsm_network *gsmnet)
+{
+ sccp_set_log_area(DSCCP);
+ sccp_system_init(msc_sccp_write_ipa, gsmnet);
+ sccp_connection_set_incoming(&sccp_ssn_bssap, msc_sccp_accept, NULL);
+ sccp_set_read(&sccp_ssn_bssap, msc_sccp_read, gsmnet);
+
+ register_signal_handler(SS_MSC, handle_msc_signal, gsmnet);
+
+ return 0;
+}