[ENB] Add CFSB support

This commit introduces CSFB (circuit switched fall-back) capabilities to
srsLTE.  Actually, all the eNB has to do is to send a
RrcConnectionRelease with the RedirectedCarrierInfo IE.

The MME triggers this by the S1AP CS Fallback Indicator IE, which may
be present either in the Initial Context Setup Requst or in the
UE Context Modification Request.

As srsLTE has no support for the UE Context Modification Request at all
yet (!), basic support for this message is introduced in this patch.

Both Mobile Originated and Mobile Terminated CSFB with a coupel of
different UE models have been verified using this patch in a setup
consisting of srsENB attached to an undisclosed EPC connected via SGsAP
to a complete  Osmocom 2G network.

Closes: #358
Closes: #363
master
Harald Welte 5 years ago committed by Andre Puschmann
parent d9577f0e1e
commit 2e426948fe

@ -267,6 +267,7 @@ public:
virtual void write_dl_info(uint16_t rnti, srslte::unique_byte_buffer_t sdu) = 0;
virtual void release_complete(uint16_t rnti) = 0;
virtual bool setup_ue_ctxt(uint16_t rnti, LIBLTE_S1AP_MESSAGE_INITIALCONTEXTSETUPREQUEST_STRUCT *msg) = 0;
virtual bool modify_ue_ctxt(uint16_t rnti, LIBLTE_S1AP_MESSAGE_UECONTEXTMODIFICATIONREQUEST_STRUCT *msg) = 0;
virtual bool setup_ue_erabs(uint16_t rnti, LIBLTE_S1AP_MESSAGE_E_RABSETUPREQUEST_STRUCT *msg) = 0;
virtual bool release_erabs(uint32_t rnti) = 0;
virtual void add_paging_id(uint32_t ueid, LIBLTE_S1AP_UEPAGINGID_STRUCT UEPagingID) = 0;

@ -156,6 +156,7 @@ public:
void write_dl_info(uint16_t rnti, srslte::unique_byte_buffer_t sdu);
void release_complete(uint16_t rnti);
bool setup_ue_ctxt(uint16_t rnti, LIBLTE_S1AP_MESSAGE_INITIALCONTEXTSETUPREQUEST_STRUCT *msg);
bool modify_ue_ctxt(uint16_t rnti, LIBLTE_S1AP_MESSAGE_UECONTEXTMODIFICATIONREQUEST_STRUCT *msg);
bool setup_ue_erabs(uint16_t rnti, LIBLTE_S1AP_MESSAGE_E_RABSETUPREQUEST_STRUCT *msg);
bool release_erabs(uint32_t rnti);
void add_paging_id(uint32_t ueid, LIBLTE_S1AP_UEPAGINGID_STRUCT UEPagingID);
@ -254,6 +255,8 @@ public:
bool connect_notified;
bool is_csfb;
private:
srslte::byte_buffer_pool *pool;
@ -378,6 +381,7 @@ private:
rrc_cfg_t cfg;
uint32_t nof_si_messages;
asn1::rrc::sib_type2_s sib2;
asn1::rrc::sib_type7_s sib7;
void run_thread();
void rem_user_thread(uint16_t rnti);

@ -118,6 +118,7 @@ private:
bool handle_uectxtreleasecommand(LIBLTE_S1AP_MESSAGE_UECONTEXTRELEASECOMMAND_STRUCT *msg);
bool handle_s1setupfailure(LIBLTE_S1AP_MESSAGE_S1SETUPFAILURE_STRUCT *msg);
bool handle_erabsetuprequest(LIBLTE_S1AP_MESSAGE_E_RABSETUPREQUEST_STRUCT *msg);
bool handle_uecontextmodifyrequest(LIBLTE_S1AP_MESSAGE_UECONTEXTMODIFICATIONREQUEST_STRUCT *msg);
bool send_initialuemessage(uint16_t rnti,
LIBLTE_S1AP_RRC_ESTABLISHMENT_CAUSE_ENUM cause,
@ -132,6 +133,8 @@ private:
bool send_initial_ctxt_setup_failure(uint16_t rnti);
bool send_erab_setup_response(uint16_t rnti, LIBLTE_S1AP_MESSAGE_E_RABSETUPRESPONSE_STRUCT *res_);
//bool send_ue_capabilities(uint16_t rnti, LIBLTE_RRC_UE_EUTRA_CAPABILITY_STRUCT *caps)
bool send_uectxmodifyresp(uint16_t rnti);
bool send_uectxmodifyfailure(uint16_t rnti, LIBLTE_S1AP_CAUSE_STRUCT *cause);
bool find_mme_ue_id(uint32_t mme_ue_id, uint16_t *rnti, uint32_t *enb_ue_id);
std::string get_cause(LIBLTE_S1AP_CAUSE_STRUCT *c);

@ -295,9 +295,6 @@ bool rrc::setup_ue_ctxt(uint16_t rnti, LIBLTE_S1AP_MESSAGE_INITIALCONTEXTSETUPRE
return false;
}
if(msg->CSFallbackIndicator_present) {
rrc_log->warning("Not handling CSFallbackIndicator\n");
}
if(msg->AdditionalCSFallbackIndicator_present) {
rrc_log->warning("Not handling AdditionalCSFallbackIndicator\n");
}
@ -346,6 +343,14 @@ bool rrc::setup_ue_ctxt(uint16_t rnti, LIBLTE_S1AP_MESSAGE_INITIALCONTEXTSETUPRE
liblte_pack(msg->SecurityKey.buffer, LIBLTE_S1AP_SECURITYKEY_BIT_STRING_LEN, key);
users[rnti].set_security_key(key, LIBLTE_S1AP_SECURITYKEY_BIT_STRING_LEN/8);
// CSFB
if (msg->CSFallbackIndicator_present) {
if (msg->CSFallbackIndicator.e == LIBLTE_S1AP_CSFALLBACKINDICATOR_CS_FALLBACK_REQUIRED ||
msg->CSFallbackIndicator.e == LIBLTE_S1AP_CSFALLBACKINDICATOR_CS_FALLBACK_HIGH_PRIORITY) {
users[rnti].is_csfb = true;
}
}
// Send RRC security mode command
users[rnti].send_security_mode_command();
@ -357,6 +362,75 @@ bool rrc::setup_ue_ctxt(uint16_t rnti, LIBLTE_S1AP_MESSAGE_INITIALCONTEXTSETUPRE
return true;
}
bool rrc::modify_ue_ctxt(uint16_t rnti, LIBLTE_S1AP_MESSAGE_UECONTEXTMODIFICATIONREQUEST_STRUCT *msg)
{
bool err = false;
pthread_mutex_lock(&user_mutex);
rrc_log->info("Modifying context for 0x%x\n", rnti);
if (users.count(rnti) == 0) {
rrc_log->warning("Unrecognised rnti: 0x%x\n", rnti);
pthread_mutex_unlock(&user_mutex);
return false;
}
if (msg->CSFallbackIndicator_present) {
if (msg->CSFallbackIndicator.e == LIBLTE_S1AP_CSFALLBACKINDICATOR_CS_FALLBACK_REQUIRED ||
msg->CSFallbackIndicator.e == LIBLTE_S1AP_CSFALLBACKINDICATOR_CS_FALLBACK_HIGH_PRIORITY) {
/* Remember that we are in a CSFB right now */
users[rnti].is_csfb = true;
}
}
if (msg->AdditionalCSFallbackIndicator_present) {
rrc_log->warning("Not handling AdditionalCSFallbackIndicator\n");
err = true;
}
if (msg->CSGMembershipStatus_present) {
rrc_log->warning("Not handling CSGMembershipStatus\n");
err = true;
}
if (msg->RegisteredLAI_present) {
rrc_log->warning("Not handling RegisteredLAI\n");
err = true;
}
if (msg->SubscriberProfileIDforRFP_present) {
rrc_log->warning("Not handling SubscriberProfileIDforRFP\n");
err = true;
}
if (err) {
// maybe pass a cause value?
return false;
}
// UEAggregateMaximumBitrate
if (msg->uEaggregateMaximumBitrate_present) {
users[rnti].set_bitrates(&msg->uEaggregateMaximumBitrate);
}
// UESecurityCapabilities
if (msg->UESecurityCapabilities_present) {
users[rnti].set_security_capabilities(&msg->UESecurityCapabilities);
}
// SecurityKey
if (msg->SecurityKey_present) {
uint8_t key[32];
liblte_pack(msg->SecurityKey.buffer, LIBLTE_S1AP_SECURITYKEY_BIT_STRING_LEN, key);
users[rnti].set_security_key(key, LIBLTE_S1AP_SECURITYKEY_BIT_STRING_LEN / 8);
// Send RRC security mode command ??
users[rnti].send_security_mode_command();
}
pthread_mutex_unlock(&user_mutex);
return true;
}
bool rrc::setup_ue_erabs(uint16_t rnti, LIBLTE_S1AP_MESSAGE_E_RABSETUPREQUEST_STRUCT *msg)
{
pthread_mutex_lock(&user_mutex);
@ -742,6 +816,10 @@ uint32_t rrc::generate_sibs()
log_rrc_message("SIB payload", Tx, sib_buffer[msg_index].get(), msg[msg_index]);
}
if (cfg.sibs[6].type() == asn1::rrc::sys_info_r8_ies_s::sib_type_and_info_item_c_::types::sib7) {
sib7 = cfg.sibs[6].sib7();
}
return nof_messages;
}
@ -952,6 +1030,7 @@ rrc::ue::ue()
integ_algo = srslte::INTEGRITY_ALGORITHM_ID_EIA0;
cipher_algo = srslte::CIPHERING_ALGORITHM_ID_EEA0;
nas_pending = false;
is_csfb = false;
state = RRC_STATE_IDLE;
pool = srslte::byte_buffer_pool::get_instance();
}
@ -1565,6 +1644,12 @@ void rrc::ue::send_connection_release()
dl_dcch_msg.msg.c1().rrc_conn_release().rrc_transaction_id = (uint8_t)((transaction_id++) % 4);
dl_dcch_msg.msg.c1().rrc_conn_release().crit_exts.set_c1().set_rrc_conn_release_r8();
dl_dcch_msg.msg.c1().rrc_conn_release().crit_exts.c1().rrc_conn_release_r8().release_cause = release_cause_e::other;
if (is_csfb) {
rrc_conn_release_r8_ies_s& rel_ies = dl_dcch_msg.msg.c1().rrc_conn_release().crit_exts.c1().rrc_conn_release_r8();
rel_ies.redirected_carrier_info_present = true;
rel_ies.redirected_carrier_info.set_geran();
rel_ies.redirected_carrier_info.geran() = parent->sib7.carrier_freqs_info_list[0].carrier_freqs;
}
send_dl_dcch(&dl_dcch_msg);
}

@ -435,6 +435,8 @@ bool s1ap::handle_initiatingmessage(LIBLTE_S1AP_INITIATINGMESSAGE_STRUCT *msg)
return handle_paging(&msg->choice.Paging);
case LIBLTE_S1AP_INITIATINGMESSAGE_CHOICE_E_RABSETUPREQUEST:
return handle_erabsetuprequest(&msg->choice.E_RABSetupRequest);
case LIBLTE_S1AP_INITIATINGMESSAGE_CHOICE_UECONTEXTMODIFICATIONREQUEST:
return handle_uecontextmodifyrequest(&msg->choice.UEContextModificationRequest);
default:
s1ap_log->error("Unhandled intiating message: %s\n", liblte_s1ap_initiatingmessage_choice_text[msg->choice_type]);
}
@ -526,6 +528,23 @@ bool s1ap::handle_initialctxtsetuprequest(LIBLTE_S1AP_MESSAGE_INITIALCONTEXTSETU
return false;
}
/* Ideally the check below would be "if (users[rnti].is_csfb)" */
if (msg->CSFallbackIndicator_present) {
if (msg->CSFallbackIndicator.e == LIBLTE_S1AP_CSFALLBACKINDICATOR_CS_FALLBACK_REQUIRED ||
msg->CSFallbackIndicator.e == LIBLTE_S1AP_CSFALLBACKINDICATOR_CS_FALLBACK_HIGH_PRIORITY) {
// Send RRC Release (cs-fallback-triggered) to MME
LIBLTE_S1AP_CAUSE_STRUCT cause;
cause.ext = false;
cause.choice_type = LIBLTE_S1AP_CAUSE_CHOICE_RADIONETWORK;
cause.choice.radioNetwork.ext = false;
cause.choice.radioNetwork.e = LIBLTE_S1AP_CAUSERADIONETWORK_CS_FALLBACK_TRIGGERED;
/* FIXME: This should normally probably only be sent after the SecurityMode procedure has completed! */
ue_ctxt_map[rnti].release_requested = true;
send_uectxtreleaserequest(rnti, &cause);
}
}
return true;
}
@ -568,6 +587,53 @@ bool s1ap::handle_erabsetuprequest(LIBLTE_S1AP_MESSAGE_E_RABSETUPREQUEST_STRUCT
return true;
}
bool s1ap::handle_uecontextmodifyrequest(LIBLTE_S1AP_MESSAGE_UECONTEXTMODIFICATIONREQUEST_STRUCT* msg)
{
s1ap_log->info("Received UeContextModificationRequest\n");
if (enbid_to_rnti_map.end() == enbid_to_rnti_map.find(msg->eNB_UE_S1AP_ID.ENB_UE_S1AP_ID)) {
s1ap_log->warning("eNB_UE_S1AP_ID not found - discarding message\n");
return false;
}
uint16_t rnti = enbid_to_rnti_map[msg->eNB_UE_S1AP_ID.ENB_UE_S1AP_ID];
if (msg->MME_UE_S1AP_ID.MME_UE_S1AP_ID != ue_ctxt_map[rnti].MME_UE_S1AP_ID) {
s1ap_log->warning("MME_UE_S1AP_ID has changed - old:%d, new:%d\n",
ue_ctxt_map[rnti].MME_UE_S1AP_ID,
msg->MME_UE_S1AP_ID.MME_UE_S1AP_ID);
ue_ctxt_map[rnti].MME_UE_S1AP_ID = msg->MME_UE_S1AP_ID.MME_UE_S1AP_ID;
}
if (!rrc->modify_ue_ctxt(rnti, msg)) {
LIBLTE_S1AP_CAUSE_STRUCT cause;
cause.ext = false;
cause.choice_type = LIBLTE_S1AP_CAUSE_CHOICE_MISC;
cause.choice.misc.ext = false;
cause.choice.misc.e = LIBLTE_S1AP_CAUSEMISC_UNSPECIFIED;
send_uectxmodifyfailure(rnti, &cause);
return true;
}
// Send UEContextModificationResponse
send_uectxmodifyresp(rnti);
/* Ideally the check below would be "if (users[rnti].is_csfb)" */
if (msg->CSFallbackIndicator_present) {
if (msg->CSFallbackIndicator.e == LIBLTE_S1AP_CSFALLBACKINDICATOR_CS_FALLBACK_REQUIRED ||
msg->CSFallbackIndicator.e == LIBLTE_S1AP_CSFALLBACKINDICATOR_CS_FALLBACK_HIGH_PRIORITY) {
// Send RRC Release (cs-fallback-triggered) to MME
LIBLTE_S1AP_CAUSE_STRUCT cause;
cause.ext = false;
cause.choice_type = LIBLTE_S1AP_CAUSE_CHOICE_RADIONETWORK;
cause.choice.radioNetwork.ext = false;
cause.choice.radioNetwork.e = LIBLTE_S1AP_CAUSERADIONETWORK_CS_FALLBACK_TRIGGERED;
ue_ctxt_map[rnti].release_requested = true;
send_uectxtreleaserequest(rnti, &cause);
}
}
return true;
}
bool s1ap::handle_uectxtreleasecommand(LIBLTE_S1AP_MESSAGE_UECONTEXTRELEASECOMMAND_STRUCT *msg)
{
s1ap_log->info("Received UEContextReleaseCommand\n");
@ -982,6 +1048,103 @@ bool s1ap::send_initial_ctxt_setup_failure(uint16_t rnti)
return true;
}
bool s1ap::send_uectxmodifyresp(uint16_t rnti)
{
if (!mme_connected) {
return false;
}
srslte::unique_byte_buffer_t buf = srslte::allocate_unique_buffer(*pool);
if (!buf) {
s1ap_log->error("Fatal Error: Couldn't allocate buffer in s1ap::send_uectxmodifyresp().\n");
return false;
}
LIBLTE_S1AP_S1AP_PDU_STRUCT tx_pdu;
tx_pdu.ext = false;
tx_pdu.choice_type = LIBLTE_S1AP_S1AP_PDU_CHOICE_SUCCESSFULOUTCOME;
LIBLTE_S1AP_SUCCESSFULOUTCOME_STRUCT* succ = &tx_pdu.choice.successfulOutcome;
succ->procedureCode = LIBLTE_S1AP_PROC_ID_UECONTEXTMODIFICATION;
succ->choice_type = LIBLTE_S1AP_SUCCESSFULOUTCOME_CHOICE_UECONTEXTMODIFICATIONRESPONSE;
LIBLTE_S1AP_MESSAGE_UECONTEXTMODIFICATIONRESPONSE_STRUCT* resp = &succ->choice.UEContextModificationResponse;
resp->ext = false;
resp->CriticalityDiagnostics_present = false;
resp->MME_UE_S1AP_ID.MME_UE_S1AP_ID = ue_ctxt_map[rnti].MME_UE_S1AP_ID;
resp->eNB_UE_S1AP_ID.ENB_UE_S1AP_ID = ue_ctxt_map[rnti].eNB_UE_S1AP_ID;
liblte_s1ap_pack_s1ap_pdu(&tx_pdu, (LIBLTE_BYTE_MSG_STRUCT*)buf.get());
s1ap_log->info_hex(buf->msg, buf->N_bytes, "Sending ContextModificationFailure for RNTI:0x%x", rnti);
ssize_t n_sent = sctp_sendmsg(socket_fd,
buf->msg,
buf->N_bytes,
(struct sockaddr*)&mme_addr,
sizeof(struct sockaddr_in),
htonl(PPID),
0,
ue_ctxt_map[rnti].stream_id,
0,
0);
if (n_sent == -1) {
s1ap_log->error("Failed to send ContextModificationFailure for RNTI:0x%x\n", rnti);
return false;
}
return true;
}
bool s1ap::send_uectxmodifyfailure(uint16_t rnti, LIBLTE_S1AP_CAUSE_STRUCT* cause)
{
if (!mme_connected) {
return false;
}
srslte::unique_byte_buffer_t buf = srslte::allocate_unique_buffer(*pool);
if (!buf) {
s1ap_log->error("Fatal Error: Couldn't allocate buffer in s1ap::send_initial_ctxt_setup_failure().\n");
return false;
}
LIBLTE_S1AP_S1AP_PDU_STRUCT tx_pdu;
tx_pdu.ext = false;
tx_pdu.choice_type = LIBLTE_S1AP_S1AP_PDU_CHOICE_UNSUCCESSFULOUTCOME;
LIBLTE_S1AP_UNSUCCESSFULOUTCOME_STRUCT* unsucc = &tx_pdu.choice.unsuccessfulOutcome;
unsucc->procedureCode = LIBLTE_S1AP_PROC_ID_UECONTEXTMODIFICATION;
unsucc->choice_type = LIBLTE_S1AP_UNSUCCESSFULOUTCOME_CHOICE_UECONTEXTMODIFICATIONFAILURE;
LIBLTE_S1AP_MESSAGE_UECONTEXTMODIFICATIONFAILURE_STRUCT* fail = &unsucc->choice.UEContextModificationFailure;
fail->ext = false;
fail->CriticalityDiagnostics_present = false;
fail->MME_UE_S1AP_ID.MME_UE_S1AP_ID = ue_ctxt_map[rnti].MME_UE_S1AP_ID;
fail->eNB_UE_S1AP_ID.ENB_UE_S1AP_ID = ue_ctxt_map[rnti].eNB_UE_S1AP_ID;
memcpy(&fail->Cause, cause, sizeof(LIBLTE_S1AP_CAUSE_STRUCT));
liblte_s1ap_pack_s1ap_pdu(&tx_pdu, (LIBLTE_BYTE_MSG_STRUCT*)buf.get());
s1ap_log->info_hex(buf->msg, buf->N_bytes, "Sending UEContextModificationFailure for RNTI:0x%x", rnti);
ssize_t n_sent = sctp_sendmsg(socket_fd,
buf->msg,
buf->N_bytes,
(struct sockaddr*)&mme_addr,
sizeof(struct sockaddr_in),
htonl(PPID),
0,
ue_ctxt_map[rnti].stream_id,
0,
0);
if (n_sent == -1) {
s1ap_log->error("Failed to send UEContextModificationFailure for RNTI:0x%x\n", rnti);
return false;
}
return true;
}
//bool s1ap::send_ue_capabilities(uint16_t rnti, LIBLTE_RRC_UE_EUTRA_CAPABILITY_STRUCT *caps)
//{

Loading…
Cancel
Save