aboutsummaryrefslogtreecommitdiffstats
path: root/src/libvlr/vlr_sgs.c
blob: 538c9f28f47e35aa7ebd32415e61291c76b8b757 (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
/* (C) 2018-2019 by sysmocom s.f.m.c. GmbH
 * All Rights Reserved
 *
 * Author: Harald Welte, Philipp Maier
 *
 * 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/fsm.h>
#include <osmocom/msc/debug.h>
#include <osmocom/msc/vlr.h>
#include <osmocom/msc/vlr_sgs.h>
#include "vlr_sgs_fsm.h"

const struct value_string sgs_state_timer_names[] = {
	{SGS_STATE_TS5, "Ts5"},
	{SGS_STATE_TS6_2, "Ts6-2"},
	{SGS_STATE_TS7, "Ts7"},
	{SGS_STATE_TS11, "Ts11"},
	{SGS_STATE_TS14, "Ts14"},
	{SGS_STATE_TS15, "Ts15"},
	{0, NULL}
};

const struct value_string sgs_state_counter_names[] = {
	{SGS_STATE_NS7, "Ns7"},
	{SGS_STATE_NS11, "Ns11"},
	{0, NULL}
};

/* Reset all SGs-Associations back to zero.
 * \param[in] vlr VLR instace. */
void vlr_sgs_reset(struct vlr_instance *vlr)
{
	struct vlr_subscr *vsub;

	OSMO_ASSERT(vlr);

	LOGP(DVLR, LOGL_INFO, "dropping all SGs associations.\n");

	llist_for_each_entry(vsub, &vlr->subscribers, list) {
		osmo_fsm_inst_dispatch(vsub->sgs_fsm, SGS_UE_E_RX_RESET_FROM_MME, NULL);
	}
}

/*! Perform an SGs location update.
 * \param[in] vlr VLR instace.
 * \param[in] cfg SGs interface configuration parameters.
 * \param[in] response_cb calback function that is called when LU is done.
 * \param[in] paging_cb calback function that is called when LU needs to page.
 * \param[in] mminfo_cb calback function that is called to provide MM info to the UE.
 * \param[in] mme_name fqdn of the requesting MME (mme-name).
 * \param[in] type location update type (normal or IMSI attach).
 * \param[in] imsi mobile identity (IMSI).
 * \param[in] new_lai identifier of the new location area.
 * \returns 0 in case of success, -EINVAL in case of error. */
int vlr_sgs_loc_update(struct vlr_instance *vlr, struct vlr_sgs_cfg *cfg,
		       vlr_sgs_lu_response_cb_t response_cb, vlr_sgs_lu_paging_cb_t paging_cb,
		       vlr_sgs_lu_mminfo_cb_t mminfo_cb, char *mme_name, enum vlr_lu_type type, const char *imsi,
		       struct osmo_location_area_id *new_lai)
{
	struct vlr_subscr *vsub = NULL;

	OSMO_ASSERT(response_cb);
	OSMO_ASSERT(paging_cb);
	OSMO_ASSERT(mminfo_cb);
	OSMO_ASSERT(cfg);
	OSMO_ASSERT(imsi);

	vsub = vlr_subscr_find_or_create_by_imsi(vlr, imsi, NULL);
	if (!vsub) {
		LOGP(DSGS, LOGL_ERROR, "VLR subscriber allocation failed\n");
		return -EINVAL;
	}

	vsub->sgs.cfg = *cfg;
	vsub->sgs.response_cb = response_cb;
	vsub->sgs.paging_cb = paging_cb;
	vsub->sgs.mminfo_cb = mminfo_cb;
	vlr_subscr_set_imsi(vsub, imsi);
	osmo_strlcpy(vsub->sgs.mme_name, mme_name, sizeof(vsub->sgs.mme_name));

	osmo_fsm_inst_dispatch(vsub->sgs_fsm, SGS_UE_E_RX_LU_FROM_MME, NULL);

	/* FIXME: Use the "type" type parameter (for what is it useful?) */

	vsub->sgs.lai = *new_lai;
	vsub->cgi.lai = *new_lai;
	vsub->cs.lac = vsub->sgs.lai.lac;

	return 0;
}

/*! Notify that the SGs Location Update accept message has been sent to MME.
 *  \param[in] vsub VLR subscriber. */
void vlr_sgs_loc_update_acc_sent(struct vlr_subscr *vsub)
{
	osmo_fsm_inst_dispatch(vsub->sgs_fsm, SGS_UE_E_TX_LU_ACCEPT, NULL);

	/* FIXME: At this point we need to check the status of Ts5 and if
	 * it is still running this means the LU has interrupted the paging,
	 * and we need to start paging again. 3GPP TS 29.118,
	 * chapter 5.2.3.2 */
}

/*! Notify that the SGs Location Update reject message has been sent to MME.
 *  \param[in] vsub VLR subscriber. */
void vlr_sgs_loc_update_rej_sent(struct vlr_subscr *vsub)
{
	osmo_fsm_inst_dispatch(vsub->sgs_fsm, SGS_UE_E_TX_LU_REJECT, NULL);
}

/*! Perform an SGs IMSI detach.
 *  \param[in] vsub VLR subscriber.
 *  \param[in] imsi mobile identity (IMSI).
 *  \param[in] type datach type. */
void vlr_sgs_imsi_detach(struct vlr_instance *vlr, const char *imsi, enum sgsap_imsi_det_noneps_type type)
{
	struct vlr_subscr *vsub;
	enum sgs_ue_fsm_event evt;

	vsub = vlr_subscr_find_by_imsi(vlr, imsi);
	if (!vsub)
		return;

	/* See also: 3GPP TS 29.118, 5.6.3 Procedures in the VLR: In case of
	 * an implicit detach, we are supposed to check if the state of the
	 * SGs-association, and only when it is not SGs-NULL, we may proceed. */
	if (vsub->sgs_fsm->state == SGS_UE_ST_NULL && type == SGSAP_ID_NONEPS_T_IMPLICIT_UE_EPS_NONEPS)
		return;

	switch (type) {
	case SGSAP_ID_NONEPS_T_EXPLICIT_UE_NONEPS:
		evt = SGS_UE_E_RX_DETACH_IND_FROM_UE;
		break;
	case SGSAP_ID_NONEPS_T_COMBINED_UE_EPS_NONEPS:
	case SGSAP_ID_NONEPS_T_IMPLICIT_UE_EPS_NONEPS:
		/* FIXME: Is that right? */
		evt = SGS_UE_E_RX_DETACH_IND_FROM_MME;
		break;
	default:
		LOGP(DSGS, LOGL_ERROR, "(sub %s) invalid SGS IMSI detach type, detaching anyway...\n",
		     vlr_subscr_msisdn_or_name(vsub));
		evt = SGS_UE_E_RX_DETACH_IND_FROM_MME;
		break;
	}

	osmo_fsm_inst_dispatch(vsub->sgs_fsm, evt, NULL);
	vlr_subscr_put(vsub);

	/* Detaching from non EPS services essentially means that the
	 * subscriber is detached from 2G. In any case the VLR will
	 * get rid of the subscriber. */
	vlr_subscr_expire(vsub);
}

/*! Perform an SGs EPS detach.
 *  \param[in] vsub VLR subscriber.
 *  \param[in] imsi mobile identity (IMSI).
 *  \param[in] type datach type. */
void vlr_sgs_eps_detach(struct vlr_instance *vlr, const char *imsi, enum sgsap_imsi_det_eps_type type)
{
	struct vlr_subscr *vsub;
	enum sgs_ue_fsm_event evt;
	vsub = vlr_subscr_find_by_imsi(vlr, imsi);
	if (!vsub)
		return;

	switch (type) {
	case SGSAP_ID_EPS_T_NETWORK_INITIATED:
		evt = SGS_UE_E_RX_DETACH_IND_FROM_MME;
		break;
	case SGSAP_ID_EPS_T_UE_INITIATED:
		evt = SGS_UE_E_RX_DETACH_IND_FROM_UE;
		break;
	case SGSAP_ID_EPS_T_EPS_NOT_ALLOWED:
		evt = SGS_UE_E_RX_DETACH_IND_FROM_MME;
		break;
	default:
		LOGP(DSGS, LOGL_ERROR, "(sub %s) invalid SGS IMSI detach type, detaching anyway...\n",
		     vlr_subscr_msisdn_or_name(vsub));
		evt = SGS_UE_E_RX_DETACH_IND_FROM_MME;
		break;
	}

	osmo_fsm_inst_dispatch(vsub->sgs_fsm, evt, NULL);
	vlr_subscr_put(vsub);
}

/*! Perform an SGs TMSI reallocation complete.
 *  \param[in] vsub VLR subscriber.
 *  \param[in] imsi mobile identity (IMSI). */
void vlr_sgs_tmsi_reall_compl(struct vlr_instance *vlr, const char *imsi)
{
	struct vlr_subscr *vsub;
	vsub = vlr_subscr_find_by_imsi(vlr, imsi);
	if (!vsub)
		return;

	osmo_fsm_inst_dispatch(vsub->sgs_fsm, SGS_UE_E_RX_TMSI_REALLOC, NULL);
	vlr_subscr_put(vsub);
}

/*! Notify that an SGs paging has been rejected by the MME.
 *  \param[in] vsub VLR subscriber.
 *  \param[in] imsi mobile identity (IMSI).
 *  \param[in] cause SGs cause code. */
void vlr_sgs_pag_rej(struct vlr_instance *vlr, const char *imsi, enum sgsap_sgs_cause cause)
{
	struct vlr_subscr *vsub;
	vsub = vlr_subscr_find_by_imsi(vlr, imsi);
	if (!vsub)
		return;

	/* On the reception of a paging rej the VLR is supposed to stop Ts5,
	   also  3GPP TS 29.118, chapter 5.1.2.4 */
	osmo_timer_del(&vsub->sgs.Ts5);
	LOGP(DSGS, LOGL_DEBUG, "(sub %s) Paging via SGs interface rejected by MME, %s stopped, cause: %s!\n",
	     vlr_subscr_msisdn_or_name(vsub), vlr_sgs_state_timer_name(SGS_STATE_TS5), sgsap_sgs_cause_name(cause));

	osmo_fsm_inst_dispatch(vsub->sgs_fsm, SGS_UE_E_RX_PAGING_FAILURE, &cause);
	vlr_subscr_put(vsub);

	/* Balance ref count increment from vlr_sgs_pag() */
	vlr_subscr_put(vsub);
}

/*! Notify that an SGs paging has been accepted by the MME.
 *  \param[in] vsub VLR subscriber.
 *  \param[in] imsi mobile identity (IMSI). */
void vlr_sgs_pag_ack(struct vlr_instance *vlr, const char *imsi)
{
	struct vlr_subscr *vsub;
	vsub = vlr_subscr_find_by_imsi(vlr, imsi);
	if (!vsub)
		return;

	/* Stop Ts5 and and consider the paging as successful */
	osmo_timer_del(&vsub->sgs.Ts5);
	vlr_subscr_put(vsub);

	/* Balance ref count increment from vlr_sgs_pag() */
	vlr_subscr_put(vsub);
}

/*! Notify that the UE has been marked as unreachable by the MME.
 *  \param[in] vsub VLR subscriber.
 *  \param[in] imsi mobile identity (IMSI).
 *  \param[in] cause SGs cause code. */
void vlr_sgs_ue_unr(struct vlr_instance *vlr, const char *imsi, enum sgsap_sgs_cause cause)
{
	struct vlr_subscr *vsub;
	vsub = vlr_subscr_find_by_imsi(vlr, imsi);
	if (!vsub)
		return;

	/* On the reception of an UE unreachable the VLR is supposed to stop
	 * Ts5, also 3GPP TS 29.118, chapter 5.1.2.5 */
	osmo_timer_del(&vsub->sgs.Ts5);
	LOGP(DSGS, LOGL_DEBUG,
	     "(sub %s) Paging via SGs interface not possible, UE unreachable, %s stopped, cause: %s\n",
	     vlr_subscr_msisdn_or_name(vsub), vlr_sgs_state_timer_name(SGS_STATE_TS5), sgsap_sgs_cause_name(cause));

	osmo_fsm_inst_dispatch(vsub->sgs_fsm, SGS_UE_E_RX_SGSAP_UE_UNREACHABLE, &cause);
	vlr_subscr_put(vsub);
}

/* Callback function that is called when an SGs paging request times out */
static void Ts5_timeout_cb(void *arg)
{
	struct vlr_subscr *vsub = arg;

	/* 3GPP TS 29.118 does not specify a specif action that has to happen
	 * in case Ts5 times out. The timeout just indicates that the paging
	 * failed. Other actions may check the status of Ts5 to see if a paging
	 * is still ongoing or not. */

	LOGP(DSGS, LOGL_ERROR, "(sub %s) Paging via SGs interface timed out (%s expired)!\n",
	     vlr_subscr_msisdn_or_name(vsub), vlr_sgs_state_timer_name(SGS_STATE_TS5));

	/* Balance ref count increment from vlr_sgs_pag() */
	vlr_subscr_put(vsub);

	return;
}

/*! Notify that a paging message has been sent and a paging is now in progress.
 *  \param[in] vsub VLR subscriber. */
void vlr_sgs_pag(struct vlr_subscr *vsub, enum sgsap_service_ind serv_ind)
{

	/* In cases where we have to respawn a paging after an intermitted LU,
	 * there may e a Ts5 still running. In those cases we have to remove
	 * the old timer first */
	if (osmo_timer_pending(&vsub->sgs.Ts5))
		osmo_timer_del(&vsub->sgs.Ts5);

	/* Note: 3GPP TS 29.118, chapter 4.2.2 mentions paging in the FSM
	 * diagram, but paging never causes a state transition except when
	 * an explicit failure is indicated (MME actively rejects paging).
	 * Apparantly it is also possible that an LU happens while the paging
	 * is still ongoing and Ts5 is running. (chapter 5.1.2.3). This means
	 * that the paging procedure is intended to run in parallel to the
	 * SGs FSM and given that the benaviour around Ts5 must be implemented
	 * also separately, to emphasize this separation Ts5 is implemented
	 * here in and not in vlr_sgs_fsm.c. */
	osmo_timer_setup(&vsub->sgs.Ts5, Ts5_timeout_cb, vsub);
	osmo_timer_schedule(&vsub->sgs.Ts5, vsub->sgs.cfg.timer[SGS_STATE_TS5], 0);

	/* Formally 3GPP TS 29.118 defines the sending of a paging request
	 * as an event, but as far as the VLR is concerned only Ts5 is
	 * started. */
	osmo_fsm_inst_dispatch(vsub->sgs_fsm, SGS_UE_E_TX_PAGING, NULL);

	/* Memorize service type in for the case that the paging must be
	 * respawned after an LU */
	vsub->sgs.paging_serv_ind = serv_ind;

	/* Ensure that the reference count is increased by one while the
	 * paging is happening. We will balance this again in vlr_sgs_pag_rej()
	 * and vlr_sgs_pag_ack(); */
	vlr_subscr_get(vsub);
}

/*! Check if the SGs interface is currently paging
 *  \param[in] vsub VLR subscriber. */
bool vlr_sgs_pag_pend(struct vlr_subscr *vsub)
{
	return osmo_timer_pending(&vsub->sgs.Ts5);
}