use warnings; use DBI; use REST::Client; use JSON; use threads; use Sys::Hostname; use Switch; use Data::Dumper; use Math::BigInt; use feature qw(switch); use Smart::Match; use constant false => 0; use constant true => 1; use Data::Dumper; use Sys::Hostname; use Data::Validate::IP qw(is_ipv4 is_ipv6); use Time::HiRes qw(gettimeofday); use constant RLM_MODULE_REJECT => 0; # /* immediately reject the request */ use constant RLM_MODULE_FAIL => 1; # /* module failed, don't reply */ use constant RLM_MODULE_OK => 2; # /* the module is OK, continue */ use constant RLM_MODULE_HANDLED => 3; # /* the module handled the request, so stop. */ use constant RLM_MODULE_INVALID => 4; # /* the module considers the request invalid. */ use constant RLM_MODULE_USERLOCK => 5; # /* reject the request (user is locked out) */ use constant RLM_MODULE_NOTFOUND => 6; # /* user not found */ use constant RLM_MODULE_NOOP => 7; # /* module succeeded without doing anything */ use constant RLM_MODULE_UPDATED => 8; # /* OK (pairs modified) */ use constant RLM_MODULE_NUMCODES => 9; # /* How many return codes there are */ use constant RAD_LOG_DEBUG => 0; use constant RAD_LOG_AUTH => 1; use constant RAD_LOG_PROXY => 2; use constant RAD_LOG_INFO => 3; use constant RAD_LOG_ERROR => 4; use vars qw(%RAD_REQUEST %RAD_REPLY %RAD_CHECK $databaseConnection $subscriberProfileSelector $subscriberProfileSelectorMultiIMSI $subscriberQuery $subscriberMultiIMSIQuery $updatedHlrPsInfoQuery $hlrZoneLookupQuery $hlrZoneLookupSgsnQuery $hlrRoamingSteeringQuery $hlrZoneLookupProfileQuery $hlrZoneLookupProfileQueryV2 $updatedHlrPsInfoCustomZoneQuery $checkIpAvailability $temporaryMultiImsiInsert $updateSessionId $restClient $technical_imsi $multi_imsi $test_data $donor $country $operatorName $conn_valid %conf); sub read_conf { $conf{'db_name'} = "mobility"; $conf{'db_user'} = "root"; $conf{'db_host'} = "10.5.48.28"; $conf{'db_port'} = "3307"; $conf{'db_passwd'} = "xM9A4!exJbQNhqJ"; $conf{'hook_host'} = "http://notification-v1.simfony:5200/api/notification/"; } sub conn_db { &read_conf; $databaseConnection->disconnect() if defined $databaseConnection; $databaseConnection = DBI->connect("DBI:mysql:database=$conf{'db_name'};host=$conf{'db_host'};port=$conf{'db_port'}", $conf{'db_user'}, $conf{'db_passwd'}) or die ({ &radiusd::radlog(RAD_LOG_ERROR, "key: AAA-MobDB, status: 0, type: SEVERE, message: AAA->MobDB service is down: $conf{'db_host'}:$conf{'db_port'} $conf{'db_name'}")}); #$databaseConnection = DBI->connect("DBI:mysql:database=$conf{'db_name'};host=$conf{'db_host'}", $conf{'db_user'}, $conf{'db_passwd'}); if ($DBI::err) {&radiusd::radlog(RAD_LOG_ERROR, "DB Connect Error. $DBI::errstr");} else { $subscriberQuery = $databaseConnection->prepare("SELECT s.subscriber_id, s.service_allowed, aaa.AccessPointName, aaa.FramedIPAddress, aaa.FramedIPNetmask, aaa.PrimaryDNS, aaa.SecondaryDNS, ent.ENTITLEMENT_VALUE AS SVC_DATA_BLOCKED, saaaprof.Username, saaaprof.`Password`, s.ICCID, ent2.ENTITLEMENT_VALUE AS SVC_DATA_AUTHENTICATE_CREDENTIALS, hps.OPERATOR_NAME, hps.OPERATOR_COUNTRY, s.core_account_id, s.core_seller_id, s.ccs_profile_id, ccsprofile.type, d.code FROM subscriber s LEFT JOIN aaa_addresspool aaa ON s.subscriber_id = aaa.subscriber_id LEFT JOIN hlr_ps_roaming_info hps ON hps.IMSI = s.IMSI LEFT JOIN subscriber_entitlements ent ON s.subscriber_id = ent.subscriber_id AND ent.ENTITLEMENT_NAME = 'SVC_DATA_BLOCKED' LEFT JOIN subscriber_entitlements ent2 ON s.subscriber_id = ent2.subscriber_id AND ent2.ENTITLEMENT_NAME = 'SVC_DATA_AUTHENTICATE_CREDENTIALS' LEFT JOIN subscriber_aaa_profile saaaprof ON s.subscriber_id = saaaprof.subscriber_id LEFT JOIN ccs_profile ccsprofile ON s.ccs_profile_id = ccsprofile.id LEFT JOIN donor d on s.donor_id = d.id WHERE s.IMSI LIKE ?"); $subscriberMultiIMSIQuery = $databaseConnection->prepare("SELECT s.subscriber_id, s.service_allowed, aaa.AccessPointName, aaa.FramedIPAddress, aaa.FramedIPNetmask, aaa.PrimaryDNS, aaa.SecondaryDNS, ent.ENTITLEMENT_VALUE AS SVC_DATA_BLOCKED, saaaprof.Username, saaaprof.`Password`, s.ICCID, ent2.ENTITLEMENT_VALUE AS SVC_DATA_AUTHENTICATE_CREDENTIALS, hps.OPERATOR_NAME, hps.OPERATOR_COUNTRY, s.core_account_id, s.core_seller_id, s.ccs_profile_id, ccsprofile.type, d.code FROM mobility.subscriber s LEFT JOIN subscriber_has_multi_imsi shmi on s.SUBSCRIBER_ID = shmi.subscriber_id LEFT JOIN aaa_addresspool aaa ON s.subscriber_id = aaa.subscriber_id LEFT JOIN hlr_ps_roaming_info hps ON hps.IMSI = s.IMSI LEFT JOIN subscriber_entitlements ent ON s.subscriber_id = ent.subscriber_id AND ent.ENTITLEMENT_NAME = 'SVC_DATA_BLOCKED' LEFT JOIN subscriber_entitlements ent2 ON s.subscriber_id = ent2.subscriber_id AND ent2.ENTITLEMENT_NAME = 'SVC_DATA_AUTHENTICATE_CREDENTIALS' LEFT JOIN subscriber_aaa_profile saaaprof ON s.subscriber_id = saaaprof.subscriber_id LEFT JOIN ccs_profile ccsprofile ON s.ccs_profile_id = ccsprofile.id LEFT JOIN donor d on s.donor_id = d.id WHERE shmi.IMSI LIKE ?"); $hlrZoneLookupSgsnQuery = $databaseConnection->prepare("SELECT * FROM hlr_zone_lookup_by_sgsn WHERE inet_aton(?) between inet_aton(SUBNET_START) and inet_aton(SUBNET_END) limit 1;"); $hlrZoneLookupProfileQuery = $databaseConnection->prepare("SELECT hzl.TADIG, hzl.OPERATOR_COUNTRY, hzl.OPERATOR_NAME, IF(po.allow, 1, 0), IF(po.ZONE IS NULL,hzl.zone,po.ZONE), po.id FROM profile_operators po LEFT JOIN hlr_zone_lookup hzl ON hzl.ID = po.hlr_zone_lookup_id WHERE CONCAT(hzl.mcc, hzl.mnc) = ? AND po.profile_id = (select s.PROFILE_ID from subscriber s where imsi = ?) limit 1;"); $hlrZoneLookupQuery = $databaseConnection->prepare("select OPERATOR_COUNTRY, OPERATOR_NAME,ROAMING_ENABLED from hlr_zone_lookup where concat(mcc,mnc) like ? limit 1;"); $hlrRoamingSteeringQuery = $databaseConnection->prepare("SELECT hzl.OPERATOR_COUNTRY, hzl.OPERATOR_NAME, hzl.ROAMING_ENABLED FROM hlr_roaming_steering hrs left join hlr_zone_lookup hzl on hzl.GTPREFIX like concat(hrs.GTPREFIX,'%') where hrs.IMSI = ? and concat(hzl.mcc,hzl.mnc) like ? limit 1;"); $updatedHlrPsInfoQuery = $databaseConnection->prepare("UPDATE hlr_ps_roaming_info AS ps_profile, (SELECT TADIG, OPERATOR_COUNTRY, OPERATOR_NAME, ZONE FROM hlr_zone_lookup WHERE concat (mcc,mnc) like ? limit 1 ) AS src SET ps_profile.LAST_UPDATE = NOW(), ps_profile.TADIG = src.TADIG, ps_profile.OPERATOR_COUNTRY = src.OPERATOR_COUNTRY, ps_profile.OPERATOR_NAME = src.OPERATOR_NAME, ps_profile.ZONE = src.ZONE WHERE ps_profile.IMSI = ?"); $updatedHlrPsInfoCustomZoneQuery = $databaseConnection->prepare("UPDATE hlr_ps_roaming_info AS ps_profile, (SELECT TADIG, OPERATOR_COUNTRY, OPERATOR_NAME FROM hlr_zone_lookup WHERE concat (mcc,mnc) like ? limit 1 ) AS src SET ps_profile.LAST_UPDATE = NOW(), ps_profile.TADIG = src.TADIG, ps_profile.OPERATOR_COUNTRY = src.OPERATOR_COUNTRY, ps_profile.OPERATOR_NAME = src.OPERATOR_NAME, ps_profile.ZONE = ? WHERE ps_profile.IMSI = ?"); $subscriberProfileSelector = $databaseConnection->prepare("SELECT s.iccid, s.profile_id, p.code, s.core_account_id, s.core_seller_id FROM SUBSCRIBER s LEFT JOIN provider p on s.provider_id = p.id WHERE IMSI LIKE ? LIMIT 1;"); $subscriberProfileSelectorMultiIMSI = $databaseConnection->prepare("SELECT s.iccid, s.profile_id, p.code, s.core_account_id, s.core_seller_id, s.eid, s.eid_32, s.imsi FROM SUBSCRIBER s LEFT JOIN provider p on s.provider_id = p.id LEFT JOIN subscriber_has_multi_imsi shmi on s.SUBSCRIBER_ID = shmi.subscriber_id WHERE shmi.IMSI LIKE ? LIMIT 1;"); $checkIpAvailability = $databaseConnection->prepare("SELECT COUNT(imsi) FROM temporary_multi_imsi_session WHERE ip_address = ?;"); $temporaryMultiImsiInsert = $databaseConnection->prepare("INSERT INTO temporary_multi_imsi_session(imsi, ip_address, start_date, source_system) VALUE (?, ?, NOW(), ?) ON DUPLICATE KEY UPDATE ip_address = ?, start_date= NOW(), source_system = ?;"); $updateSessionId = $databaseConnection->prepare("UPDATE temporary_multi_imsi_session SET session_id = ? WHERE imsi = ?;"); $hlrZoneLookupProfileQueryV2 = $databaseConnection->prepare("select hzl.TADIG, hzl.OPERATOR_COUNTRY, hzl.OPERATOR_NAME, IF(hzl.ROAMING_ENABLED, 0, 1), IF(phz.custom_zone IS NULL,z.code,phz.custom_zone), p.id from hlr_zone_lookup hzl left join zone_has_hlr_zone_lookup zhhzl on hzl.ID = zhhzl.hlr_zone_lookup_id and CONCAT(hzl.mcc, hzl.mnc) = ? left join zone z on zhhzl.zone_id = z.id left join profile_has_zone phz on z.id = phz.zone_id left join profile p on p.id = phz.profile_id where p.id = (select s.PROFILE_ID from subscriber s where imsi = ?) limit 1;"); } $conn_valid = (!$DBI::err); &radiusd::radlog(RAD_LOG_INFO, "key: AAA-MobDB, status: 1, type: CLEAR, message: AAA->MobDB service is up: $conf{'db_host'}:$conf{'db_port'} $conf{'db_name'}") } sub sendPlatformNotification { my ($imsi, $apn, $username, $outcome, $reason, $operatorName, $country, $iccid, $rat_type, $provider, $accountId, $sellerId, $decodedUserLocationInfo, $technical_imsi, $multi_imsi) = @_; # my $actualResponse = {}; # for my $key (keys %RAD_REPLY) { # $actualResponse->{$key} = $RAD_REPLY{$key}; # } # # TODO filter out passwords for PAP / CHAP authentication @Tudor # my $actualRequest = {}; # for my $key (keys %RAD_REQUEST) { # $actualRequest->{$key} = $RAD_REQUEST{$key}; # } my $eventJson = { eventTimestamp => int(gettimeofday() * 1000), sourceSystem => hostname, accountId => $accountId, sellerId => $sellerId, # radiusRequest => $actualRequest, # radiusResponse => $actualResponse, simCardCharacteristicCollection => [ { simCardCharacteristicName => 'IMSI', simCardCharacteristicValue => $imsi }, { simCardCharacteristicName => 'APN_NAME', simCardCharacteristicValue => $apn }, { simCardCharacteristicName => 'APN_USERNAME', simCardCharacteristicValue => $username }, { simCardCharacteristicName => 'OUTCOME', simCardCharacteristicValue => $outcome }, { simCardCharacteristicName => 'REASON', simCardCharacteristicValue => $reason }, { simCardCharacteristicName => 'OPERATOR_NAME', simCardCharacteristicValue => $operatorName }, { simCardCharacteristicName => 'COUNTRY', simCardCharacteristicValue => $country }, { simCardCharacteristicName => 'RAT_TYPE', simCardCharacteristicValue => $rat_type }, { simCardCharacteristicName => 'TECHNICAL_IMSI', simCardCharacteristicValue => $technical_imsi }, { simCardCharacteristicName => 'MULTI_IMSI', simCardCharacteristicValue => $multi_imsi }, { simCardCharacteristicName => 'LOCATION_INFO', simCardCharacteristicValue => encode_json($decodedUserLocationInfo) } ], eventName => 'DATA_SESSION_AUTHORIZATION', provider => $provider, }; if ($sellerId ne "") { if (!defined($restClient)) { $restClient = REST::Client->new(); } &radiusd::radlog(RAD_LOG_INFO, $conf{'hook_host'} . $iccid . " -> " . encode_json($eventJson)); #$restClient->POST($conf{'hook_host'} . $iccid, encode_json($eventJson), { 'Content-type' => 'application/json' }); } undef $imsi, $apn, $username, $outcome, $reason, $operatorName, $country, $iccid, $rat_type, $provider, $accountId, $sellerId, $decodedUserLocationInfo, $technical_imsi, $multi_imsi, $test_data, $eventJson; } sub authorize { &log_request_attributes; # my $congestion = int(rand(9))+1; my $congestion = 1; # #congestion > 1, only 10% is being accepted, rest of 90% rejected # if ($congestion > 4) { &radiusd::radlog(RAD_LOG_ERROR, "$RAD_REQUEST{'3GPP-IMSI'} -> CONGESTION REACHED ${congestion}"); undef $congestion; return RLM_MODULE_REJECT; } if (!$conn_valid) { &radiusd::radlog(RAD_LOG_INFO, "$RAD_REQUEST{'3GPP-IMSI'} -> NEW DB CONNECTION"); &conn_db; } unless ($databaseConnection->ping) { &radiusd::radlog(RAD_LOG_INFO, "$RAD_REQUEST{'3GPP-IMSI'} -> NEW DB CONNECTION ON PING"); &conn_db; } my $visitedMccMnc = $RAD_REQUEST{'3GPP-SGSN-MCC-MNC'}; my $imsi = $RAD_REQUEST{'3GPP-IMSI'}; my $technical_imsi = $RAD_REQUEST{'3GPP-IMSI'}; my $multi_imsi = false; my $test_data = false; my $userLocationInfo = $RAD_REQUEST{'3GPP-User-Location-Info'}; my $decodedUserLocationInfo; # check if mcc mnc is pressent in request if ($visitedMccMnc eq "") { #if mcc mnc is missing start query by sgsn address my $visitedIp = $RAD_REQUEST{'3GPP-SGSN-Address'}; &radiusd::radlog(RAD_LOG_INFO, "$imsi -> no 3GPP-SGSN-MCC-MNC -> check by ip addr ${visitedIp}"); $hlrZoneLookupSgsnQuery->execute($visitedIp); my @hlrsgsn = $hlrZoneLookupSgsnQuery->fetchrow_array; if (scalar(@hlrsgsn) != 0) { # set visitedMccMnc to value from hlr_zone_lookup_by_sgsn table $visitedMccMnc = @hlrsgsn[4]; &radiusd::radlog(RAD_LOG_INFO, "$imsi -> ${visitedIp} -> ${visitedMccMnc}"); } undef $visitedIp, @hlrsgsn; } if ($userLocationInfo) { $decodedUserLocationInfo = getDecodedLocationInfo($userLocationInfo); $decodedUserLocationInfo->{MCC} = substr($visitedMccMnc, 0, 3); $decodedUserLocationInfo->{MNC} = substr($visitedMccMnc, 3, length($visitedMccMnc) - 3); } # check if subscriber has a custom profile $subscriberProfileSelector->execute($imsi); my @iccidAndProfileId = $subscriberProfileSelector->fetchrow_array; if (scalar(@iccidAndProfileId) == 0) { $subscriberProfileSelectorMultiIMSI->execute($imsi); @iccidAndProfileId = $subscriberProfileSelectorMultiIMSI->fetchrow_array; &radiusd::radlog(RAD_LOG_INFO, "$imsi -> MAPPED TO @iccidAndProfileId[7], CONTINUING WITH @iccidAndProfileId[7]"); &radiusd::radlog(RAD_LOG_INFO, "$imsi -> MAPPED TO ALL: " . join(", ", @iccidAndProfileId)); $imsi = @iccidAndProfileId[7]; $multi_imsi = true; } my $customZone = ''; my $isZonedDeny; if ((index(@iccidAndProfileId[2], 'v2_') == 0) or (index(@iccidAndProfileId[2], 'dev') == 0)) { &radiusd::radlog(RAD_LOG_INFO, "$imsi -> v2 engine check"); $hlrZoneLookupProfileQueryV2->execute($visitedMccMnc, $imsi); my @profileZone = $hlrZoneLookupProfileQueryV2->fetchrow_array; if (scalar(@profileZone) != 0) { &radiusd::radlog(RAD_LOG_INFO, "$imsi -> {@profileZone}"); &radiusd::radlog(RAD_LOG_INFO, "$imsi -> CUSTOM PROFILE OPERATOR: TADIG: {@profileZone[0]} COUNTRY: {@profileZone[1]} OPERATOR: {@profileZone[2]} IS_ALLOW: {@profileZone[3]} ZONE: {@profileZone[4]}"); $customZone = @profileZone[4]; $isZonedDeny = @profileZone[3] == 1 ? 0 : 1; &radiusd::radlog(RAD_LOG_INFO, "$imsi -> CUSTOM PROFILE OPERATOR: TADIG: {@profileZone[0]} COUNTRY: {@profileZone[1]} OPERATOR: {@profileZone[2]} IS_DENY: ${isZonedDeny} ZONE: ${customZone}"); $country = @profileZone[1]; $operatorName = @profileZone[2]; undef @profileZone; } else { #maybe do another select here to actually show the country and operator where the session was denied &radiusd::radlog(RAD_LOG_ERROR, "$imsi -> NO PROFILE OPERATOR FOUND: IMSI= $imsi, PROFILE_ID = {@iccidAndProfileId[1]} MCC-MNC ${visitedMccMnc} "); &sendPlatformNotification($imsi, $RAD_REQUEST{'Called-Station-Id'}, $RAD_REQUEST{'User-Name'}, 'REJECT', 'Data session not allowed by current profile', "N/A", "N/A", @iccidAndProfileId[0], $RAD_REQUEST{'3GPP-RAT-Type'}, @iccidAndProfileId[2], @iccidAndProfileId[3], @iccidAndProfileId[4], $decodedUserLocationInfo); &radiusd::radlog(RAD_LOG_ERROR, "$imsi -> REJECTED "); undef $visitedMccMnc, $imsi, @iccidAndProfileId, $customZone, $isZonedDeny, $decodedUserLocationInfo, $operatorName, $country, $donor; return RLM_MODULE_REJECT; } } else { if (@iccidAndProfileId[1] != "") { &radiusd::radlog(RAD_LOG_INFO, "$imsi has custom profile: {@iccidAndProfileId[1]} for MccMnc $visitedMccMnc"); $hlrZoneLookupProfileQuery->execute($visitedMccMnc, $imsi); my @profileZone = $hlrZoneLookupProfileQuery->fetchrow_array; # if custom profile is find for request mcc mnc set if is allowed and custom zone if (scalar(@profileZone) != 0) { &radiusd::radlog(RAD_LOG_INFO, "$imsi -> {@profileZone}"); &radiusd::radlog(RAD_LOG_INFO, "$imsi -> CUSTOM PROFILE OPERATOR: TADIG: {@profileZone[0]} COUNTRY: {@profileZone[1]} OPERATOR: {@profileZone[2]} IS_ALLOW: {@profileZone[3]} ZONE: {@profileZone[4]}"); $customZone = @profileZone[4]; $isZonedDeny = @profileZone[3] == 1 ? 0 : 1; &radiusd::radlog(RAD_LOG_INFO, "$imsi -> CUSTOM PROFILE OPERATOR: TADIG: {@profileZone[0]} COUNTRY: {@profileZone[1]} OPERATOR: {@profileZone[2]} IS_DENY: ${isZonedDeny} ZONE: ${customZone}"); $country = @profileZone[1]; $operatorName = @profileZone[2]; undef @profileZone; } else { &radiusd::radlog(RAD_LOG_ERROR, "$imsi -> NO PROFILE OPERATOR FOUND: PROFILE_ID = {@iccidAndProfileId[1]} MCC-MNC ${visitedMccMnc} "); #TODO FIX THIS &sendPlatformNotification($imsi, $RAD_REQUEST{'Called-Station-Id'}, $RAD_REQUEST{'User-Name'}, 'REJECT', 'Data session not allowed by current profile', "N/A", "N/A", @iccidAndProfileId[0], $RAD_REQUEST{'3GPP-RAT-Type'}, @iccidAndProfileId[2], @iccidAndProfileId[3], @iccidAndProfileId[4], $decodedUserLocationInfo); &radiusd::radlog(RAD_LOG_ERROR, "$imsi -> REJECTED "); undef $visitedMccMnc, $imsi, $multi_imsi, $technical_imsi, $test_data, $donor, @iccidAndProfileId, $customZone, $isZonedDeny, $decodedUserLocationInfo, $operatorName, $country; return RLM_MODULE_REJECT; } } } #find subscriber $subscriberQuery->execute($imsi); my @ref = $subscriberQuery->fetchrow_array; if (scalar(@ref) == 0) { $subscriberMultiIMSIQuery->execute($imsi); @ref = $subscriberMultiIMSIQuery->fetchrow_array; } my $nasIP = $RAD_REQUEST{'NAS-IP-Address'}; if (scalar(@ref) == 0 && $nasIP != '172.29.1.20') { $imsi = $RAD_REQUEST{'3GPP-IMSI'}; &radiusd::radlog(RAD_LOG_ERROR, "IMSI = ${imsi} NOT_FOUND TRYING TO FALLBACK TO MULTI IMSI SCENARIO"); my $randomIp = generateRandomIP($nasIP); $checkIpAvailability->execute($randomIp); my $counter = $checkIpAvailability->fetchrow_array; while ($counter != 0) { $randomIp = generateRandomIP($nasIP); $checkIpAvailability->execute($randomIp); $counter = $checkIpAvailability->fetchrow_array; } if ($counter == 0 && is_ipv4($randomIp)) { &radiusd::radlog(RAD_LOG_INFO, "MULTI IMSI $imsi ASSIGN TEMPORARY IP $randomIp"); $temporaryMultiImsiInsert->bind_param(1, $imsi); $temporaryMultiImsiInsert->bind_param(2, $randomIp); $temporaryMultiImsiInsert->bind_param(3, $nasIP); $temporaryMultiImsiInsert->bind_param(4, $randomIp); $temporaryMultiImsiInsert->bind_param(5, $nasIP); $temporaryMultiImsiInsert->execute(); $RAD_REPLY{'Framed-IP-Address'} = $randomIp; $RAD_REPLY{'Framed-IP-Netmask'} = '255.255.192.0'; $RAD_REPLY{'MS-Primary-DNS-Server'} = '8.8.8.8'; $RAD_REPLY{'MS-Secondary-DNS-Server'} = '8.8.4.4'; $RAD_REPLY{'Session-Timeout'} = '604800'; &radiusd::radlog(RAD_LOG_INFO, "IMSI = ${imsi} SUCCESS"); undef $visitedMccMnc, $imsi, @iccidAndProfileId, $customZone, $isZonedDeny, @ref, $randomIp, $counter; &log_response_success($imsi, ""); return RLM_MODULE_OK; } undef $visitedMccMnc, $imsi, @iccidAndProfileId, $customZone, $isZonedDeny, @ref, $counter, $randomIp; &radiusd::radlog(RAD_LOG_ERROR, "IMSI = ${imsi} MULTI IMSI SCENARIO: IP COULD NOT BE GENERATED BASED ON NAS-IP-Address"); return RLM_MODULE_REJECT; } my $donor = @ref[18]; #how can we change the @ref entry as it contains the old stuff, or do we just change the notification # update ps roaming info for subscriber without ss7 signaling for now only iBasis SIM s if (index($donor, 'IBasis') == 0) { &radiusd::radlog(RAD_LOG_INFO, "$imsi -> DONOR IS ${donor}"); if (length($customZone) == 0) { &radiusd::radlog(RAD_LOG_INFO, "$imsi -> UPDATE PS PROFILE ${visitedMccMnc}"); $updatedHlrPsInfoQuery->bind_param(1, $visitedMccMnc); $updatedHlrPsInfoQuery->bind_param(2, $imsi); $updatedHlrPsInfoQuery->execute(); } else { &radiusd::radlog(RAD_LOG_INFO, "$imsi -> UPDATE PS PROFILE ${visitedMccMnc} CUSTOM ZONE ${customZone}"); #if custom zone is not empty make a custom update with values from hlr_zone_lookup and $customZone $updatedHlrPsInfoCustomZoneQuery->bind_param(1, $visitedMccMnc); $updatedHlrPsInfoCustomZoneQuery->bind_param(2, $customZone); $updatedHlrPsInfoCustomZoneQuery->bind_param(3, $imsi); $updatedHlrPsInfoCustomZoneQuery->execute(); } } my $iccid = @ref[10]; #if $isZonedDeny is empty profile dosen't contains values for this mcc mcn so get value from hlr_zone_lookup if (!defined $isZonedDeny) { &radiusd::radlog(RAD_LOG_DEBUG, "ICCID = ${iccid} IMSI = ${imsi} ZONE IS DENIAL IS NOT DEFINED CHECK IN HLR ZONE LOOKUP"); $hlrZoneLookupQuery->execute($visitedMccMnc); my @hlr = $hlrZoneLookupQuery->fetchrow_array; $isZonedDeny = @hlr[2]; undef @hlr; } if ($isZonedDeny != 0) { #TODO FIX THIS &radiusd::radlog(RAD_LOG_ERROR, "ICCID = ${iccid} IMSI = ${imsi} DATA_SESSION_NOT_ALLOWED - ZONE DENIAL"); &sendPlatformNotification($imsi, $RAD_REQUEST{'Called-Station-Id'}, $RAD_REQUEST{'User-Name'}, 'REJECT', 'Data session not allowed', ($operatorName ? $operatorName : @ref[12]), ($country ? $country : @ref[13]), $iccid, $RAD_REQUEST{'3GPP-RAT-Type'}, @iccidAndProfileId[2], @ref[14], @ref[15], $decodedUserLocationInfo, $technical_imsi, $multi_imsi); &radiusd::radlog(RAD_LOG_ERROR, "$imsi -> REJECTED "); undef $visitedMccMnc, $imsi, $iccid, @iccidAndProfileId, $customZone, $isZonedDeny, @ref, $decodedUserLocationInfo; return RLM_MODULE_REJECT; } $hlrRoamingSteeringQuery->execute($imsi, $visitedMccMnc); my @hrs = $hlrRoamingSteeringQuery->fetchrow_array; if (scalar(@hrs) != 0) { &radiusd::radlog(RAD_LOG_ERROR, "ICCID = ${iccid} IMSI = ${imsi} DATA_SESSION_BLOCKED_BY_USER"); &sendPlatformNotification($imsi, $RAD_REQUEST{'Called-Station-Id'}, $RAD_REQUEST{'User-Name'}, 'REJECT', 'Mobile Data blocked by user for ' . @hrs[1], @hrs[1], @hrs[0], $iccid, $RAD_REQUEST{'3GPP-RAT-Type'}, @iccidAndProfileId[2], @ref[14], @ref[15], $decodedUserLocationInfo, $technical_imsi, $multi_imsi); &radiusd::radlog(RAD_LOG_ERROR, "$imsi -> REJECTED "); undef $visitedMccMnc, $imsi, $iccid, @iccidAndProfileId, $customZone, $isZonedDeny, @ref, @hrs, $decodedUserLocationInfo; return RLM_MODULE_REJECT; } # check if subscriber is suspended if (@ref[1] == 1) { # check if subscriber is suspended and has access to test data # if (@ref[1] == 1) my $ccs_profile_id = @ref[16]; my $ccs_profile_type = @ref[17]; &radiusd::radlog(RAD_LOG_ERROR, "ccs_profile_id = ${ccs_profile_id}, ccs_profile_type = ${ccs_profile_type}"); if ($ccs_profile_type eq 'TEST_DATA') { &radiusd::radlog(RAD_LOG_ERROR, "ICCID = ${iccid} IMSI = ${imsi} SUBSCRIBER_SUSPENDED BUT ALLOWING TEST DATA"); $test_data = true; #continue } else { &radiusd::radlog(RAD_LOG_ERROR, "ICCID = ${iccid} IMSI = ${imsi} SUBSCRIBER_SUSPENDED"); &sendPlatformNotification($imsi, $RAD_REQUEST{'Called-Station-Id'}, $RAD_REQUEST{'User-Name'}, 'REJECT', 'Subscriber is suspended', ($operatorName ? $operatorName : @ref[12]), ($country ? $country : @ref[13]), $iccid, $RAD_REQUEST{'3GPP-RAT-Type'}, @iccidAndProfileId[2], @ref[14], @ref[15], $decodedUserLocationInfo, $technical_imsi, $multi_imsi); &radiusd::radlog(RAD_LOG_ERROR, "$imsi -> REJECTED "); undef $visitedMccMnc, $imsi, $iccid, @iccidAndProfileId, $customZone, $isZonedDeny, @ref, @hrs, $decodedUserLocationInfo, $ccs_profile_id, $ccs_profile_type; return RLM_MODULE_REJECT; } } # check if mobile data is blocked by user if (@ref[7] eq 'true') { &radiusd::radlog(RAD_LOG_ERROR, "ICCID = ${iccid} IMSI = ${imsi} MOBILE_DATA_BLOCKED_BY_USER"); &sendPlatformNotification($imsi, $RAD_REQUEST{'Called-Station-Id'}, $RAD_REQUEST{'User-Name'}, 'REJECT', 'Mobile Data blocked by user', ($operatorName ? $operatorName : @ref[12]), ($country ? $country : @ref[13]), $iccid, $RAD_REQUEST{'3GPP-RAT-Type'}, @iccidAndProfileId[2], @ref[14], @ref[15], $decodedUserLocationInfo, $technical_imsi, $multi_imsi); &radiusd::radlog(RAD_LOG_ERROR, "$imsi -> REJECTED "); undef $visitedMccMnc, $imsi, $iccid, @iccidAndProfileId, $customZone, $isZonedDeny, @ref, @hrs, $decodedUserLocationInfo; return RLM_MODULE_REJECT; } # check if apn credentials match if ((@ref[8] ne $RAD_REQUEST{'User-Name'} || @ref[9] ne $RAD_REQUEST{'User-Password'}) && @ref[11] eq 'true') { &radiusd::radlog(RAD_LOG_ERROR, "ICCID = ${iccid} IMSI = ${imsi} INVALID_APN_CREDENTIALS"); &sendPlatformNotification($imsi, $RAD_REQUEST{'Called-Station-Id'}, $RAD_REQUEST{'User-Name'}, 'REJECT', 'APN Authentication failed', ($operatorName ? $operatorName : @ref[12]), ($country ? $country : @ref[13]), $iccid, $RAD_REQUEST{'3GPP-RAT-Type'}, @iccidAndProfileId[2], @ref[14], @ref[15], $decodedUserLocationInfo, $technical_imsi, $multi_imsi); &radiusd::radlog(RAD_LOG_ERROR, "$imsi -> REJECTED "); undef $visitedMccMnc, $imsi, $iccid, @iccidAndProfileId, $customZone, $isZonedDeny, @ref, @hrs, $decodedUserLocationInfo; return RLM_MODULE_REJECT; } # check if apn match # if (not lc($RAD_REQUEST{'Called-Station-Id'}) eq lc($ref[2])) { #if (index(lc $RAD_REQUEST{'Called-Station-Id'}, lc @ref[2]) != 0) my @apns = split(/,/, lc @ref[2]); my $found = 0; foreach my $apn (@apns) { $apn =~ s/^\s+|\s+$//g; # trimming whitespace from start and end if (index(lc $RAD_REQUEST{'Called-Station-Id'}, $apn) != -1) { $found = 1; last; } } unless ($found) { &radiusd::radlog(RAD_LOG_ERROR, "ICCID = ${iccid} IMSI = ${imsi} INVALID_APN {@ref[2]} REQUESTED {$RAD_REQUEST{'Called-Station-Id'}}"); &sendPlatformNotification($imsi, $RAD_REQUEST{'Called-Station-Id'}, $RAD_REQUEST{'User-Name'}, 'REJECT', 'Invalid APN', ($operatorName ? $operatorName : @ref[12]), ($country ? $country : @ref[13]), $iccid, $RAD_REQUEST{'3GPP-RAT-Type'}, @iccidAndProfileId[2], @ref[14], @ref[15], $decodedUserLocationInfo, $technical_imsi, $multi_imsi); &radiusd::radlog(RAD_LOG_ERROR, "$imsi -> REJECTED "); undef $visitedMccMnc, $imsi, $iccid, @iccidAndProfileId, $customZone, $isZonedDeny, @ref, @hrs, $decodedUserLocationInfo; return RLM_MODULE_REJECT; } # success &radiusd::radlog(RAD_LOG_INFO, "!!!!!!!!{@ref[12]} ICCID = ${iccid} IMSI = ${imsi} SUCCESS 0"); my $ip = parallelIPProcedure($imsi, $RAD_REQUEST{'NAS-IP-Address'}, @ref[3]); $RAD_REPLY{'Framed-IP-Address'} = $ip; $RAD_REPLY{'Framed-IP-Netmask'} = @ref[4]; $RAD_REPLY{'MS-Primary-DNS-Server'} = @ref[5]; $RAD_REPLY{'MS-Secondary-DNS-Server'} = @ref[6]; $RAD_REPLY{'Session-Timeout'} = '604800'; #$RAD_REPLY{'Framed-Route'} = '45.148.166.176/29 45.148.166.161 1 2 -1 3 400'; &log_response_success($imsi, $iccid); &sendPlatformNotification( $imsi, $RAD_REQUEST{'Called-Station-Id'}, $RAD_REQUEST{'User-Name'}, 'ACCEPT', 'Mobile Data Session Authorized', ($operatorName ? $operatorName : @ref[12]), ($country ? $country : @ref[13]), $iccid, $RAD_REQUEST{'3GPP-RAT-Type'}, @iccidAndProfileId[2], @ref[14], @ref[15], $decodedUserLocationInfo, $technical_imsi, $multi_imsi, $RAD_REPLY); undef $visitedMccMnc, $imsi, $test_data, $donor, $multi_imsi, $technical_imsi, @iccidAndProfileId, $customZone, $isZonedDeny, @ref, $iccid, @hrs, $decodedUserLocationInfo, $ip, $operatorName, $country; return RLM_MODULE_OK; } # Function to handle authenticate sub authenticate { return RLM_MODULE_OK; } # Function to handle preacct sub preacct { # For debugging purposes only # &log_request_attributes; return RLM_MODULE_OK; } # Function to handle accounting sub accounting { &log_request_attributes; my $sessionId = $RAD_REQUEST{'Acct-Session-Id'}; my $imsi = $RAD_REQUEST{'3GPP-IMSI'}; $updateSessionId->bind_param(1, $sessionId); $updateSessionId->bind_param(2, $imsi); $updateSessionId->execute(); undef $sessionId, $imsi; return RLM_MODULE_OK; } # Function to handle checksimul sub checksimul { # For debugging purposes only # &log_request_attributes; return RLM_MODULE_OK; } # Function to handle pre_proxy sub pre_proxy { # For debugging purposes only # &log_request_attributes; return RLM_MODULE_OK; } # Function to handle post_proxy sub post_proxy { # For debugging purposes only # &log_request_attributes; return RLM_MODULE_OK; } # Function to handle post_auth sub post_auth { # For debugging purposes only # &log_request_attributes; return RLM_MODULE_OK; } # Function to handle detach sub detach { # For debugging purposes only # &log_request_attributes; # Do some logging. &radiusd::radlog(0, "rlm_perl::Detaching. Reloading. Done."); } sub log_request_attributes { my $logJson = {}; for my $key (keys %RAD_REQUEST) { $logJson->{$key} = $RAD_REQUEST{$key}; } &radiusd::radlog(RAD_LOG_INFO, 'REQUEST: ' . encode_json($logJson)); undef $logJson; } sub log_response_success { my ($imsi, $iccid) = @_; my $logJson = {}; for my $key (keys %RAD_REPLY) { $logJson->{$key} = $RAD_REPLY{$key}; } &radiusd::radlog(RAD_LOG_INFO, "ICCID = ${iccid} IMSI = ${imsi} RESPONSE: " . encode_json($logJson)); undef $logJson; } sub getDecodedLocationInfo { my $type = 'not_set'; my $decodedLocationInfo = {}; my @hex = ($_[0] =~ /(..)/g); my @dec = map {hex($_)} @hex; my @bytes = map {pack('C', $_)} @dec; my $intType = $dec[1]; given ($intType) { when (0) { $type = "CGI"; $decodedLocationInfo->{type} = $type; $decodedLocationInfo->{LAC} = Math::BigInt->new('0x' . join('', map sprintf('%.2x', $_), @dec[5 .. 6]))->{value}[0]; $decodedLocationInfo->{CI} = Math::BigInt->new('0x' . join('', map sprintf('%.2x', $_), @dec[7 .. 8]))->{value}[0] } when (1) { $type = "SAI"; $decodedLocationInfo->{type} = $type; $decodedLocationInfo->{LAC} = Math::BigInt->new('0x' . join('', map sprintf('%.2x', $_), @dec[5 .. 6]))->{value}[0]; $decodedLocationInfo->{SAC} = Math::BigInt->new('0x' . join('', map sprintf('%.2x', $_), @dec[7 .. 8]))->{value}[0] } when (2) { $type = "RAI"; $decodedLocationInfo->{type} = $type; $decodedLocationInfo->{LAC} = Math::BigInt->new('0x' . join('', map sprintf('%.2x', $_), @dec[5 .. 6]))->{value}[0]; $decodedLocationInfo->{RAC} = Math::BigInt->new('0x' . join('', map sprintf('%.2x', $_), @dec[7 .. 7]))->{value}[0] } when (128) { $type = "TAI"; $decodedLocationInfo->{type} = $type; $decodedLocationInfo->{LAC} = "NOT IMPLEMENTED"; $decodedLocationInfo->{CI} = "NOT IMPLEMENTED" } when (129) { $type = "ECGI"; $decodedLocationInfo->{type} = $type; $decodedLocationInfo->{LAC} = "NOT IMPLEMENTED"; $decodedLocationInfo->{CI} = "NOT IMPLEMENTED" } when (130) { $type = "TAI & ECGI"; $decodedLocationInfo->{type} = $type; $decodedLocationInfo->{TAC} = Math::BigInt->new('0x' . join('', map sprintf('%.2x', $_), @dec[5 .. 6]))->{value}[0]; $decodedLocationInfo->{ECI} = Math::BigInt->new('0x' . join('', map sprintf('%.2x', $_), @dec[10 .. 13]))->{value}[0] } } return $decodedLocationInfo; } sub generateRandomIP { &radiusd::radlog(0, "rlm_perl::generateRandomIP start"); my ($nasIP) = @_; my @digits = ""; if ($nasIP eq "216.168.191.35") { @digits = "10.192.0"; } if ($nasIP eq "216.168.191.33") { @digits = "10.193.0"; } if ($nasIP eq "10.5.146.35") { @digits = "10.187.0"; } if ($nasIP eq "202.63.22.8") { @digits = "10.192.0"; } push @digits, int(rand(253)) + 1; &radiusd::radlog(0, "rlm_perl::generateRandomIP stop"); return join '.', @digits; } sub parallelIPProcedure() { &radiusd::radlog(0, "rlm_perl::parallelIPProcedure start"); my ($imsi, $nasIP, $ip) = @_; &radiusd::radlog(RAD_LOG_INFO, "$imsi -> $nasIP -> $ip"); my @spl = split('\.', $ip); my $ipMask = @spl[0] . "." . @spl[1]; my $ipTermination = @spl[2] . "." . @spl[3]; my $query = ""; if ($nasIP eq "216.168.191.33") { $query = "SELECT SUBSTRING_INDEX(DAL, '.', 2), SUBSTRING_INDEX(AMS, '.', 2) FROM aaa_parallel_ranges WHERE SUBSTRING_INDEX(AMS, '.', 2) = " . $ipMask . " OR SUBSTRING_INDEX(DAL, '.', 2) = " . $ipMask . ";"; my $aaaIP = $databaseConnection->prepare($query); $aaaIP->execute; my @result = $aaaIP->fetchrow_array; if (scalar(@result) == 0) { &radiusd::radlog(RAD_LOG_ERROR, "NO RESULT FOUND FOR $ipMask IN AMS"); } else { if (@result[0] ne $ipMask) { &radiusd::radlog(RAD_LOG_INFO, "$imsi -> $ip will change into @result[0].$ipTermination"); $ip = @result[0] . "." . $ipTermination } } undef $query, $aaaIP, $ipTermination, $ipMask, @spl, $imsi, $nasIP; } if ($nasIP eq "216.168.191.35") { $query = "SELECT SUBSTRING_INDEX(DAL, '.', 2), SUBSTRING_INDEX(AMS, '.', 2) FROM aaa_parallel_ranges WHERE SUBSTRING_INDEX(AMS, '.', 2) = " . $ipMask . " OR SUBSTRING_INDEX(DAL, '.', 2) = " . $ipMask . ";"; my $aaaIP = $databaseConnection->prepare($query); $aaaIP->execute; my @result = $aaaIP->fetchrow_array; if (scalar(@result) == 0) { &radiusd::radlog(RAD_LOG_ERROR, "NO RESULT FOUND FOR $ipMask IN AMS"); } else { if (@result[1] ne $ipMask) { &radiusd::radlog(RAD_LOG_INFO, "$imsi -> $ip will change into @result[1].$ipTermination"); $ip = @result[1] . "." . $ipTermination } } undef $query, $aaaIP, $ipTermination, $ipMask, @spl, $imsi, $nasIP; } if($nasIP eq "202.63.22.8"){ $query = "SELECT SUBSTRING_INDEX(DAL, '.', 2), SUBSTRING_INDEX(AMS, '.', 2) FROM aaa_parallel_ranges WHERE SUBSTRING_INDEX(AMS, '.', 2) = " . $ipMask . " OR SUBSTRING_INDEX(DAL, '.', 2) = " . $ipMask . ";"; my $aaaIP = $databaseConnection->prepare($query); $aaaIP->execute; my @result = $aaaIP->fetchrow_array; if (scalar(@result) == 0) { &radiusd::radlog(RAD_LOG_ERROR, "NO RESULT FOUND FOR $ipMask IN AMS"); } else { if(@result[1] ne $ipMask){ &radiusd::radlog(RAD_LOG_INFO, "$imsi -> $ip will change into @result[ 1].$ipTermination"); $ip = @result[1] . "." . $ipTermination } } undef $query, $aaaIP, $ipTermination, $ipMask, @spl, $imsi, $nasIP; } &radiusd::radlog(0, "rlm_perl::parallelIPProcedure stop"); return $ip; }