pespin has submitted this change. ( https://gerrit.osmocom.org/c/osmo-uecups/+/40763?usp=email )
Change subject: Introduce GTPv1U QFI support ......................................................................
Introduce GTPv1U QFI support
* Allow user configuring over the json interface the Tx of PDU Session Container with UL/DL direction and a given QFI per flow. * Support decoding incoming GTPv1U packets containing extended headers. * Update ttcn3/UECUPTS_Types.ttcn accordingly to be able to use the new features.
With this patch it is now possible to use osmo-uecups against a 5GC.
Related: SYS#7073 Change-Id: I6262c3dfbf774b361aadf0aa53ce09b5fdc38da4 --- M daemon/cups_client.c M daemon/gtp.h M daemon/gtp_endpoint.c M daemon/gtp_tunnel.c M daemon/internal.h M daemon/tun_device.c M ttcn3/UECUPS_Types.ttcn 7 files changed, 339 insertions(+), 76 deletions(-)
Approvals: Jenkins Builder: Verified laforge: Looks good to me, approved
diff --git a/daemon/cups_client.c b/daemon/cups_client.c index c9d8652..b72ebd7 100644 --- a/daemon/cups_client.c +++ b/daemon/cups_client.c @@ -15,6 +15,7 @@ #include <osmocom/core/exec.h>
#include "internal.h" +#include "gtp.h"
#include <netinet/sctp.h>
@@ -202,6 +203,58 @@ return 0; }
+static int parse_ext_hdr_pdu_session_container(struct gtp1u_exthdr_pdu_sess_container *out, json_t *jpdu_sess_cont) +{ + json_t *jpdu_type, *jqfi; + const char *str_pdu_type; + + if (!json_is_object(jpdu_sess_cont)) + return -EINVAL; + + memset(out, 0, sizeof(*out)); + + out->enabled = true; + + jpdu_type = json_object_get(jpdu_sess_cont, "pdu_type"); + if (!json_is_string(jpdu_type)) + return -EINVAL; + str_pdu_type = json_string_value(jpdu_type); + if (!strcmp(str_pdu_type, "ul_pdu_sess_info")) + out->pdu_type = GTP1_EXTHDR_PDU_TYPE_UL_PDU_SESSION_INFORMATION; + else if (!strcmp(str_pdu_type, "dl_pdu_sess_info")) + out->pdu_type = GTP1_EXTHDR_PDU_TYPE_DL_PDU_SESSION_INFORMATION; + else + return -EINVAL; + + jqfi = json_object_get(jpdu_sess_cont, "qfi"); + if (!json_is_integer(jqfi)) + return -EINVAL; + out->qos_flow_identifier = json_number_value(jqfi); + + return 0; +} +static int parse_ext_hdr(struct gtp1u_exthdrs *out, json_t *jexthdr) +{ + json_t *jseq_num, *jn_pdu_num, *jpdu_sess_cont; + int rc = 0; + + if (!json_is_object(jexthdr)) + return -EINVAL; + + jseq_num = json_object_get(jexthdr, "sequence_number"); + if (jseq_num) + out->seq_num_enabled = true; + + jn_pdu_num = json_object_get(jexthdr, "n_pdu_number"); + if (jn_pdu_num) + out->n_pdu_num_enabled = true; + + jpdu_sess_cont = json_object_get(jexthdr, "pdu_session_container"); + if (jpdu_sess_cont) + rc = parse_ext_hdr_pdu_session_container(&out->pdu_sess_container, jpdu_sess_cont); + + return rc; +}
static int parse_create_tun(struct gtp_tunnel_params *out, json_t *ctun) { @@ -209,6 +262,7 @@ json_t *jrx_teid, *jtx_teid; json_t *jtun_dev_name, *jtun_netns_name; json_t *juser_addr, *juser_addr_type; + json_t *jgtp_ext_hdr; int rc;
/* '{"create_tun":{"tx_teid":1234,"rx_teid":5678,"user_addr_type":"IPV4","user_addr":"21222324","local_gtp_ep":{"addr_type":"IPV4","ip":"31323334","Port":2152},"remote_gtp_ep":{"addr_type":"IPV4","ip":"41424344","Port":2152},"tun_dev_name":"tun23","tun_netns_name":"foo"}}' */ @@ -257,6 +311,13 @@ out->tun_netns_name = talloc_strdup(out, json_string_value(jtun_netns_name)); }
+ jgtp_ext_hdr = json_object_get(ctun, "gtp_ext_hdr"); + if (jgtp_ext_hdr) { + rc = parse_ext_hdr(&out->exthdr, jgtp_ext_hdr); + if (rc < 0) + return rc; + } + return 0; }
diff --git a/daemon/gtp.h b/daemon/gtp.h index f2acd36..2947d25 100644 --- a/daemon/gtp.h +++ b/daemon/gtp.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0 */ #pragma once #include <stdint.h> +#include <osmocom/core/endian.h>
/* General GTP protocol related definitions. */
@@ -31,3 +32,60 @@ #define GTP1_F_SEQ 0x02 #define GTP1_F_EXTHDR 0x04 #define GTP1_F_MASK 0x07 + + +/* + * 5GC GTP Header (16byte) + * o Flags(1byte) : 0x34 + * o Message Type(1byte) : T-PDU (0xff) + * o Length(2byte) : 36 + * o TEID(4byte) : 0x00000001 + * o Sequence Number(2byte) : 0x0000 + * o N PDU Number(1byte) : 0x00 + * o Next extension header type(4byte): PDU Session container(1byte) : (0x85) + * o Extension header(4byte) + * - Extension HEader Length(1byte) : 1 + * - PDU Session Container(2byte) + * ; PDU Type : UL PDU SESSION INFORMATION (1) + * ; QoS Flow Identifier (QFI) : 1 + * - Next extension header type : No more extension headers (0x00) + */ + +#define GTP1_EXTHDR_UDP_PORT 0x40 +#define GTP1_EXTHDR_PDU_SESSION_CONTAINER 0x85 +#define GTP1_EXTHDR_PDCP_NUMBER 0xc0 +#define GTP1_EXTHDR_NO_MORE_EXTENSION_HEADERS 0x0 + +#define GTP1_EXTHDR_PDU_TYPE_DL_PDU_SESSION_INFORMATION 0 +#define GTP1_EXTHDR_PDU_TYPE_UL_PDU_SESSION_INFORMATION 1 + +struct gtp1_exthdr { + uint16_t sequence_number; + uint8_t n_pdu_number; + struct { +#if OSMO_IS_LITTLE_ENDIAN + uint8_t type; + uint8_t len; + union { + struct { /* TODO: make sure order of fields is correct or swapped */ + uint8_t spare1:4, + pdu_type:4; + uint8_t qos_flow_identifier:6, + reflective_qos_indicator:1, + paging_policy_presence:1; +#elif OSMO_IS_BIG_ENDIAN +/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */ + uint8_t type; + uint8_t len; + union { + struct { + uint8_t spare1:4, + pdu_type:4; + uint8_t paging_policy_presence:1, reflective_qos_indicator:1, qos_flow_identifier:6; +#endif + }; + uint16_t udp_port; + uint16_t pdcp_number; + }; + } __attribute__ ((packed)) array[8]; +} __attribute__ ((packed)); diff --git a/daemon/gtp_endpoint.c b/daemon/gtp_endpoint.c index 4221e14..bb417da 100644 --- a/daemon/gtp_endpoint.c +++ b/daemon/gtp_endpoint.c @@ -27,19 +27,98 @@ * GTP Endpoint (UDP socket) ***********************************************************************/
+static void handle_gtp1u(struct gtp_endpoint *ep, const uint8_t *buffer, unsigned int nread) +{ + struct gtp_daemon *d = ep->d; + struct gtp_tunnel *t; + const struct gtp1_header *gtph; + const uint8_t *payload; + int rc, outfd; + uint32_t teid; + uint16_t gtp_len; + + if (nread < sizeof(*gtph)) { + LOGEP(ep, LOGL_NOTICE, "Short read: %u < %lu\n", nread, sizeof(*gtph)); + return; + } + gtph = (struct gtp1_header *)buffer; + + /* check GTP header contents */ + if ((gtph->flags & 0xf0) != 0x30) { + LOGEP(ep, LOGL_NOTICE, "Unexpected GTP Flags: 0x%02x\n", gtph->flags); + return; + } + if (gtph->type != GTP_TPDU) { + LOGEP(ep, LOGL_NOTICE, "Unexpected GTP Message Type: 0x%02x\n", gtph->type); + return; + } + + gtp_len = ntohs(gtph->length); + if (sizeof(*gtph)+gtp_len > nread) { + LOGEP(ep, LOGL_NOTICE, "Short GTP Message: %lu < len=%u\n", + sizeof(*gtph)+gtp_len, nread); + return; + } + teid = ntohl(gtph->tid); + + payload = buffer + sizeof(*gtph); + if (gtph->flags & GTP1_F_MASK) { + const struct gtp1_exthdr *exthdr = (const struct gtp1_exthdr *)payload; + if (gtp_len < 4) { + LOGEP(ep, LOGL_NOTICE, "Short GTP Message according to flags 0x%02x: %lu < len=%u\n", + gtph->flags, sizeof(*gtph) + gtp_len, nread); + return; + } + gtp_len -= 4; + payload += 4; + const uint8_t *it = &exthdr->array[0].type; + while (*it != 0) { + unsigned int ext_len; + if (gtp_len < 1) { + LOGEP(ep, LOGL_NOTICE, "Short GTP Message according to flags 0x%02x: %lu < len=%u\n", + gtph->flags, sizeof(*gtph) + gtp_len, nread); + return; + } + ext_len = 1 + 1 + it[1] + 1; + if (gtp_len < ext_len) { + LOGEP(ep, LOGL_NOTICE, "Short GTP Message according to flags 0x%02x: %lu < len=%u\n", + gtph->flags, sizeof(*gtph) + gtp_len, nread); + return; + } + gtp_len -= ext_len; + payload += ext_len; + it = payload - 1; + } + } + + /* 2) look-up tunnel based on TEID */ + pthread_rwlock_rdlock(&d->rwlock); + t = _gtp_tunnel_find_r(d, teid, ep); + if (!t) { + pthread_rwlock_unlock(&d->rwlock); + LOGEP(ep, LOGL_NOTICE, "Unable to find tunnel for TEID=0x%08x\n", teid); + return; + } + outfd = t->tun_dev->fd; + pthread_rwlock_unlock(&d->rwlock); + + /* 3) write to TUN device */ + rc = write(outfd, payload, gtp_len); + if (rc < gtp_len) { + LOGEP(ep, LOGL_FATAL, "Error writing to tun device %s\n", strerror(errno)); + exit(1); + } +} + /* one thread for reading from each GTP/UDP socket (GTP decapsulation -> tun) */ static void *gtp_endpoint_thread(void *arg) { struct gtp_endpoint *ep = (struct gtp_endpoint *)arg; - struct gtp_daemon *d = ep->d;
- uint8_t buffer[MAX_UDP_PACKET+sizeof(struct gtp1_header)]; + uint8_t buffer[sizeof(struct gtp1_header) + sizeof(struct gtp1_exthdr) + MAX_UDP_PACKET];
while (1) { - struct gtp_tunnel *t; - const struct gtp1_header *gtph; - int rc, nread, outfd; - uint32_t teid; + int rc;
/* 1) read GTP packet from UDP socket */ rc = recvfrom(ep->fd, buffer, sizeof(buffer), 0, (struct sockaddr *)NULL, 0); @@ -47,46 +126,7 @@ LOGEP(ep, LOGL_FATAL, "Error reading from UDP socket: %s\n", strerror(errno)); exit(1); } - nread = rc; - if (nread < sizeof(*gtph)) { - LOGEP(ep, LOGL_NOTICE, "Short read: %d < %lu\n", nread, sizeof(*gtph)); - continue; - } - gtph = (struct gtp1_header *)buffer; - - /* check GTP heaader contents */ - if (gtph->flags != 0x30) { - LOGEP(ep, LOGL_NOTICE, "Unexpected GTP Flags: 0x%02x\n", gtph->flags); - continue; - } - if (gtph->type != GTP_TPDU) { - LOGEP(ep, LOGL_NOTICE, "Unexpected GTP Message Type: 0x%02x\n", gtph->type); - continue; - } - if (sizeof(*gtph)+ntohs(gtph->length) > nread) { - LOGEP(ep, LOGL_NOTICE, "Shotr GTP Message: %lu < len=%d\n", - sizeof(*gtph)+ntohs(gtph->length), nread); - continue; - } - teid = ntohl(gtph->tid); - - /* 2) look-up tunnel based on TEID */ - pthread_rwlock_rdlock(&d->rwlock); - t = _gtp_tunnel_find_r(d, teid, ep); - if (!t) { - pthread_rwlock_unlock(&d->rwlock); - LOGEP(ep, LOGL_NOTICE, "Unable to find tunnel for TEID=0x%08x\n", teid); - continue; - } - outfd = t->tun_dev->fd; - pthread_rwlock_unlock(&d->rwlock); - - /* 3) write to TUN device */ - rc = write(outfd, buffer+sizeof(*gtph), ntohs(gtph->length)); - if (rc < nread-sizeof(struct gtp1_header)) { - LOGEP(ep, LOGL_FATAL, "Error writing to tun device %s\n", strerror(errno)); - exit(1); - } + handle_gtp1u(ep, buffer, rc); } }
diff --git a/daemon/gtp_tunnel.c b/daemon/gtp_tunnel.c index 87d1e69..6c3e81c 100644 --- a/daemon/gtp_tunnel.c +++ b/daemon/gtp_tunnel.c @@ -56,6 +56,7 @@
t->rx_teid = cpars->rx_teid; t->tx_teid = cpars->tx_teid; + memcpy(&t->exthdr, &cpars->exthdr, sizeof(t->exthdr)); memcpy(&t->user_addr, &cpars->user_addr, sizeof(t->user_addr)); memcpy(&t->remote_udp, &cpars->remote_udp, sizeof(t->remote_udp));
diff --git a/daemon/internal.h b/daemon/internal.h index 25a8f91..19cf16a 100644 --- a/daemon/internal.h +++ b/daemon/internal.h @@ -181,6 +181,18 @@ * this is what happens when IP arrives on the tun device */
+struct gtp1u_exthdr_pdu_sess_container { + bool enabled; + uint8_t pdu_type; /* GTP1_EXTHDR_PDU_TYPE_* */ + uint8_t qos_flow_identifier; +}; + +struct gtp1u_exthdrs { + bool seq_num_enabled; + bool n_pdu_num_enabled; + struct gtp1u_exthdr_pdu_sess_container pdu_sess_container; +}; + struct gtp_tunnel { /* entry in global list / hash table */ struct llist_head list; @@ -205,6 +217,9 @@ /* Remote UDP IP/Port*/ struct sockaddr_storage remote_udp;
+ /* GTP Extension Header */ + struct gtp1u_exthdrs exthdr; + /* TODO: Filter */ };
@@ -219,6 +234,9 @@ uint32_t rx_teid; uint32_t tx_teid;
+ /* GTPv1U Extension Headers: */ + struct gtp1u_exthdrs exthdr; + /* end user address */ struct sockaddr_storage user_addr;
diff --git a/daemon/tun_device.c b/daemon/tun_device.c index e9c0400..6a0d8b6 100644 --- a/daemon/tun_device.c +++ b/daemon/tun_device.c @@ -128,23 +128,81 @@ OSMO_ASSERT(rc == 0); }
+/* Note: This function is called with d->rwlock locked, and it's responsible of unlocking it before returning. */ +static int tx_gtp1u_pkt(struct gtp_tunnel *t, uint8_t *base_buffer, const uint8_t *payload, unsigned int payload_len) +{ + struct gtp1_header *gtph; + unsigned int head_len = payload - base_buffer; + unsigned int hdr_len_needed; + unsigned int opt_hdr_len_needed = 0; + struct sockaddr_storage daddr; + int outfd = t->gtp_ep->fd; + int rc; + uint8_t flags; + +#define GTP1_F_NPDU 0x01 +#define GTP1_F_SEQ 0x02 +#define GTP1_F_EXTHDR 0x04 +#define GTP1_F_MASK 0x07 + + flags = 0x30; /* Version */ + + if (t->exthdr.seq_num_enabled) + flags |= GTP1_F_SEQ; + + if (t->exthdr.n_pdu_num_enabled) + flags |= GTP1_F_NPDU; + + if (t->exthdr.pdu_sess_container.enabled) { + flags |= GTP1_F_EXTHDR; + opt_hdr_len_needed += 4; /* Extra Header struct */ + } + + /* Make sure the Next Extension Header Type is counted: */ + if (flags & GTP1_F_MASK) + opt_hdr_len_needed += 4; + + hdr_len_needed = sizeof(struct gtp1_header) + opt_hdr_len_needed; + OSMO_ASSERT(hdr_len_needed < head_len); + gtph = (struct gtp1_header *)(payload - hdr_len_needed); + /* initialize the fixed part of the GTP header */ + gtph->flags = flags; + gtph->type = GTP_TPDU; + gtph->length = htons(opt_hdr_len_needed + payload_len); + gtph->tid = htonl(t->tx_teid); + + if (flags & GTP1_F_MASK) { + struct gtp1_exthdr *exthdr = (struct gtp1_exthdr *)(((uint8_t *)gtph) + sizeof(*gtph)); + exthdr->sequence_number = htons(0); /* TODO: increment sequence_number in "t". */ + exthdr->n_pdu_number = 0; /* TODO: increment n_pdu_number in "t". */ + if (t->exthdr.pdu_sess_container.enabled) { + exthdr->array[0].type = GTP1_EXTHDR_PDU_SESSION_CONTAINER; + exthdr->array[0].len = 1; + exthdr->array[0].pdu_type = t->exthdr.pdu_sess_container.pdu_type; + exthdr->array[0].qos_flow_identifier = t->exthdr.pdu_sess_container.qos_flow_identifier; + exthdr->array[1].type = 0; /* No extension headers */ + } else { + exthdr->array[0].type = 0; /* No extension headers */ + } + } + + memcpy(&daddr, &t->remote_udp, sizeof(daddr)); + pthread_rwlock_unlock(&t->d->rwlock); + + /* 4) write to GTP/UDP socket */ + rc = sendto(outfd, gtph, hdr_len_needed + payload_len, 0, + (struct sockaddr *)&daddr, sizeof(daddr)); + return rc; +} + /* one thread for reading from each TUN device (TUN -> GTP encapsulation) */ static void *tun_device_thread(void *arg) { struct tun_device *tun = (struct tun_device *)arg; struct gtp_daemon *d = tun->d; - - uint8_t base_buffer[MAX_UDP_PACKET+sizeof(struct gtp1_header)]; - struct gtp1_header *gtph = (struct gtp1_header *)base_buffer; - uint8_t *buffer = base_buffer + sizeof(struct gtp1_header); - - struct sockaddr_storage daddr; + uint8_t base_buffer[sizeof(struct gtp1_header) + sizeof(struct gtp1_exthdr) + MAX_UDP_PACKET]; int old_cancelst_unused;
- /* initialize the fixed part of the GTP header */ - gtph->flags = 0x30; - gtph->type = GTP_TPDU; - pthread_cleanup_push(tun_device_pthread_cleanup_routine, tun); /* IMPORTANT!: All logging functions in this function block must be called with * PTHREAD_CANCEL_DISABLE set, otherwise the thread could be cancelled while @@ -154,7 +212,8 @@ while (1) { struct gtp_tunnel *t; struct pkt_info pinfo; - int rc, nread, outfd; + int rc, nread; + uint8_t *buffer = base_buffer + sizeof(base_buffer) - MAX_UDP_PACKET;
/* 1) read from tun */ rc = read(tun->fd, buffer, MAX_UDP_PACKET); @@ -164,7 +223,6 @@ exit(1); } nread = rc; - gtph->length = htons(nread);
rc = parse_pkt(&pinfo, buffer, nread); if (rc < 0) { @@ -194,14 +252,7 @@ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old_cancelst_unused); continue; } - outfd = t->gtp_ep->fd; - memcpy(&daddr, &t->remote_udp, sizeof(daddr)); - gtph->tid = htonl(t->tx_teid); - pthread_rwlock_unlock(&d->rwlock); - - /* 4) write to GTP/UDP socket */ - rc = sendto(outfd, base_buffer, nread+sizeof(*gtph), 0, - (struct sockaddr *)&daddr, sizeof(daddr)); + rc = tx_gtp1u_pkt(t, base_buffer, buffer, nread); if (rc < 0) { pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_cancelst_unused); LOGTUN(tun, LOGL_FATAL, "Error Writing to UDP socket: %s\n", strerror(errno)); diff --git a/ttcn3/UECUPS_Types.ttcn b/ttcn3/UECUPS_Types.ttcn index 2e6686b..29fa738 100644 --- a/ttcn3/UECUPS_Types.ttcn +++ b/ttcn3/UECUPS_Types.ttcn @@ -22,23 +22,37 @@ uint16_t Port };
+const charstring UECUPS_GtpExtHdr_PduSessContainer_Type_ul_pdu_sess_info := "ul_pdu_sess_info"; +const charstring UECUPS_GtpExtHdr_PduSessContainer_Type_dl_pdu_sess_info := "dl_pdu_sess_info"; +type record UECUPS_GtpExtHdr_PduSessContainer { + charstring pdu_type, /* ("ul_pdu_sess_info"|"dl_pdu_sess_info") */ + uint32_t qfi +}; + +type record UECUPS_GtpExtHdr { + boolean sequence_number optional, + boolean n_pdu_number optional, + UECUPS_GtpExtHdr_PduSessContainer pdu_session_container optional +}; + /* Create a new GTP-U tunnel in the user plane */ type record UECUPS_CreateTun { /* TEID in transmit + receive direction */ - uint32_t tx_teid, - uint32_t rx_teid, + uint32_t tx_teid, + uint32_t rx_teid,
/* user address (allocated inside the tunnel) */ - UECUPS_AddrType user_addr_type, - OCT4_16n user_addr, + UECUPS_AddrType user_addr_type, + OCT4_16n user_addr,
/* GTP endpoint (UDP IP/Port tuples) */ - UECUPS_SockAddr local_gtp_ep, - UECUPS_SockAddr remote_gtp_ep, + UECUPS_SockAddr local_gtp_ep, + UECUPS_SockAddr remote_gtp_ep,
/* TUN device */ - charstring tun_dev_name, - charstring tun_netns_name optional + charstring tun_dev_name, + charstring tun_netns_name optional, + UECUPS_GtpExtHdr gtp_ext_hdr optional };
type record UECUPS_CreateTunRes { @@ -128,6 +142,24 @@ Port := Port }
+template (value) UECUPS_GtpExtHdr_PduSessContainer +ts_UECUPS_GtpExtHdr_PduSessContainer(template (value) charstring pdu_type, + template (value) uint32_t qfi) +:= { + pdu_type := pdu_type, + qfi := qfi +}; + +template (value) UECUPS_GtpExtHdr +ts_UECUPS_GtpExtHdr(template (omit) boolean sequence_number := omit, + template (omit) boolean n_pdu_number := omit, + template (omit) UECUPS_GtpExtHdr_PduSessContainer pdu_session_container := omit) +:= { + sequence_number := sequence_number, + n_pdu_number := n_pdu_number, + pdu_session_container := pdu_session_container +}; + template (value) UECUPS_CreateTun ts_UECUPS_CreateTun(template (value) uint32_t tx_teid, template (value) uint32_t rx_teid, @@ -136,7 +168,8 @@ template (value) UECUPS_SockAddr local_gtp_ep, template (value) UECUPS_SockAddr remote_gtp_ep, template (value) charstring tun_dev_name := "tun", - template (omit) charstring tun_netns_name := omit) + template (omit) charstring tun_netns_name := omit, + template (omit) UECUPS_GtpExtHdr gtp_ext_hdr := omit) := { tx_teid := tx_teid, rx_teid := rx_teid, @@ -145,7 +178,8 @@ local_gtp_ep := local_gtp_ep, remote_gtp_ep := remote_gtp_ep, tun_dev_name := tun_dev_name, - tun_netns_name := tun_netns_name + tun_netns_name := tun_netns_name, + gtp_ext_hdr := gtp_ext_hdr };
template (value) UECUPS_DestroyTun