Files
openairinterface5g/openair-cn/OPENAIRHSS/s6a/s6a_up_loc.c
T
Lionel Gauthier 74338d01ae Debug traces
git-svn-id: http://svn.eurecom.fr/openair4G/trunk@7265 818b1a75-f10b-46b9-bf7c-635c3b92a50f
2015-04-24 08:55:58 +00:00

433 lines
15 KiB
C

/*******************************************************************************
OpenAirInterface
Copyright(c) 1999 - 2014 Eurecom
OpenAirInterface is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenAirInterface 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 OpenAirInterface.The full GNU General Public License is
included in this distribution in the file called "COPYING". If not,
see <http://www.gnu.org/licenses/>.
Contact Information
OpenAirInterface Admin: openair_admin@eurecom.fr
OpenAirInterface Tech : openair_tech@eurecom.fr
OpenAirInterface Dev : openair4g-devel@eurecom.fr
Address : Eurecom, Compus SophiaTech 450, route des chappes, 06451 Biot, France.
*******************************************************************************/
/*! \file s6a_up_loc.c
* \brief Handle an update location message and create the answer.
* \author Sebastien ROUX <sebastien.roux@eurecom.fr>
* \date 2013
* \version 0.1
*/
#include <freeDiameter/freeDiameter-host.h>
#include <freeDiameter/libfdproto.h>
#include "hss_config.h"
#include "db_proto.h"
#include "s6a_proto.h"
#include "access_restriction.h"
int s6a_up_loc_cb(struct msg **msg, struct avp *paramavp,
struct session *sess, void *opaque,
enum disp_action *act)
{
struct msg *ans, *qry;
struct avp *avp, *origin_host, *origin_realm;
struct avp *failed_avp = NULL;
struct avp_hdr *origin_host_hdr, *origin_realm_hdr;
struct avp_hdr *hdr;
union avp_value value;
int ret = 0;
int result_code = ER_DIAMETER_SUCCESS;
int experimental = 0;
uint32_t ulr_flags = 0;
mysql_ul_ans_t mysql_ans;
mysql_ul_push_t mysql_push;
if (msg == NULL) {
return EINVAL;
}
memset(&mysql_push, 0, sizeof(mysql_ul_push_t));
memset(&mysql_ans, 0, sizeof(mysql_ul_ans_t));
fprintf(stdout, "Received new update location request\n");
qry = *msg;
/* Create the answer */
CHECK_FCT(fd_msg_new_answer_from_req(fd_g_config->cnf_dict, msg, 0));
ans = *msg;
/* Retrieving IMSI AVP */
CHECK_FCT(fd_msg_search_avp(qry, s6a_cnf.dataobj_s6a_imsi, &avp));
if (avp) {
CHECK_FCT(fd_msg_avp_hdr(avp, &hdr));
if (hdr->avp_value->os.len > IMSI_LENGTH) {
fprintf(stdout, "IMSI_LENGTH ER_DIAMETER_INVALID_AVP_VALUE\n");
result_code = ER_DIAMETER_INVALID_AVP_VALUE;
goto out;
}
// 3GPP TS 29.272-910 / 5.2.1.1.3 Detailed behaviour of the HSS
// When receiving an Update Location request the HSS shall check whether the IMSI is known.
// If it is not known, a Result Code of DIAMETER_ERROR_USER_UNKNOWN shall be returned.
// If it is known, but the subscriber has no EPS subscription, the HSS may (as an operator option)
// return a Result Code of DIAMETER_ERROR_UNKNOWN_EPS_SUBSCRIPTION.
// If the Update Location Request is received over the S6a interface, and the subscriber has not
// any APN configuration, the HSS shall return a Result Code of DIAMETER_ERROR_UNKNOWN_EPS_SUBSCRIPTION.
// The HSS shall check whether the RAT type the UE is using is allowed. If it is not,
// a Result Code of DIAMETER_ERROR_RAT_NOT_ALLOWED shall be returned.
// ...
sprintf(mysql_push.imsi, "%*s", (int)hdr->avp_value->os.len,
(char*)hdr->avp_value->os.data);
if ((ret = hss_mysql_update_loc(mysql_push.imsi, &mysql_ans)) != 0) {
/* We failed to find the IMSI in the database. Replying to the request
* with the user unknown cause.
*/
experimental = 1;
fprintf(stdout, "IMSI %s DIAMETER_ERROR_USER_UNKNOWN\n", mysql_push.imsi);
result_code = DIAMETER_ERROR_USER_UNKNOWN;
goto out;
}
} else {
fprintf(stderr, "Cannot get IMSI AVP which is mandatory\n");
result_code = ER_DIAMETER_MISSING_AVP;
goto out;
}
/* Retrieving Origin host AVP */
CHECK_FCT(fd_msg_search_avp(qry, s6a_cnf.dataobj_s6a_origin_host, &origin_host));
if (!origin_host) {
fprintf(stderr, "origin_host ER_DIAMETER_MISSING_AVP\n");
result_code = ER_DIAMETER_MISSING_AVP;
goto out;
}
/* Retrieving Origin realm AVP */
CHECK_FCT(fd_msg_search_avp(qry, s6a_cnf.dataobj_s6a_origin_realm, &origin_realm));
if (!origin_realm) {
fprintf(stderr, "origin_realm ER_DIAMETER_MISSING_AVP\n");
result_code = ER_DIAMETER_MISSING_AVP;
goto out;
}
/* Retrieve the header from origin host and realm avps */
CHECK_FCT(fd_msg_avp_hdr(origin_host, &origin_host_hdr));
CHECK_FCT(fd_msg_avp_hdr(origin_realm, &origin_realm_hdr));
sprintf(mysql_push.mme_identity.mme_host, "%*s",
(int)origin_host_hdr->avp_value->os.len,
(char*)origin_host_hdr->avp_value->os.data);
sprintf(mysql_push.mme_identity.mme_realm, "%*s",
(int)origin_realm_hdr->avp_value->os.len,
(char*)origin_realm_hdr->avp_value->os.data);
/* Retrieving RAT type AVP */
CHECK_FCT(fd_msg_search_avp(qry, s6a_cnf.dataobj_s6a_rat_type, &avp));
if (avp) {
CHECK_FCT(fd_msg_avp_hdr(avp, &hdr));
/* As we are in E-UTRAN stand-alone HSS, we have to reject incoming
* location request with a RAT-Type != than E-UTRAN.
* The user may be disallowed to use the specified RAT, check the access
* restriction bit mask received from DB.
*/
if ((hdr->avp_value->u32 != 1004) ||
(FLAG_IS_SET(mysql_ans.access_restriction, E_UTRAN_NOT_ALLOWED))) {
experimental = 1;
fprintf(stderr, "access_restriction DIAMETER_ERROR_RAT_NOT_ALLOWED\n");
result_code = DIAMETER_ERROR_RAT_NOT_ALLOWED;
goto out;
}
} else {
fprintf(stderr, "rat_type ER_DIAMETER_MISSING_AVP\n");
result_code = ER_DIAMETER_MISSING_AVP;
goto out;
}
/* Retrieving ULR Flags AVP */
CHECK_FCT(fd_msg_search_avp(qry, s6a_cnf.dataobj_s6a_ulr_flags, &avp));
if (avp) {
CHECK_FCT(fd_msg_avp_hdr(avp, &hdr));
ulr_flags = hdr->avp_value->u32;
/* Check the flags received */
if (FLAG_IS_SET(ulr_flags, ULR_SINGLE_REGISTRATION_IND)) {
/* We don't handle cases where we have to inform SGSN */
result_code = ER_DIAMETER_INVALID_AVP_VALUE;
fprintf(stderr, "ULR single registration bit set (SGSN to MME): "
"not handled by standalone E-UTRAN HSS\n");
goto out;
}
if (!FLAG_IS_SET(ulr_flags, ULR_S6A_S6D_INDICATOR)) {
/* The request is coming from s6d interface (SGSN). */
result_code = ER_DIAMETER_INVALID_AVP_VALUE;
fprintf(stderr, "ULR S6D bit set: "
"not handled by standalone E-UTRAN HSS\n");
goto out;
}
if (FLAG_IS_SET(ulr_flags, ULR_NODE_TYPE_IND)) {
/* Request coming from combined SGSN/MME. */
result_code = ER_DIAMETER_INVALID_AVP_VALUE;
fprintf(stderr, "ULR conbined SGSN/MME bit set: "
"not handled by standalone E-UTRAN HSS\n");
goto out;
}
if (FLAG_IS_SET(ulr_flags, ULR_INITIAL_ATTACH_IND)) {
/* This bit, when set, indicates that the HSS shall send Cancel
* Location to the MME or SGSN if there is the MME or SGSN
* registration.
*/
// TODO: check if an MME is already registered, serving the UE.
// If so, it should be informed.
/* The identity of the MME will be added to db */
mysql_push.mme_identity_present = MME_IDENTITY_PRESENT;
} else {
/* The bit is not set, we are expecting that the mme contained in db
* matches the original MME.
*/
if ((mysql_ans.mme_identity.mme_host != NULL) &&
(mysql_ans.mme_identity.mme_realm != NULL)) {
/* Compare if values match expected */
if (memcmp(mysql_ans.mme_identity.mme_host, origin_host_hdr->avp_value->os.data,
origin_host_hdr->avp_value->os.len > strlen(mysql_ans.mme_identity.mme_host) ?
strlen(mysql_ans.mme_identity.mme_host) : origin_host_hdr->avp_value->os.len) != 0) {
experimental = 1;
fprintf(stderr, "DIAMETER_ERROR_UNKOWN_SERVING_NODE (host)\n");
result_code = DIAMETER_ERROR_UNKOWN_SERVING_NODE;
goto out;
}
if (memcmp(mysql_ans.mme_identity.mme_realm, origin_realm_hdr->avp_value->os.data,
origin_realm_hdr->avp_value->os.len > strlen(mysql_ans.mme_identity.mme_realm) ?
strlen(mysql_ans.mme_identity.mme_realm) : origin_realm_hdr->avp_value->os.len) != 0) {
experimental = 1;
fprintf(stderr, "DIAMETER_ERROR_UNKOWN_SERVING_NODE (realm)\n");
result_code = DIAMETER_ERROR_UNKOWN_SERVING_NODE;
goto out;
}
} else {
/* Failed to retrieve current serving MME and the ULR is not
* marked as an initial attach indication...
*/
experimental = 1;
fprintf(stderr, "DIAMETER_ERROR_UNKOWN_SERVING_NODE\n");
result_code = DIAMETER_ERROR_UNKOWN_SERVING_NODE;
goto out;
}
}
if (!ULR_PAD_VALID(ulr_flags)) {
/* Padding is not zero'ed, may be the MME/SGSN supports newer
* release. Inform it.
*/
fprintf(stderr, "ULR flags ER_DIAMETER_INVALID_AVP_VALUE\n");
result_code = ER_DIAMETER_INVALID_AVP_VALUE;
goto out;
}
} else {
fprintf(stderr, "ULR flags ER_DIAMETER_MISSING_AVP\n");
result_code = ER_DIAMETER_MISSING_AVP;
goto out;
}
/* Retrieving Visited-PLMN-Id AVP */
CHECK_FCT(fd_msg_search_avp(qry, s6a_cnf.dataobj_s6a_visited_plmn_id, &avp));
if (avp) {
CHECK_FCT(fd_msg_avp_hdr(avp, &hdr));
/* Roaming cases are not allowed for now.
* Reject the connectivity if PLMN visited and IMSI PLMN disagree.
*/
/* TODO */
if (hdr->avp_value->os.len == 3) {
// if (apply_access_restriction(mysql_push.imsi, hdr->avp_value->os.data) != 0) {
// /* We found that user is roaming and has no right to do it ->
// * reject the connection
// */
// result_code = DIAMETER_ERROR_ROAMING_NOT_ALLOWED;
// experimental = 1;
// goto out;
// }
} else {
fprintf(stderr, "PLMN ID ER_DIAMETER_INVALID_AVP_VALUE\n");
result_code = ER_DIAMETER_INVALID_AVP_VALUE;
goto out;
}
} else {
fprintf(stderr, "PLMN ID ER_DIAMETER_MISSING_AVP\n");
result_code = ER_DIAMETER_MISSING_AVP;
goto out;
}
/* Retrieving Terminal-Information AVP */
CHECK_FCT(fd_msg_search_avp(qry, s6a_cnf.dataobj_s6a_terminal_info, &avp));
if (avp) {
struct avp *child_avp;
/* Walk through childs avp */
CHECK_FCT(fd_msg_browse(avp, MSG_BRW_FIRST_CHILD, &child_avp, NULL));
while (child_avp) {
/* Retrieve the header of the child avp */
CHECK_FCT(fd_msg_avp_hdr(child_avp, &hdr));
switch(hdr->avp_code) {
case AVP_CODE_IMEI: {
/* Check that we do not exceed the maximum size for IMEI */
if (hdr->avp_value->os.len > IMEI_LENGTH) {
fprintf(stderr, "terminal info ER_DIAMETER_INVALID_AVP_VALUE\n");
result_code = ER_DIAMETER_INVALID_AVP_VALUE;
failed_avp = child_avp;
goto out;
}
sprintf(mysql_push.imei, "%*s", (int)hdr->avp_value->os.len,
hdr->avp_value->os.data);
mysql_push.imei_present = IMEI_PRESENT;
}
break;
case AVP_CODE_SOFTWARE_VERSION: {
/* Check the size for SV */
if (hdr->avp_value->os.len != SV_LENGTH) {
fprintf(stderr, "software version ER_DIAMETER_INVALID_AVP_VALUE\n");
result_code = ER_DIAMETER_INVALID_AVP_VALUE;
failed_avp = child_avp;
goto out;
}
mysql_push.software_version[0] = hdr->avp_value->os.data[0];
mysql_push.software_version[1] = hdr->avp_value->os.data[1];
mysql_push.sv_present = SV_PRESENT;
}
break;
default: /* Fall through */
case AVP_CODE_3GPP2_MEID: {
/* This AVP is not expected on s6a interface */
fprintf(stderr, "AVP_CODE_3GPP2_MEID ER_DIAMETER_AVP_UNSUPPORTED\n");
result_code = ER_DIAMETER_AVP_UNSUPPORTED;
failed_avp = child_avp;
goto out;
}
}
/* Go to next AVP in the grouped AVP */
CHECK_FCT(fd_msg_browse(child_avp, MSG_BRW_NEXT, &child_avp, NULL));
}
}
/* Retrieving UE-SRVCC AVP */
CHECK_FCT(fd_msg_search_avp(qry, s6a_cnf.dataobj_s6a_ue_srvcc_cap, &avp));
if (avp) {
CHECK_FCT(fd_msg_avp_hdr(avp, &hdr));
mysql_push.ue_srvcc_present = UE_SRVCC_PRESENT;
mysql_push.ue_srvcc = hdr->avp_value->u32;
}
/* Retrieving Supported features list AVP */
CHECK_FCT(fd_msg_search_avp(qry, s6a_cnf.dataobj_s6a_supported_features, &avp));
if (avp) {
struct avp *child_avp;
/* Walk through childs avp */
CHECK_FCT(fd_msg_browse(avp, MSG_BRW_FIRST_CHILD, &child_avp, NULL));
while (child_avp) {
/* Retrieve the header of the child avp */
CHECK_FCT(fd_msg_avp_hdr(child_avp, &hdr));
switch(hdr->avp_code) {
case AVP_CODE_VENDOR_ID: {
if (hdr->avp_value->u32 != VENDOR_3GPP) {
/* features from a vendor other than 3GPP is not supported */
fprintf(stderr, "Cannot interpret features list with vendor id "
"different than 3GPP(%d)\n", VENDOR_3GPP);
continue;
}
}
break;
case AVP_CODE_FEATURE_LIST: {
mysql_push.mme_supported_features_present = MME_SUPPORTED_FEATURES_PRESENT;
mysql_push.mme_supported_features = hdr->avp_value->u32;
}
break;
}
/* Go to next AVP in the grouped AVP */
CHECK_FCT(fd_msg_browse(child_avp, MSG_BRW_NEXT, &child_avp, NULL));
}
}
mysql_push_up_loc(&mysql_push);
/* ULA flags */
CHECK_FCT(fd_msg_avp_new(s6a_cnf.dataobj_s6a_ula_flags, 0, &avp));
value.u32 = 1;
CHECK_FCT(fd_msg_avp_setvalue(avp, &value));
CHECK_FCT(fd_msg_avp_add(ans, MSG_BRW_LAST_CHILD, avp));
/* Only add the subscriber data if not marked as skipped by MME */
if (!FLAG_IS_SET(ulr_flags, ULR_SKIP_SUBSCRIBER_DATA)) {
if (s6a_add_subscription_data_avp(ans, &mysql_ans) != 0) {
fprintf(stderr, "ULR_SKIP_SUBSCRIBER_DATA DIAMETER_ERROR_UNKNOWN_EPS_SUBSCRIPTION\n");
result_code = DIAMETER_ERROR_UNKNOWN_EPS_SUBSCRIPTION;
experimental = 1;
goto out;
}
}
out:
/* Add the Auth-Session-State AVP */
CHECK_FCT(fd_msg_search_avp(qry, s6a_cnf.dataobj_s6a_auth_session_state, &avp));
CHECK_FCT(fd_msg_avp_hdr(avp, &hdr));
CHECK_FCT(fd_msg_avp_new(s6a_cnf.dataobj_s6a_auth_session_state, 0, &avp));
CHECK_FCT(fd_msg_avp_setvalue(avp, hdr->avp_value));
CHECK_FCT(fd_msg_avp_add(ans, MSG_BRW_LAST_CHILD, avp));
/* Append the result code to the answer */
CHECK_FCT(s6a_add_result_code(ans, failed_avp, result_code, experimental));
CHECK_FCT(fd_msg_send(msg, NULL, NULL ));
return 0;
}