Files
open5gs/lib/gtp/v1/conv.c
Pau Espin Pedrol 3d693da73e [MME] Gn: Introduce initial support for 4G->2G cell reselection
In an Inter-RAT setup a UE could perform a RAU coming from a 4G network.
In that case the UE/MS is unknown to the SGSN and it should request the
SGSN context (MM, PDP) from the MME. This is done through the following
GTPv1C message exchange on the Gn interface of SGSN and MME:
SGSN -> MME: SGSN Context Request
SGSN <- MME: SGSN Context Response
SGSN -> MME: SGSN Context Acknowledge

This commit doesn't aim to be a complete implementation of the mentioned
procedure, since it's quite a complex one, with lots of fields and logic
required. This so far only implements in general the minimally
successful case by filling as much as possible the required set of
fields.
This will allow for a base onto which do incremental improvements and
fixes while testing against UEs and SGSNs (such as osmo-sgsn, which
doesn't yet support this procedure but will potentially earn it soon).

This commit doesn't implement the reverse direction, aka UE issuing cell
reselection 2G->4G. Initial support for this scenario will hopefully be
added soon as a follow-up patch, similar to this one.

Related: https://osmocom.org/issues/6294
2023-12-23 09:56:55 +09:00

303 lines
8.7 KiB
C

/*
* Copyright (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
*
* This file is part of Open5GS.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "ogs-gtp.h"
int ogs_gtp1_gsn_addr_to_sockaddr(const ogs_gtp1_gsn_addr_t *gsnaddr,
uint16_t gsnaddr_len, uint16_t port, ogs_sockaddr_t **list)
{
ogs_sockaddr_t *addr = NULL, *addr6 = NULL;
ogs_assert(gsnaddr);
ogs_assert(list);
switch (gsnaddr_len) {
case OGS_GTP_GSN_ADDRESS_IPV4_LEN:
addr = ogs_calloc(1, sizeof(ogs_sockaddr_t));
if (!addr) {
ogs_error("ogs_calloc() failed");
return OGS_ERROR;
}
addr->ogs_sa_family = AF_INET;
addr->ogs_sin_port = port;
addr->sin.sin_addr.s_addr = gsnaddr->addr;
*list = addr;
break;
case OGS_GTP_GSN_ADDRESS_IPV6_LEN:
addr6 = ogs_calloc(1, sizeof(ogs_sockaddr_t));
if (!addr6) {
ogs_error("ogs_calloc() failed");
return OGS_ERROR;
}
addr6->ogs_sa_family = AF_INET6;
addr6->ogs_sin_port = port;
memcpy(addr6->sin6.sin6_addr.s6_addr, gsnaddr->addr6, OGS_IPV6_LEN);
*list = addr6;
break;
default:
ogs_error("No IPv4 or IPv6");
return OGS_ERROR;
}
return OGS_OK;
}
int ogs_gtp1_sockaddr_to_gsn_addr(const ogs_sockaddr_t *addr,
const ogs_sockaddr_t *addr6, ogs_gtp1_gsn_addr_t *gsnaddr, int *len)
{
ogs_assert(gsnaddr);
if (addr && addr6) {
ogs_error("GSN Address: Both IPv4 and IPv6 not supported");
return OGS_ERROR;
} else if (addr) {
gsnaddr->addr = addr->sin.sin_addr.s_addr;
*len = OGS_GTP_GSN_ADDRESS_IPV4_LEN;
} else if (addr6) {
memcpy(gsnaddr->addr6, addr6->sin6.sin6_addr.s6_addr, OGS_IPV6_LEN);
*len = OGS_GTP_GSN_ADDRESS_IPV6_LEN;
} else {
ogs_error("No IPv4 or IPv6");
return OGS_ERROR;
}
return OGS_OK;
}
int ogs_gtp1_gsn_addr_to_ip(const ogs_gtp1_gsn_addr_t *gsnaddr, uint16_t gsnaddr_len,
ogs_ip_t *ip)
{
ogs_assert(ip);
ogs_assert(gsnaddr);
memset(ip, 0, sizeof(ogs_ip_t));
if (gsnaddr_len == OGS_GTP_GSN_ADDRESS_IPV4_LEN) {
ip->ipv4 = 1;
ip->ipv6 = 0;
ip->addr = gsnaddr->addr;
} else if (gsnaddr_len == OGS_GTP_GSN_ADDRESS_IPV6_LEN) {
ip->ipv4 = 0;
ip->ipv6 = 1;
memcpy(ip->addr6, gsnaddr->addr6, OGS_IPV6_LEN);
} else {
ogs_error("No IPv4 or IPv6");
return OGS_ERROR;
}
return OGS_OK;
}
int ogs_gtp1_pdu_session_type_to_eua_ietf_type(uint8_t session_type)
{
switch (session_type) {
case OGS_PDU_SESSION_TYPE_IPV4:
return OGS_PDP_EUA_IETF_IPV4;
case OGS_PDU_SESSION_TYPE_IPV6:
return OGS_PDP_EUA_IETF_IPV6;
case OGS_PDU_SESSION_TYPE_IPV4V6:
return OGS_PDP_EUA_IETF_IPV4V6;
default:
return OGS_ERROR;
}
}
int ogs_gtp1_eua_ietf_type_to_pdu_session_type(uint8_t eua_ietf_type)
{
switch (eua_ietf_type) {
case OGS_PDP_EUA_IETF_IPV4:
return OGS_PDU_SESSION_TYPE_IPV4;
case OGS_PDP_EUA_IETF_IPV6:
return OGS_PDU_SESSION_TYPE_IPV6;
case OGS_PDP_EUA_IETF_IPV4V6:
return OGS_PDU_SESSION_TYPE_IPV4V6;
default:
return OGS_ERROR;
}
}
int ogs_gtp1_eua_to_ip(const ogs_eua_t *eua, uint16_t eua_len, ogs_ip_t *ip,
uint8_t *pdu_session_type)
{
ogs_assert(eua);
ogs_assert(ip);
memset(ip, 0, sizeof *ip);
switch (eua->organization) {
case OGS_PDP_EUA_ORG_IETF:
break;
case OGS_PDP_EUA_ORG_ETSI:
default:
ogs_error("Unsupported EUA organization %u", eua->organization);
return OGS_ERROR;
}
eua_len -= 2;
switch (eua->type) {
case OGS_PDP_EUA_IETF_IPV4:
if (eua_len == OGS_IPV4_LEN) {
ip->addr = eua->addr;
} else if (eua_len != 0) {
ogs_error("Wrong IPv4 EUA length %u", eua_len);
return OGS_ERROR;
}
ip->ipv4 = 1;
ip->ipv6 = 0;
*pdu_session_type = OGS_PDU_SESSION_TYPE_IPV4;
break;
case OGS_PDP_EUA_IETF_IPV6:
if (eua_len == OGS_IPV6_LEN) {
memcpy(ip->addr6, eua->addr6, OGS_IPV6_LEN);
} else if (eua_len != 0) {
ogs_error("Wrong IPv6 EUA length %u", eua_len);
return OGS_ERROR;
}
ip->ipv4 = 0;
ip->ipv6 = 1;
*pdu_session_type = OGS_PDU_SESSION_TYPE_IPV6;
break;
case OGS_PDP_EUA_IETF_IPV4V6:
if (eua_len == OGS_IPV4_LEN) {
ip->addr = eua->addr;
} else if (eua_len == OGS_IPV6_LEN) {
memcpy(ip->addr6, eua->addr6, OGS_IPV6_LEN);
} else if (eua_len == OGS_IPV4_LEN + OGS_IPV6_LEN) {
ip->addr = eua->both.addr;
memcpy(ip->addr6, eua->both.addr6, OGS_IPV6_LEN);
} else if (eua_len != 0) {
ogs_error("Wrong IPv4v6 EUA length %u", eua_len);
return OGS_ERROR;
}
ip->ipv4 = 1;
ip->ipv6 = 1;
*pdu_session_type = OGS_PDU_SESSION_TYPE_IPV4V6;
break;
default:
ogs_error("No IPv4 or IPv6");
return OGS_ERROR;
}
return OGS_OK;
}
int ogs_gtp1_ip_to_eua(uint8_t pdu_session_type, const ogs_ip_t *ip,
ogs_eua_t *eua, uint8_t *eua_len)
{
ogs_assert(eua);
ogs_assert(ip);
ogs_assert(eua_len);
memset(eua, 0, sizeof *eua);
eua->spare = 0xf; /* TS 29.060 Figure 35 */
eua->organization = OGS_PDP_EUA_ORG_IETF;
switch (pdu_session_type)
{
case OGS_PDU_SESSION_TYPE_IPV4:
if (!ip->ipv4) {
ogs_error("EUA type IPv4 but no IPv4 address available");
return OGS_ERROR;
}
eua->addr = ip->addr;
*eua_len = 2 + OGS_IPV4_LEN;
eua->type = OGS_PDP_EUA_IETF_IPV4;
break;
case OGS_PDU_SESSION_TYPE_IPV6:
if (!ip->ipv6) {
ogs_error("EUA type IPv4 but no IPv6 address available");
return OGS_ERROR;
}
memcpy(eua->addr6, ip->addr6, OGS_IPV6_LEN);
*eua_len = 2 + OGS_IPV6_LEN;
eua->type = OGS_PDP_EUA_IETF_IPV6;
break;
case OGS_PDU_SESSION_TYPE_IPV4V6:
if (ip->ipv4 && ip->ipv6) {
eua->both.addr = ip->addr;
memcpy(eua->both.addr6, ip->addr6, OGS_IPV6_LEN);
*eua_len = 2 + OGS_IPV4_LEN + OGS_IPV6_LEN;
} else if (ip->ipv4) {
eua->addr = ip->addr;
*eua_len = 2 + OGS_IPV4_LEN;
} else if (ip->ipv6) {
memcpy(eua->addr6, ip->addr6, OGS_IPV6_LEN);
*eua_len = 2 + OGS_IPV6_LEN;
} else {
ogs_error("EUA type IPv4 but no IPv4 nor IPv6 address available");
return OGS_ERROR;
}
eua->type = OGS_PDP_EUA_IETF_IPV4V6;
break;
default:
ogs_error("Unexpected session type");
return OGS_ERROR;
}
return OGS_OK;
}
/* 3GPP TS 23.401 Table E.3 */
int ogs_gtp1_qos_profile_to_qci(const ogs_gtp1_qos_profile_decoded_t *decoded,
uint8_t *qci)
{
ogs_assert(decoded);
ogs_assert(qci);
if (!decoded->data_octet6_to_13_present) {
/* traffic class not present, take QCI 9 as default */
*qci = 9;
return OGS_OK;
}
switch (decoded->qos_profile.data.traffic_class) {
case OGS_GTP1_QOS_TRAFFIC_CLASS_CONVERSATIONAL:
if (decoded->qos_profile.data.source_statistics_descriptor == OGS_GTP1_QOS_SRC_STATS_DESC_SPEECH)
*qci = 1;
else if (decoded->dec_transfer_delay >= 150)
*qci = 2;
else
*qci = 3;
break;
case OGS_GTP1_QOS_TRAFFIC_CLASS_STREAMING:
*qci = 4;
break;
case OGS_GTP1_QOS_TRAFFIC_CLASS_INTERACTIVE:
switch (decoded->qos_profile.data.traffic_handling_priority) {
case 1:
*qci = decoded->qos_profile.data.signalling_indication ? 5 : 6;
break;
case 2:
*qci = 7;
break;
case 3:
*qci = 8;
break;
default:
*qci = 9;
}
break;
case OGS_GTP1_QOS_TRAFFIC_CLASS_SUBSCRIBED:
case OGS_GTP1_QOS_TRAFFIC_CLASS_BACKGROUND:
default:
*qci = 9;
}
return OGS_OK;
}