From c4bcd6e287fc67bf85fb82a41d1f8f2c901ed8d5 Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Sat, 3 Aug 2019 22:07:39 +0200 Subject: [PATCH] [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 --- .../srslte/interfaces/enb_interfaces.h | 1 + srsenb/hdr/stack/rrc/rrc.h | 4 + srsenb/hdr/stack/upper/s1ap.h | 3 + srsenb/src/stack/rrc/rrc.cc | 91 +++++++++- srsenb/src/stack/upper/s1ap.cc | 163 ++++++++++++++++++ 5 files changed, 259 insertions(+), 3 deletions(-) diff --git a/lib/include/srslte/interfaces/enb_interfaces.h b/lib/include/srslte/interfaces/enb_interfaces.h index 3b3c16ff1..bd844fcc0 100644 --- a/lib/include/srslte/interfaces/enb_interfaces.h +++ b/lib/include/srslte/interfaces/enb_interfaces.h @@ -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; diff --git a/srsenb/hdr/stack/rrc/rrc.h b/srsenb/hdr/stack/rrc/rrc.h index 0dd4da54f..60649e500 100644 --- a/srsenb/hdr/stack/rrc/rrc.h +++ b/srsenb/hdr/stack/rrc/rrc.h @@ -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); diff --git a/srsenb/hdr/stack/upper/s1ap.h b/srsenb/hdr/stack/upper/s1ap.h index bdcfb016a..5ad28980e 100644 --- a/srsenb/hdr/stack/upper/s1ap.h +++ b/srsenb/hdr/stack/upper/s1ap.h @@ -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); diff --git a/srsenb/src/stack/rrc/rrc.cc b/srsenb/src/stack/rrc/rrc.cc index d3f13aab6..f561bdc84 100644 --- a/srsenb/src/stack/rrc/rrc.cc +++ b/srsenb/src/stack/rrc/rrc.cc @@ -292,9 +292,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"); } @@ -343,6 +340,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(); @@ -354,6 +359,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); @@ -739,6 +813,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; } @@ -949,6 +1027,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(); } @@ -1563,6 +1642,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); } diff --git a/srsenb/src/stack/upper/s1ap.cc b/srsenb/src/stack/upper/s1ap.cc index 6bfdae822..c94c3b059 100644 --- a/srsenb/src/stack/upper/s1ap.cc +++ b/srsenb/src/stack/upper/s1ap.cc @@ -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) //{