288 lines
8.0 KiB
C++
Executable File
288 lines
8.0 KiB
C++
Executable File
//
|
|
// This file is a part of UERANSIM project.
|
|
// Copyright (c) 2023 ALİ GÜNGÖR.
|
|
//
|
|
// https://github.com/aligungr/UERANSIM/
|
|
// See README, LICENSE, and CONTRIBUTING files for licensing details.
|
|
//
|
|
|
|
#include "task.hpp"
|
|
|
|
#include <gnb/gtp/proto.hpp>
|
|
#include <gnb/rls/task.hpp>
|
|
#include <utils/constants.hpp>
|
|
#include <utils/libc_error.hpp>
|
|
|
|
#include <asn/ngap/ASN_NGAP_QosFlowSetupRequestItem.h>
|
|
|
|
namespace nr::gnb
|
|
{
|
|
|
|
GtpTask::GtpTask(TaskBase *base)
|
|
: m_base{base}, m_udpServer{}, m_ueContexts{}, m_rateLimiter(std::make_unique<RateLimiter>()), m_pduSessions{},
|
|
m_sessionTree{}
|
|
{
|
|
m_logger = m_base->logBase->makeUniqueLogger("gtp");
|
|
}
|
|
|
|
void GtpTask::onStart()
|
|
{
|
|
try
|
|
{
|
|
m_udpServer = new udp::UdpServerTask(m_base->config->gtpIp, cons::GtpPort, this);
|
|
m_udpServer->start();
|
|
}
|
|
catch (const LibError &e)
|
|
{
|
|
m_logger->err("GTP/UDP task could not be created. %s", e.what());
|
|
}
|
|
}
|
|
|
|
void GtpTask::onQuit()
|
|
{
|
|
m_udpServer->quit();
|
|
delete m_udpServer;
|
|
|
|
m_ueContexts.clear();
|
|
}
|
|
|
|
void GtpTask::onLoop()
|
|
{
|
|
auto msg = take();
|
|
if (!msg)
|
|
return;
|
|
|
|
switch (msg->msgType)
|
|
{
|
|
case NtsMessageType::GNB_NGAP_TO_GTP: {
|
|
auto &w = dynamic_cast<NmGnbNgapToGtp &>(*msg);
|
|
switch (w.present)
|
|
{
|
|
case NmGnbNgapToGtp::UE_CONTEXT_UPDATE: {
|
|
handleUeContextUpdate(*w.update);
|
|
break;
|
|
}
|
|
case NmGnbNgapToGtp::UE_CONTEXT_RELEASE: {
|
|
handleUeContextDelete(w.ueId);
|
|
break;
|
|
}
|
|
case NmGnbNgapToGtp::SESSION_CREATE: {
|
|
handleSessionCreate(w.resource);
|
|
break;
|
|
}
|
|
case NmGnbNgapToGtp::SESSION_RELEASE: {
|
|
handleSessionRelease(w.ueId, w.psi);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case NtsMessageType::GNB_RLS_TO_GTP: {
|
|
auto &w = dynamic_cast<NmGnbRlsToGtp &>(*msg);
|
|
switch (w.present)
|
|
{
|
|
case NmGnbRlsToGtp::DATA_PDU_DELIVERY: {
|
|
handleUplinkData(w.ueId, w.psi, std::move(w.pdu));
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case NtsMessageType::UDP_SERVER_RECEIVE:
|
|
handleUdpReceive(dynamic_cast<udp::NwUdpServerReceive &>(*msg));
|
|
break;
|
|
default:
|
|
m_logger->unhandledNts(*msg);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void GtpTask::handleUeContextUpdate(const GtpUeContextUpdate &msg)
|
|
{
|
|
if (!m_ueContexts.count(msg.ueId))
|
|
m_ueContexts[msg.ueId] = std::make_unique<GtpUeContext>(msg.ueId);
|
|
|
|
auto &ue = m_ueContexts[msg.ueId];
|
|
ue->ueAmbr = msg.ueAmbr;
|
|
|
|
updateAmbrForUe(ue->ueId);
|
|
}
|
|
|
|
void GtpTask::handleSessionCreate(PduSessionResource *session)
|
|
{
|
|
if (!m_ueContexts.count(session->ueId))
|
|
{
|
|
m_logger->err("PDU session resource could not be created, UE context with ID[%d] not found", session->ueId);
|
|
return;
|
|
}
|
|
|
|
uint64_t sessionInd = MakeSessionResInd(session->ueId, session->psi);
|
|
m_pduSessions[sessionInd] = std::unique_ptr<PduSessionResource>(session);
|
|
|
|
m_sessionTree.insert(sessionInd, session->downTunnel.teid);
|
|
|
|
updateAmbrForUe(session->ueId);
|
|
updateAmbrForSession(sessionInd);
|
|
}
|
|
|
|
void GtpTask::handleSessionRelease(int ueId, int psi)
|
|
{
|
|
if (!m_ueContexts.count(ueId))
|
|
{
|
|
m_logger->err("PDU session resource could not be released, UE context with ID[%d] not found", ueId);
|
|
return;
|
|
}
|
|
|
|
uint64_t sessionInd = MakeSessionResInd(ueId, psi);
|
|
|
|
// Remove all session information from rate limiter
|
|
m_rateLimiter->updateSessionUplinkLimit(sessionInd, 0);
|
|
m_rateLimiter->updateUeDownlinkLimit(ueId, 0);
|
|
|
|
// And remove from PDU session table
|
|
if (m_pduSessions.count(sessionInd))
|
|
{
|
|
uint32_t teid = m_pduSessions[sessionInd]->downTunnel.teid;
|
|
m_pduSessions.erase(sessionInd);
|
|
|
|
// And remove from the tree
|
|
m_sessionTree.remove(sessionInd, teid);
|
|
}
|
|
}
|
|
|
|
void GtpTask::handleUeContextDelete(int ueId)
|
|
{
|
|
// Find PDU sessions of the UE
|
|
std::vector<uint64_t> sessions{};
|
|
m_sessionTree.enumerateByUe(ueId, sessions);
|
|
|
|
for (auto &session : sessions)
|
|
{
|
|
// Remove all session information from rate limiter
|
|
m_rateLimiter->updateSessionUplinkLimit(session, 0);
|
|
m_rateLimiter->updateUeDownlinkLimit(ueId, 0);
|
|
|
|
// And remove from PDU session table
|
|
uint32_t teid = m_pduSessions[session]->downTunnel.teid;
|
|
m_pduSessions.erase(session);
|
|
|
|
// And remove from the tree
|
|
m_sessionTree.remove(session, teid);
|
|
}
|
|
|
|
// Remove all user information from rate limiter
|
|
m_rateLimiter->updateUeUplinkLimit(ueId, 0);
|
|
m_rateLimiter->updateUeDownlinkLimit(ueId, 0);
|
|
|
|
// Remove UE context
|
|
m_ueContexts.erase(ueId);
|
|
}
|
|
|
|
void GtpTask::handleUplinkData(int ueId, int psi, OctetString &&pdu)
|
|
{
|
|
const uint8_t *data = pdu.data();
|
|
|
|
// ignore non IPv4 packets
|
|
if ((data[0] >> 4 & 0xF) != 4)
|
|
return;
|
|
|
|
uint64_t sessionInd = MakeSessionResInd(ueId, psi);
|
|
|
|
if (!m_pduSessions.count(sessionInd))
|
|
{
|
|
m_logger->err("Uplink data failure, PDU session not found. UE[%d] PSI[%d]", ueId, psi);
|
|
return;
|
|
}
|
|
|
|
auto &pduSession = m_pduSessions[sessionInd];
|
|
|
|
if (m_rateLimiter->allowUplinkPacket(sessionInd, static_cast<int64_t>(pdu.length())))
|
|
{
|
|
gtp::GtpMessage gtp{};
|
|
gtp.payload = std::move(pdu);
|
|
gtp.msgType = gtp::GtpMessage::MT_G_PDU;
|
|
gtp.teid = pduSession->upTunnel.teid;
|
|
|
|
auto ul = std::make_unique<gtp::UlPduSessionInformation>();
|
|
// TODO: currently using first QSI
|
|
ul->qfi = static_cast<int>(pduSession->qosFlows->list.array[0]->qosFlowIdentifier);
|
|
|
|
auto cont = std::make_unique<gtp::PduSessionContainerExtHeader>();
|
|
cont->pduSessionInformation = std::move(ul);
|
|
gtp.extHeaders.push_back(std::move(cont));
|
|
|
|
OctetString gtpPdu;
|
|
if (!gtp::EncodeGtpMessage(gtp, gtpPdu))
|
|
m_logger->err("Uplink data failure, GTP encoding failed");
|
|
else
|
|
m_udpServer->send(InetAddress(pduSession->upTunnel.address, cons::GtpPort), gtpPdu);
|
|
}
|
|
}
|
|
|
|
void GtpTask::handleUdpReceive(const udp::NwUdpServerReceive &msg)
|
|
{
|
|
OctetView buffer{msg.packet};
|
|
auto gtp = gtp::DecodeGtpMessage(buffer);
|
|
|
|
switch (gtp->msgType)
|
|
{
|
|
case gtp::GtpMessage::MT_G_PDU: {
|
|
auto sessionInd = m_sessionTree.findByDownTeid(gtp->teid);
|
|
if (sessionInd == 0)
|
|
{
|
|
m_logger->err("TEID %d not found on GTP-U Downlink", gtp->teid);
|
|
return;
|
|
}
|
|
|
|
if (m_rateLimiter->allowDownlinkPacket(sessionInd, gtp->payload.length()))
|
|
{
|
|
auto w = std::make_unique<NmGnbGtpToRls>(NmGnbGtpToRls::DATA_PDU_DELIVERY);
|
|
w->ueId = GetUeId(sessionInd);
|
|
w->psi = GetPsi(sessionInd);
|
|
w->pdu = std::move(gtp->payload);
|
|
m_base->rlsTask->push(std::move(w));
|
|
}
|
|
return;
|
|
}
|
|
case gtp::GtpMessage::MT_ECHO_REQUEST: {
|
|
gtp::GtpMessage gtpResponse{};
|
|
gtpResponse.msgType = gtp::GtpMessage::MT_ECHO_RESPONSE;
|
|
gtpResponse.seq = gtp->seq;
|
|
gtpResponse.payload = OctetString::FromOctet2({14, 0});
|
|
|
|
OctetString gtpPdu;
|
|
if (gtp::EncodeGtpMessage(gtpResponse, gtpPdu))
|
|
m_udpServer->send(msg.fromAddress, gtpPdu);
|
|
else
|
|
m_logger->err("Uplink data failure, GTP encoding failed");
|
|
return;
|
|
}
|
|
default: {
|
|
m_logger->err("Unhandled GTP-U message type: %d", gtp->msgType);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GtpTask::updateAmbrForUe(int ueId)
|
|
{
|
|
if (!m_ueContexts.count(ueId))
|
|
return;
|
|
|
|
auto &ue = m_ueContexts[ueId];
|
|
m_rateLimiter->updateUeUplinkLimit(ueId, ue->ueAmbr.ulAmbr);
|
|
m_rateLimiter->updateUeDownlinkLimit(ueId, ue->ueAmbr.dlAmbr);
|
|
}
|
|
|
|
void GtpTask::updateAmbrForSession(uint64_t pduSession)
|
|
{
|
|
if (!m_pduSessions.count(pduSession))
|
|
return;
|
|
|
|
auto &sess = m_pduSessions[pduSession];
|
|
m_rateLimiter->updateSessionUplinkLimit(pduSession, sess->sessionAmbr.ulAmbr);
|
|
m_rateLimiter->updateSessionDownlinkLimit(pduSession, sess->sessionAmbr.dlAmbr);
|
|
}
|
|
|
|
} // namespace nr::gnb
|