/* * Copyright (C) 2019 by Sukchan Lee * * This file is part of Open5GS. * * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "namf-handler.h" #include "nsmf-handler.h" #include "nas-path.h" #include "ngap-path.h" #include "sbi-path.h" int amf_namf_comm_handle_n1_n2_message_transfer( ogs_sbi_stream_t *stream, ogs_sbi_message_t *recvmsg) { int status; amf_ue_t *amf_ue = NULL; amf_sess_t *sess = NULL; ogs_pkbuf_t *n1buf = NULL; ogs_pkbuf_t *n2buf = NULL; ogs_pkbuf_t *gmmbuf = NULL; ogs_pkbuf_t *ngapbuf = NULL; char *supi = NULL; uint8_t pdu_session_id = OGS_NAS_PDU_SESSION_IDENTITY_UNASSIGNED; ogs_sbi_message_t sendmsg; ogs_sbi_response_t *response = NULL; OpenAPI_n1_n2_message_transfer_req_data_t *N1N2MessageTransferReqData; OpenAPI_n1_n2_message_transfer_rsp_data_t N1N2MessageTransferRspData; OpenAPI_n1_message_container_t *n1MessageContainer = NULL; OpenAPI_ref_to_binary_data_t *n1MessageContent = NULL; OpenAPI_n2_info_container_t *n2InfoContainer = NULL; OpenAPI_n2_sm_information_t *smInfo = NULL; OpenAPI_n2_info_content_t *n2InfoContent = NULL; OpenAPI_ref_to_binary_data_t *ngapData = NULL; OpenAPI_ngap_ie_type_e ngapIeType = OpenAPI_ngap_ie_type_NULL; ogs_assert(stream); ogs_assert(recvmsg); N1N2MessageTransferReqData = recvmsg->N1N2MessageTransferReqData; if (!N1N2MessageTransferReqData) { ogs_error("No N1N2MessageTransferReqData"); return OGS_ERROR; } if (N1N2MessageTransferReqData->is_pdu_session_id == false) { ogs_error("No PDU Session Identity"); return OGS_ERROR; } pdu_session_id = N1N2MessageTransferReqData->pdu_session_id; supi = recvmsg->h.resource.component[1]; if (!supi) { ogs_error("No SUPI"); return OGS_ERROR; } amf_ue = amf_ue_find_by_supi(supi); if (!amf_ue) { ogs_error("No UE context [%s]", supi); return OGS_ERROR; } sess = amf_sess_find_by_psi(amf_ue, pdu_session_id); if (!sess) { ogs_error("[%s] No PDU Session Context [%d]", amf_ue->supi, pdu_session_id); return OGS_ERROR; } n1MessageContainer = N1N2MessageTransferReqData->n1_message_container; if (n1MessageContainer) { n1MessageContent = n1MessageContainer->n1_message_content; if (!n1MessageContent || !n1MessageContent->content_id) { ogs_error("No n1MessageContent"); return OGS_ERROR; } n1buf = ogs_sbi_find_part_by_content_id( recvmsg, n1MessageContent->content_id); if (!n1buf) { ogs_error("[%s] No N1 SM Content", amf_ue->supi); return OGS_ERROR; } /* * NOTE : The pkbuf created in the SBI message will be removed * from ogs_sbi_message_free(), so it must be copied. */ n1buf = ogs_pkbuf_copy(n1buf); ogs_assert(n1buf); } n2InfoContainer = N1N2MessageTransferReqData->n2_info_container; if (n2InfoContainer) { smInfo = n2InfoContainer->sm_info; if (!smInfo) { ogs_error("No smInfo"); return OGS_ERROR; } n2InfoContent = smInfo->n2_info_content; if (!n2InfoContent) { ogs_error("No n2InfoContent"); return OGS_ERROR; } ngapIeType = n2InfoContent->ngap_ie_type; ngapData = n2InfoContent->ngap_data; if (!ngapData || !ngapData->content_id) { ogs_error("No ngapData"); return OGS_ERROR; } n2buf = ogs_sbi_find_part_by_content_id( recvmsg, ngapData->content_id); if (!n2buf) { ogs_error("[%s] No N2 SM Content", amf_ue->supi); return OGS_ERROR; } /* * NOTE : The pkbuf created in the SBI message will be removed * from ogs_sbi_message_free(), so it must be copied. */ n2buf = ogs_pkbuf_copy(n2buf); ogs_assert(n2buf); } memset(&sendmsg, 0, sizeof(sendmsg)); status = OGS_SBI_HTTP_STATUS_OK; memset(&N1N2MessageTransferRspData, 0, sizeof(N1N2MessageTransferRspData)); N1N2MessageTransferRspData.cause = OpenAPI_n1_n2_message_transfer_cause_N1_N2_TRANSFER_INITIATED; sendmsg.N1N2MessageTransferRspData = &N1N2MessageTransferRspData; switch (ngapIeType) { case OpenAPI_ngap_ie_type_PDU_RES_SETUP_REQ: if (!n2buf) { ogs_error("[%s] No N2 SM Content", amf_ue->supi); return OGS_ERROR; } if (n1buf) { gmmbuf = gmm_build_dl_nas_transport(sess, OGS_NAS_PAYLOAD_CONTAINER_N1_SM_INFORMATION, n1buf, 0, 0); ogs_assert(gmmbuf); } if (gmmbuf) { ran_ue_t *ran_ue = NULL; /*********************************** * 4.3.2 PDU Session Establishment * ***********************************/ ran_ue = ran_ue_cycle(amf_ue->ran_ue); ogs_assert(ran_ue); if (sess->pdu_session_establishment_accept) { ogs_pkbuf_free(sess->pdu_session_establishment_accept); sess->pdu_session_establishment_accept = NULL; } if (ran_ue->initial_context_setup_request_sent == true) { ngapbuf = ngap_sess_build_pdu_session_resource_setup_request( sess, gmmbuf, n2buf); ogs_assert(ngapbuf); } else { ngapbuf = ngap_sess_build_initial_context_setup_request( sess, gmmbuf, n2buf); ogs_assert(ngapbuf); ran_ue->initial_context_setup_request_sent = true; } if (SESSION_CONTEXT_IN_SMF(sess)) { /* * [1-CLIENT] /nsmf-pdusession/v1/sm-contexts * [2-SERVER] /namf-comm/v1/ue-contexts/{supi}/n1-n2-messages * * If [2-SERVER] arrives after [1-CLIENT], * sm-context-ref is created in [1-CLIENT]. * So, the PDU session establishment accpet can be transmitted. */ if (nas_5gs_send_to_gnb(amf_ue, ngapbuf) != OGS_OK) ogs_error("nas_5gs_send_to_gnb() failed"); } else { sess->pdu_session_establishment_accept = ngapbuf; } } else { /********************************************* * 4.2.3.3 Network Triggered Service Request * *********************************************/ if (CM_IDLE(amf_ue)) { ogs_sbi_server_t *server = NULL; ogs_sbi_header_t header; ogs_sbi_client_t *client = NULL; ogs_sockaddr_t *addr = NULL; if (!N1N2MessageTransferReqData->n1n2_failure_txf_notif_uri) { ogs_error("[%s:%d] No n1-n2-failure-notification-uri", amf_ue->supi, sess->psi); return OGS_ERROR; } addr = ogs_sbi_getaddr_from_uri( N1N2MessageTransferReqData->n1n2_failure_txf_notif_uri); if (!addr) { ogs_error("[%s:%d] Invalid URI [%s]", amf_ue->supi, sess->psi, N1N2MessageTransferReqData-> n1n2_failure_txf_notif_uri); return OGS_ERROR;; } client = ogs_sbi_client_find(addr); if (!client) { client = ogs_sbi_client_add(addr); ogs_assert(client); } OGS_SBI_SETUP_CLIENT(&sess->paging, client); ogs_freeaddrinfo(addr); status = OGS_SBI_HTTP_STATUS_ACCEPTED; N1N2MessageTransferRspData.cause = OpenAPI_n1_n2_message_transfer_cause_ATTEMPTING_TO_REACH_UE; /* Location */ server = ogs_sbi_server_from_stream(stream); ogs_assert(server); memset(&header, 0, sizeof(header)); header.service.name = (char *)OGS_SBI_SERVICE_NAME_NAMF_COMM; header.api.version = (char *)OGS_SBI_API_V1; header.resource.component[0] = (char *)OGS_SBI_RESOURCE_NAME_UE_CONTEXTS; header.resource.component[1] = amf_ue->supi; header.resource.component[2] = (char *)OGS_SBI_RESOURCE_NAME_N1_N2_MESSAGES; header.resource.component[3] = sess->sm_context_ref; sendmsg.http.location = ogs_sbi_server_uri(server, &header); /* Store Paging Info */ AMF_SESS_STORE_PAGING_INFO( sess, sendmsg.http.location, N1N2MessageTransferReqData->n1n2_failure_txf_notif_uri); /* Store N2 Transfer message */ AMF_SESS_STORE_N2_TRANSFER( sess, pdu_session_resource_setup_request, n2buf); ogs_assert(OGS_OK == ngap_send_paging(amf_ue)); } else if (CM_CONNECTED(amf_ue)) { ogs_assert(OGS_OK == ngap_send_pdu_resource_setup_request(sess, n2buf)); } else { ogs_fatal("[%s] Invalid AMF-UE state", amf_ue->supi); ogs_assert_if_reached(); } } break; case OpenAPI_ngap_ie_type_PDU_RES_MOD_REQ: if (!n1buf) { ogs_error("[%s] No N1 SM Content", amf_ue->supi); return OGS_ERROR; } if (!n2buf) { ogs_error("[%s] No N2 SM Content", amf_ue->supi); return OGS_ERROR; } if (CM_IDLE(amf_ue)) { ogs_sbi_server_t *server = NULL; ogs_sbi_header_t header; status = OGS_SBI_HTTP_STATUS_ACCEPTED; N1N2MessageTransferRspData.cause = OpenAPI_n1_n2_message_transfer_cause_ATTEMPTING_TO_REACH_UE; /* Location */ server = ogs_sbi_server_from_stream(stream); ogs_assert(server); memset(&header, 0, sizeof(header)); header.service.name = (char *)OGS_SBI_SERVICE_NAME_NAMF_COMM; header.api.version = (char *)OGS_SBI_API_V1; header.resource.component[0] = (char *)OGS_SBI_RESOURCE_NAME_UE_CONTEXTS; header.resource.component[1] = amf_ue->supi; header.resource.component[2] = (char *)OGS_SBI_RESOURCE_NAME_N1_N2_MESSAGES; header.resource.component[3] = sess->sm_context_ref; sendmsg.http.location = ogs_sbi_server_uri(server, &header); /* Store Paging Info */ AMF_SESS_STORE_PAGING_INFO( sess, sendmsg.http.location, NULL); /* Store 5GSM Message */ AMF_SESS_STORE_5GSM_MESSAGE(sess, OGS_NAS_5GS_PDU_SESSION_MODIFICATION_COMMAND, n1buf, n2buf); ogs_assert(OGS_OK == ngap_send_paging(amf_ue)); } else if (CM_CONNECTED(amf_ue)) { gmmbuf = gmm_build_dl_nas_transport(sess, OGS_NAS_PAYLOAD_CONTAINER_N1_SM_INFORMATION, n1buf, 0, 0); ogs_assert(gmmbuf); ngapbuf = ngap_build_pdu_session_resource_modify_request( sess, gmmbuf, n2buf); ogs_assert(ngapbuf); if (nas_5gs_send_to_gnb(amf_ue, ngapbuf) != OGS_OK) ogs_error("nas_5gs_send_to_gnb() failed"); } else { ogs_fatal("[%s] Invalid AMF-UE state", amf_ue->supi); ogs_assert_if_reached(); } break; case OpenAPI_ngap_ie_type_PDU_RES_REL_CMD: if (!n2buf) { ogs_error("[%s] No N2 SM Content", amf_ue->supi); return OGS_ERROR; } if (n1buf) ogs_pkbuf_free(n1buf); if (CM_IDLE(amf_ue)) { if (n2buf) ogs_pkbuf_free(n2buf); if (N1N2MessageTransferReqData->is_skip_ind == true && N1N2MessageTransferReqData->skip_ind == true) { N1N2MessageTransferRspData.cause = OpenAPI_n1_n2_message_transfer_cause_N1_MSG_NOT_TRANSFERRED; } else { ogs_fatal("[%s] No skipInd", amf_ue->supi); ogs_assert_if_reached(); } } else if (CM_CONNECTED(amf_ue)) { ngapbuf = ngap_build_pdu_session_resource_release_command( sess, NULL, n2buf); ogs_assert(ngapbuf); if (nas_5gs_send_to_gnb(amf_ue, ngapbuf) != OGS_OK) ogs_error("nas_5gs_send_to_gnb() failed"); } else { ogs_fatal("[%s] Invalid AMF-UE state", amf_ue->supi); ogs_assert_if_reached(); } break; default: ogs_error("Not implemented ngapIeType[%d]", ngapIeType); ogs_assert_if_reached(); } response = ogs_sbi_build_response(&sendmsg, status); ogs_assert(response); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); if (sendmsg.http.location) ogs_free(sendmsg.http.location); return OGS_OK; } int amf_namf_callback_handle_sm_context_status( ogs_sbi_stream_t *stream, ogs_sbi_message_t *recvmsg) { int status = OGS_SBI_HTTP_STATUS_NO_CONTENT; amf_ue_t *amf_ue = NULL; amf_sess_t *sess = NULL; uint8_t pdu_session_identity; ogs_sbi_message_t sendmsg; ogs_sbi_response_t *response = NULL; OpenAPI_sm_context_status_notification_t *SmContextStatusNotification; OpenAPI_status_info_t *StatusInfo; ogs_assert(stream); ogs_assert(recvmsg); if (!recvmsg->h.resource.component[0]) { status = OGS_SBI_HTTP_STATUS_BAD_REQUEST; ogs_error("No SUPI"); goto cleanup; } amf_ue = amf_ue_find_by_supi(recvmsg->h.resource.component[0]); if (!amf_ue) { status = OGS_SBI_HTTP_STATUS_NOT_FOUND; ogs_error("Cannot find SUPI [%s]", recvmsg->h.resource.component[0]); goto cleanup; } if (!recvmsg->h.resource.component[2]) { status = OGS_SBI_HTTP_STATUS_BAD_REQUEST; ogs_error("[%s] No PDU Session Identity", amf_ue->supi); goto cleanup; } pdu_session_identity = atoi(recvmsg->h.resource.component[2]); if (pdu_session_identity == OGS_NAS_PDU_SESSION_IDENTITY_UNASSIGNED) { status = OGS_SBI_HTTP_STATUS_BAD_REQUEST; ogs_error("[%s] PDU Session Identity is unassigned", amf_ue->supi); goto cleanup; } sess = amf_sess_find_by_psi(amf_ue, pdu_session_identity); if (!sess) { status = OGS_SBI_HTTP_STATUS_NOT_FOUND; ogs_warn("[%s] Cannot find session", amf_ue->supi); goto cleanup; } SmContextStatusNotification = recvmsg->SmContextStatusNotification; if (!SmContextStatusNotification) { status = OGS_SBI_HTTP_STATUS_BAD_REQUEST; ogs_error("[%s:%d] No SmContextStatusNotification", amf_ue->supi, sess->psi); goto cleanup; } StatusInfo = SmContextStatusNotification->status_info; if (!StatusInfo) { status = OGS_SBI_HTTP_STATUS_BAD_REQUEST; ogs_error("[%s:%d] No StatusInfo", amf_ue->supi, sess->psi); goto cleanup; } sess->resource_status = StatusInfo->resource_status; /* * Race condition for PDU session release complete * - CLIENT : /nsmf-pdusession/v1/sm-contexts/{smContextRef}/modify * - SERVER : /namf-callback/v1/{supi}/sm-context-status/{psi}) * * If NOTIFICATION is received before the CLIENT response is received, * CLIENT sync is not finished. In this case, the session context * should not be removed. * * If NOTIFICATION comes after the CLIENT response is received, * sync is done. So, the session context can be removed. */ if (sess->n1_released == true && sess->n2_released == true && sess->resource_status == OpenAPI_resource_status_RELEASED) { ogs_debug("[%s:%d] SM context remove", amf_ue->supi, sess->psi); amf_nsmf_pdusession_handle_release_sm_context( sess, AMF_RELEASE_SM_CONTEXT_NO_STATE); } cleanup: memset(&sendmsg, 0, sizeof(sendmsg)); response = ogs_sbi_build_response(&sendmsg, status); ogs_assert(response); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); return OGS_OK; } int amf_namf_callback_handle_dereg_notify( ogs_sbi_stream_t *stream, ogs_sbi_message_t *recvmsg) { int status = OGS_SBI_HTTP_STATUS_NO_CONTENT; amf_ue_t *amf_ue = NULL; ogs_sbi_message_t sendmsg; ogs_sbi_response_t *response = NULL; OpenAPI_deregistration_data_t *DeregistrationData; ogs_assert(stream); ogs_assert(recvmsg); if (!recvmsg->h.resource.component[0]) { status = OGS_SBI_HTTP_STATUS_BAD_REQUEST; ogs_error("No SUPI"); goto cleanup; } amf_ue = amf_ue_find_by_supi(recvmsg->h.resource.component[0]); if (!amf_ue) { status = OGS_SBI_HTTP_STATUS_NOT_FOUND; ogs_error("Cannot find SUPI [%s]", recvmsg->h.resource.component[0]); goto cleanup; } DeregistrationData = recvmsg->DeregistrationData; if (!DeregistrationData) { status = OGS_SBI_HTTP_STATUS_BAD_REQUEST; ogs_error("[%s] No DeregistrationData", amf_ue->supi); goto cleanup; } if (DeregistrationData->access_type != OpenAPI_access_type_3GPP_ACCESS) { status = OGS_SBI_HTTP_STATUS_BAD_REQUEST; ogs_error("[%s] Deregistration access type not 3GPP", amf_ue->supi); goto cleanup; } ogs_info("Deregistration notify reason: %s:%s:%s", amf_ue->supi, OpenAPI_deregistration_reason_ToString(DeregistrationData->dereg_reason), OpenAPI_access_type_ToString(DeregistrationData->access_type)); /* * TODO: do not start deregistration if UE has emergency sessions * 4.2.2.3.3 * If the UE has established PDU Session associated with emergency service, the AMF shall not initiate * Deregistration procedure. In this case, the AMF performs network requested PDU Session Release for any PDU * session associated with non-emergency service as described in clause 4.3.4. */ if (CM_CONNECTED(amf_ue)) { amf_ue->network_initiated_de_reg = true; ogs_assert(OGS_OK == nas_5gs_send_de_registration_request(amf_ue)); amf_sbi_send_release_all_sessions( amf_ue, AMF_RELEASE_SM_CONTEXT_NO_STATE); if (ogs_list_count(&amf_ue->sess_list) == 0) ogs_assert(true == amf_ue_sbi_discover_and_send( OpenAPI_nf_type_PCF, amf_ue, NULL, amf_npcf_am_policy_control_build_delete)); OGS_FSM_TRAN(&amf_ue->sm, &gmm_state_de_registered); } else if (CM_IDLE(amf_ue)) { /* TODO: need to page UE */ /*ngap_send_paging(amf_ue);*/ } cleanup: memset(&sendmsg, 0, sizeof(sendmsg)); response = ogs_sbi_build_response(&sendmsg, status); ogs_assert(response); ogs_assert(true == ogs_sbi_server_send_response(stream, response)); return OGS_OK; }