[MME] Gn: Introduce initial support for 2G->4G cell reselection

In an Inter-RAT setup a UE could perform a TAU coming from a 2G/3G network.
In that case the UE/MS is unknown to the MME and it should request the
SGSN context (MM, PDP) from the old SGSN. This is done through the following
GTPv1C message exchange on the Gn interface of SGSN and MME:
SGSN <- MME: SGSN Context Request
SGSN -> MME: SGSN Context Response
SGSN <- MME: SGSN Context Acknowledge

Diagram with full set of steps can be found at 3GPP TS 23.401 D.3.6.

This commit doesn't aim to be a complete implementation of the mentioned
procedure, since it's quite a complex one, with lots of fields and logic
required. This so far only implements in general the minimally
successful case by filling as much as possible the required set of
fields.
This will allow for a base onto which do incremental improvements and
fixes while testing against UEs and SGSNs (such as osmo-sgsn, which
doesn't yet support this procedure but will potentially earn it soon).

The reverse direction, aka UE issuing cell reselection 4G->2G was
already implemented (same as here, initial non-complete implementation)
in open5gs-mmed in commit 3d693da73e.

Related: https://osmocom.org/issues/6294
This commit is contained in:
Pau Espin 2024-01-16 11:13:47 +01:00 committed by Sukchan Lee
parent 4088cdf17d
commit 60691b02d2
16 changed files with 641 additions and 19 deletions

View File

@ -121,6 +121,7 @@ typedef struct ogs_gtp_xact_s {
#define OGS_GTP_CREATE_IN_ATTACH_REQUEST 1
#define OGS_GTP_CREATE_IN_UPLINK_NAS_TRANSPORT 2
#define OGS_GTP_CREATE_IN_PATH_SWITCH_REQUEST 3
#define OGS_GTP_CREATE_IN_TRACKING_AREA_UPDATE 4 /* 3GPP TS 33.401 9.1.2 */
int create_action;
#define OGS_GTP_MODIFY_IN_PATH_SWITCH_REQUEST 1

View File

@ -398,6 +398,18 @@ ogs_pkbuf_t *emm_build_security_mode_command(mme_ue_t *mme_ue)
imeisv_request->type = OGS_NAS_IMEISV_TYPE;
imeisv_request->value = OGS_NAS_IMEISV_REQUESTED;
if (mme_ue->nonceue) {
security_mode_command->presencemask |=
OGS_NAS_EPS_SECURITY_MODE_COMMAND_REPLAYED_NONCEUE_PRESENT;
security_mode_command->replayed_nonceue = mme_ue->nonceue;
}
if (mme_ue->noncemme) {
security_mode_command->presencemask |=
OGS_NAS_EPS_SECURITY_MODE_COMMAND_NONCEMME_PRESENT;
security_mode_command->noncemme = mme_ue->noncemme;
}
/*
* TS24.301
* 5.4.3.2 NAS security mode control initiation by the network

View File

@ -549,6 +549,36 @@ int emm_handle_service_request(
return OGS_OK;
}
bool emm_tau_request_ue_comes_from_gb_or_iu(const ogs_nas_eps_tracking_area_update_request_t *tau_request)
{
/* "When the tracking area updating procedure is initiated in EMM-IDLE mode
* to perform an inter-system change from A/Gb mode or Iu mode to S1 mode
* and the TIN is set to "P-TMSI", the UE shall include the GPRS ciphering
* key sequence number applicable for A/Gb mode or Iu mode and a nonce UE in
* the TRACKING AREA UPDATE REQUEST message."
*/
if (!(tau_request->presencemask &
OGS_NAS_EPS_TRACKING_AREA_UPDATE_REQUEST_NONCEUE_PRESENT))
return false;
if (tau_request->presencemask &
OGS_NAS_EPS_TRACKING_AREA_UPDATE_REQUEST_OLD_GUTI_TYPE_PRESENT) {
/* 0 = Native, 1 = Mapped */
return tau_request->old_guti_type.guti_type;
} else {
/* TS 23.003 2.8.2.2.2:
* "The most significant bit of the <LAC> shall be set to zero;
* and the most significant bit of <MME group id> shall be set to
* one. Based on this definition, the most significant bit of the
* <MME group id> can be used to distinguish the node type, i.e.
* whether it is an MME or SGSN */
const ogs_nas_eps_mobile_identity_t *eps_mobile_identity = &tau_request->old_guti;
if (eps_mobile_identity->imsi.type != OGS_NAS_EPS_MOBILE_IDENTITY_GUTI)
return false;
return !(eps_mobile_identity->guti.mme_gid & 0x8000);
}
}
int emm_handle_tau_request(mme_ue_t *mme_ue,
ogs_nas_eps_tracking_area_update_request_t *tau_request, ogs_pkbuf_t *pkbuf)
{
@ -660,6 +690,19 @@ int emm_handle_tau_request(mme_ue_t *mme_ue,
sizeof(tau_request->ms_network_capability));
}
if (tau_request->presencemask &
OGS_NAS_EPS_TRACKING_AREA_UPDATE_REQUEST_NONCEUE_PRESENT) {
mme_ue->gprs_ciphering_key_sequence_number = tau_request->gprs_ciphering_key_sequence_number.key_sequence;
} else {
/* Mark as unavailable, Table 10.5.2/3GPP TS 24.008 */
mme_ue->gprs_ciphering_key_sequence_number = OGS_NAS_CIPHERING_KEY_SEQUENCE_NUMBER_NO_KEY_FROM_MS;
}
if (tau_request->presencemask &
OGS_NAS_EPS_TRACKING_AREA_UPDATE_REQUEST_NONCEUE_PRESENT) {
mme_ue->nonceue = tau_request->nonceue;
}
/* TODO:
* 1) Consider if MME is changed or not.
* 2) Consider if SGW is changed or not.
@ -679,6 +722,10 @@ int emm_handle_tau_request(mme_ue_t *mme_ue,
nas_guti.m_tmsi,
MME_UE_HAVE_IMSI(mme_ue)
? mme_ue->imsi_bcd : "Unknown");
memcpy(&mme_ue->next.guti,
&nas_guti, sizeof(ogs_nas_eps_guti_t));
break;
default:
ogs_error("Not implemented[%d]", eps_mobile_identity->imsi.type);

View File

@ -50,6 +50,9 @@ int emm_handle_extended_service_request(mme_ue_t *mme_ue,
int emm_handle_security_mode_complete(mme_ue_t *mme_ue,
ogs_nas_eps_security_mode_complete_t *security_mode_complete);
bool emm_tau_request_ue_comes_from_gb_or_iu(
const ogs_nas_eps_tracking_area_update_request_t *tau_request);
#ifdef __cplusplus
}
#endif

View File

@ -20,6 +20,7 @@
#include "mme-event.h"
#include "mme-timer.h"
#include "s1ap-handler.h"
#include "mme-gn-handler.h"
#include "mme-fd-path.h"
#include "emm-handler.h"
#include "emm-build.h"
@ -286,7 +287,9 @@ static void common_register_state(ogs_fsm_t *s, mme_event_t *e,
mme_ue_t *mme_ue = NULL;
enb_ue_t *enb_ue = NULL;
mme_sgsn_t *sgsn = NULL;
ogs_nas_eps_message_t *message = NULL;
ogs_nas_rai_t rai;
ogs_nas_security_header_type_t h;
ogs_assert(e);
@ -489,6 +492,26 @@ static void common_register_state(ogs_fsm_t *s, mme_event_t *e,
break;
}
if (emm_tau_request_ue_comes_from_gb_or_iu(&message->emm.tracking_area_update_request)) {
ogs_info("TAU request : UE comes from SGSN, attempt retrieving context");
guti_to_rai_ptmsi(&mme_ue->next.guti, &rai, NULL, NULL);
sgsn = mme_sgsn_find_by_routing_address(&rai, 0xffff);
if (!sgsn) {
ogs_plmn_id_t plmn_id;
ogs_nas_to_plmn_id(&plmn_id, &rai.lai.nas_plmn_id);
ogs_warn("No SGSN route matching RAI[MCC:%u MNC:%u LAC:%u RAC:%u]",
ogs_plmn_id_mcc(&plmn_id), ogs_plmn_id_mnc(&plmn_id),
rai.lai.lac, rai.rac);
r = nas_eps_send_tau_reject(mme_ue,
OGS_NAS_EMM_CAUSE_UE_IDENTITY_CANNOT_BE_DERIVED_BY_THE_NETWORK);
OGS_FSM_TRAN(s, &emm_state_exception);
break;
}
mme_gtp1_send_sgsn_context_request(sgsn, mme_ue);
/* FIXME: use a specific FSM state here to state we are waiting for resolution from Gn? */
break;
}
if (!MME_UE_HAVE_IMSI(mme_ue)) {
ogs_info("TAU request : Unknown UE");
r = nas_eps_send_tau_reject(mme_ue,

View File

@ -3184,6 +3184,11 @@ void mme_ue_confirm_guti(mme_ue_t *mme_ue)
/* Clear Next GUTI */
mme_ue->next.m_tmsi = NULL;
ogs_debug("Confirm GUTI[G:%d,C:%d,M_TMSI:0x%x]",
mme_ue->current.guti.mme_gid,
mme_ue->current.guti.mme_code,
mme_ue->current.guti.m_tmsi);
}
static bool compare_ue_info(mme_sgw_t *node, enb_ue_t *enb_ue)

View File

@ -489,6 +489,8 @@ struct mme_ue_s {
} ul_count;
uint8_t kenb[OGS_SHA256_DIGEST_SIZE];
uint8_t hash_mme[OGS_HASH_MME_LEN];
uint32_t nonceue, noncemme;
uint8_t gprs_ciphering_key_sequence_number;
struct {
ED2(uint8_t nhcc_spare:5;,

View File

@ -20,11 +20,11 @@
#include "mme-context.h"
#include "mme-gn-build.h"
#include "mme-gn-handler.h"
static int sess_fill_mm_context_decoded(mme_sess_t *sess, ogs_gtp1_mm_context_decoded_t *mmctx_dec)
{
mme_ue_t *mme_ue = sess->mme_ue;
mme_bearer_t *bearer = NULL;
*mmctx_dec = (ogs_gtp1_mm_context_decoded_t) {
.gupii = 1, /* Integrity Protection not required */
.ugipai = 1, /* Ignore "Used GPRS integrity protection algorithm" field" */
@ -40,7 +40,6 @@ static int sess_fill_mm_context_decoded(mme_sess_t *sess, ogs_gtp1_mm_context_de
.nrsrna = 0,
};
//TODO: derive cK Ki from mme_ue->kasme
ogs_kdf_ck_ik_idle_mobility(mme_ue->ul_count.i32, mme_ue->kasme, &mmctx_dec->ck[0], &mmctx_dec->ik[0]);
mmctx_dec->imeisv_len = sizeof(mme_ue->nas_mobile_identity_imeisv);
@ -49,12 +48,6 @@ static int sess_fill_mm_context_decoded(mme_sess_t *sess, ogs_gtp1_mm_context_de
mmctx_dec->ms_network_capability_len = mme_ue->ms_network_capability.length;
memcpy(&mmctx_dec->ms_network_capability[0], ((uint8_t*)&mme_ue->ms_network_capability)+1, sizeof(mme_ue->ms_network_capability) - 1);
ogs_list_for_each(&sess->bearer_list, bearer) {
/* FIXME: only 1 PDP Context supported in the message so far. */
break;
}
return OGS_OK;
}
@ -177,6 +170,103 @@ static int sess_fill_pdp_context_decoded(mme_sess_t *sess, ogs_gtp1_pdp_context_
return OGS_OK;
}
/* 3GPP TS 29.060 7.5.3 SGSN Context Request */
ogs_pkbuf_t *mme_gn_build_sgsn_context_request(
mme_ue_t *mme_ue)
{
ogs_gtp1_message_t gtp1_message;
ogs_gtp1_sgsn_context_request_t *req = NULL;
ogs_nas_rai_t rai;
mme_p_tmsi_t ptmsi;
uint32_t ptmsi_sig;
ogs_gtp1_gsn_addr_t mme_gnc_gsnaddr, mme_gnc_alt_gsnaddr;
int gsn_len;
int rv;
ogs_debug("[Gn] build SGSN Context Request");
ogs_assert(mme_ue);
req = &gtp1_message.sgsn_context_request;
memset(&gtp1_message, 0, sizeof(ogs_gtp1_message_t));
guti_to_rai_ptmsi(&mme_ue->next.guti, &rai, &ptmsi, &ptmsi_sig);
req->imsi.presence = 0;
req->routeing_area_identity.presence = 1;
req->routeing_area_identity.data = &rai;
req->routeing_area_identity.len = sizeof(ogs_nas_rai_t);
req->temporary_logical_link_identifier.presence = 0;
req->packet_tmsi.presence = 1;
req->packet_tmsi.u32 = be32toh(ptmsi);
req->p_tmsi_signature.presence = 1;
req->p_tmsi_signature.u24 = ptmsi_sig;
req->ms_validated.presence = 0;
req->tunnel_endpoint_identifier_control_plane.presence = 1;
req->tunnel_endpoint_identifier_control_plane.u32 = mme_ue->gn.mme_gn_teid;
/* SGSN Address for Control Plane */
if (ogs_gtp_self()->gtpc_addr && ogs_gtp_self()->gtpc_addr6) {
rv = ogs_gtp1_sockaddr_to_gsn_addr(NULL, ogs_gtp_self()->gtpc_addr6,
&mme_gnc_gsnaddr, &gsn_len);
if (rv != OGS_OK) {
ogs_error("ogs_gtp1_sockaddr_to_gsn_addr() failed");
return NULL;
}
req->sgsn_address_for_control_plane.presence = 1;
req->sgsn_address_for_control_plane.data = &mme_gnc_gsnaddr;
req->sgsn_address_for_control_plane.len = gsn_len;
rv = ogs_gtp1_sockaddr_to_gsn_addr(ogs_gtp_self()->gtpc_addr, NULL,
&mme_gnc_alt_gsnaddr, &gsn_len);
if (rv != OGS_OK) {
ogs_error("ogs_gtp1_sockaddr_to_gsn_addr() failed");
return NULL;
}
req->sgsn_address_for_control_plane.presence = 1;
req->sgsn_address_for_control_plane.data = &mme_gnc_alt_gsnaddr;
req->sgsn_address_for_control_plane.len = gsn_len;
} else if (ogs_gtp_self()->gtpc_addr6) {
rv = ogs_gtp1_sockaddr_to_gsn_addr(NULL, ogs_gtp_self()->gtpc_addr6,
&mme_gnc_gsnaddr, &gsn_len);
if (rv != OGS_OK) {
ogs_error("ogs_gtp1_sockaddr_to_gsn_addr() failed");
return NULL;
}
req->sgsn_address_for_control_plane.presence = 1;
req->sgsn_address_for_control_plane.data = &mme_gnc_gsnaddr;
req->sgsn_address_for_control_plane.len = gsn_len;
req->alternative_sgsn_address_for_control_plane.presence = 0;
} else {
rv = ogs_gtp1_sockaddr_to_gsn_addr(ogs_gtp_self()->gtpc_addr, NULL,
&mme_gnc_gsnaddr, &gsn_len);
if (rv != OGS_OK) {
ogs_error("ogs_gtp1_sockaddr_to_gsn_addr() failed");
return NULL;
}
req->sgsn_address_for_control_plane.presence = 1;
req->sgsn_address_for_control_plane.data = &mme_gnc_gsnaddr;
req->sgsn_address_for_control_plane.len = gsn_len;
req->alternative_sgsn_address_for_control_plane.presence = 0;
}
req->sgsn_number.presence = 0;
req->rat_type.presence = 1;
req->rat_type.u8 = OGS_GTP1_RAT_TYPE_EUTRAN;
req->hop_counter.presence = 0;
gtp1_message.h.type = OGS_GTP1_SGSN_CONTEXT_REQUEST_TYPE;
return ogs_gtp1_build_msg(&gtp1_message);
}
/* 3GPP TS 29.060 7.5.4 SGSN Context Response */
ogs_pkbuf_t *mme_gn_build_sgsn_context_response(
mme_ue_t *mme_ue, uint8_t cause)
@ -275,6 +365,67 @@ ogs_pkbuf_t *mme_gn_build_sgsn_context_response(
rsp->sgsn_address_for_control_plane.len = gsn_len;
build_ret:
return ogs_gtp1_build_msg(&gtp1_message);
}
/* 3GPP TS 29.060 7.5.5 SGSN Context Acknowledge */
ogs_pkbuf_t *mme_gn_build_sgsn_context_ack(
mme_ue_t *mme_ue, uint8_t cause)
{
ogs_gtp1_message_t gtp1_message;
ogs_gtp1_sgsn_context_acknowledge_t *ack = NULL;
mme_sess_t *sess = NULL;
ogs_gtp1_gsn_addr_t reserved_gnc_gsnaddr;
ogs_gtp1_teidII_t teidII;
ogs_debug("[Gn] build SGSN Context Acknowledge");
ack = &gtp1_message.sgsn_context_acknowledge;
memset(&gtp1_message, 0, sizeof(ogs_gtp1_message_t));
gtp1_message.h.type = OGS_GTP1_SGSN_CONTEXT_ACKNOWLEDGE_TYPE;
/* 3GPP TS 29.060 7.7.1 Cause, Mandatory */
ack->cause.presence = 1;
ack->cause.u8 = cause;
if (cause != OGS_GTP1_CAUSE_REQUEST_ACCEPTED)
goto build_ret;
ogs_list_for_each(&mme_ue->sess_list, sess) {
mme_bearer_t *bearer = NULL;
if (!MME_HAVE_SGW_S1U_PATH(sess))
continue;
ogs_list_for_each(&sess->bearer_list, bearer) {
/* MME, acting as a new SGSN, shall send the following values in the SGSN Context
* Acknowledge message in order to discard the packets received from the old SGSN
* (because the MME and the S4-SGSN do not have user plane):
* - any reserved TEID (e.g. all 0's, or all 1's) for Tunnel Endpoint Identifier
* Data II value;
* - any reserved (implementation dependent) IP address for SGSN Address for user
traffic value.
*/
/* 3GPP TS 29.060 7.7.15 Tunnel Endpoint Identifier Data II, Conditional */
teidII.nsapi = bearer->ebi;
teidII.teid = 0xffffffff;
ack->tunnel_endpoint_identifier_data_ii.presence = 1;
ack->tunnel_endpoint_identifier_data_ii.data = &teidII;
ack->tunnel_endpoint_identifier_data_ii.len = sizeof(teidII);
/* Use IPv4 0.0.0.0 as reserved address: */
reserved_gnc_gsnaddr.addr = 0;
ack->sgsn_address_for_user_traffic.presence = 1;
ack->sgsn_address_for_user_traffic.data = &reserved_gnc_gsnaddr;
ack->sgsn_address_for_user_traffic.len = OGS_GTP_GSN_ADDRESS_IPV4_LEN;
/* FIXME: only 1 PDP Context supported in the message so far. */
break;
}
/* FIXME: right now we only support encoding 1 context in the message. */
break;
}
build_ret:
return ogs_gtp1_build_msg(&gtp1_message);
}

View File

@ -30,9 +30,15 @@ extern "C" {
}
#endif
ogs_pkbuf_t *mme_gn_build_sgsn_context_request(
mme_ue_t *mme_ue);
ogs_pkbuf_t *mme_gn_build_sgsn_context_response(
mme_ue_t *mme_ue, uint8_t cause);
ogs_pkbuf_t *mme_gn_build_sgsn_context_ack(
mme_ue_t *mme_ue, uint8_t cause);
ogs_pkbuf_t *mme_gn_build_ran_information_relay(
uint8_t type, const uint8_t *buf, size_t len,
const ogs_nas_rai_t *rai, uint16_t cell_id);

View File

@ -30,6 +30,7 @@
#include "mme-gn-handler.h"
#include "s1ap-path.h"
#include "nas-path.h"
void mme_gn_handle_echo_request(
ogs_gtp_xact_t *xact, ogs_gtp1_echo_request_t *req)
@ -68,12 +69,26 @@ static int decode_global_enb_id(S1AP_Global_ENB_ID_t *glob_enb_id, const uint8_t
return OGS_OK;
}
/* 3GPP TS 23.003 2.8.2.2 Mapping from RAI and P-TMSI to GUT */
/* 3GPP TS 23.003 2.8.2.1 Mapping from GUTI to RAI, P-TMSI and P-TMSI signature */
void guti_to_rai_ptmsi(const ogs_nas_eps_guti_t *nas_guti, ogs_nas_rai_t *rai, mme_p_tmsi_t *ptmsi, uint32_t *ptmsi_sig)
{
rai->lai.nas_plmn_id = nas_guti->nas_plmn_id;
rai->lai.lac = nas_guti->mme_gid;
rai->rac = nas_guti->mme_code;
if (ptmsi)
*ptmsi = 0xC0000000 |
(nas_guti->m_tmsi & 0x3f000000) |
(nas_guti->mme_code & 0x0ff) << 16 |
(nas_guti->m_tmsi & 0x0000ffff);
if (ptmsi_sig)
*ptmsi_sig = (nas_guti->m_tmsi & 0x00ff0000);
}
/* 3GPP TS 23.003 2.8.2.2 Mapping from RAI and P-TMSI to GUTI */
static void rai_ptmsi_to_guti(const ogs_nas_rai_t *rai, mme_p_tmsi_t ptmsi, uint32_t ptmsi_sig, ogs_nas_eps_guti_t *nas_guti)
{
uint16_t lac = be16toh(rai->lai.lac);;
nas_guti->nas_plmn_id =rai->lai.nas_plmn_id;
nas_guti->mme_gid = lac;
nas_guti->nas_plmn_id = rai->lai.nas_plmn_id;
nas_guti->mme_gid = rai->lai.lac;
nas_guti->mme_code = rai->rac;
nas_guti->m_tmsi = 0xC0000000 | (ptmsi & 0x3f000000) | (ptmsi_sig & 0x00ff0000) | (ptmsi & 0x0000ffff);
}
@ -83,7 +98,8 @@ void mme_gn_handle_sgsn_context_request(
ogs_gtp_xact_t *xact, ogs_gtp1_sgsn_context_request_t *req)
{
ogs_nas_eps_guti_t nas_guti;
ogs_nas_rai_t *rai;
ogs_plmn_id_t plmn_id;
ogs_nas_rai_t rai;
mme_ue_t *mme_ue = NULL;
int rv;
@ -94,9 +110,10 @@ void mme_gn_handle_sgsn_context_request(
mme_gtp1_send_sgsn_context_response(NULL, OGS_GTP1_CAUSE_MANDATORY_IE_MISSING, xact);
return;
}
if (req->routeing_area_identity.len != sizeof(*rai)) {
if (req->routeing_area_identity.len != sizeof(rai)) {
ogs_warn("[Gn] Rx SGSN Context Request RAI wrong size %u vs exp %zu!",
req->routeing_area_identity.len, sizeof(*rai));
req->routeing_area_identity.len, sizeof(rai));
mme_gtp1_send_sgsn_context_response(NULL, OGS_GTP1_CAUSE_MANDATORY_IE_INCORRECT, xact);
return;
}
@ -126,17 +143,35 @@ void mme_gn_handle_sgsn_context_request(
return;
}
rai = req->routeing_area_identity.data;
memcpy(&rai, req->routeing_area_identity.data, sizeof(rai));
rai.lai.lac = be16toh(rai.lai.lac);
ogs_nas_to_plmn_id(&plmn_id, &rai.lai.nas_plmn_id);
ogs_debug(" RAI[MCC:%u MNC:%u LAC:%u RAC:%u]",
ogs_plmn_id_mcc(&plmn_id), ogs_plmn_id_mnc(&plmn_id),
rai.lai.lac, rai.rac);
if (req->imsi.presence) {
char imsi_bcd[OGS_MAX_IMSI_BCD_LEN+1];
ogs_buffer_to_bcd(req->imsi.data, req->imsi.len, imsi_bcd);
ogs_debug(" IMSI[%s]", imsi_bcd);
mme_ue = mme_ue_find_by_imsi(req->imsi.data, req->imsi.len);
if (!mme_ue)
ogs_warn("[Gn] Rx SGSN Context Request: Unknown UE with IMSI[%s]", imsi_bcd);
} else if (req->packet_tmsi.presence) { /* P-TMSI */
if (!req->p_tmsi_signature.presence) {
ogs_warn("[Gn] Rx SGSN Context Request with 'P-TMSI' but no P-TMSI Signature! Assuming value 0.");
req->p_tmsi_signature.u24 = 0;
}
rai_ptmsi_to_guti(rai, req->packet_tmsi.u32, req->p_tmsi_signature.u24, &nas_guti);
rai_ptmsi_to_guti(&rai, req->packet_tmsi.u32, req->p_tmsi_signature.u24, &nas_guti);
ogs_debug(" PTMSI[0x%08x] PTMSI_SIG[0x%06x] -> GUTI[G:%d,C:%d,M_TMSI:0x%x]",
req->packet_tmsi.u32, req->p_tmsi_signature.u24,
nas_guti.mme_gid, nas_guti.mme_code, nas_guti.m_tmsi);
mme_ue = mme_ue_find_by_guti(&nas_guti);
if (!mme_ue)
ogs_warn("[Gn] Rx SGSN Context Request: Unknown UE with RAI[MCC:%u MNC:%u LAC:%u RAC:%u] PTMSI[0x%08x] PTMSI_SIG[0x%06x] -> GUTI[G:%d,C:%d,M_TMSI:0x%x]",
ogs_plmn_id_mcc(&plmn_id), ogs_plmn_id_mnc(&plmn_id), rai.lai.lac, rai.rac,
req->packet_tmsi.u32, req->p_tmsi_signature.u24,
nas_guti.mme_gid, nas_guti.mme_code, nas_guti.m_tmsi);
} else if (req->temporary_logical_link_identifier.presence) {
if (!req->p_tmsi_signature.presence) {
ogs_warn("[Gn] Rx SGSN Context Request with 'TLLI' but no P-TMSI Signature! Assuming value 0.");
@ -145,8 +180,16 @@ void mme_gn_handle_sgsn_context_request(
/* TS 29.060 7.5.3 "The TLLI/P-TMSI and RAI is a foreign TLLI/P-TMSI and an RAI in the old SGSN."
* A foregin TLLI is "tlli = (p_tmsi & 0x3fffffff) | 0x80000000", and since we only use 0x3fffffff
* bits of P-TMSI to derive the GUTI, it's totally fine passing the TLLI as P-TMSI. */
rai_ptmsi_to_guti(rai, req->temporary_logical_link_identifier.u32, req->p_tmsi_signature.u24, &nas_guti);
rai_ptmsi_to_guti(&rai, req->temporary_logical_link_identifier.u32, req->p_tmsi_signature.u24, &nas_guti);
ogs_debug(" TLLI[0x%08x] PTMSI_SIG[0x%06x] -> GUTI[G:%d,C:%d,M_TMSI:0x%x]",
req->temporary_logical_link_identifier.u32, req->p_tmsi_signature.u24,
nas_guti.mme_gid, nas_guti.mme_code, nas_guti.m_tmsi);
mme_ue = mme_ue_find_by_guti(&nas_guti);
if (!mme_ue)
ogs_warn("[Gn] Rx SGSN Context Request: Unknown UE with RAI[MCC:%u MNC:%u LAC:%u RAC:%u] TLLI[0x%08x] PTMSI_SIG[0x%06x] -> GUTI[G:%d,C:%d,M_TMSI:0x%x]",
ogs_plmn_id_mcc(&plmn_id), ogs_plmn_id_mnc(&plmn_id), rai.lai.lac, rai.rac,
req->temporary_logical_link_identifier.u32, req->p_tmsi_signature.u24,
nas_guti.mme_gid, nas_guti.mme_code, nas_guti.m_tmsi);
}
if (!mme_ue) {
@ -177,6 +220,223 @@ void mme_gn_handle_sgsn_context_request(
mme_gtp1_send_sgsn_context_response(mme_ue, OGS_GTP1_CAUSE_REQUEST_ACCEPTED, xact);
}
static mme_sess_t *mme_ue_session_from_gtp1_pdp_ctx(mme_ue_t *mme_ue, const ogs_gtp1_pdp_context_decoded_t *gtp1_pdp_ctx)
{
mme_sess_t *sess = NULL;
mme_bearer_t *bearer = NULL;
const ogs_gtp1_qos_profile_decoded_t *qos_pdec = &gtp1_pdp_ctx->qos_sub;
uint8_t pti = gtp1_pdp_ctx->trans_id;
uint8_t qci = 0;
ogs_session_t *ogs_sess;
ogs_sess = mme_session_find_by_apn(mme_ue, gtp1_pdp_ctx->apn);
if (!ogs_sess) {
ogs_assert(mme_ue->num_of_session < OGS_MAX_NUM_OF_SESS);
ogs_sess = &mme_ue->session[mme_ue->num_of_session];
mme_ue->num_of_session++;
ogs_sess->name = ogs_strdup(gtp1_pdp_ctx->apn);
}
ogs_sess->smf_ip = gtp1_pdp_ctx->ggsn_address_c;
ogs_sess->context_identifier = gtp1_pdp_ctx->pdp_ctx_id;
ogs_sess->session_type = gtp1_pdp_ctx->pdp_type_num[0];
ogs_sess->ue_ip = gtp1_pdp_ctx->pdp_address[0];
/* TODO: sess->paa with gtp1_pdp_ctx->pdp_address[0],
using/implementing ogs_gtp2_ip_to_paa ? */
ogs_ip_to_paa(&ogs_sess->ue_ip, &ogs_sess->paa);
/* 3GPP TS 23.060 section 9.2.1A: "The QoS profiles of the PDP context and EPS bearer are mapped as specified in TS 23.401"
* 3GPP TS 23.401 Annex E: "Mapping between EPS and Release 99 QoS parameters"
*/
ogs_gtp1_qos_profile_to_qci(qos_pdec, &qci);
ogs_sess->qos.index = qci;
ogs_sess->qos.arp.priority_level = qos_pdec->qos_profile.arp; /* 3GPP TS 23.401 Annex E Table E.2 */
ogs_sess->qos.arp.pre_emption_capability = 0; /* ignored as per 3GPP TS 23.401 Annex E */
ogs_sess->qos.arp.pre_emption_vulnerability = 0; /* ignored as per 3GPP TS 23.401 Annex E */
if (qos_pdec->data_octet6_to_13_present) {
ogs_sess->ambr.downlink = qos_pdec->dec_mbr_kbps_dl * 1000;
ogs_sess->ambr.uplink = qos_pdec->dec_mbr_kbps_ul * 1000;
}
sess = mme_sess_find_by_pti(mme_ue, pti);
if (!sess) {
sess = mme_sess_add(mme_ue, pti);
ogs_assert(sess);
}
sess->session = ogs_sess;
sess->pgw_s5c_teid = gtp1_pdp_ctx->ul_teic;
sess->pgw_s5c_ip = gtp1_pdp_ctx->ggsn_address_c;
switch (ogs_sess->session_type) {
case OGS_PDU_SESSION_TYPE_IPV4:
sess->request_type.type = OGS_NAS_EPS_PDN_TYPE_IPV4;
break;
case OGS_PDU_SESSION_TYPE_IPV6:
sess->request_type.type = OGS_NAS_EPS_PDN_TYPE_IPV6;
break;
case OGS_PDU_SESSION_TYPE_IPV4V6:
sess->request_type.type = OGS_NAS_EPS_PDN_TYPE_IPV4V6;
break;
}
sess->request_type.value = OGS_NAS_EPS_REQUEST_TYPE_INITIAL;
bearer = mme_bearer_find_by_sess_ebi(sess, gtp1_pdp_ctx->nsapi);
if (!bearer) {
bearer = mme_default_bearer_in_sess(sess);
if (!bearer) {
bearer = mme_bearer_add(sess);
ogs_assert(bearer);
}
}
bearer->pgw_s5u_teid = gtp1_pdp_ctx->ul_teid;
bearer->pgw_s5u_ip = gtp1_pdp_ctx->ggsn_address_u;
/* Send invalid Remote Address and TEID since it makes no sense that SGW
* forwards GTPUv2 traffic to SGSN: */
bearer->enb_s1u_ip.ipv4 = 1;
bearer->enb_s1u_ip.addr = 0;
bearer->enb_s1u_teid = 0xffffffff;
return sess;
}
/* TS 29.060 7.5.4 SGSN Context Response */
int mme_gn_handle_sgsn_context_response(
ogs_gtp_xact_t *xact, mme_ue_t *mme_ue, ogs_gtp1_sgsn_context_response_t *resp)
{
int rv;
int gtp1_cause, emm_cause = OGS_NAS_EMM_CAUSE_NETWORK_FAILURE;
char imsi_bcd[OGS_MAX_IMSI_BCD_LEN+1];
ogs_gtp1_mm_context_decoded_t gtp1_mm_ctx;
ogs_gtp1_pdp_context_decoded_t gtp1_pdp_ctx;
mme_sess_t *sess = NULL;
uint8_t ret_cause = OGS_GTP1_CAUSE_REQUEST_ACCEPTED;
ogs_debug("[Gn] Rx SGSN Context Response");
rv = ogs_gtp_xact_commit(xact);
if (rv != OGS_OK) {
ogs_error("ogs_gtp_xact_commit() failed");
return OGS_GTP1_CAUSE_SYSTEM_FAILURE;
}
if (!mme_ue) {
ogs_error("MME-UE Context has already been removed");
return OGS_GTP1_CAUSE_IMSI_IMEI_NOT_KNOWN;
}
switch (resp->cause.u8) {
case OGS_GTP1_CAUSE_REQUEST_ACCEPTED:
break; /* Handle below */
case OGS_GTP1_CAUSE_TGT_ACC_RESTRICTED_SUBSCRIBER:
emm_cause = OGS_NAS_EMM_CAUSE_REQUESTED_SERVICE_OPTION_NOT_AUTHORIZED_IN_THIS_PLMN;
break;
case OGS_GTP1_CAUSE_IMSI_IMEI_NOT_KNOWN:
case OGS_GTP1_CAUSE_SYSTEM_FAILURE:
case OGS_GTP1_CAUSE_MANDATORY_IE_INCORRECT:
case OGS_GTP1_CAUSE_MANDATORY_IE_MISSING:
case OGS_GTP1_CAUSE_OPTIONAL_IE_INCORRECT:
case OGS_GTP1_CAUSE_INVALID_MESSAGE_FORMAT:
case OGS_GTP1_CAUSE_P_TMSI_SIGNATURE_MISMATCH:
default:
emm_cause = OGS_NAS_EMM_CAUSE_NETWORK_FAILURE;
break;
}
if (resp->cause.u8 != OGS_GTP1_CAUSE_REQUEST_ACCEPTED) {
ogs_error("[Gn] Rx SGSN Context Response cause:%u", resp->cause.u8);
rv = nas_eps_send_tau_reject(mme_ue, emm_cause);
return OGS_GTP1_CAUSE_SYSTEM_FAILURE;
}
if (!resp->imsi.presence) {
ogs_error("[Gn] Rx SGSN Context Response with no IMSI!");
gtp1_cause = OGS_GTP1_CAUSE_MANDATORY_IE_MISSING;
goto nack_and_reject;
}
ogs_buffer_to_bcd(resp->imsi.data, resp->imsi.len, imsi_bcd);
ogs_info(" IMSI[%s]", imsi_bcd);
mme_ue_set_imsi(mme_ue, imsi_bcd);
if (!resp->tunnel_endpoint_identifier_control_plane.presence) {
ogs_error("[Gn] Rx SGSN Context Response with no Tunnel Endpoint Identifier Control Plane!");
gtp1_cause = OGS_GTP1_CAUSE_MANDATORY_IE_MISSING;
goto nack_and_reject;
}
mme_ue->gn.sgsn_gn_teid = resp->tunnel_endpoint_identifier_control_plane.u32;
if (!resp->mm_context.presence) {
ogs_error("[Gn] Rx SGSN Context Response with no MM Context!");
gtp1_cause = OGS_GTP1_CAUSE_MANDATORY_IE_MISSING;
goto nack_and_reject;
}
if (!resp->pdp_context.presence) {
ogs_error("[Gn] Rx SGSN Context Response with no PDP Context!");
gtp1_cause = OGS_GTP1_CAUSE_MANDATORY_IE_MISSING;
goto nack_and_reject;
}
rv = ogs_gtp1_parse_mm_context(&gtp1_mm_ctx, &resp->mm_context);
if (rv != OGS_OK) {
ogs_error("[Gn] Rx SGSN Context Response: Failed parsing MM Context");
gtp1_cause = OGS_GTP1_CAUSE_MANDATORY_IE_INCORRECT;
goto nack_and_reject;
}
rv = ogs_gtp1_parse_pdp_context(&gtp1_pdp_ctx, &resp->pdp_context);
if (rv != OGS_OK) {
ogs_error("[Gn] Rx SGSN Context Response: Failed parsing PDP Context");
gtp1_cause = OGS_GTP1_CAUSE_MANDATORY_IE_INCORRECT;
goto nack_and_reject;
}
if (gtp1_mm_ctx.imeisv_len > 0) {
memcpy(&mme_ue->nas_mobile_identity_imeisv, &gtp1_mm_ctx.imeisv[0],
ogs_min(gtp1_mm_ctx.imeisv_len, sizeof(mme_ue->nas_mobile_identity_imeisv)));
} else {
/* 3GPP TS 23.401 D3.6: we need to request IMEI to the UE over EUTRAN */
ret_cause = OGS_GTP1_CAUSE_REQUEST_IMEI;
}
mme_ue->ms_network_capability.length = gtp1_mm_ctx.ms_network_capability_len;
if (gtp1_mm_ctx.ms_network_capability_len > 0)
memcpy(((uint8_t*)&mme_ue->ms_network_capability)+1, &gtp1_mm_ctx.ms_network_capability[0],
ogs_min(gtp1_mm_ctx.ms_network_capability_len, sizeof(mme_ue->ms_network_capability) - 1));
/* TODO: how to fill first byte of mme_ue->ms_network_capability ? */
mme_ue->nas_eps.ksi = gtp1_mm_ctx.ksi;
/* 3GPP TS 33.401 A.10, A.11: */
mme_ue->noncemme = ogs_random32();
/* 3GPP TS 33.401 7.2.6.2 Establishment of keys for cryptographically protected radio bearers: */
/* See also 3GPP TS 33.401 9.1.2 */
ogs_kdf_kasme_idle_mobility(gtp1_mm_ctx.ck, gtp1_mm_ctx.ik, mme_ue->nonceue, mme_ue->noncemme, mme_ue->kasme);
ogs_kdf_kenb(mme_ue->kasme, mme_ue->ul_count.i32, mme_ue->kenb);
ogs_kdf_nh_enb(mme_ue->kasme, mme_ue->kenb, mme_ue->nh);
mme_ue->nhcc = 1;
if (gtp1_mm_ctx.num_vectors > 0) {
mme_ue->xres_len = gtp1_mm_ctx.auth_quintuplets[0].xres_len;
memcpy(mme_ue->xres, gtp1_mm_ctx.auth_quintuplets[0].xres, mme_ue->xres_len);
memcpy(mme_ue->rand, gtp1_mm_ctx.auth_quintuplets[0].rand, OGS_RAND_LEN);
memcpy(mme_ue->autn, gtp1_mm_ctx.auth_quintuplets[0].autn, OGS_AUTN_LEN);
}
sess = mme_ue_session_from_gtp1_pdp_ctx(mme_ue, &gtp1_pdp_ctx);
rv = mme_gtp1_send_sgsn_context_ack(mme_ue, OGS_GTP1_CAUSE_REQUEST_ACCEPTED, xact);
mme_gtp_send_create_session_request(sess, OGS_GTP_CREATE_IN_TRACKING_AREA_UPDATE);
return ret_cause;
nack_and_reject:
rv = mme_gtp1_send_sgsn_context_ack(mme_ue, gtp1_cause, xact);
ogs_info("[%s] TAU Reject [OGS_NAS_EMM_CAUSE:%d]", mme_ue->imsi_bcd, emm_cause);
rv = nas_eps_send_tau_reject(mme_ue, emm_cause);
return OGS_GTP1_CAUSE_SYSTEM_FAILURE;
}
/* TS 29.060 7.5.5 SGSN Context Acknowledge */
void mme_gn_handle_sgsn_context_acknowledge(
ogs_gtp_xact_t *xact, mme_ue_t *mme_ue, ogs_gtp1_sgsn_context_acknowledge_t *req)

View File

@ -35,12 +35,18 @@ void mme_gn_handle_echo_response(
void mme_gn_handle_sgsn_context_request(
ogs_gtp_xact_t *xact, ogs_gtp1_sgsn_context_request_t *req);
int mme_gn_handle_sgsn_context_response(
ogs_gtp_xact_t *xact, mme_ue_t *mme_ue, ogs_gtp1_sgsn_context_response_t *resp);
void mme_gn_handle_sgsn_context_acknowledge(
ogs_gtp_xact_t *xact, mme_ue_t *mme_ue, ogs_gtp1_sgsn_context_acknowledge_t *req);
void mme_gn_handle_ran_information_relay(
ogs_gtp_xact_t *xact, ogs_gtp1_ran_information_relay_t *req);
void guti_to_rai_ptmsi(const ogs_nas_eps_guti_t *nas_guti, ogs_nas_rai_t *rai,
mme_p_tmsi_t *ptmsi, uint32_t *ptmsi_sig);
#ifdef __cplusplus
}
#endif

View File

@ -758,6 +758,45 @@ int mme_gtp_send_bearer_resource_command(
return rv;
}
/*************************
* GTPv1C (Gn interface):
*************************/
int mme_gtp1_send_sgsn_context_request(
mme_sgsn_t *sgsn, mme_ue_t *mme_ue)
{
int rv;
ogs_gtp1_header_t h;
ogs_pkbuf_t *pkbuf = NULL;
ogs_gtp_xact_t *xact = NULL;
ogs_assert(sgsn);
memset(&h, 0, sizeof(ogs_gtp1_header_t));
h.type = OGS_GTP1_SGSN_CONTEXT_REQUEST_TYPE;
h.teid = 0;
pkbuf = mme_gn_build_sgsn_context_request(mme_ue);
if (!pkbuf) {
ogs_error("mme_gn_build_ran_information_relay() failed");
return OGS_ERROR;
}
xact = ogs_gtp1_xact_local_create(&sgsn->gnode, &h, pkbuf, NULL, NULL);
if (!xact) {
ogs_error("ogs_gtp1_xact_local_create() failed");
return OGS_ERROR;
}
/* TS 29.060 8.2: "The SGSN Context Request message, where the Tunnel
* Endpoint Identifier shall be set to all zeroes." */
xact->local_teid = 0;
rv = ogs_gtp_xact_commit(xact);
ogs_expect(rv == OGS_OK);
return rv;
}
int mme_gtp1_send_sgsn_context_response(
mme_ue_t *mme_ue, uint8_t cause, ogs_gtp_xact_t *xact)
{
@ -788,6 +827,38 @@ int mme_gtp1_send_sgsn_context_response(
return rv;
}
int mme_gtp1_send_sgsn_context_ack(
mme_ue_t *mme_ue, uint8_t cause, ogs_gtp_xact_t *xact)
{
int rv;
ogs_gtp1_header_t h;
ogs_pkbuf_t *pkbuf = NULL;
ogs_assert(mme_ue);
memset(&h, 0, sizeof(ogs_gtp1_header_t));
h.type = OGS_GTP1_SGSN_CONTEXT_ACKNOWLEDGE_TYPE;
h.teid = mme_ue->gn.sgsn_gn_teid;
pkbuf = mme_gn_build_sgsn_context_ack(mme_ue, cause);
if (!pkbuf) {
ogs_error("mme_gn_build_sgsn_context_response() failed");
return OGS_ERROR;
}
xact->local_teid = mme_ue->gn.mme_gn_teid;
rv = ogs_gtp1_xact_update_tx(xact, &h, pkbuf);
if (rv != OGS_OK) {
ogs_error("ogs_gtp1_xact_update_tx() failed");
return OGS_ERROR;
}
rv = ogs_gtp_xact_commit(xact);
ogs_expect(rv == OGS_OK);
return rv;
}
int mme_gtp1_send_ran_information_relay(
mme_sgsn_t *sgsn, const uint8_t *buf, size_t len,
const ogs_nas_rai_t *rai, uint16_t cell_id)

View File

@ -55,9 +55,15 @@ int mme_gtp_send_delete_indirect_data_forwarding_tunnel_request(
int mme_gtp_send_bearer_resource_command(
mme_bearer_t *bearer, ogs_nas_eps_message_t *nas_message);
int mme_gtp1_send_sgsn_context_request(
mme_sgsn_t *sgsn, mme_ue_t *mme_ue);
int mme_gtp1_send_sgsn_context_response(
mme_ue_t *mme_ue, uint8_t cause, ogs_gtp_xact_t *xact);
int mme_gtp1_send_sgsn_context_ack(
mme_ue_t *mme_ue, uint8_t cause, ogs_gtp_xact_t *xact);
int mme_gtp1_send_ran_information_relay(
mme_sgsn_t *sgsn, const uint8_t *buf, size_t len,
const ogs_nas_rai_t *rai, uint16_t cell_id);

View File

@ -219,7 +219,8 @@ ogs_pkbuf_t *mme_s11_build_create_session_request(
if (sess->request_type.value == OGS_NAS_EPS_REQUEST_TYPE_HANDOVER)
indication.handover_indication = 1;
if (create_action == OGS_GTP_CREATE_IN_PATH_SWITCH_REQUEST)
if (create_action == OGS_GTP_CREATE_IN_PATH_SWITCH_REQUEST ||
create_action == OGS_GTP_CREATE_IN_TRACKING_AREA_UPDATE)
indication.operation_indication = 1;
session->paa.session_type = req->pdn_type.u8;
@ -288,6 +289,10 @@ ogs_pkbuf_t *mme_s11_build_create_session_request(
&enb_s1u_teid[i];
req->bearer_contexts_to_be_created[i].s1_u_enodeb_f_teid.len =
enb_s1u_len[i];
}
if (create_action == OGS_GTP_CREATE_IN_PATH_SWITCH_REQUEST ||
create_action == OGS_GTP_CREATE_IN_TRACKING_AREA_UPDATE) {
/* Data Plane(DL) : PGW-S5U */
memset(&pgw_s5u_teid[i], 0, sizeof(ogs_gtp2_f_teid_t));

View File

@ -232,6 +232,12 @@ void mme_s11_handle_create_session_response(
OGS_NAS_ESM_CAUSE_NETWORK_FAILURE);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
} else if (create_action == OGS_GTP_CREATE_IN_TRACKING_AREA_UPDATE) {
ogs_error("[%s] TAU reject [Cause:%d]",
mme_ue->imsi_bcd, session_cause);
r = nas_eps_send_tau_reject(mme_ue, OGS_NAS_EMM_CAUSE_NETWORK_FAILURE);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
}
mme_send_delete_session_or_mme_ue_context_release(mme_ue);
return;
@ -342,6 +348,10 @@ void mme_s11_handle_create_session_response(
ogs_debug(" ENB_S1U_TEID[%d] SGW_S1U_TEID[%d] PGW_S5U_TEID[%d]",
bearer->enb_s1u_teid, bearer->sgw_s1u_teid, bearer->pgw_s5u_teid);
if (create_action == OGS_GTP_CREATE_IN_TRACKING_AREA_UPDATE &&
!OGS_FSM_CHECK(&bearer->sm, esm_state_active))
OGS_FSM_TRAN(&bearer->sm, esm_state_active);
}
/* Bearer Level QoS */
@ -428,6 +438,9 @@ void mme_s11_handle_create_session_response(
ogs_assert(r != OGS_ERROR);
}
} else if (create_action == OGS_GTP_CREATE_IN_TRACKING_AREA_UPDATE) {
/* 3GPP TS 23.401 D.3.6 step 13, 14: */
mme_s6a_send_ulr(mme_ue);
} else if (create_action == OGS_GTP_CREATE_IN_UPLINK_NAS_TRANSPORT) {
ogs_assert(OGS_PDU_SESSION_TYPE_IS_VALID(session->paa.session_type));
r = nas_eps_send_activate_default_bearer_context_request(

View File

@ -674,6 +674,17 @@ void mme_state_operational(ogs_fsm_t *s, mme_event_t *e)
case OGS_GTP1_SGSN_CONTEXT_REQUEST_TYPE:
mme_gn_handle_sgsn_context_request(xact, &gtp1_message.sgsn_context_request);
break;
case OGS_GTP1_SGSN_CONTEXT_RESPONSE_TYPE:
/* 3GPP TS 23.401 Figure D.3.6-1 step 5 */
rv = mme_gn_handle_sgsn_context_response(xact, mme_ue, &gtp1_message.sgsn_context_response);
if (rv == OGS_GTP1_CAUSE_ACCEPT) {
OGS_FSM_TRAN(&mme_ue->sm, &emm_state_initial_context_setup);
} else if (rv == OGS_GTP1_CAUSE_REQUEST_IMEI) {
OGS_FSM_TRAN(&mme_ue->sm, &emm_state_security_mode);
} else {
OGS_FSM_TRAN(&mme_ue->sm, &emm_state_exception);
}
break;
case OGS_GTP1_SGSN_CONTEXT_ACKNOWLEDGE_TYPE:
mme_gn_handle_sgsn_context_acknowledge(xact, mme_ue, &gtp1_message.sgsn_context_acknowledge);
break;