neels has submitted this change. ( https://gerrit.osmocom.org/c/osmo-upf/+/28312 )
Change subject: implement GTPv1-U ECHO response ......................................................................
implement GTPv1-U ECHO response
Accept data on the GTPv1-U socket and respond to GTPv1-U ECHO REQUEST messages.
We should keep a deterministic recovery counter that increases with every restart. As a quick and dirty way just use the current time at startup for now, until osmo-upf reaches production maturity.
Related: OS#5599 Change-Id: I135370a7723e2c667ec681f50c21107cde63ea5b --- M include/osmocom/upf/Makefile.am M include/osmocom/upf/upf.h A include/osmocom/upf/upf_gtpu_echo.h M src/osmo-upf/Makefile.am M src/osmo-upf/upf.c M src/osmo-upf/upf_gtp.c A src/osmo-upf/upf_gtpu_echo.c 7 files changed, 176 insertions(+), 0 deletions(-)
Approvals: Jenkins Builder: Verified neels: Looks good to me, approved
diff --git a/include/osmocom/upf/Makefile.am b/include/osmocom/upf/Makefile.am index 333132c..127e03e 100644 --- a/include/osmocom/upf/Makefile.am +++ b/include/osmocom/upf/Makefile.am @@ -4,6 +4,7 @@ up_session.h \ upf.h \ upf_gtp.h \ + upf_gtpu_echo.h \ upf_nft.h \ up_gtp_action.h \ $(NULL) diff --git a/include/osmocom/upf/upf.h b/include/osmocom/upf/upf.h index 0380855..1f8355d 100644 --- a/include/osmocom/upf/upf.h +++ b/include/osmocom/upf/upf.h @@ -87,6 +87,8 @@
struct mnl_socket *nl; int32_t genl_id; + + uint8_t recovery_count; } gtp;
/* Tunnel forwarding via linux netfilter */ diff --git a/include/osmocom/upf/upf_gtpu_echo.h b/include/osmocom/upf/upf_gtpu_echo.h new file mode 100644 index 0000000..2575424 --- /dev/null +++ b/include/osmocom/upf/upf_gtpu_echo.h @@ -0,0 +1,4 @@ +/* GTP-U ECHO implementation for osmo-upf */ +#pragma once + +int upf_gtpu_echo_setup(struct upf_gtp_dev *dev); diff --git a/src/osmo-upf/Makefile.am b/src/osmo-upf/Makefile.am index 1265051..1d43ee8 100644 --- a/src/osmo-upf/Makefile.am +++ b/src/osmo-upf/Makefile.am @@ -37,6 +37,7 @@ up_session.c \ upf.c \ upf_gtp.c \ + upf_gtpu_echo.c \ upf_nft.c \ upf_vty.c \ $(NULL) diff --git a/src/osmo-upf/upf.c b/src/osmo-upf/upf.c index 70891ab..db699c7 100644 --- a/src/osmo-upf/upf.c +++ b/src/osmo-upf/upf.c @@ -53,6 +53,10 @@ .nft = { .priority = -300, }, + .gtp = { + /* TODO: recovery count state file; use lower byte of current time, poor person's random. */ + .recovery_count = time(NULL), + }, };
INIT_LLIST_HEAD(&g_upf->gtp.vty_cfg.devs); diff --git a/src/osmo-upf/upf_gtp.c b/src/osmo-upf/upf_gtp.c index 6a8deac..8eef49f 100644 --- a/src/osmo-upf/upf_gtp.c +++ b/src/osmo-upf/upf_gtp.c @@ -36,6 +36,7 @@
#include <osmocom/upf/upf.h> #include <osmocom/upf/upf_gtp.h> +#include <osmocom/upf/upf_gtpu_echo.h>
#define LOG_GTP_TUN(TUN, LEVEL, FMT, ARGS...) \ LOGP(DGTP, LEVEL, "%s: " FMT, upf_gtp_tun_to_str_c(OTC_SELECT, (TUN)), ##ARGS) @@ -228,6 +229,8 @@ return -EIO; }
+ upf_gtpu_echo_setup(dev); + return 0; }
diff --git a/src/osmo-upf/upf_gtpu_echo.c b/src/osmo-upf/upf_gtpu_echo.c new file mode 100644 index 0000000..f6dfe5c --- /dev/null +++ b/src/osmo-upf/upf_gtpu_echo.c @@ -0,0 +1,161 @@ +/* GTP-U ECHO implementation for osmo-upf */ + +#include <stdint.h> +#include <errno.h> + +#include <osmocom/core/endian.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/msgb.h> + +#include <osmocom/upf/upf.h> +#include <osmocom/upf/upf_gtp.h> + +#define GTP1U_PORT 2152 + +enum gtp1u_msgt { + GTP1U_MSGTYPE_ECHO_REQ = 1, + GTP1U_MSGTYPE_ECHO_RSP = 2, +}; + +enum gtp1u_iei { + GTP1U_IEI_RECOVERY = 14, +}; + +/* 3GPP TS 29.281 */ +struct gtp1u_hdr { +#if OSMO_IS_LITTLE_ENDIAN + uint8_t pn:1, /*< N-PDU Number flag */ + s:1, /*< Sequence number flag */ + e:1, /*< Extension header flag */ + spare:1, + pt:1, /*< Protocol Type: GTP=1, GTP'=0 */ + version:3; /*< Version: 1 */ +#elif OSMO_IS_BIG_ENDIAN + uint8_t version:3, pt:1, spare:1, e:1, s:1, pn:1; +#endif + uint8_t msg_type; + uint16_t length; + uint32_t tei; /*< 05 - 08 Tunnel Endpoint ID */ + union { + uint8_t data1[0]; + struct { + uint16_t seq_nr; + uint8_t n_pdu_nr; + uint8_t next_ext_type; + } ext; + }; + uint8_t data2[0]; +} __attribute__((packed)); + +static int tx_echo_resp(struct upf_gtp_dev *dev, const struct osmo_sockaddr *remote, uint16_t seq_nr); + +static int rx_echo_req(struct upf_gtp_dev *dev, const struct osmo_sockaddr *remote, const struct gtp1u_hdr *rx_h) +{ + if (!rx_h->s) { + LOG_GTP_DEV(dev, LOGL_ERROR, "rx GTPv1-U ECHO REQ without sequence nr\n"); + return -1; + } + + return tx_echo_resp(dev, remote, rx_h->ext.seq_nr); +} + +static int tx_echo_resp(struct upf_gtp_dev *dev, const struct osmo_sockaddr *remote, uint16_t seq_nr) +{ + struct msgb *msg; + struct gtp1u_hdr *tx_h; + int rc; + + msg = msgb_alloc_headroom(1024, 128, "GTP-echo-resp"); + tx_h = (void *)msgb_put(msg, sizeof(*tx_h)); + + *tx_h = (struct gtp1u_hdr){ + /* 3GPP TS 29.281 5.1 defines that the ECHO REQ & RESP shall contain a sequence nr */ + .s = 1, + .pt = 1, + .version = 1, + .msg_type = GTP1U_MSGTYPE_ECHO_RSP, + .ext = { + .seq_nr = seq_nr, + }, + }; + + OSMO_ASSERT(msg->tail == tx_h->data2); + + /* ECHO RESPONSE shall contain a recovery counter */ + msgb_put_u8(msg, GTP1U_IEI_RECOVERY); + msgb_put_u8(msg, g_upf->gtp.recovery_count); + + osmo_store16be(msg->tail - tx_h->data1, &tx_h->length); + + rc = sendto(dev->gtpv1.ofd.fd, msgb_data(msg), msgb_length(msg), 0, &remote->u.sa, sizeof(*remote)); + if (rc < 0) { + int err = errno; + LOG_GTP_DEV(dev, LOGL_ERROR, "GTP1-U sendto(len=%d, to=%s): %s\n", msgb_length(msg), + osmo_sockaddr_to_str(remote), strerror(err)); + } + return rc; +} + +int upf_gtpu_echo_read_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct upf_gtp_dev *dev = ofd->data; + + ssize_t sz; + uint8_t buf[4096]; + struct osmo_sockaddr remote; + socklen_t remote_len = sizeof(remote); + const struct gtp1u_hdr *h; + uint16_t h_length; + + if ((sz = recvfrom(dev->gtpv1.ofd.fd, buf, sizeof(buf), 0, &remote.u.sa, &remote_len)) < 0) { + LOG_GTP_DEV(dev, LOGL_ERROR, "recvfrom() failed: %s\n", strerror(errno)); + return -1; + } + if (sz == 0) { + LOG_GTP_DEV(dev, LOGL_ERROR, "recvfrom() yields zero bytes\n"); + return -1; + } + + /* A GTPv1-U header of size 8 is valid, but this code expects to handle only ECHO REQUEST messages. These are + * required to have a sequence number, hence this check here consciously uses the full sizeof(*h) == 12. */ + if (sz < sizeof(*h)) { + LOG_GTP_DEV(dev, LOGL_ERROR, "rx GTP packet smaller than the GTPv1-U header + sequence nr: %zd < %zu\n", + sz, sizeof(*h)); + return -1; + } + + h = (const struct gtp1u_hdr *)buf; + if (h->version != 1) { + LOG_GTP_DEV(dev, LOGL_ERROR, "rx GTP v%u: only GTP version 1 supported\n", h->version); + return -1; + } + + h_length = osmo_load16be(&h->length); + if (offsetof(struct gtp1u_hdr, data1) + h_length > sz) { + LOG_GTP_DEV(dev, LOGL_ERROR, "rx GTP: header + h.length = %zu > received bytes = %zd\n", + offsetof(struct gtp1u_hdr, data1) + h_length, sz); + return -1; + } + + switch (h->msg_type) { + case GTP1U_MSGTYPE_ECHO_REQ: + return rx_echo_req(dev, &remote, h); + default: + LOG_GTP_DEV(dev, LOGL_ERROR, "rx: GTPv1-U message type %u not supported\n", h->msg_type); + return -1; + } + + return 0; +} + +int upf_gtpu_echo_setup(struct upf_gtp_dev *dev) +{ + if (dev->gtpv1.ofd.fd == -1) { + LOGP(DGTP, LOGL_ERROR, "Cannot setup GTP-U ECHO: GTP-v1 socket not initialized\n"); + return -EINVAL; + } + + dev->gtpv1.ofd.cb = upf_gtpu_echo_read_cb; + dev->gtpv1.ofd.data = dev; + return osmo_fd_register(&dev->gtpv1.ofd); +}