/* * 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 "sbi-path.h" #include "pfcp-path.h" #include "nas-path.h" #include "binding.h" #include "npcf-handler.h" static void update_authorized_pcc_rule_and_qos( smf_sess_t *sess, OpenAPI_sm_policy_decision_t *SmPolicyDecision) { OpenAPI_lnode_t *node = NULL, *node2 = NULL; ogs_assert(sess); ogs_assert(SmPolicyDecision); /* * TS29.512 * 4.2.6 Provisioning and Enforcement of Policy Decisions * * * If no other rules are defined for specific data types * within the SmPolicyDecision data structure, the encoding of changes * of the policies decisions in the SmPolicyDecision data structure * shall follow the following principles: * * 1) To modify an attribute with a value of type map * (e.g. the "sessRules" attribute, the "pccRules" attribute, * the "qosDecs" attribute, the "traffContDecs" attribute, * the "umDecs" attribute, and the "conds" attribute) the attribute * shall be provided with a value containing a map with entries * according to the following principles: * * - A new entry shall be added by supplying a new identifier * (e.g. rule / decision identifier) as key and the corresponding * structured data type instance (e.g. PCC rule) with complete contents * as value as an entry within the map. * - An existing entry shall be modified by supplying the existing * identifier as key and the corresponding structured data type instance * with the same existing identifier (e.g. set the "qosId" * to the same existing QoS data decision identifier), * which shall describe the modifications following bullets 1 to 6, * as value as an entry within the map. * - An existing entry shall be deleted by supplying the existing * identifier as key and "NULL" as value as an entry within the map. * - For an unmodified entry, no entry needs to be provided within the map. * * 2) To modify an attribute with a structured data type instance as value, * the attribute shall be provided with a value containing a structured data * type instance with entries according to bullets 1 to 6. * * 3) To modify an attribute with another type than map or structured data * type as value, the attribute shall be provided with a complete * representation of its value that shall replace the previous value. * * 4) To create an attribute of any type, the attribute shall be provided * with a complete representation of its value. * * 5) To delete an attribute of any type, the attribute shall be provided * with NULL as value. * NOTE 1: Attributes that are allowed to be deleted need to be marked as * "nullable" within the OpenAPI file in Annex A. * * 6) Attributes that are not added, modified, or deleted do not need to be * provided. * NOTE 2: In related data structures no attrib */ if (SmPolicyDecision->pcc_rules) { OpenAPI_map_t *PccRuleMap = NULL; OpenAPI_pcc_rule_t *PccRule = NULL; OpenAPI_flow_information_t *FlowInformation = NULL; OpenAPI_qos_data_t *QosData = NULL; char *QosId = NULL; int i; for (i = 0; i < sess->policy.num_of_pcc_rule; i++) OGS_PCC_RULE_FREE(&sess->policy.pcc_rule[i]); sess->policy.num_of_pcc_rule = 0; OpenAPI_list_for_each(SmPolicyDecision->pcc_rules, node) { ogs_pcc_rule_t *pcc_rule = &sess->policy.pcc_rule[sess->policy.num_of_pcc_rule]; ogs_assert(pcc_rule); PccRuleMap = node->data; if (!PccRuleMap) { ogs_error("No PccRuleMap"); continue; } if (!PccRuleMap->key) { ogs_error("No PccRule->id"); continue; } PccRule = PccRuleMap->value; if (!PccRule) { pcc_rule->type = OGS_PCC_RULE_TYPE_REMOVE; pcc_rule->id = ogs_strdup(PccRuleMap->key); ogs_assert(pcc_rule->id); sess->policy.num_of_pcc_rule++; continue; } if (!PccRule->ref_qos_data) { ogs_error("No RefQosData"); continue; } if (!PccRule->ref_qos_data->first) { ogs_error("No RefQosData->first"); continue; } QosId = PccRule->ref_qos_data->first->data; if (!QosId) { ogs_error("no QosId"); continue; } if (SmPolicyDecision->qos_decs) { OpenAPI_map_t *QosDecisionMap = NULL; OpenAPI_qos_data_t *QosDataIter = NULL; OpenAPI_list_for_each(SmPolicyDecision->qos_decs, node2) { QosDecisionMap = node2->data; if (!QosDecisionMap) { ogs_error("No QosDecisionMap"); continue; } QosDataIter = QosDecisionMap->value; if (!QosDataIter) { ogs_error("No QosData"); continue; } if (!QosDataIter->qos_id) { ogs_error("No QosId"); continue; } if (strcmp(QosId, QosDataIter->qos_id) == 0) { QosData = QosDataIter; break; } } } if (!QosData) { ogs_error("no qosData"); continue; } pcc_rule->type = OGS_PCC_RULE_TYPE_INSTALL; pcc_rule->id = ogs_strdup(PccRule->pcc_rule_id); ogs_assert(pcc_rule->id); pcc_rule->precedence = PccRule->precedence; if (PccRule->flow_infos) { ogs_assert(pcc_rule->num_of_flow == 0); OpenAPI_list_for_each(PccRule->flow_infos, node2) { ogs_flow_t *flow = &pcc_rule->flow[pcc_rule->num_of_flow]; ogs_assert(flow); FlowInformation = node2->data; if (!FlowInformation) { ogs_error("No FlowInformation"); continue; } if (FlowInformation->flow_direction == OpenAPI_flow_direction_UPLINK) flow->direction = OGS_FLOW_UPLINK_ONLY; else if (FlowInformation->flow_direction == OpenAPI_flow_direction_DOWNLINK) flow->direction = OGS_FLOW_DOWNLINK_ONLY; else { ogs_fatal("Unsupported direction [%d]", FlowInformation->flow_direction); ogs_assert_if_reached(); } flow->description = ogs_strdup(FlowInformation->flow_description); ogs_assert(flow->description); pcc_rule->num_of_flow++; } } pcc_rule->qos.index = QosData->_5qi; pcc_rule->qos.arp.priority_level = QosData->priority_level; if (QosData->arp) { pcc_rule->qos.arp.priority_level = QosData->arp->priority_level; if (QosData->arp->preempt_cap == OpenAPI_preemption_capability_NOT_PREEMPT) pcc_rule->qos.arp.pre_emption_capability = OGS_5GC_PRE_EMPTION_DISABLED; else if (QosData->arp->preempt_cap == OpenAPI_preemption_capability_MAY_PREEMPT) pcc_rule->qos.arp.pre_emption_capability = OGS_5GC_PRE_EMPTION_ENABLED; ogs_assert(pcc_rule->qos.arp.pre_emption_capability); if (QosData->arp->preempt_vuln == OpenAPI_preemption_vulnerability_NOT_PREEMPTABLE) pcc_rule->qos.arp.pre_emption_vulnerability = OGS_5GC_PRE_EMPTION_DISABLED; else if (QosData->arp->preempt_vuln == OpenAPI_preemption_vulnerability_PREEMPTABLE) pcc_rule->qos.arp.pre_emption_vulnerability = OGS_5GC_PRE_EMPTION_ENABLED; ogs_assert(pcc_rule->qos.arp.pre_emption_vulnerability); } if (QosData->maxbr_ul) pcc_rule->qos.mbr.uplink = ogs_sbi_bitrate_from_string(QosData->maxbr_ul); if (QosData->maxbr_dl) pcc_rule->qos.mbr.downlink = ogs_sbi_bitrate_from_string(QosData->maxbr_dl); if (QosData->gbr_ul) pcc_rule->qos.gbr.uplink = ogs_sbi_bitrate_from_string(QosData->gbr_ul); if (QosData->gbr_dl) pcc_rule->qos.gbr.downlink = ogs_sbi_bitrate_from_string(QosData->gbr_dl); if (pcc_rule->qos.mbr.downlink || pcc_rule->qos.mbr.uplink || pcc_rule->qos.gbr.downlink || pcc_rule->qos.gbr.uplink) { if (pcc_rule->qos.mbr.downlink == 0) pcc_rule->qos.mbr.downlink = MAX_BIT_RATE; if (pcc_rule->qos.mbr.uplink == 0) pcc_rule->qos.mbr.uplink = MAX_BIT_RATE; if (pcc_rule->qos.gbr.downlink == 0) pcc_rule->qos.gbr.downlink = MAX_BIT_RATE; if (pcc_rule->qos.gbr.uplink == 0) pcc_rule->qos.gbr.uplink = MAX_BIT_RATE; } sess->policy.num_of_pcc_rule++; } } } bool smf_npcf_smpolicycontrol_handle_create( smf_sess_t *sess, ogs_sbi_stream_t *stream, int state, ogs_sbi_message_t *recvmsg) { int rv; char buf1[OGS_ADDRSTRLEN]; char buf2[OGS_ADDRSTRLEN]; uint64_t supported_features; char *strerror = NULL; smf_ue_t *smf_ue = NULL; smf_bearer_t *qos_flow = NULL; ogs_pfcp_pdr_t *dl_pdr = NULL; ogs_pfcp_pdr_t *ul_pdr = NULL; ogs_pfcp_pdr_t *cp2up_pdr = NULL; ogs_pfcp_pdr_t *up2cp_pdr = NULL; ogs_pfcp_far_t *up2cp_far = NULL; ogs_pfcp_qer_t *qer = NULL; OpenAPI_sm_policy_decision_t *SmPolicyDecision = NULL; OpenAPI_lnode_t *node = NULL; #define MAX_TRIGGER_ID 128 bool trigger_results[MAX_TRIGGER_ID]; ogs_sbi_message_t message; ogs_sbi_header_t header; ogs_assert(sess); ogs_assert(stream); smf_ue = sess->smf_ue; ogs_assert(smf_ue); ogs_assert(recvmsg); if (!recvmsg->http.location) { strerror = ogs_msprintf("[%s:%d] No http.location", smf_ue->supi, sess->psi); goto cleanup; } SmPolicyDecision = recvmsg->SmPolicyDecision; if (!SmPolicyDecision) { strerror = ogs_msprintf("[%s:%d] No SmPolicyDecision", smf_ue->supi, sess->psi); goto cleanup; } memset(&header, 0, sizeof(header)); header.uri = recvmsg->http.location; rv = ogs_sbi_parse_header(&message, &header); if (rv != OGS_OK) { strerror = ogs_msprintf("[%s:%d] Cannot parse http.location [%s]", smf_ue->supi, sess->psi, recvmsg->http.location); goto cleanup; } if (!message.h.resource.component[1]) { strerror = ogs_msprintf("[%s:%d] No Assocication ID [%s]", smf_ue->supi, sess->psi, recvmsg->http.location); ogs_sbi_header_free(&header); goto cleanup; } if (sess->policy_association_id) ogs_free(sess->policy_association_id); sess->policy_association_id = ogs_strdup(message.h.resource.component[1]); ogs_assert(sess->policy_association_id); ogs_sbi_header_free(&header); /* SBI Features */ if (SmPolicyDecision->supp_feat) { supported_features = ogs_uint64_from_string(SmPolicyDecision->supp_feat); sess->smpolicycontrol_features &= supported_features; } else { sess->smpolicycontrol_features = 0; } /********************************************************************* * Handle Policy Control Request Triggers *********************************************************************/ /* Get policy control request triggers */ memset(&trigger_results, 0, sizeof(trigger_results)); OpenAPI_list_for_each(SmPolicyDecision->policy_ctrl_req_triggers, node) { if (node->data) { OpenAPI_policy_control_request_trigger_e trigger_id = (intptr_t)node->data; ogs_assert(trigger_id < MAX_TRIGGER_ID); trigger_results[trigger_id] = true; } } /* Update authorized session-AMBR */ if (SmPolicyDecision->sess_rules) { OpenAPI_map_t *SessRuleMap = NULL; OpenAPI_session_rule_t *SessionRule = NULL; OpenAPI_ambr_t *AuthSessAmbr = NULL; OpenAPI_authorized_default_qos_t *AuthDefQos = NULL; OpenAPI_list_for_each(SmPolicyDecision->sess_rules, node) { SessRuleMap = node->data; if (!SessRuleMap) { ogs_error("No SessRuleMap"); continue; } SessionRule = SessRuleMap->value; if (!SessionRule) { ogs_error("No SessionRule"); continue; } AuthSessAmbr = SessionRule->auth_sess_ambr; if (AuthSessAmbr && trigger_results[ OpenAPI_policy_control_request_trigger_SE_AMBR_CH] == true) { if (AuthSessAmbr->uplink) sess->session.ambr.uplink = ogs_sbi_bitrate_from_string(AuthSessAmbr->uplink); if (AuthSessAmbr->downlink) sess->session.ambr.downlink = ogs_sbi_bitrate_from_string(AuthSessAmbr->downlink); } AuthDefQos = SessionRule->auth_def_qos; if (AuthDefQos && trigger_results[ OpenAPI_policy_control_request_trigger_DEF_QOS_CH] == true) { sess->session.qos.index = AuthDefQos->_5qi; sess->session.qos.arp.priority_level = AuthDefQos->priority_level; if (AuthDefQos->arp) { sess->session.qos.arp.priority_level = AuthDefQos->arp->priority_level; if (AuthDefQos->arp->preempt_cap == OpenAPI_preemption_capability_NOT_PREEMPT) sess->session.qos.arp.pre_emption_capability = OGS_5GC_PRE_EMPTION_DISABLED; else if (AuthDefQos->arp->preempt_cap == OpenAPI_preemption_capability_MAY_PREEMPT) sess->session.qos.arp.pre_emption_capability = OGS_5GC_PRE_EMPTION_ENABLED; ogs_assert(sess->session.qos.arp.pre_emption_capability); if (AuthDefQos->arp->preempt_vuln == OpenAPI_preemption_vulnerability_NOT_PREEMPTABLE) sess->session.qos.arp.pre_emption_vulnerability = OGS_5GC_PRE_EMPTION_DISABLED; else if (AuthDefQos->arp->preempt_vuln == OpenAPI_preemption_vulnerability_PREEMPTABLE) sess->session.qos.arp.pre_emption_vulnerability = OGS_5GC_PRE_EMPTION_ENABLED; ogs_assert(sess->session.qos.arp.pre_emption_vulnerability); } } } } /* Update authorized PCC rule & QoS */ update_authorized_pcc_rule_and_qos(sess, SmPolicyDecision); /********************************************************************* * Send PFCP Session Establiashment Request to the UPF *********************************************************************/ /* Select UPF based on UE Location Information */ smf_sess_select_upf(sess); /* Check if selected UPF is associated with SMF */ ogs_assert(sess->pfcp_node); if (!OGS_FSM_CHECK(&sess->pfcp_node->sm, smf_pfcp_state_associated)) { ogs_error("[%s] No associated UPF", smf_ue->supi); return false; } /* Remove all previous QoS flow */ smf_bearer_remove_all(sess); /* Setup Default QoS flow */ qos_flow = smf_qos_flow_add(sess); ogs_assert(qos_flow); /* Setup CP/UP Data Forwarding PDR/FAR */ smf_sess_create_cp_up_data_forwarding(sess); /* Copy Session QoS information to Default QoS Flow */ memcpy(&qos_flow->qos, &sess->session.qos, sizeof(ogs_qos_t)); /* Setup QER */ qer = qos_flow->qer; ogs_assert(qer); qer->mbr.uplink = sess->session.ambr.uplink; qer->mbr.downlink = sess->session.ambr.downlink; /* Setup PDR */ dl_pdr = qos_flow->dl_pdr; ogs_assert(dl_pdr); ul_pdr = qos_flow->ul_pdr; ogs_assert(ul_pdr); cp2up_pdr = sess->cp2up_pdr; ogs_assert(cp2up_pdr); up2cp_pdr = sess->up2cp_pdr; ogs_assert(up2cp_pdr); /* Setup FAR */ up2cp_far = sess->up2cp_far; ogs_assert(up2cp_far); ogs_assert(OGS_OK == ogs_pfcp_paa_to_ue_ip_addr(&sess->session.paa, &dl_pdr->ue_ip_addr, &dl_pdr->ue_ip_addr_len)); dl_pdr->ue_ip_addr.sd = OGS_PFCP_UE_IP_DST; ogs_info("UE SUPI[%s] DNN[%s] IPv4[%s] IPv6[%s]", smf_ue->supi, sess->session.name, sess->ipv4 ? OGS_INET_NTOP(&sess->ipv4->addr, buf1) : "", sess->ipv6 ? OGS_INET6_NTOP(&sess->ipv6->addr, buf2) : ""); /* Set UE-to-CP Flow-Description and Outer-Header-Creation */ up2cp_pdr->flow_description[up2cp_pdr->num_of_flow++] = (char *)"permit out 58 from ff02::2/128 to assigned"; ogs_assert(OGS_OK == ogs_pfcp_ip_to_outer_header_creation( &ogs_gtp_self()->gtpu_ip, &up2cp_far->outer_header_creation, &up2cp_far->outer_header_creation_len)); up2cp_far->outer_header_creation.teid = sess->index; /* Set UPF-N3 TEID & ADDR to the Default UL PDR */ ogs_assert(sess->pfcp_node); if (sess->pfcp_node->up_function_features.ftup) { ul_pdr->f_teid.ch = 1; ul_pdr->f_teid.chid = 1; ul_pdr->f_teid.choose_id = OGS_PFCP_DEFAULT_CHOOSE_ID; ul_pdr->f_teid_len = 2; cp2up_pdr->f_teid.ch = 1; cp2up_pdr->f_teid_len = 1; up2cp_pdr->f_teid.ch = 1; up2cp_pdr->f_teid.chid = 1; up2cp_pdr->f_teid.choose_id = OGS_PFCP_DEFAULT_CHOOSE_ID; up2cp_pdr->f_teid_len = 2; } else { ogs_gtpu_resource_t *resource = NULL; resource = ogs_pfcp_find_gtpu_resource( &sess->pfcp_node->gtpu_resource_list, sess->session.name, OGS_PFCP_INTERFACE_ACCESS); if (resource) { ogs_user_plane_ip_resource_info_to_sockaddr(&resource->info, &sess->upf_n3_addr, &sess->upf_n3_addr6); if (resource->info.teidri) sess->upf_n3_teid = OGS_PFCP_GTPU_INDEX_TO_TEID( ul_pdr->index, resource->info.teidri, resource->info.teid_range); else sess->upf_n3_teid = ul_pdr->index; } else { if (sess->pfcp_node->addr.ogs_sa_family == AF_INET) ogs_assert(OGS_OK == ogs_copyaddrinfo( &sess->upf_n3_addr, &sess->pfcp_node->addr)); else if (sess->pfcp_node->addr.ogs_sa_family == AF_INET6) ogs_assert(OGS_OK == ogs_copyaddrinfo( &sess->upf_n3_addr6, &sess->pfcp_node->addr)); else ogs_assert_if_reached(); sess->upf_n3_teid = ul_pdr->index; } ogs_assert(OGS_OK == ogs_pfcp_sockaddr_to_f_teid( sess->upf_n3_addr, sess->upf_n3_addr6, &ul_pdr->f_teid, &ul_pdr->f_teid_len)); ul_pdr->f_teid.teid = sess->upf_n3_teid; ogs_assert(OGS_OK == ogs_pfcp_sockaddr_to_f_teid( sess->upf_n3_addr, sess->upf_n3_addr6, &cp2up_pdr->f_teid, &cp2up_pdr->f_teid_len)); cp2up_pdr->f_teid.teid = cp2up_pdr->index; ogs_assert(OGS_OK == ogs_pfcp_sockaddr_to_f_teid( sess->upf_n3_addr, sess->upf_n3_addr6, &up2cp_pdr->f_teid, &up2cp_pdr->f_teid_len)); up2cp_pdr->f_teid.teid = sess->upf_n3_teid; } dl_pdr->precedence = OGS_PFCP_DEFAULT_PDR_PRECEDENCE; ul_pdr->precedence = OGS_PFCP_DEFAULT_PDR_PRECEDENCE; cp2up_pdr->precedence = OGS_PFCP_CP2UP_PDR_PRECEDENCE; up2cp_pdr->precedence = OGS_PFCP_UP2CP_PDR_PRECEDENCE; ogs_assert(OGS_OK == smf_5gc_pfcp_send_session_establishment_request(sess, stream)); return true; cleanup: ogs_assert(strerror); ogs_error("%s", strerror); ogs_assert(true == ogs_sbi_server_send_error(stream, OGS_SBI_HTTP_STATUS_BAD_REQUEST, recvmsg, strerror, NULL)); ogs_free(strerror); return false; } bool smf_npcf_smpolicycontrol_handle_update_notify( smf_sess_t *sess, ogs_sbi_stream_t *stream, ogs_sbi_message_t *recvmsg) { char *strerror = NULL; smf_ue_t *smf_ue = NULL; OpenAPI_sm_policy_notification_t *SmPolicyNotification = NULL; OpenAPI_sm_policy_decision_t *SmPolicyDecision = NULL; ogs_assert(sess); ogs_assert(stream); smf_ue = sess->smf_ue; ogs_assert(smf_ue); ogs_assert(recvmsg); SmPolicyNotification = recvmsg->SmPolicyNotification; if (!SmPolicyNotification) { strerror = ogs_msprintf("[%s:%d] No SmPolicyNotification", smf_ue->supi, sess->psi); goto cleanup; } SmPolicyDecision = SmPolicyNotification->sm_policy_decision; if (!SmPolicyDecision) { strerror = ogs_msprintf("[%s:%d] No SmPolicyDecision", smf_ue->supi, sess->psi); goto cleanup; } /* Update authorized PCC rule & QoS */ update_authorized_pcc_rule_and_qos(sess, SmPolicyDecision); ogs_assert(true == ogs_sbi_send_http_status_no_content(stream)); smf_qos_flow_binding(sess); return true; cleanup: ogs_assert(strerror); ogs_error("%s", strerror); ogs_assert(true == ogs_sbi_server_send_error(stream, OGS_SBI_HTTP_STATUS_BAD_REQUEST, recvmsg, strerror, NULL)); ogs_free(strerror); return false; } bool smf_npcf_smpolicycontrol_handle_terminate_notify( smf_sess_t *sess, ogs_sbi_stream_t *stream, ogs_sbi_message_t *recvmsg) { smf_ue_t *smf_ue = NULL; smf_npcf_smpolicycontrol_param_t param; ogs_assert(sess); ogs_assert(stream); smf_ue = sess->smf_ue; ogs_assert(smf_ue); ogs_assert(recvmsg); ogs_assert(true == ogs_sbi_send_http_status_no_content(stream)); memset(¶m, 0, sizeof(param)); ogs_assert(true == smf_sbi_discover_and_send(OpenAPI_nf_type_PCF, sess, NULL, OGS_PFCP_DELETE_TRIGGER_PCF_INITIATED, ¶m, smf_npcf_smpolicycontrol_build_delete)); return true; }