aboutsummaryrefslogtreecommitdiffstats
path: root/src/libmsc/e_link.c
blob: 0a2be795c51cc5f66b7b8601f39dbaa98daab6eb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
/* E-interface messaging over a GSUP connection */
/*
 * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
 * All Rights Reserved
 *
 * SPDX-License-Identifier: AGPL-3.0+
 *
 * Author: Neels Hofmeyr
 *
 * 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/fsm.h>
#include <osmocom/gsupclient/gsup_client.h>

#include <osmocom/msc/gsm_data.h>
#include <osmocom/msc/gsup_client_mux.h>
#include <osmocom/msc/e_link.h>
#include <osmocom/msc/msub.h>
#include <osmocom/msc/msc_roles.h>
#include <osmocom/msc/vlr.h>
#include <osmocom/msc/ran_infra.h>
#include <osmocom/msc/msc_a.h>
#include <osmocom/msc/msc_a_remote.h>
#include <osmocom/msc/msc_i.h>
#include <osmocom/msc/msc_i_remote.h>
#include <osmocom/msc/msc_t.h>
#include <osmocom/msc/msc_t_remote.h>

#define LOG_E_LINK(e_link, level, fmt, args...) \
	LOGPFSML(e_link->msc_role, level, fmt, ##args)

#define LOG_E_LINK_CAT(e_link, ss, level, fmt, args...) \
	LOGPFSMSL(e_link->msc_role, ss, level, fmt, ##args)

void e_link_assign(struct e_link *e, struct osmo_fsm_inst *msc_role)
{
	struct msc_role_common *c;
	if (e->msc_role) {
		c = e->msc_role->priv;
		if (c->remote_to == e) {
			c->remote_to = NULL;
			msub_update_id(c->msub);
		}
	}

	c = msc_role->priv;
	e->msc_role = msc_role;
	c->remote_to = e;

	msub_update_id(c->msub);
	LOG_E_LINK(e, LOGL_DEBUG, "Assigned E-link to %s\n", e_link_name(e));
}

struct e_link *e_link_alloc(struct gsup_client_mux *gcm, struct osmo_fsm_inst *msc_role,
			    const uint8_t *remote_name, size_t remote_name_len)
{
	struct e_link *e;
	struct msc_role_common *c = msc_role->priv;
	size_t use_len;

	/* use msub as talloc parent, so we can move an e_link from msc_t to msc_i when it is established. */
	e = talloc_zero(c->msub, struct e_link);
	if (!e)
		return NULL;

	e->gcm = gcm;

	/* FIXME: this is a braindamaged duality of char* and blob, which we can't seem to get rid of easily.
	 * See also osmo-hlr change I01a45900e14d41bcd338f50ad85d9fabf2c61405 which resolved this on the
	 * osmo-hlr side, but was abandoned. Not sure which way is the right solution. */
	/* To be able to include a terminating NUL character when sending the IPA name, append one if there is none yet.
	 * Current osmo-hlr needs the terminating NUL to be included. */
	use_len = remote_name_len;
	if (remote_name[use_len-1] != '\0')
		use_len++;
	e->remote_name = talloc_size(e, use_len);
	OSMO_ASSERT(e->remote_name);
	memcpy(e->remote_name, remote_name, remote_name_len);
	e->remote_name[use_len-1] = '\0';
	e->remote_name_len = use_len;

	e_link_assign(e, msc_role);
	return e;
}

void e_link_free(struct e_link *e)
{
	if (!e)
		return;
	if (e->msc_role) {
		struct msc_role_common *c = e->msc_role->priv;
		if (c->remote_to == e)
			c->remote_to = NULL;
	}
	talloc_free(e);
}

/* Set up IMSI, source and destination names in given gsup_msg struct. */
int e_prep_gsup_msg(struct e_link *e, struct osmo_gsup_message *gsup_msg)
{
	struct msc_role_common *c;
	struct vlr_subscr *vsub;
	const char *local_msc_name = NULL;

	if (e->gcm && e->gcm->gsup_client && e->gcm->gsup_client->ipa_dev) {
		local_msc_name = e->gcm->gsup_client->ipa_dev->serno;
		if (!local_msc_name)
			local_msc_name = e->gcm->gsup_client->ipa_dev->unit_name;
	}

	if (!local_msc_name) {
		LOG_E_LINK(e, LOGL_ERROR, "Cannot prep E-interface GSUP message: no local MSC name defined\n");
		return -ENODEV;
	}

	c = e->msc_role->priv;
	vsub = c->msub->vsub;
	*gsup_msg = (struct osmo_gsup_message){
		.message_class = OSMO_GSUP_MESSAGE_CLASS_INTER_MSC,
		.source_name = (const uint8_t*)local_msc_name,
		.source_name_len = strlen(local_msc_name)+1, /* include terminating nul */
		.destination_name = (const uint8_t*)e->remote_name,
		.destination_name_len = e->remote_name_len, /* the nul here is also included, from e_link_alloc() */
	};

	if (vsub)
		OSMO_STRLCPY_ARRAY(gsup_msg->imsi, vsub->imsi);
	return 0;
}

int e_tx(struct e_link *e, const struct osmo_gsup_message *gsup_msg)
{
	LOG_E_LINK_CAT(e, DLGSUP, LOGL_DEBUG, "Tx GSUP %s to %s\n",
		       osmo_gsup_message_type_name(gsup_msg->message_type),
		       e_link_name(e));
	return gsup_client_mux_tx(e->gcm, gsup_msg);
}

const char *e_link_name(struct e_link *e)
{
       return osmo_escape_str((const char*)e->remote_name, e->remote_name_len);
}

static struct msub *msc_new_msc_t_for_handover_request(struct gsm_network *net,
						       const struct osmo_gsup_message *gsup_msg)
{
	struct ran_infra *ran;
	struct msub *msub;
	struct msc_a *msc_a;
	struct vlr_subscr *vsub;

	switch (gsup_msg->an_apdu.access_network_proto) {
	case OSMO_GSUP_ACCESS_NETWORK_PROTOCOL_TS3G_48006:
		ran = &msc_ran_infra[OSMO_RAT_GERAN_A];
		break;
	case OSMO_GSUP_ACCESS_NETWORK_PROTOCOL_TS3G_25413:
		ran = &msc_ran_infra[OSMO_RAT_UTRAN_IU];
		break;
	default:
		ran = NULL;
		break;
	}

	if (!ran || !ran->ran_dec_l2) {
		LOGP(DLGSUP, LOGL_ERROR, "Cannot handle AN-proto %s\n",
		     an_proto_name(gsup_msg->an_apdu.access_network_proto));
		return NULL;
	}

	msub = msub_alloc(net);

	/* To properly compose GSUP messages going back to the remote peer, make sure the incoming IMSI is set in a
	 * vlr_subscr associated with the msub. */
	vsub = vlr_subscr_find_or_create_by_imsi(net->vlr, gsup_msg->imsi, __func__, NULL);
	msub_set_vsub(msub, vsub);
	vlr_subscr_put(vsub, __func__);

	LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "New subscriber for incoming inter-MSC Handover Request\n");

	msc_a = msc_a_remote_alloc(msub, ran, gsup_msg->source_name, gsup_msg->source_name_len);
	if (!msc_a) {
		osmo_fsm_inst_term(msub->fi, OSMO_FSM_TERM_REQUEST, NULL);
		return NULL;
	}

	LOG_MSC_A_REMOTE_CAT(msc_a, DLGSUP, LOGL_DEBUG, "New subscriber for incoming inter-MSC Handover Request\n");
	return msub;
}

static bool name_matches(const uint8_t *name, size_t len, const uint8_t *match_name, size_t match_len)
{
	if (!match_name)
		return !name || !len;
	if (len != match_len)
		return false;
	return memcmp(name, match_name, len) == 0;
}

static bool e_link_matches_gsup_msg_source_name(const struct e_link *e, const struct osmo_gsup_message *gsup_msg)
{
	return name_matches(gsup_msg->source_name, gsup_msg->source_name_len, e->remote_name, e->remote_name_len);
}

static int msc_a_i_t_gsup_rx(struct gsup_client_mux *gcm, void *data, const struct osmo_gsup_message *gsup_msg)
{
	struct gsm_network *net = data;
	struct vlr_instance *vlr = net->vlr;
	struct vlr_subscr *vsub;
	struct msub *msub;
	struct osmo_fsm_inst *msc_role = NULL;
	struct e_link *e;
	struct msc_role_common *c;
	int i;

	OSMO_ASSERT(net);

	vsub = vlr_subscr_find_by_imsi(vlr, gsup_msg->imsi, __func__);
	if (vsub)
		LOGP(DLGSUP, LOGL_DEBUG, "Found VLR entry for IMSI %s\n", gsup_msg->imsi);

	msub = msub_for_vsub(vsub);
	if (msub)
		LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "Found already attached subscriber for IMSI %s\n",
			     gsup_msg->imsi);

	if (vsub) {
		vlr_subscr_put(vsub, __func__);
		vsub = NULL;
	}

	/* Only for an incoming Handover Request: create a new remote-MSC-A as proxy for the MSC-A that is sending the
	 * Handover Request */
	if (!msub && gsup_msg->message_type == OSMO_GSUP_MSGT_E_PREPARE_HANDOVER_REQUEST) {
		msub = msc_new_msc_t_for_handover_request(net, gsup_msg);
	}

	if (!msub) {
		LOGP(DLGSUP, LOGL_ERROR, "%s: Cannot find subscriber for IMSI %s\n",
		     __func__, osmo_quote_str(gsup_msg->imsi, -1));
		return -EINVAL;
	}

	LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "Rx GSUP %s\n", osmo_gsup_message_type_name(gsup_msg->message_type));

	e = NULL;
	for (i = 0; i < ARRAY_SIZE(msub->role); i++) {
		msc_role = msub->role[i];
		if (!msc_role) {
			LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "No %s\n", msc_role_name(i));
			continue;
		}
		c = msc_role->priv;
		if (!c->remote_to) {
			LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "%s has no remote\n", msc_role_name(i));
			continue;
		}
		if (!e_link_matches_gsup_msg_source_name(c->remote_to, gsup_msg)) {
			LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG, "%s has remote to mismatching %s\n", msc_role_name(i),
				     c->remote_to->remote_name);
			continue;
		}
		/* Found a match. */
		e = c->remote_to;
		break;
	}

	if (!e) {
		LOG_MSUB_CAT(msub, DLGSUP, LOGL_ERROR,
			     "There is no E link that matches: Rx GSUP %s from %s\n",
			     osmo_gsup_message_type_name(gsup_msg->message_type),
			     osmo_quote_str((const char*)gsup_msg->source_name, gsup_msg->source_name_len));
		return -EINVAL;
	}

	LOG_MSUB_CAT(msub, DLGSUP, LOGL_DEBUG,
		     "Rx GSUP %s from %s %s\n",
		     osmo_gsup_message_type_name(gsup_msg->message_type),
		     msc_role_name(c->role),
		     e_link_name(e));

	return osmo_fsm_inst_dispatch(msc_role, MSC_REMOTE_EV_RX_GSUP, (void*)gsup_msg);
}

void msc_a_i_t_gsup_init(struct gsm_network *net)
{
	OSMO_ASSERT(net->gcm);
	OSMO_ASSERT(net->vlr);

	net->gcm->rx_cb[OSMO_GSUP_MESSAGE_CLASS_INTER_MSC] = (struct gsup_client_mux_rx_cb){
		.func = msc_a_i_t_gsup_rx,
		.data = net,
	};
}

int gsup_msg_assign_an_apdu(struct osmo_gsup_message *gsup_msg, struct an_apdu *an_apdu)
{
	if (!an_apdu) {
		LOGP(DLGSUP, LOGL_ERROR, "Cannot assign NULL AN-APDU\n");
		return -EINVAL;
	}

	gsup_msg->an_apdu = (struct osmo_gsup_an_apdu){
		.access_network_proto = an_apdu->an_proto,
	};

	if (an_apdu->msg) {
		gsup_msg->an_apdu.data = msgb_l2(an_apdu->msg);
		gsup_msg->an_apdu.data_len = msgb_l2len(an_apdu->msg);
		if (!gsup_msg->an_apdu.data || !gsup_msg->an_apdu.data_len) {
			LOGP(DLGSUP, LOGL_ERROR, "Cannot assign AN-APDU without msg->l2 to GSUP message: %s\n",
			     msgb_hexdump(an_apdu->msg));
			return -EINVAL;
		}
	}

	/* We are composing a struct osmo_gsup_msg from the osmo-msc internal struct an_apdu. The an_apdu may contain
	 * additional info in form of a partly filled an_apdu->e_info. Make sure that data ends up in the resulting full
	 * osmo_gsup_message. */
	if (an_apdu->e_info) {
		const struct osmo_gsup_message *s = an_apdu->e_info;

		gsup_msg->msisdn_enc = s->msisdn_enc;
		gsup_msg->msisdn_enc_len = s->msisdn_enc_len;

		if (s->cause_rr_set) {
			gsup_msg->cause_rr = s->cause_rr;
			gsup_msg->cause_rr_set = true;
		}
		if (s->cause_bssap_set) {
			gsup_msg->cause_bssap = s->cause_bssap;
			gsup_msg->cause_bssap_set = true;
		}
		if (s->cause_sm)
			gsup_msg->cause_sm = s->cause_sm;
	}
	return 0;
}

/* Allocate a new msgb to contain the gsup_msg->an_apdu's data as l2h.
 * The msgb will have sufficient headroom to be passed down a RAN peer's SCCP user SAP. */
struct msgb *gsup_msg_to_msgb(const struct osmo_gsup_message *gsup_msg)
{
	struct msgb *pdu;
	const uint8_t *pdu_data = gsup_msg->an_apdu.data;
	uint8_t pdu_len = gsup_msg->an_apdu.data_len;

	if (!pdu_data || !pdu_len)
		return NULL;

	/* Strictly speaking this is not limited to BSSMAP, but why not just use those sizes. */
	pdu = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "AN-APDU from gsup_msg");

	pdu->l2h = msgb_put(pdu, pdu_len);
	memcpy(pdu->l2h, pdu_data, pdu_len);
	return pdu;
}

/* Compose a struct an_apdu from the data found in gsup_msg.  gsup_msg_to_msgb() is used to wrap the data in a static
 * msgb, so the returned an_apdu->msg must be freed if not NULL. */
void gsup_msg_to_an_apdu(struct an_apdu *an_apdu, const struct osmo_gsup_message *gsup_msg)
{
	*an_apdu = (struct an_apdu){
		.an_proto = gsup_msg->an_apdu.access_network_proto,
		.msg = gsup_msg_to_msgb(gsup_msg),
		.e_info = gsup_msg,
	};
}