mirror of https://github.com/pvnis/srsRAN_4G.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
345 lines
10 KiB
C++
345 lines
10 KiB
C++
/**
|
|
*
|
|
* \section COPYRIGHT
|
|
*
|
|
* Copyright 2013-2020 Software Radio Systems Limited
|
|
*
|
|
* By using this file, you agree to the terms and conditions set
|
|
* forth in the LICENSE file which can be found at the top level of
|
|
* the distribution.
|
|
*
|
|
*/
|
|
|
|
#include "srsepc/hdr/spgw/gtpu.h"
|
|
#include "srsepc/hdr/mme/mme_gtpc.h"
|
|
#include "srslte/upper/gtpu.h"
|
|
#include <algorithm>
|
|
#include <arpa/inet.h>
|
|
#include <fcntl.h>
|
|
#include <inttypes.h> // for printing uint64_t
|
|
#include <linux/if.h>
|
|
#include <linux/if_tun.h>
|
|
#include <linux/ip.h>
|
|
#include <netinet/in.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
|
|
namespace srsepc {
|
|
|
|
/**************************************
|
|
*
|
|
* GTP-U class that handles the packet
|
|
* forwarding to and from eNBs
|
|
*
|
|
**************************************/
|
|
|
|
spgw::gtpu::gtpu() : m_sgi_up(false), m_s1u_up(false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
spgw::gtpu::~gtpu()
|
|
{
|
|
return;
|
|
}
|
|
|
|
int spgw::gtpu::init(spgw_args_t* args, spgw* spgw, gtpc_interface_gtpu* gtpc)
|
|
{
|
|
int err;
|
|
|
|
// Store interfaces
|
|
m_spgw = spgw;
|
|
m_gtpc = gtpc;
|
|
|
|
// Init SGi interface
|
|
err = init_sgi(args);
|
|
if (err != SRSLTE_SUCCESS) {
|
|
srslte::console("Could not initialize the SGi interface.\n");
|
|
return err;
|
|
}
|
|
|
|
// Init S1-U
|
|
err = init_s1u(args);
|
|
if (err != SRSLTE_SUCCESS) {
|
|
srslte::console("Could not initialize the S1-U interface.\n");
|
|
return err;
|
|
}
|
|
|
|
m_logger.info("SPGW GTP-U Initialized.");
|
|
srslte::console("SPGW GTP-U Initialized.\n");
|
|
return SRSLTE_SUCCESS;
|
|
}
|
|
|
|
void spgw::gtpu::stop()
|
|
{
|
|
// Clean up SGi interface
|
|
if (m_sgi_up) {
|
|
close(m_sgi);
|
|
}
|
|
// Clean up S1-U socket
|
|
if (m_s1u_up) {
|
|
close(m_s1u);
|
|
}
|
|
}
|
|
|
|
int spgw::gtpu::init_sgi(spgw_args_t* args)
|
|
{
|
|
struct ifreq ifr;
|
|
int sgi_sock;
|
|
|
|
if (m_sgi_up) {
|
|
return SRSLTE_ERROR_ALREADY_STARTED;
|
|
}
|
|
|
|
// Construct the TUN device
|
|
m_sgi = open("/dev/net/tun", O_RDWR);
|
|
m_logger.info("TUN file descriptor = %d", m_sgi);
|
|
if (m_sgi < 0) {
|
|
m_logger.error("Failed to open TUN device: %s", strerror(errno));
|
|
return SRSLTE_ERROR_CANT_START;
|
|
}
|
|
|
|
memset(&ifr, 0, sizeof(ifr));
|
|
ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
|
|
strncpy(
|
|
ifr.ifr_ifrn.ifrn_name, args->sgi_if_name.c_str(), std::min(args->sgi_if_name.length(), (size_t)(IFNAMSIZ - 1)));
|
|
ifr.ifr_ifrn.ifrn_name[IFNAMSIZ - 1] = '\0';
|
|
|
|
if (ioctl(m_sgi, TUNSETIFF, &ifr) < 0) {
|
|
m_logger.error("Failed to set TUN device name: %s", strerror(errno));
|
|
close(m_sgi);
|
|
return SRSLTE_ERROR_CANT_START;
|
|
}
|
|
|
|
// Bring up the interface
|
|
sgi_sock = socket(AF_INET, SOCK_DGRAM, 0);
|
|
if (ioctl(sgi_sock, SIOCGIFFLAGS, &ifr) < 0) {
|
|
m_logger.error("Failed to bring up socket: %s", strerror(errno));
|
|
close(sgi_sock);
|
|
close(m_sgi);
|
|
return SRSLTE_ERROR_CANT_START;
|
|
}
|
|
|
|
ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
|
|
if (ioctl(sgi_sock, SIOCSIFFLAGS, &ifr) < 0) {
|
|
m_logger.error("Failed to set socket flags: %s", strerror(errno));
|
|
close(sgi_sock);
|
|
close(m_sgi);
|
|
return SRSLTE_ERROR_CANT_START;
|
|
}
|
|
|
|
// Set IP of the interface
|
|
struct sockaddr_in* addr = (struct sockaddr_in*)&ifr.ifr_addr;
|
|
addr->sin_family = AF_INET;
|
|
addr->sin_addr.s_addr = inet_addr(args->sgi_if_addr.c_str());
|
|
addr->sin_port = 0;
|
|
|
|
if (ioctl(sgi_sock, SIOCSIFADDR, &ifr) < 0) {
|
|
m_logger.error(
|
|
"Failed to set TUN interface IP. Address: %s, Error: %s", args->sgi_if_addr.c_str(), strerror(errno));
|
|
close(m_sgi);
|
|
close(sgi_sock);
|
|
return SRSLTE_ERROR_CANT_START;
|
|
}
|
|
|
|
ifr.ifr_netmask.sa_family = AF_INET;
|
|
((struct sockaddr_in*)&ifr.ifr_netmask)->sin_addr.s_addr = inet_addr("255.255.255.0");
|
|
if (ioctl(sgi_sock, SIOCSIFNETMASK, &ifr) < 0) {
|
|
m_logger.error("Failed to set TUN interface Netmask. Error: %s", strerror(errno));
|
|
close(m_sgi);
|
|
close(sgi_sock);
|
|
return SRSLTE_ERROR_CANT_START;
|
|
}
|
|
|
|
close(sgi_sock);
|
|
m_sgi_up = true;
|
|
m_logger.info("Initialized SGi interface");
|
|
return SRSLTE_SUCCESS;
|
|
}
|
|
|
|
int spgw::gtpu::init_s1u(spgw_args_t* args)
|
|
{
|
|
// Open S1-U socket
|
|
m_s1u = socket(AF_INET, SOCK_DGRAM, 0);
|
|
if (m_s1u == -1) {
|
|
m_logger.error("Failed to open socket: %s", strerror(errno));
|
|
return SRSLTE_ERROR_CANT_START;
|
|
}
|
|
m_s1u_up = true;
|
|
|
|
// Bind the socket
|
|
m_s1u_addr.sin_family = AF_INET;
|
|
m_s1u_addr.sin_addr.s_addr = inet_addr(args->gtpu_bind_addr.c_str());
|
|
m_s1u_addr.sin_port = htons(GTPU_RX_PORT);
|
|
|
|
if (bind(m_s1u, (struct sockaddr*)&m_s1u_addr, sizeof(struct sockaddr_in))) {
|
|
m_logger.error("Failed to bind socket: %s", strerror(errno));
|
|
return SRSLTE_ERROR_CANT_START;
|
|
}
|
|
m_logger.info("S1-U socket = %d", m_s1u);
|
|
m_logger.info("S1-U IP = %s, Port = %d ", inet_ntoa(m_s1u_addr.sin_addr), ntohs(m_s1u_addr.sin_port));
|
|
|
|
m_logger.info("Initialized S1-U interface");
|
|
return SRSLTE_SUCCESS;
|
|
}
|
|
|
|
void spgw::gtpu::handle_sgi_pdu(srslte::unique_byte_buffer_t msg)
|
|
{
|
|
bool usr_found = false;
|
|
bool ctr_found = false;
|
|
|
|
std::map<uint32_t, srslte::gtpc_f_teid_ie>::iterator gtpu_fteid_it;
|
|
std::map<in_addr_t, uint32_t>::iterator gtpc_teid_it;
|
|
srslte::gtpc_f_teid_ie enb_fteid;
|
|
uint32_t spgw_teid;
|
|
struct iphdr* iph = (struct iphdr*)msg->msg;
|
|
m_logger.debug("Received SGi PDU. Bytes %d", msg->N_bytes);
|
|
|
|
if (iph->version != 4) {
|
|
m_logger.info("IPv6 not supported yet.");
|
|
return;
|
|
}
|
|
if (ntohs(iph->tot_len) < 20) {
|
|
m_logger.warning("Invalid IP header length. IP length %d.", ntohs(iph->tot_len));
|
|
return;
|
|
}
|
|
|
|
// Logging PDU info
|
|
m_logger.debug("SGi PDU -- IP version %d, Total length %d", int(iph->version), ntohs(iph->tot_len));
|
|
m_logger.debug("SGi PDU -- IP src addr %s", srslte::gtpu_ntoa(iph->saddr).c_str());
|
|
m_logger.debug("SGi PDU -- IP dst addr %s", srslte::gtpu_ntoa(iph->daddr).c_str());
|
|
|
|
// Find user and control tunnel
|
|
gtpu_fteid_it = m_ip_to_usr_teid.find(iph->daddr);
|
|
if (gtpu_fteid_it != m_ip_to_usr_teid.end()) {
|
|
usr_found = true;
|
|
enb_fteid = gtpu_fteid_it->second;
|
|
}
|
|
gtpc_teid_it = m_ip_to_ctr_teid.find(iph->daddr);
|
|
if (gtpc_teid_it != m_ip_to_ctr_teid.end()) {
|
|
ctr_found = true;
|
|
spgw_teid = gtpc_teid_it->second;
|
|
}
|
|
|
|
// Handle SGi packet
|
|
if (usr_found == false && ctr_found == false) {
|
|
m_logger.debug("Packet for unknown UE.");
|
|
} else if (usr_found == false && ctr_found == true) {
|
|
m_logger.debug("Packet for attached UE that is not ECM connected.");
|
|
m_logger.debug("Triggering Donwlink Notification Requset.");
|
|
m_gtpc->send_downlink_data_notification(spgw_teid);
|
|
m_gtpc->queue_downlink_packet(spgw_teid, std::move(msg));
|
|
return;
|
|
} else if (usr_found == false && ctr_found == true) {
|
|
m_logger.error("User plane tunnel found without a control plane tunnel present.");
|
|
} else {
|
|
send_s1u_pdu(enb_fteid, msg.get());
|
|
}
|
|
}
|
|
|
|
void spgw::gtpu::handle_s1u_pdu(srslte::byte_buffer_t* msg)
|
|
{
|
|
srslte::gtpu_header_t header;
|
|
srslte::gtpu_read_header(msg, &header, m_logger);
|
|
|
|
m_logger.debug("Received PDU from S1-U. Bytes=%d", msg->N_bytes);
|
|
m_logger.debug("TEID 0x%x. Bytes=%d", header.teid, msg->N_bytes);
|
|
int n = write(m_sgi, msg->msg, msg->N_bytes);
|
|
if (n < 0) {
|
|
m_logger.error("Could not write to TUN interface.");
|
|
} else {
|
|
m_logger.debug("Forwarded packet to TUN interface. Bytes= %d/%d", n, msg->N_bytes);
|
|
}
|
|
return;
|
|
}
|
|
|
|
void spgw::gtpu::send_s1u_pdu(srslte::gtp_fteid_t enb_fteid, srslte::byte_buffer_t* msg)
|
|
{
|
|
// Set eNB destination address
|
|
struct sockaddr_in enb_addr;
|
|
enb_addr.sin_family = AF_INET;
|
|
enb_addr.sin_port = htons(GTPU_RX_PORT);
|
|
enb_addr.sin_addr.s_addr = enb_fteid.ipv4;
|
|
|
|
// Setup GTP-U header
|
|
srslte::gtpu_header_t header;
|
|
header.flags = GTPU_FLAGS_VERSION_V1 | GTPU_FLAGS_GTP_PROTOCOL;
|
|
header.message_type = GTPU_MSG_DATA_PDU;
|
|
header.length = msg->N_bytes;
|
|
header.teid = enb_fteid.teid;
|
|
|
|
m_logger.debug("User plane tunnel found SGi PDU. Forwarding packet to S1-U.");
|
|
m_logger.debug("eNB F-TEID -- eNB IP %s, eNB TEID 0x%x.", inet_ntoa(enb_addr.sin_addr), enb_fteid.teid);
|
|
|
|
// Write header into packet
|
|
int n;
|
|
if (!srslte::gtpu_write_header(&header, msg, m_logger)) {
|
|
m_logger.error("Error writing GTP-U header on PDU");
|
|
goto out;
|
|
}
|
|
|
|
// Send packet to destination
|
|
n = sendto(m_s1u, msg->msg, msg->N_bytes, 0, (struct sockaddr*)&enb_addr, sizeof(enb_addr));
|
|
if (n < 0) {
|
|
m_logger.error("Error sending packet to eNB");
|
|
} else if ((unsigned int)n != msg->N_bytes) {
|
|
m_logger.error("Mis-match between packet bytes and sent bytes: Sent: %d/%d", n, msg->N_bytes);
|
|
}
|
|
|
|
out:
|
|
m_logger.debug("Deallocating packet after sending S1-U message");
|
|
return;
|
|
}
|
|
|
|
void spgw::gtpu::send_all_queued_packets(srslte::gtp_fteid_t dw_user_fteid,
|
|
std::queue<srslte::unique_byte_buffer_t>& pkt_queue)
|
|
{
|
|
m_logger.debug("Sending all queued packets");
|
|
while (!pkt_queue.empty()) {
|
|
srslte::unique_byte_buffer_t msg = std::move(pkt_queue.front());
|
|
send_s1u_pdu(dw_user_fteid, msg.get());
|
|
pkt_queue.pop();
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Tunnel managment
|
|
*/
|
|
bool spgw::gtpu::modify_gtpu_tunnel(in_addr_t ue_ipv4, srslte::gtpc_f_teid_ie dw_user_fteid, uint32_t up_ctrl_teid)
|
|
{
|
|
m_logger.info("Modifying GTP-U Tunnel.");
|
|
m_logger.info("UE IP %s", srslte::gtpu_ntoa(ue_ipv4).c_str());
|
|
m_logger.info("Downlink eNB addr %s, U-TEID 0x%x", srslte::gtpu_ntoa(dw_user_fteid.ipv4).c_str(), dw_user_fteid.teid);
|
|
m_logger.info("Uplink C-TEID: 0x%x", up_ctrl_teid);
|
|
m_ip_to_usr_teid[ue_ipv4] = dw_user_fteid;
|
|
m_ip_to_ctr_teid[ue_ipv4] = up_ctrl_teid;
|
|
return true;
|
|
}
|
|
|
|
bool spgw::gtpu::delete_gtpu_tunnel(in_addr_t ue_ipv4)
|
|
{
|
|
// Remove GTP-U connections, if any.
|
|
if (m_ip_to_usr_teid.count(ue_ipv4)) {
|
|
m_ip_to_usr_teid.erase(ue_ipv4);
|
|
} else {
|
|
m_logger.error("Could not find GTP-U Tunnel to delete.");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool spgw::gtpu::delete_gtpc_tunnel(in_addr_t ue_ipv4)
|
|
{
|
|
// Remove Ctrl TEID from IP mapping.
|
|
if (m_ip_to_ctr_teid.count(ue_ipv4)) {
|
|
m_ip_to_ctr_teid.erase(ue_ipv4);
|
|
} else {
|
|
m_logger.error("Could not find GTP-C Tunnel info to delete.");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace srsepc
|