// // 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 #include #include #include #include namespace nr::gnb { GtpTask::GtpTask(TaskBase *base) : m_base{base}, m_udpServer{}, m_ueContexts{}, m_rateLimiter(std::make_unique()), 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(*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(*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(*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(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(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 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(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(); // TODO: currently using first QSI ul->qfi = static_cast(pduSession->qosFlows->list.array[0]->qosFlowIdentifier); auto cont = std::make_unique(); 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::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