/* * Copyright (C) 2019 by Sukchan Lee * Copyright (C) 2022 by sysmocom - s.f.m.c. GmbH * * 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 "event.h" #include "context.h" #include "gtp-path.h" #include "fd-path.h" #include "gn-build.h" #include "gn-handler.h" #include "n4-handler.h" #include "pfcp-path.h" #include "ipfw/ipfw2.h" void smf_gn_handle_echo_request( ogs_gtp_xact_t *xact, ogs_gtp1_echo_request_t *req) { ogs_assert(xact); ogs_assert(req); ogs_debug("[PGW] Receiving Echo Request"); /* FIXME : Implementing recovery counter correctly */ ogs_gtp1_send_echo_response(xact, 0); } void smf_gn_handle_echo_response( ogs_gtp_xact_t *xact, ogs_gtp1_echo_response_t *req) { /* Not Implemented */ } uint8_t smf_gn_handle_create_pdp_context_request( smf_sess_t *sess, ogs_gtp_xact_t *xact, ogs_gtp1_create_pdp_context_request_t *req) { char buf1[OGS_ADDRSTRLEN]; char buf2[OGS_ADDRSTRLEN]; int rv; uint8_t cause_value = 0; uint8_t pfcp_cause; ogs_gtp1_uli_t uli; smf_ue_t *smf_ue = NULL; ogs_eua_t *eua = NULL; smf_bearer_t *bearer = NULL; ogs_gtp1_qos_profile_decoded_t qos_pdec; uint8_t qci = 9; ogs_assert(sess); ogs_assert(xact); ogs_assert(req); ogs_debug("Create PDP Context Request"); cause_value = OGS_GTP1_CAUSE_REQUEST_ACCEPTED; if (req->imsi.presence == 0) { ogs_error("No IMSI"); cause_value = OGS_GTP1_CAUSE_MANDATORY_IE_MISSING; } if (req->selection_mode.presence == 0) { ogs_error("No Selection Mode"); cause_value = OGS_GTP1_CAUSE_MANDATORY_IE_MISSING; } if (req->tunnel_endpoint_identifier_data_i.presence == 0) { ogs_error("No TEID"); cause_value = OGS_GTP1_CAUSE_MANDATORY_IE_MISSING; } if (req->nsapi.presence == 0) { ogs_error("No NSAPI"); cause_value = OGS_GTP1_CAUSE_MANDATORY_IE_MISSING; } if (req->sgsn_address_for_signalling.presence == 0) { ogs_error("No SGSN Address for signalling"); cause_value = OGS_GTP1_CAUSE_MANDATORY_IE_MISSING; } if (req->sgsn_address_for_user_traffic.presence == 0) { ogs_error("No SGSN Address for user traffic"); cause_value = OGS_GTP1_CAUSE_MANDATORY_IE_MISSING; } if (req->msisdn.presence == 0) { ogs_error("No MSISDN"); cause_value = OGS_GTP1_CAUSE_MANDATORY_IE_MISSING; } if (req->quality_of_service_profile.presence == 0) { ogs_error("No QoS Profile"); cause_value = OGS_GTP1_CAUSE_MANDATORY_IE_MISSING; } if (req->user_location_information.presence == 0) { ogs_error("No User Location Info"); cause_value = OGS_GTP1_CAUSE_MANDATORY_IE_MISSING; } if (!ogs_diam_app_connected(OGS_DIAM_GX_APPLICATION_ID)) { ogs_error("No Gx Diameter Peer"); cause_value = OGS_GTP1_CAUSE_NO_RESOURCES_AVAILABLE; } if (cause_value != OGS_GTP1_CAUSE_REQUEST_ACCEPTED) return cause_value; smf_ue = sess->smf_ue; ogs_assert(smf_ue); /* Store NSAPI */ sess->gtp.v1.nsapi = req->nsapi.u8; /* Selection Mode, TS 29.060 7.7.12 */ sess->gtp.selection_mode = req->selection_mode.u8 & 0x03; if (sess->gtp.selection_mode > 2) sess->gtp.selection_mode = 2; if (req->charging_characteristics.presence) { OGS_TLV_STORE_DATA(&sess->gtp.charging_characteristics, &req->charging_characteristics); } /* Control Plane(DL) : SGW-S5C */ sess->sgw_s5c_teid = req->tunnel_endpoint_identifier_control_plane.u32; rv = ogs_gtp1_gsn_addr_to_ip(req->sgsn_address_for_signalling.data, req->sgsn_address_for_signalling.len, &sess->sgw_s5c_ip); ogs_assert(rv == OGS_OK); ogs_debug(" SGW_S5C_TEID[0x%x] SMF_N4_TEID[0x%x]", sess->sgw_s5c_teid, sess->smf_n4_teid); if (ogs_gtp1_parse_uli(&uli, &req->user_location_information) == 0) return OGS_GTP1_CAUSE_MANDATORY_IE_INCORRECT; /* TODO: Copy uli->cgi/sai/rai into sess-> */ switch (uli.geo_loc_type) { case OGS_GTP1_GEO_LOC_TYPE_CGI: ogs_nas_to_plmn_id(&sess->plmn_id, &uli.cgi.nas_plmn_id); break; case OGS_GTP1_GEO_LOC_TYPE_SAI: ogs_nas_to_plmn_id(&sess->plmn_id, &uli.sai.nas_plmn_id); break; case OGS_GTP1_GEO_LOC_TYPE_RAI: ogs_nas_to_plmn_id(&sess->plmn_id, &uli.rai.nas_plmn_id); break; } /* Set MSISDN: */ /* TS 29.060 sec 7.7.33, TS 29.002 ISDN-AddressString * 1 byte offset: Get rid of address and numbering plan indicator */ if (req->msisdn.len == 0 || (req->msisdn.len - 1) > sizeof(smf_ue->msisdn)) { ogs_error("MSISDN wrong size %u > %zu", (req->msisdn.len - 1), sizeof(smf_ue->msisdn)); return OGS_GTP1_CAUSE_MANDATORY_IE_INCORRECT; } smf_ue->msisdn_len = req->msisdn.len - 1; if (smf_ue->msisdn_len > 0) { memcpy(smf_ue->msisdn, (uint8_t*)req->msisdn.data + 1, smf_ue->msisdn_len); ogs_buffer_to_bcd( smf_ue->msisdn, smf_ue->msisdn_len, smf_ue->msisdn_bcd); } /* Set Bearer QoS */ rv = ogs_gtp1_parse_qos_profile(&qos_pdec, &req->quality_of_service_profile); if(rv < 0) return OGS_GTP1_CAUSE_MANDATORY_IE_INCORRECT; /* 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); sess->session.qos.index = qci; sess->session.qos.arp.priority_level = qos_pdec.qos_profile.arp; /* 3GPP TS 23.401 Annex E Table E.2 */ sess->session.qos.arp.pre_emption_capability = 0; /* ignored as per 3GPP TS 23.401 Annex E */ sess->session.qos.arp.pre_emption_vulnerability = 0; /* ignored as per 3GPP TS 23.401 Annex E */ if (qos_pdec.data_octet6_to_13_present) { sess->session.ambr.downlink = qos_pdec.dec_mbr_kbps_dl * 1000; sess->session.ambr.uplink = qos_pdec.dec_mbr_kbps_ul * 1000; } else { /* Set some sane default if infomation not present in Qos Profile IE: */ sess->session.ambr.downlink = 102400000; sess->session.ambr.uplink = 102400000; } /* PCO */ if (req->protocol_configuration_options.presence) { OGS_TLV_STORE_DATA(&sess->gtp.ue_pco, &req->protocol_configuration_options); } #if 0 /* Set User Location Information */ /* TODO: the IE is probably different between GTPv1C and GTPv2, see what needs to be adapted */ if (req->user_location_information.presence) { OGS_TLV_STORE_DATA(&sess->gtp.user_location_information, &req->user_location_information); } #endif /* Set UE Timezone */ if (req->ms_time_zone.presence) { /* value part is compatible between UE Time Zone and MS Time Zone */ OGS_TLV_STORE_DATA(&sess->gtp.ue_timezone, &req->ms_time_zone); } /* Set IMEI(SV) */ if (req->imei.presence && req->imei.len > 0) { smf_ue->imeisv_len = req->imei.len; memcpy(smf_ue->imeisv, (uint8_t*)req->imei.data, smf_ue->imeisv_len); ogs_buffer_to_bcd( smf_ue->imeisv, smf_ue->imeisv_len, smf_ue->imeisv_bcd); } /* UE IP Address */ eua = req->end_user_address.data; ogs_assert(eua); rv = ogs_gtp1_eua_to_ip(eua, req->end_user_address.len, &sess->session.ue_ip, &sess->ue_session_type); if(rv != OGS_OK) return OGS_GTP1_CAUSE_MANDATORY_IE_INCORRECT; /* Initially Set Session Type from UE */ sess->session.session_type = sess->ue_session_type; /* Remove all previous bearer */ smf_bearer_remove_all(sess); /* Setup Default Bearer */ bearer = smf_bearer_add(sess); ogs_assert(bearer); /* Set Bearer EBI */ /* 3GPP TS 23.060 clause 9.2.1A: "1:1 mapping between NSAPI and EPS Bearer ID" */ /* 3GPP TS 23.401 clause 5.2.1: "the same identity value is used for the EPS bearer ID and the NSAPI/RAB ID" */ bearer->ebi = req->nsapi.u8; /* User Plane(DL) : SGW-S5C */ bearer->sgw_s5u_teid = req->tunnel_endpoint_identifier_data_i.u32; rv = ogs_gtp1_gsn_addr_to_ip(req->sgsn_address_for_user_traffic.data, req->sgsn_address_for_user_traffic.len, &bearer->sgw_s5u_ip); ogs_assert(rv == OGS_OK); ogs_debug(" SGW_S5U_TEID[0x%x] PGW_S5U_TEID[0x%x]", bearer->sgw_s5u_teid, bearer->pgw_s5u_teid); /* Select PGW based on UE Location Information */ smf_sess_select_upf(sess); /* Check if selected PGW is associated with SMF */ ogs_assert(sess->pfcp_node); if (!OGS_FSM_CHECK(&sess->pfcp_node->sm, smf_pfcp_state_associated)) return OGS_GTP1_CAUSE_NO_RESOURCES_AVAILABLE; if ((pfcp_cause = smf_sess_set_ue_ip(sess)) != OGS_PFCP_CAUSE_REQUEST_ACCEPTED) { cause_value = gtp_cause_from_pfcp(pfcp_cause, 1); return cause_value; } ogs_info("UE IMSI[%s] APN[%s] IPv4[%s] IPv6[%s]", smf_ue->imsi_bcd, sess->session.name, sess->ipv4 ? OGS_INET_NTOP(&sess->ipv4->addr, buf1) : "", sess->ipv6 ? OGS_INET6_NTOP(&sess->ipv6->addr, buf2) : ""); return cause_value; } uint8_t smf_gn_handle_delete_pdp_context_request( smf_sess_t *sess, ogs_gtp_xact_t *xact, ogs_gtp1_delete_pdp_context_request_t *req) { ogs_debug("Delete PDP Context Request"); if (!ogs_diam_app_connected(OGS_DIAM_GX_APPLICATION_ID)) { ogs_error("No Gx Diameter Peer"); return OGS_GTP1_CAUSE_NO_RESOURCES_AVAILABLE; } ogs_debug(" SGW_S5C_TEID[0x%x] SMF_N4_TEID[0x%x]", sess->sgw_s5c_teid, sess->smf_n4_teid); return OGS_GTP1_CAUSE_REQUEST_ACCEPTED; } void smf_gn_handle_update_pdp_context_request( smf_sess_t *sess, ogs_gtp_xact_t *xact, ogs_gtp1_update_pdp_context_request_t *req) { int rv; uint8_t cause_value = OGS_GTP1_CAUSE_REQUEST_ACCEPTED; ogs_gtp1_header_t h; ogs_pfcp_pdr_t *pdr = NULL; smf_bearer_t *bearer = NULL; smf_ue_t *smf_ue = NULL; ogs_debug("Update PDP Context Request"); ogs_assert(xact); ogs_assert(req); if (req->nsapi.presence == 0) { ogs_error("No NSAPI"); cause_value = OGS_GTP1_CAUSE_MANDATORY_IE_MISSING; } if (req->tunnel_endpoint_identifier_data_i.presence == 0) { ogs_error("No TEID"); cause_value = OGS_GTP1_CAUSE_MANDATORY_IE_MISSING; } if (req->sgsn_address_for_control_plane.presence == 0) { ogs_error("SGSN Address for signalling"); cause_value = OGS_GTP1_CAUSE_MANDATORY_IE_MISSING; } if (req->sgsn_address_for_user_traffic.presence == 0) { ogs_error("SGSN Address for user traffic"); cause_value = OGS_GTP1_CAUSE_MANDATORY_IE_MISSING; } if (req->quality_of_service_profile.presence == 0) { ogs_error("No QoS Profile"); cause_value = OGS_GTP1_CAUSE_MANDATORY_IE_MISSING; } if (!sess) { ogs_warn("No Context"); cause_value = OGS_GTP1_CAUSE_NON_EXISTENT; } if (cause_value != OGS_GTP1_CAUSE_REQUEST_ACCEPTED) { ogs_gtp1_send_error_message(xact, sess ? sess->sgw_s5c_teid : 0, OGS_GTP1_UPDATE_PDP_CONTEXT_RESPONSE_TYPE, cause_value); return; } ogs_assert(sess); smf_ue = sess->smf_ue; ogs_assert(smf_ue); ogs_debug(" SGW_S5C_TEID[0x%x] SMF_N4_TEID[0x%x]", sess->sgw_s5c_teid, sess->smf_n4_teid); bearer = smf_bearer_find_by_ebi(sess, req->nsapi.u8); if (!bearer) { ogs_warn("No bearer with id %u, taking default", req->nsapi.u8); bearer = smf_default_bearer_in_sess(sess); if (!bearer) { ogs_warn("No bearer"); ogs_gtp1_send_error_message(xact, sess->sgw_s5c_teid, OGS_GTP1_UPDATE_PDP_CONTEXT_RESPONSE_TYPE, OGS_GTP1_CAUSE_NON_EXISTENT); return; } } /* Control Plane(DL) : SGW-S5C */ if (req->tunnel_endpoint_identifier_control_plane.presence) { sess->sgw_s5c_teid = req->tunnel_endpoint_identifier_control_plane.u32; rv = ogs_gtp1_gsn_addr_to_ip(req->sgsn_address_for_control_plane.data, req->sgsn_address_for_control_plane.len, &sess->sgw_s5c_ip); ogs_assert(rv == OGS_OK); ogs_debug(" Updated SGW_S5C_TEID[0x%x] SMF_N4_TEID[0x%x]", sess->sgw_s5c_teid, sess->smf_n4_teid); } /* User Plane(DL) : SGW-S5U */ bearer->sgw_s5u_teid = req->tunnel_endpoint_identifier_data_i.u32; rv = ogs_gtp1_gsn_addr_to_ip(req->sgsn_address_for_user_traffic.data, req->sgsn_address_for_user_traffic.len, &bearer->sgw_s5u_ip); ogs_assert(rv == OGS_OK); ogs_debug(" Updated SGW_S5U_TEID[0x%x] PGW_S5U_TEID[0x%x]", bearer->sgw_s5u_teid, bearer->pgw_s5u_teid); /* FIXME: UPF needs to be updated through PFCP here, see: * https://github.com/open5gs/open5gs/issues/1367 */ memset(&h, 0, sizeof(ogs_gtp2_header_t)); h.type = OGS_GTP1_UPDATE_PDP_CONTEXT_RESPONSE_TYPE; h.teid = sess->sgw_s5c_teid; /* Set bearer so it's accessible later when handling PFCP Session Modification Response */ xact->data = bearer; /* Update remote TEID and GTP-U IP address on the UPF. UpdatePDPContextResp * will be sent when UPF answers back this request */ ogs_list_for_each(&sess->pfcp.pdr_list, pdr) { ogs_pfcp_far_t *far = pdr->far; ogs_assert(far); if (pdr->src_if != OGS_PFCP_INTERFACE_CORE || far->dst_if != OGS_PFCP_INTERFACE_ACCESS) continue; if (!(far->apply_action & OGS_PFCP_APPLY_ACTION_FORW)) continue; if (pdr->id == bearer->dl_pdr->id) { rv = ogs_pfcp_ip_to_outer_header_creation(&bearer->sgw_s5u_ip, &far->outer_header_creation, &far->outer_header_creation_len); ogs_assert(rv == OGS_OK); far->outer_header_creation.teid = bearer->sgw_s5u_teid; } } rv = smf_epc_pfcp_send_pdr_modification_request(sess, xact, NULL, OGS_PFCP_MODIFY_DL_ONLY|OGS_PFCP_MODIFY_ACTIVATE, OGS_NAS_PROCEDURE_TRANSACTION_IDENTITY_UNASSIGNED, OGS_GTP1_CAUSE_REACTIACTION_REQUESTED); ogs_assert(rv == OGS_OK); /* TODO: TS 29.061: Upon reception of an UpdatePDPContextRequest from the SGSN, the GGSN may send an Accounting Request (Interim) to the Diameter server to update the necessary information related to this PDP context. */ /* The P-GW need not wait for the Diameter Accounting Answer message from the Diameter server before sending the response for the triggering signalling message (e.g. Modify Bearer Response). The P-GW may delete the bearer if the Accounting Answer is not received from the Diameter server.*/ }