initial check-in of the UECUPS daemon and the related TTCN3 code
Change-Id: I5acf7059722b58e6f57dbf31e18abcc385533970
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c0de8b5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+*.o
+*.d
+daemon/osmo-uecups-daemon
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1acc4c0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,66 @@
+osmo-uecups - Osmocom UE simulation control/user plane separation
+=================================================================
+
+This repository contains a C-language implementation of a simulator for
+the SGW/MME/UE side of GTP-U. It is part of the
+[Osmocom](https://osmocom.org/) Open Source Mobile Communications
+project.
+
+This code is primarily intended to be used in testing of 2G/3G/4G GGSN and P-GW.
+
+Every GTP tunnel (corresponding to a PDP context or EPC bearer) is terminated
+in a local 'tun' device, which in turn is put into its own network namespace.
+
+This means you can simulate any number of users / sessions / bearers on a single
+machine without any routing nightmare.
+
+The code only implements the user plane (GTP1U), and not the control plane like
+GTP1C or GTP2C. osmo-uecups-daemon exposes a JSON-over-SCTP protocol calleD UECUPS,
+which allows any external control plane instance to add/remove tunnels in the
+daemon
+
+Homepage
+--------
+
+The official homepage of the project is
+https://osmocom.org/projects/osmo-ggsn/wiki/osmo-uecups
+
+GIT Repository
+--------------
+
+You can clone from the official osmo-bts.git repository using
+
+ git clone git://git.osmocom.org/osmo-uecups.git
+
+There is a cgit interface at httsp://git.osmocom.org/osmo-uecups/
+
+Documentation
+-------------
+
+FIXME
+
+Mailing List
+------------
+
+Discussions related to this software are happening on the
+osmocom-net-gprs@lists.osmocom.org mailing list, please see
+https://lists.osmocom.org/mailman/listinfo/osmocom-net-gprs for subscription
+options and the list archive.
+
+Please observe the [Osmocom Mailing List
+Rules](https://osmocom.org/projects/cellular-infrastructure/wiki/Mailing_List_Rules)
+when posting.
+
+
+Contributing
+------------
+Our coding standards are described at
+https://osmocom.org/projects/cellular-infrastructure/wiki/Coding_standards
+
+We us a gerrit based patch submission/review process for managing
+contributions. Please see
+https://osmocom.org/projects/cellular-infrastructure/wiki/Gerrit for
+more details
+
+The current patch queue for osmo-bts can be seen at
+https://gerrit.osmocom.org/#/q/project:osmo-uecups+status:open
diff --git a/daemon/Makefile b/daemon/Makefile
new file mode 100644
index 0000000..6cbd59c
--- /dev/null
+++ b/daemon/Makefile
@@ -0,0 +1,16 @@
+LIBS=-lpthread -lsctp $(shell pkg-config --libs libosmocore libosmovty libosmo-netif jansson libnl-route-3.0)
+INC=$(shell pkg-config --cflags libosmocore libosmovty libosmo-netif jansson libnl-route-3.0)
+VER=$(shell git describe --tags)
+CFLAGS=-Wall -g -DPACKAGE_VERSION='"$(VER)"' $(INC)
+
+all: osmo-uecups-daemon
+
+%.o: %.c
+ $(CC) $(CFLAGS) -o $@ -c $^
+
+osmo-uecups-daemon: utility.o netdev.o netns.o tun_device.o \
+ gtp_endpoint.o gtp_tunnel.o daemon_vty.o main.o
+ $(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
+
+clean:
+ @rm -f *.o osmo-uecups-daemon
diff --git a/daemon/daemon_vty.c b/daemon/daemon_vty.c
new file mode 100644
index 0000000..efd9c0f
--- /dev/null
+++ b/daemon/daemon_vty.c
@@ -0,0 +1,290 @@
+#include <string.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/rate_ctr.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/misc.h>
+
+#include "internal.h"
+#include "gtp.h"
+
+#define TUN_STR "tun device commands\n"
+#define GTP_EP_STR "GTP endpoint commands\n"
+#define TUNNEL_STR "GTP tunnel commands\n"
+
+static void show_tun_hdr(struct vty *vty)
+{
+ vty_out(vty,
+ " tun device name | netwk namespace | use count%s", VTY_NEWLINE);
+ vty_out(vty,
+ "---------------- | ---------------- | ---------%s", VTY_NEWLINE);
+}
+
+static void show_one_tun(struct vty *vty, const struct tun_device *tun)
+{
+ vty_out(vty, "%16s | %16s | %lu%s",
+ tun->devname, tun->netns_name, tun->use_count, VTY_NEWLINE);
+}
+
+DEFUN(show_tun, show_tun_cmd,
+ "show tun-device [IFNAME]",
+ SHOW_STR TUN_STR
+ "Name of TUN network device\n")
+{
+ struct tun_device *tun;
+
+ show_tun_hdr(vty);
+ pthread_rwlock_rdlock(&g_daemon->rwlock);
+ if (argc) {
+ tun = _tun_device_find(g_daemon, argv[0]);
+ if (!tun) {
+ pthread_rwlock_unlock(&g_daemon->rwlock);
+ vty_out(vty, "Cannot find TUN device '%s'%s", argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ show_one_tun(vty, tun);
+ } else {
+ llist_for_each_entry(tun, &g_daemon->tun_devices, list)
+ show_one_tun(vty, tun);
+ }
+ pthread_rwlock_unlock(&g_daemon->rwlock);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(tun_create, tun_create_cmd,
+ "tun-device create IFNAME [NETNS]",
+ TUN_STR "Create a new TUN interface\n"
+ "Name of TUN network device\n"
+ "Name of network namespace for tun device\n"
+ )
+{
+ struct tun_device *tun;
+ const char *ifname = argv[0];
+ const char *netns_name = NULL;
+
+ if (argc > 1)
+ netns_name = argv[1];
+
+ tun = tun_device_find_or_create(g_daemon, ifname, netns_name);
+ if (!tun) {
+ vty_out(vty, "Error creating TUN%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(tun_destroy, tun_destroy_cmd,
+ "tun-device destroy IFNAME",
+ TUN_STR "Destroy a TUN interface\n"
+ "Name of TUN network device\n"
+ )
+{
+ struct tun_device *tun;
+ const char *ifname = argv[0];
+
+ pthread_rwlock_wrlock(&g_daemon->rwlock);
+ tun = _tun_device_find(g_daemon, ifname);
+ if (!tun) {
+ pthread_rwlock_unlock(&g_daemon->rwlock);
+ vty_out(vty, "Cannot destrory non-existant TUN%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ _tun_device_deref_destroy(tun);
+ pthread_rwlock_unlock(&g_daemon->rwlock);
+
+ return CMD_SUCCESS;
+}
+
+
+static void show_ep_hdr(struct vty *vty)
+{
+ vty_out(vty,
+ " address port | use count%s", VTY_NEWLINE);
+ vty_out(vty,
+ " ------------------------------- | ---------%s", VTY_NEWLINE);
+}
+
+static void show_one_ep(struct vty *vty, const struct gtp_endpoint *ep)
+{
+ vty_out(vty, "%32s | %lu%s",
+ ep->name, ep->use_count, VTY_NEWLINE);
+
+}
+
+DEFUN(show_gtp, show_gtp_cmd,
+ "show gtp-endpoint [(A.B.C.D|X:X::X:X) [<0-65535>]]",
+ SHOW_STR GTP_EP_STR
+ "Local IP address\n" "Local UDP Port\n")
+{
+ struct gtp_endpoint *ep;
+ struct addrinfo *ai;
+ const char *ipstr;
+ uint16_t port = GTP1U_PORT;
+
+ if (argc > 0) {
+ ipstr = argv[0];
+ if (argc > 1)
+ port = atoi(argv[1]);
+
+ ai = addrinfo_helper(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, ipstr, port, true);
+ if (!ai) {
+ vty_out(vty, "Error parsing IP/Port%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ }
+
+ show_ep_hdr(vty);
+ pthread_rwlock_rdlock(&g_daemon->rwlock);
+ if (argc) {
+ ep = _gtp_endpoint_find(g_daemon, (const struct sockaddr_storage *) ai->ai_addr);
+ if (!ep) {
+ pthread_rwlock_unlock(&g_daemon->rwlock);
+ vty_out(vty, "Cannot find GTP endpoint %s:%s%s", argv[0], argv[1], VTY_NEWLINE);
+ freeaddrinfo(ai);
+ return CMD_WARNING;
+ }
+ show_one_ep(vty, ep);
+ } else {
+ llist_for_each_entry(ep, &g_daemon->gtp_endpoints, list)
+ show_one_ep(vty, ep);
+ }
+ pthread_rwlock_unlock(&g_daemon->rwlock);
+
+ freeaddrinfo(ai);
+ return CMD_SUCCESS;
+}
+
+DEFUN(gtp_create, gtp_create_cmd,
+ "gtp-endpoint create (A.B.C.D|X:X::X:X) [<0-65535>]",
+ GTP_EP_STR "Create a new GTP endpoint (UDP socket)\n"
+ "Local IP address\n" "Local UDP Port\n")
+{
+ struct addrinfo *ai;
+ struct gtp_endpoint *ep;
+ const char *ipstr = argv[0];
+ uint16_t port = GTP1U_PORT;
+
+ if (argc > 1)
+ port = atoi(argv[1]);
+
+ ai = addrinfo_helper(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, ipstr, port, true);
+ if (!ai) {
+ vty_out(vty, "Error parsing IP/Port%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ ep = gtp_endpoint_find_or_create(g_daemon, (struct sockaddr_storage *) ai->ai_addr);
+ if (!ep) {
+ vty_out(vty, "Error creating endpoint%s", VTY_NEWLINE);
+ freeaddrinfo(ai);
+ return CMD_WARNING;
+ }
+
+ freeaddrinfo(ai);
+ return CMD_SUCCESS;
+}
+
+DEFUN(gtp_destroy, gtp_destroy_cmd,
+ "gtp-endpoint destroy (A.B.C.D|X:X::X:X) [<0-65535>]",
+ GTP_EP_STR "Destroy a GTP endpoint\n"
+ "Local IP address\n" "Local UDP Port\n")
+{
+ struct addrinfo *ai;
+ struct gtp_endpoint *ep;
+ const char *ipstr = argv[0];
+ uint16_t port = GTP1U_PORT;
+
+ if (argc > 1)
+ port = atoi(argv[1]);
+
+ ai = addrinfo_helper(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, ipstr, port, true);
+ if (!ai) {
+ vty_out(vty, "Error parsing IP/Port%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ pthread_rwlock_wrlock(&g_daemon->rwlock);
+ ep = _gtp_endpoint_find(g_daemon, (struct sockaddr_storage *) ai->ai_addr);
+ if (!ep) {
+ pthread_rwlock_unlock(&g_daemon->rwlock);
+ vty_out(vty, "Cannot find to-be-destoryed endpoint%s", VTY_NEWLINE);
+ freeaddrinfo(ai);
+ return CMD_WARNING;
+ }
+ _gtp_endpoint_deref_destroy(ep);
+ pthread_rwlock_unlock(&g_daemon->rwlock);
+
+ freeaddrinfo(ai);
+ return CMD_SUCCESS;
+}
+
+static void show_one_tunnel(struct vty *vty, const struct gtp_tunnel *t)
+{
+ char remote_ip[64], remote_port[16], user_addr[64];
+
+ getnameinfo((struct sockaddr *) &t->remote_udp, sizeof(t->remote_udp),
+ remote_ip, sizeof(remote_ip), remote_port, sizeof(remote_port),
+ NI_NUMERICHOST|NI_NUMERICSERV);
+
+ getnameinfo((struct sockaddr *) &t->user_addr, sizeof(t->user_addr),
+ user_addr, sizeof(user_addr), NULL, 0,
+ NI_NUMERICHOST|NI_NUMERICSERV);
+
+
+ vty_out(vty, "%s/%08X - %s:%s/%08X %s(%s) %s%s",
+ t->gtp_ep->name, t->rx_teid, remote_ip, remote_port, t->tx_teid,
+ t->tun_dev->devname, t->tun_dev->netns_name, user_addr, VTY_NEWLINE);
+}
+
+DEFUN(show_tunnel, show_tunnel_cmd,
+ "show gtp-tunnel",
+ SHOW_STR TUNNEL_STR)
+{
+ struct gtp_tunnel *t;
+
+ pthread_rwlock_rdlock(&g_daemon->rwlock);
+ llist_for_each_entry(t, &g_daemon->gtp_tunnels, list) {
+ show_one_tunnel(vty, t);
+ }
+ pthread_rwlock_unlock(&g_daemon->rwlock);
+ return CMD_SUCCESS;
+}
+
+
+int gtpud_vty_init(void)
+{
+ install_element_ve(&show_tun_cmd);
+ install_element(ENABLE_NODE, &tun_create_cmd);
+ install_element(ENABLE_NODE, &tun_destroy_cmd);
+
+ install_element_ve(&show_gtp_cmd);
+ install_element(ENABLE_NODE, >p_create_cmd);
+ install_element(ENABLE_NODE, >p_destroy_cmd);
+
+ install_element_ve(&show_tunnel_cmd);
+
+ return 0;
+}
+
+
+static const char copyright[] =
+ "Copyright (C) 2020 Harald Welte <laforge@gnumonks.org>\r\n"
+ "License GPLv2: GNU GPL version 2 <http://gnu.org/licenses/gpl-2.0.html>\r\n"
+ "This is free software: you are free to change and redistribute it.\r\n"
+ "There is NO WARRANTY, to the extent permitted by law.\r\n";
+
+struct vty_app_info g_vty_info = {
+ .name = "osmo-gtpud",
+ .version = PACKAGE_VERSION,
+ .copyright = copyright,
+};
diff --git a/daemon/gtp.h b/daemon/gtp.h
new file mode 100644
index 0000000..f2acd36
--- /dev/null
+++ b/daemon/gtp.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#pragma once
+#include <stdint.h>
+
+/* General GTP protocol related definitions. */
+
+#define GTP0_PORT 3386
+#define GTP1U_PORT 2152
+
+#define GTP_TPDU 255
+
+struct gtp0_header { /* According to GSM TS 09.60. */
+ uint8_t flags;
+ uint8_t type;
+ uint16_t length;
+ uint16_t seq;
+ uint16_t flow;
+ uint8_t number;
+ uint8_t spare[3];
+ uint64_t tid;
+} __attribute__ ((packed));
+
+struct gtp1_header { /* According to 3GPP TS 29.060. */
+ uint8_t flags;
+ uint8_t type;
+ uint16_t length;
+ uint32_t tid;
+} __attribute__ ((packed));
+
+#define GTP1_F_NPDU 0x01
+#define GTP1_F_SEQ 0x02
+#define GTP1_F_EXTHDR 0x04
+#define GTP1_F_MASK 0x07
diff --git a/daemon/gtp_endpoint.c b/daemon/gtp_endpoint.c
new file mode 100644
index 0000000..4221e14
--- /dev/null
+++ b/daemon/gtp_endpoint.c
@@ -0,0 +1,249 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#include <pthread.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/logging.h>
+
+#include "gtp.h"
+#include "internal.h"
+
+#define LOGEP(ep, lvl, fmt, args ...) \
+ LOGP(DEP, lvl, "%s: " fmt, (ep)->name, ## args)
+
+/***********************************************************************
+ * GTP Endpoint (UDP socket)
+ ***********************************************************************/
+
+/* 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)];
+
+ while (1) {
+ struct gtp_tunnel *t;
+ const struct gtp1_header *gtph;
+ int rc, nread, outfd;
+ uint32_t teid;
+
+ /* 1) read GTP packet from UDP socket */
+ rc = recvfrom(ep->fd, buffer, sizeof(buffer), 0, (struct sockaddr *)NULL, 0);
+ if (rc < 0) {
+ 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);
+ }
+ }
+}
+
+static struct gtp_endpoint *
+_gtp_endpoint_create(struct gtp_daemon *d, const struct sockaddr_storage *bind_addr)
+{
+ struct gtp_endpoint *ep = talloc_zero(d, struct gtp_endpoint);
+ char ipstr[INET6_ADDRSTRLEN];
+ char portstr[8];
+ int rc;
+
+ if (!ep)
+ return NULL;
+
+ rc = getnameinfo((struct sockaddr *)bind_addr, sizeof(*bind_addr),
+ ipstr, sizeof(ipstr), portstr, sizeof(portstr), NI_NUMERICHOST|NI_NUMERICSERV);
+ if (rc != 0)
+ goto out_free;
+ ep->name = talloc_asprintf(ep, "%s:%s", ipstr, portstr);
+
+ ep->d = d;
+ ep->use_count = 1;
+ ep->bind_addr = *bind_addr;
+ ep->fd = socket(ep->bind_addr.ss_family, SOCK_DGRAM, IPPROTO_UDP);
+ if (ep->fd < 0) {
+ LOGEP(ep, LOGL_ERROR, "Cannot create UDP socket: %s\n", strerror(errno));
+ goto out_free;
+ }
+ rc = bind(ep->fd, (struct sockaddr *) &ep->bind_addr, sizeof(ep->bind_addr));
+ if (rc < 0) {
+ LOGEP(ep, LOGL_ERROR, "Cannot bind UDP socket: %s\n", strerror(errno));
+ goto out_close;
+ }
+
+ if (pthread_create(&ep->thread, NULL, gtp_endpoint_thread, ep)) {
+ LOGEP(ep, LOGL_ERROR, "Cannot start GTP thread: %s\n", strerror(errno));
+ goto out_close;
+ }
+
+ llist_add_tail(&ep->list, &d->gtp_endpoints);
+ LOGEP(ep, LOGL_INFO, "Created\n");
+
+ return ep;
+
+out_close:
+ close(ep->fd);
+out_free:
+ talloc_free(ep);
+ return NULL;
+}
+
+struct gtp_endpoint *
+_gtp_endpoint_find(struct gtp_daemon *d, const struct sockaddr_storage *bind_addr)
+{
+ struct gtp_endpoint *ep;
+
+ llist_for_each_entry(ep, &d->gtp_endpoints, list) {
+ if (sockaddr_equals((const struct sockaddr *) &ep->bind_addr,
+ (const struct sockaddr *) bind_addr)) {
+ return ep;
+ }
+ }
+ return NULL;
+}
+
+struct gtp_endpoint *
+gtp_endpoint_find_or_create(struct gtp_daemon *d, const struct sockaddr_storage *bind_addr)
+{
+ struct gtp_endpoint *ep;
+
+ /* talloc is not thread safe, all alloc/free must come from main thread */
+ ASSERT_MAIN_THREAD(d);
+
+ pthread_rwlock_wrlock(&d->rwlock);
+ ep = _gtp_endpoint_find(d, bind_addr);
+ if (ep)
+ ep->use_count++;
+ else
+ ep = _gtp_endpoint_create(d, bind_addr);
+ pthread_rwlock_unlock(&d->rwlock);
+
+ return ep;
+}
+
+/* UNLOCKED hard/forced destroy; caller must make sure references are cleaned up */
+static void _gtp_endpoint_destroy(struct gtp_endpoint *ep)
+{
+ /* talloc is not thread safe, all alloc/free must come from main thread */
+ ASSERT_MAIN_THREAD(ep->d);
+
+ if (ep->use_count)
+ LOGEP(ep, LOGL_ERROR, "Destroying despite use_count %lu != 0\n", ep->use_count);
+ else
+ LOGEP(ep, LOGL_INFO, "Destroying\n");
+
+ pthread_cancel(ep->thread);
+ llist_del(&ep->list);
+ close(ep->fd);
+ talloc_free(ep);
+}
+
+/* UNLOCKED remove all objects referencing this ep and then destroy */
+void _gtp_endpoint_deref_destroy(struct gtp_endpoint *ep)
+{
+ struct gtp_daemon *d = ep->d;
+ struct sockaddr_storage ss = ep->bind_addr;
+ struct gtp_tunnel *t, *t2;
+ struct gtp_endpoint *ep2;
+
+ /* talloc is not thread safe, all alloc/free must come from main thread */
+ ASSERT_MAIN_THREAD(ep->d);
+
+ /* iterate over all tunnels; delete all references to ep */
+ llist_for_each_entry_safe(t, t2, &d->gtp_tunnels, list) {
+ if (t->gtp_ep == ep)
+ _gtp_tunnel_destroy(t);
+ }
+
+ /* _gtp_endpoint_destroy may already have been called via
+ * _gtp_tunnel_destroy -> gtp_endpoint_release, so we have to
+ * check if the ep can still be found in the list */
+ ep2 = _gtp_endpoint_find(d, &ss);
+ if (ep2 && ep2 == ep)
+ _gtp_endpoint_destroy(ep2);
+}
+
+/* UNLOCKED release a reference; destroy if refcount drops to 0 */
+bool _gtp_endpoint_release(struct gtp_endpoint *ep)
+{
+ bool released = false;
+
+ /* talloc is not thread safe, all alloc/free must come from main thread */
+ ASSERT_MAIN_THREAD(ep->d);
+
+ ep->use_count--;
+ if (ep->use_count == 0) {
+ _gtp_endpoint_destroy(ep);
+ released = true;
+ } else
+ LOGEP(ep, LOGL_DEBUG, "Release; new use_count=%lu\n", ep->use_count);
+
+ return released;
+}
+
+
+/* release a reference; destroy if refcount drops to 0 */
+bool gtp_endpoint_release(struct gtp_endpoint *ep)
+{
+ struct gtp_daemon *d = ep->d;
+ bool released;
+
+ /* talloc is not thread safe, all alloc/free must come from main thread */
+ ASSERT_MAIN_THREAD(ep->d);
+
+ pthread_rwlock_wrlock(&d->rwlock);
+ released = _gtp_endpoint_release(ep);
+ pthread_rwlock_unlock(&d->rwlock);
+
+ return released;
+}
diff --git a/daemon/gtp_tunnel.c b/daemon/gtp_tunnel.c
new file mode 100644
index 0000000..0d049c7
--- /dev/null
+++ b/daemon/gtp_tunnel.c
@@ -0,0 +1,169 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <unistd.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+
+#include <pthread.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/logging.h>
+
+#include "internal.h"
+
+#define LOGT(t, lvl, fmt, args ...) \
+ LOGP(DGT, lvl, "%s: " fmt, (t)->name, ## args)
+
+/***********************************************************************
+ * GTP Tunnel
+ ***********************************************************************/
+struct gtp_tunnel *gtp_tunnel_alloc(struct gtp_daemon *d, const struct gtp_tunnel_params *cpars)
+{
+ struct gtp_tunnel *t;
+
+ t = talloc_zero(d, struct gtp_tunnel);
+ if (!t)
+ goto out_unlock;
+ t->d = d;
+ t->name = talloc_asprintf(t, "%s-R%08x-T%08x", cpars->tun_name, cpars->rx_teid, cpars->tx_teid);
+ t->tun_dev = tun_device_find_or_create(d, cpars->tun_name, cpars->tun_netns_name);
+ if (!t->tun_dev) {
+ LOGT(t, LOGL_ERROR, "Cannot find or create tun device %s\n", cpars->tun_name);
+ goto out_free;
+ }
+
+ t->gtp_ep = gtp_endpoint_find_or_create(d, &cpars->local_udp);
+ if (!t->gtp_ep) {
+ LOGT(t, LOGL_ERROR, "Cannot find or create GTP endpoint\n");
+ goto out_tun;
+ }
+
+ pthread_rwlock_wrlock(&d->rwlock);
+ /* check if we already have a tunnel with same Rx-TEID + endpoint */
+ if (_gtp_tunnel_find_r(d, cpars->rx_teid, t->gtp_ep)) {
+ LOGT(t, LOGL_ERROR, "Error: We already have a tunnel for RxTEID 0x%08x "
+ "on this endpoint (%s)\n", cpars->rx_teid, t->gtp_ep->name);
+ goto out_ep;
+ }
+
+ /* FIXME: check if we already have a tunnel with same Tx-TEID + peer */
+ /* FIXME: check if we already have a tunnel with same tun + EUA + filter */
+
+ t->rx_teid = cpars->rx_teid;
+ t->tx_teid = cpars->tx_teid;
+ memcpy(&t->user_addr, &cpars->user_addr, sizeof(t->user_addr));
+ memcpy(&t->remote_udp, &cpars->remote_udp, sizeof(t->remote_udp));
+
+ if (netdev_add_addr(t->tun_dev->nl, t->tun_dev->ifindex, &t->user_addr) < 0) {
+ LOGT(t, LOGL_ERROR, "Cannot add user addr to tun device: %s\n",
+ strerror(errno));
+ }
+
+ /* TODO: hash table? */
+ llist_add_tail(&t->list, &d->gtp_tunnels);
+ pthread_rwlock_unlock(&d->rwlock);
+ LOGT(t, LOGL_NOTICE, "Created\n");
+
+ return t;
+
+out_ep:
+ _gtp_endpoint_release(t->gtp_ep);
+out_tun:
+ _tun_device_release(t->tun_dev);
+out_free:
+ talloc_free(t);
+out_unlock:
+ pthread_rwlock_unlock(&d->rwlock);
+
+ return NULL;
+}
+
+/* find tunnel by R(x_teid), T(x_teid) + A(ddr) */
+static struct gtp_tunnel *
+_gtp_tunnel_find_rta(struct gtp_daemon *d, uint32_t rx_teid, uint32_t tx_teid,
+ const struct sockaddr_storage *user_addr)
+{
+ struct gtp_tunnel *t;
+ llist_for_each_entry(t, &d->gtp_tunnels, list) {
+ if (t->rx_teid == rx_teid && t->tx_teid == tx_teid &&
+ sockaddr_equals((struct sockaddr *) &t->user_addr, (struct sockaddr *)user_addr))
+ return t;
+ }
+ return NULL;
+}
+
+/* find tunnel by R(x_teid) + optionally local endpoint */
+struct gtp_tunnel *
+_gtp_tunnel_find_r(struct gtp_daemon *d, uint32_t rx_teid, struct gtp_endpoint *ep)
+{
+ struct gtp_tunnel *t;
+ llist_for_each_entry(t, &d->gtp_tunnels, list) {
+ if (t->rx_teid == rx_teid) {
+ if (!ep)
+ return t;
+ if (t->gtp_ep == ep)
+ return t;
+ }
+ }
+ return NULL;
+}
+
+/* UNLOCKED find tunnel by tun + EUA ip (+proto/port) */
+struct gtp_tunnel *
+_gtp_tunnel_find_eua(struct tun_device *tun, const struct sockaddr *sa, uint8_t proto)
+{
+ struct gtp_daemon *d = tun->d;
+ struct gtp_tunnel *t;
+
+ llist_for_each_entry(t, &d->gtp_tunnels, list) {
+ /* TODO: Find best matching filter */
+ if (t->tun_dev == tun && sockaddr_equals(sa, (struct sockaddr *) &t->user_addr))
+ return t;
+ }
+ return NULL;
+}
+
+/* UNLOCKED destroy of tunnel; drops references to EP + TUN */
+void _gtp_tunnel_destroy(struct gtp_tunnel *t)
+{
+ LOGT(t, LOGL_NOTICE, "Destroying\n");
+ /* talloc is not thread safe, all alloc/free must come from main thread */
+ ASSERT_MAIN_THREAD(t->d);
+
+ if (netdev_del_addr(t->tun_dev->nl, t->tun_dev->ifindex, &t->user_addr) < 0)
+ LOGT(t, LOGL_ERROR, "Cannot remove user address: %s\n", strerror(errno));
+
+ llist_del(&t->list);
+
+ /* drop reference to endpoint + tun */
+ _gtp_endpoint_release(t->gtp_ep);
+ _tun_device_release(t->tun_dev);
+
+ talloc_free(t);
+}
+
+bool gtp_tunnel_destroy(struct gtp_daemon *d, const struct sockaddr_storage *bind_addr, uint32_t rx_teid)
+{
+ struct gtp_endpoint *ep;
+ bool rc = false;
+
+ pthread_rwlock_wrlock(&d->rwlock);
+ /* find endpoint for bind_addr */
+ ep = _gtp_endpoint_find(d, bind_addr);
+ if (ep) {
+ /* find tunnel for rx TEID within endpoint */
+ struct gtp_tunnel *t = _gtp_tunnel_find_r(d, rx_teid, ep);
+ if (t) {
+ _gtp_tunnel_destroy(t);
+ rc = true;
+ }
+ }
+ pthread_rwlock_unlock(&d->rwlock);
+
+ return rc;
+}
diff --git a/daemon/internal.h b/daemon/internal.h
new file mode 100644
index 0000000..cbccbfc
--- /dev/null
+++ b/daemon/internal.h
@@ -0,0 +1,223 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <pthread.h>
+#include <sys/socket.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/utils.h>
+
+struct nl_sock;
+struct osmo_stream_srv_link;
+
+/***********************************************************************
+ * Utility
+ ***********************************************************************/
+/* ensure we are called from main thread context */
+#define ASSERT_MAIN_THREAD(d) OSMO_ASSERT(pthread_self() == (d)->main_thread)
+
+#define MAX_UDP_PACKET 65535
+
+bool sockaddr_equals(const struct sockaddr *a, const struct sockaddr *b);
+
+struct addrinfo *addrinfo_helper(uint16_t family, uint16_t type, uint8_t proto,
+ const char *host, uint16_t port, bool passive);
+enum {
+ DTUN,
+ DEP,
+ DGT,
+ DUECUPS,
+};
+
+/***********************************************************************
+ * netdev / netlink
+ ***********************************************************************/
+
+int netdev_add_addr(struct nl_sock *nlsk, int ifindex, const struct sockaddr_storage *ss);
+int netdev_del_addr(struct nl_sock *nlsk, int ifindex, const struct sockaddr_storage *ss);
+int netdev_set_link(struct nl_sock *nlsk, int ifindex, bool up);
+int netdev_add_defaultroute(struct nl_sock *nlsk, int ifindex, uint8_t family);
+
+
+/***********************************************************************
+ * GTP Endpoint (UDP socket)
+ ***********************************************************************/
+
+struct gtp_daemon;
+
+/* local UDP socket for GTP communication */
+struct gtp_endpoint {
+ /* entry in global list */
+ struct llist_head list;
+ /* back-pointer to daemon */
+ struct gtp_daemon *d;
+ unsigned long use_count;
+
+ /* file descriptor */
+ int fd;
+
+ /* local IP:port */
+ struct sockaddr_storage bind_addr;
+ char *name;
+
+ /* the thread handling Rx from the fd/socket */
+ pthread_t thread;
+};
+
+
+struct gtp_endpoint *
+gtp_endpoint_find_or_create(struct gtp_daemon *d, const struct sockaddr_storage *bind_addr);
+
+struct gtp_endpoint *
+_gtp_endpoint_find(struct gtp_daemon *d, const struct sockaddr_storage *bind_addr);
+
+void _gtp_endpoint_deref_destroy(struct gtp_endpoint *ep);
+
+bool _gtp_endpoint_release(struct gtp_endpoint *ep);
+
+bool gtp_endpoint_release(struct gtp_endpoint *ep);
+
+
+
+/***********************************************************************
+ * TUN Device
+ ***********************************************************************/
+
+struct tun_device {
+ /* entry in global list */
+ struct llist_head list;
+ /* back-pointer to daemon */
+ struct gtp_daemon *d;
+ unsigned long use_count;
+
+ /* which device we refer to */
+ const char *devname;
+ int ifindex;
+
+ /* file descriptor */
+ int fd;
+
+ /* network namespace */
+ const char *netns_name;
+ int netns_fd;
+
+ /* netlink socket in the namespace of the tun device */
+ struct nl_sock *nl;
+
+ /* list of local addresses? or simply only have the kernel know thses? */
+
+ /* the thread handling Rx from the tun fd */
+ pthread_t thread;
+};
+
+struct tun_device *
+tun_device_find_or_create(struct gtp_daemon *d, const char *devname, const char *netns_name);
+
+struct tun_device *
+_tun_device_find(struct gtp_daemon *d, const char *devname);
+
+void _tun_device_deref_destroy(struct tun_device *tun);
+
+bool _tun_device_release(struct tun_device *tun);
+
+bool tun_device_release(struct tun_device *tun);
+
+
+
+/***********************************************************************
+ * GTP Tunnel
+ ***********************************************************************/
+
+/* Every tunnel is identified uniquely by the following tuples:
+ *
+ * a) local endpoint + TEID
+ * this is what happens on incoming GTP messages
+ *
+ * b) tun device + end-user-address (+ filter, if any)
+ * this is what happens when IP arrives on the tun device
+ */
+
+struct gtp_tunnel {
+ /* entry in global list / hash table */
+ struct llist_head list;
+ /* back-pointer to daemon */
+ struct gtp_daemon *d;
+
+ const char *name;
+
+ /* the TUN device associated with this tunnel */
+ struct tun_device *tun_dev;
+ /* the GTP endpoint (UDP socket) associated with this tunnel */
+ struct gtp_endpoint *gtp_ep;
+
+ /* TEID on transmit (host byte order) */
+ uint32_t tx_teid;
+ /* TEID one receive (host byte order) */
+ uint32_t rx_teid;
+
+ /* End user Address (inner IP) */
+ struct sockaddr_storage user_addr;
+
+ /* Remote UDP IP/Port*/
+ struct sockaddr_storage remote_udp;
+
+ /* TODO: Filter */
+};
+
+struct gtp_tunnel *
+_gtp_tunnel_find_r(struct gtp_daemon *d, uint32_t rx_teid, struct gtp_endpoint *ep);
+
+struct gtp_tunnel *
+_gtp_tunnel_find_eua(struct tun_device *tun, const struct sockaddr *sa, uint8_t proto);
+
+struct gtp_tunnel_params {
+ /* TEID in receive and transmit direction */
+ uint32_t rx_teid;
+ uint32_t tx_teid;
+
+ /* end user address */
+ struct sockaddr_storage user_addr;
+
+ /* remote GTP/UDP IP+Port */
+ struct sockaddr_storage remote_udp;
+
+ /* local GTP/UDP IP+Port (used to lookup/create local EP) */
+ struct sockaddr_storage local_udp;
+
+ /* local TUN device name (used to lookup/create local tun) */
+ const char *tun_name;
+ const char *tun_netns_name;
+};
+struct gtp_tunnel *gtp_tunnel_alloc(struct gtp_daemon *d, const struct gtp_tunnel_params *cpars);
+
+void _gtp_tunnel_destroy(struct gtp_tunnel *t);
+bool gtp_tunnel_destroy(struct gtp_daemon *d, const struct sockaddr_storage *bind_addr, uint32_t rx_teid);
+
+
+/***********************************************************************
+ * GTP Daemon
+ ***********************************************************************/
+
+struct gtp_daemon {
+ /* global lists of various objects */
+ struct llist_head gtp_endpoints;
+ struct llist_head tun_devices;
+ struct llist_head gtp_tunnels;
+ /* lock protecting all of the above lists */
+ pthread_rwlock_t rwlock;
+ /* main thread ID */
+ pthread_t main_thread;
+ /* client CUPS interface */
+ struct llist_head cups_clients;
+ struct osmo_stream_srv_link *cups_link;
+
+ struct {
+ char *cups_local_ip;
+ uint16_t cups_local_port;
+ } cfg;
+};
+extern struct gtp_daemon *g_daemon;
+
+int gtpud_vty_init(void);
diff --git a/daemon/main.c b/daemon/main.c
new file mode 100644
index 0000000..2375877
--- /dev/null
+++ b/daemon/main.c
@@ -0,0 +1,538 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <unistd.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <errno.h>
+
+#include <pthread.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/stats.h>
+#include <osmocom/vty/ports.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/misc.h>
+
+#include <osmocom/netif/stream.h>
+#include <netinet/sctp.h>
+
+#include <jansson.h>
+
+#include "internal.h"
+#include "netns.h"
+#include "gtp.h"
+
+/***********************************************************************
+ * Client (Contol/User Plane Separation) Socket
+ ***********************************************************************/
+
+#define CUPS_MSGB_SIZE 1024
+
+#define LOGCC(cc, lvl, fmt, args ...) \
+ LOGP(DUECUPS, lvl, "%s: " fmt, (cc)->sockname, ## args)
+
+struct cups_client {
+ /* member in daemon->cups_clients */
+ struct llist_head list;
+ /* back-pointer to daemon */
+ struct gtp_daemon *d;
+ /* client socket */
+ struct osmo_stream_srv *srv;
+ char sockname[OSMO_SOCK_NAME_MAXLEN];
+};
+
+/* Send JSON to a given client/connection */
+static int cups_client_tx_json(struct cups_client *cc, json_t *jtx)
+{
+ struct msgb *msg = msgb_alloc(CUPS_MSGB_SIZE, "Tx JSON");
+ char *json_str = json_dumps(jtx, JSON_SORT_KEYS);
+ char *out;
+ int json_strlen;
+
+ json_decref(jtx);
+ if (!json_str) {
+ LOGCC(cc, LOGL_ERROR, "Error encoding JSON\n");
+ return 0;
+ }
+ json_strlen = strlen(json_str);
+
+ LOGCC(cc, LOGL_DEBUG, "JSON Tx '%s'\n", json_str);
+
+ if (json_strlen > msgb_tailroom(msg)) {
+ LOGCC(cc, LOGL_ERROR, "Not enough room for JSON in msgb\n");
+ free(json_str);
+ return 0;
+ }
+
+ out = (char *)msgb_put(msg, json_strlen);
+ memcpy(out, json_str, json_strlen);
+ free(json_str);
+ osmo_stream_srv_send(cc->srv, msg);
+
+ return 0;
+}
+
+static json_t *gen_uecups_result(const char *name, const char *res)
+{
+ json_t *jres = json_object();
+ json_t *jret = json_object();
+
+ json_object_set_new(jres, "result", json_string(res));
+ json_object_set_new(jret, name, jres);
+
+ return jret;
+}
+
+static int parse_ep(struct sockaddr_storage *out, json_t *in)
+{
+ json_t *jaddr_type, *jport, *jip;
+ const char *addr_type, *ip;
+ uint8_t buf[16];
+
+ /* {"addr_type":"IPV4","ip":"31323334","Port":2152} */
+
+ if (!json_is_object(in))
+ return -EINVAL;
+
+ jaddr_type = json_object_get(in, "addr_type");
+ jport = json_object_get(in, "Port");
+ jip = json_object_get(in, "ip");
+
+ if (!jaddr_type || !jport || !jip)
+ return -EINVAL;
+
+ if (!json_is_string(jaddr_type) || !json_is_integer(jport) || !json_is_string(jip))
+ return -EINVAL;
+
+ addr_type = json_string_value(jaddr_type);
+ ip = json_string_value(jip);
+
+ memset(out, 0, sizeof(*out));
+
+ if (!strcmp(addr_type, "IPV4")) {
+ struct sockaddr_in *sin = (struct sockaddr_in *) out;
+ if (osmo_hexparse(ip, buf, sizeof(buf)) != 4)
+ return -EINVAL;
+ memcpy(&sin->sin_addr, buf, 4);
+ sin->sin_family = AF_INET;
+ sin->sin_port = htons(json_integer_value(jport));
+ } else if (!strcmp(addr_type, "IPV6")) {
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) out;
+ if (osmo_hexparse(ip, buf, sizeof(buf)) != 16)
+ return -EINVAL;
+ memcpy(&sin6->sin6_addr, buf, 16);
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_port = htons(json_integer_value(jport));
+ } else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int parse_eua(struct sockaddr_storage *out, json_t *jip, json_t *jaddr_type)
+{
+ const char *addr_type, *ip;
+ uint8_t buf[16];
+
+ if (!json_is_string(jip) || !json_is_string(jaddr_type))
+ return -EINVAL;
+
+ addr_type = json_string_value(jaddr_type);
+ ip = json_string_value(jip);
+
+ memset(out, 0, sizeof(*out));
+
+ if (!strcmp(addr_type, "IPV4")) {
+ struct sockaddr_in *sin = (struct sockaddr_in *) out;
+ if (osmo_hexparse(ip, buf, sizeof(buf)) != 4)
+ return -EINVAL;
+ memcpy(&sin->sin_addr, buf, 4);
+ sin->sin_family = AF_INET;
+ } else if (!strcmp(addr_type, "IPV6")) {
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) out;
+ if (osmo_hexparse(ip, buf, sizeof(buf)) != 16)
+ return -EINVAL;
+ memcpy(&sin6->sin6_addr, buf, 16);
+ sin6->sin6_family = AF_INET6;
+ } else
+ return -EINVAL;
+
+ return 0;
+}
+
+
+static int parse_create_tun(struct gtp_tunnel_params *out, json_t *ctun)
+{
+ json_t *jlocal_gtp_ep, *jremote_gtp_ep;
+ json_t *jrx_teid, *jtx_teid;
+ json_t *jtun_dev_name, *jtun_netns_name;
+ json_t *juser_addr, *juser_addr_type;
+ 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"}}' */
+
+ if (!json_is_object(ctun))
+ return -EINVAL;
+
+ /* mandatory IEs */
+ jlocal_gtp_ep = json_object_get(ctun, "local_gtp_ep");
+ jremote_gtp_ep = json_object_get(ctun, "remote_gtp_ep");
+ jrx_teid = json_object_get(ctun, "rx_teid");
+ jtx_teid = json_object_get(ctun, "tx_teid");
+ jtun_dev_name = json_object_get(ctun, "tun_dev_name");
+ juser_addr = json_object_get(ctun, "user_addr");
+ juser_addr_type = json_object_get(ctun, "user_addr_type");
+
+ if (!jlocal_gtp_ep || !jremote_gtp_ep || !jrx_teid || !jtx_teid || !jtun_dev_name ||
+ !juser_addr || !juser_addr_type)
+ return -EINVAL;
+ if (!json_is_object(jlocal_gtp_ep) || !json_is_object(jremote_gtp_ep) ||
+ !json_is_integer(jrx_teid) || !json_is_integer(jtx_teid) ||
+ !json_is_string(jtun_dev_name) ||
+ !json_is_string(juser_addr) || !json_is_string(juser_addr_type))
+ return -EINVAL;
+
+ memset(out, 0, sizeof(*out));
+
+ rc = parse_ep(&out->local_udp, jlocal_gtp_ep);
+ if (rc < 0)
+ return rc;
+ rc = parse_ep(&out->remote_udp, jremote_gtp_ep);
+ if (rc < 0)
+ return rc;
+ rc = parse_eua(&out->user_addr, juser_addr, juser_addr_type);
+ if (rc < 0)
+ return rc;
+ out->rx_teid = json_integer_value(jrx_teid);
+ out->tx_teid = json_integer_value(jtx_teid);
+ out->tun_name = talloc_strdup(out, json_string_value(jtun_dev_name));
+
+ /* optional IEs */
+ jtun_netns_name = json_object_get(ctun, "tun_netns_name");
+ if (jtun_netns_name) {
+ if (!json_is_string(jtun_netns_name))
+ return -EINVAL;
+ out->tun_netns_name = talloc_strdup(out, json_string_value(jtun_netns_name));
+ }
+
+ return 0;
+}
+
+
+static int cups_client_handle_create_tun(struct cups_client *cc, json_t *ctun)
+{
+ int rc;
+ struct gtp_tunnel_params *tpars = talloc_zero(cc, struct gtp_tunnel_params);
+ struct gtp_tunnel *t;
+
+ rc = parse_create_tun(tpars, ctun);
+ if (rc < 0) {
+ talloc_free(tpars);
+ return rc;
+ }
+
+ t = gtp_tunnel_alloc(g_daemon, tpars);
+ if (!t) {
+ LOGCC(cc, LOGL_NOTICE, "Failed to allocate tunnel\n");
+ cups_client_tx_json(cc, gen_uecups_result("create_tun_res", "ERR_NOT_FOUND"));
+ } else {
+ cups_client_tx_json(cc, gen_uecups_result("create_tun_res", "OK"));
+ }
+
+ talloc_free(tpars);
+ return 0;
+}
+
+static int cups_client_handle_destroy_tun(struct cups_client *cc, json_t *dtun)
+{
+ struct sockaddr_storage local_ep_addr;
+ json_t *jlocal_gtp_ep, *jrx_teid;
+ uint32_t rx_teid;
+ int rc;
+
+ jlocal_gtp_ep = json_object_get(dtun, "local_gtp_ep");
+ jrx_teid = json_object_get(dtun, "rx_teid");
+
+ if (!jlocal_gtp_ep || !jrx_teid)
+ return -EINVAL;
+
+ if (!json_is_object(jlocal_gtp_ep) || !json_is_integer(jrx_teid))
+ return -EINVAL;
+
+ rc = parse_ep(&local_ep_addr, jlocal_gtp_ep);
+ if (rc < 0)
+ return rc;
+ rx_teid = json_integer_value(jrx_teid);
+
+ rc = gtp_tunnel_destroy(g_daemon, &local_ep_addr, rx_teid);
+ if (rc < 0) {
+ LOGCC(cc, LOGL_NOTICE, "Failed to destroy tunnel\n");
+ cups_client_tx_json(cc, gen_uecups_result("destroy_tun_res", "ERR_NOT_FOUND"));
+ } else {
+ cups_client_tx_json(cc, gen_uecups_result("destroy_tun_res", "OK"));
+ }
+
+ return 0;
+}
+
+static int cups_client_handle_json(struct cups_client *cc, json_t *jroot)
+{
+ void *iter;
+ const char *key;
+ json_t *cmd;
+ int rc;
+
+ if (!json_is_object(jroot))
+ return -EINVAL;
+
+ iter = json_object_iter(jroot);
+ key = json_object_iter_key(iter);
+ cmd = json_object_iter_value(iter);
+ if (!iter || !key || !cmd)
+ return -EINVAL;
+
+ if (!strcmp(key, "create_tun")) {
+ rc = cups_client_handle_create_tun(cc, cmd);
+ } else if (!strcmp(key, "destroy_tun")) {
+ rc = cups_client_handle_destroy_tun(cc, cmd);
+ } else {
+ LOGCC(cc, LOGL_NOTICE, "Unknown command '%s' received\n", key);
+ return -EINVAL;
+ }
+
+ if (rc < 0) {
+ LOGCC(cc, LOGL_NOTICE, "Error %d handling '%s' command\n", rc, key);
+ char buf[64];
+ snprintf(buf, sizeof(buf), "%s_res", key);
+ cups_client_tx_json(cc, gen_uecups_result(buf, "ERR_INVALID_DATA"));
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* control/user plane separation per-client read cb */
+static int cups_client_read_cb(struct osmo_stream_srv *conn)
+{
+ struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn);
+ struct cups_client *cc = osmo_stream_srv_get_data(conn);
+ struct msgb *msg = msgb_alloc(CUPS_MSGB_SIZE, "Rx JSON");
+ struct sctp_sndrcvinfo sinfo;
+ json_error_t jerr;
+ json_t *jroot;
+ int flags = 0;
+ int rc = 0;
+
+ /* Read message from socket */
+ /* we cannot use osmo_stream_srv_recv() here, as we might get some out-of-band info from
+ * SCTP. FIXME: add something like osmo_stream_srv_recv_sctp() to libosmo-netif and use
+ * it here as well as in libosmo-sigtran and osmo-msc */
+ rc = sctp_recvmsg(ofd->fd, msg->tail, msgb_tailroom(msg), NULL, NULL, &sinfo,&flags);
+ if (rc <= 0) {
+ osmo_stream_srv_destroy(conn);
+ rc = -1;
+ goto out;
+ } else
+ msgb_put(msg, rc);
+
+ if (flags & MSG_NOTIFICATION) {
+ union sctp_notification *notif = (union sctp_notification *) msgb_data(msg);
+ switch (notif->sn_header.sn_type) {
+ case SCTP_SHUTDOWN_EVENT:
+ osmo_stream_srv_destroy(conn);
+ rc = -EBADF;
+ goto out;
+ default:
+ break;
+ }
+ goto out;
+ }
+
+ LOGCC(cc, LOGL_DEBUG, "Rx '%s'\n", msgb_data(msg));
+
+ /* Parse the JSON */
+ jroot = json_loadb((const char *) msgb_data(msg), msgb_length(msg), 0, &jerr);
+ if (!jroot) {
+ LOGCC(cc, LOGL_ERROR, "Error decoding JSON (%s)", jerr.text);
+ rc = -1;
+ goto out;
+ }
+
+ /* Dispatch */
+ rc = cups_client_handle_json(cc, jroot);
+
+ json_decref(jroot);
+ msgb_free(msg);
+
+ return 0;
+out:
+ msgb_free(msg);
+ return rc;
+}
+
+static int cups_client_closed_cb(struct osmo_stream_srv *conn)
+{
+ struct cups_client *cc = osmo_stream_srv_get_data(conn);
+
+ LOGCC(cc, LOGL_INFO, "UECUPS connection lost\n");
+ llist_del(&cc->list);
+ return 0;
+}
+
+
+/* the control/user plane separation server bind/accept fd */
+static int cups_accept_cb(struct osmo_stream_srv_link *link, int fd)
+{
+ struct gtp_daemon *d = osmo_stream_srv_link_get_data(link);
+ struct cups_client *cc;
+
+ cc = talloc_zero(d, struct cups_client);
+ if (!cc)
+ return -1;
+
+ osmo_sock_get_name_buf(cc->sockname, sizeof(cc->sockname), fd);
+ cc->srv = osmo_stream_srv_create(cc, link, fd, cups_client_read_cb, cups_client_closed_cb, cc);
+ if (!cc->srv) {
+ talloc_free(cc);
+ return -1;
+ }
+ LOGCC(cc, LOGL_INFO, "Accepted new UECUPS connection\n");
+
+ llist_add_tail(&cc->list, &d->cups_clients);
+
+ return 0;
+}
+
+/***********************************************************************
+ * GTP Daemon
+ ***********************************************************************/
+
+struct gtp_daemon *g_daemon;
+static int g_daemonize;
+static char *g_config_file = "osmo-gtpu-daemon.cfg";
+extern struct vty_app_info g_vty_info;
+
+static struct gtp_daemon *gtp_daemon_alloc(void *ctx)
+{
+ struct gtp_daemon *d = talloc_zero(ctx, struct gtp_daemon);
+ if (!d)
+ return NULL;
+
+ INIT_LLIST_HEAD(&d->gtp_endpoints);
+ INIT_LLIST_HEAD(&d->tun_devices);
+ INIT_LLIST_HEAD(&d->gtp_tunnels);
+ pthread_rwlock_init(&d->rwlock, NULL);
+ d->main_thread = pthread_self();
+
+ INIT_LLIST_HEAD(&d->cups_clients);
+
+ d->cfg.cups_local_ip = talloc_strdup(d, "localhost");
+ d->cfg.cups_local_port = 4300;
+
+ return d;
+}
+
+static const struct log_info_cat log_categories[] = {
+ [DTUN] = {
+ .name ="DTUN",
+ .description = "Tunnel interface (tun device)",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DEP] = {
+ .name = "DEP",
+ .description = "GTP endpoint (UDP socket)",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DGT] = {
+ .name = "DGT",
+ .description = "GTP tunnel (session)",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DUECUPS] = {
+ .name = "DUECUPS",
+ .description = "UE Control User Plane Separation",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+
+};
+
+static const struct log_info log_info = {
+ .cat = log_categories,
+ .num_cat = ARRAY_SIZE(log_categories),
+};
+
+int main(int argc, char **argv)
+{
+ void *ctx = talloc_named_const(NULL, 0, "root");
+ int rc;
+
+ g_vty_info.tall_ctx = ctx;
+
+ osmo_init_ignore_signals();
+ osmo_init_logging2(ctx, &log_info);
+
+ g_daemon = gtp_daemon_alloc(ctx);
+ OSMO_ASSERT(g_daemon);
+
+ osmo_stats_init(ctx);
+ vty_init(&g_vty_info);
+ logging_vty_add_cmds();
+ osmo_talloc_vty_add_cmds();
+ osmo_stats_vty_add_cmds();
+ rate_ctr_init(ctx);
+ gtpud_vty_init();
+
+ init_netns();
+
+ rc = vty_read_config_file(g_config_file, NULL);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to open config file: '%s'\n", g_config_file);
+ exit(2);
+ }
+
+ rc = telnet_init_dynif(ctx, NULL, vty_get_bind_addr(), OSMO_VTY_PORT_GGSN);
+ if (rc < 0)
+ exit(1);
+
+ g_daemon->cups_link = osmo_stream_srv_link_create(g_daemon);
+ if (!g_daemon->cups_link) {
+ fprintf(stderr, "Failed to create CUPS socket %s:%u (%s)\n",
+ g_daemon->cfg.cups_local_ip, g_daemon->cfg.cups_local_port, strerror(errno));
+ exit(1);
+ }
+
+ /* UECUPS socket for control from control plane side */
+ osmo_stream_srv_link_set_nodelay(g_daemon->cups_link, true);
+ osmo_stream_srv_link_set_addr(g_daemon->cups_link, g_daemon->cfg.cups_local_ip);
+ osmo_stream_srv_link_set_port(g_daemon->cups_link, g_daemon->cfg.cups_local_port);
+ osmo_stream_srv_link_set_proto(g_daemon->cups_link, IPPROTO_SCTP);
+ osmo_stream_srv_link_set_data(g_daemon->cups_link, g_daemon);
+ osmo_stream_srv_link_set_accept_cb(g_daemon->cups_link, cups_accept_cb);
+ osmo_stream_srv_link_open(g_daemon->cups_link);
+
+ if (g_daemonize) {
+ rc = osmo_daemonize();
+ if (rc < 0) {
+ perror("Error during daemonize");
+ exit(1);
+ }
+ }
+
+ while (1) {
+ osmo_select_main(0);
+ }
+}
diff --git a/daemon/netdev.c b/daemon/netdev.c
new file mode 100644
index 0000000..2022d24
--- /dev/null
+++ b/daemon/netdev.c
@@ -0,0 +1,139 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <unistd.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+
+#include <linux/if.h>
+#include <linux/if_tun.h>
+#include <sys/ioctl.h>
+
+#include <netlink/socket.h>
+#include <netlink/route/link.h>
+#include <netlink/route/addr.h>
+#include <netlink/route/route.h>
+#include <netlink/route/nexthop.h>
+
+#include <osmocom/core/utils.h>
+
+/***********************************************************************
+ * netlink helper functions
+ ***********************************************************************/
+
+static int _netdev_addr(struct nl_sock *nlsk, int ifindex, const struct sockaddr_storage *ss, bool add)
+{
+ const struct sockaddr_in6 *sin6;
+ const struct sockaddr_in *sin;
+ struct nl_addr *local = NULL;
+ struct rtnl_addr *addr;
+ int rc;
+
+ switch (ss->ss_family) {
+ case AF_INET:
+ sin = (struct sockaddr_in *) ss;
+ local = nl_addr_build(AF_INET, &sin->sin_addr, 4);
+ break;
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *) ss;
+ local = nl_addr_build(AF_INET6, &sin6->sin6_addr, 16);
+ break;
+ }
+ OSMO_ASSERT(local);
+
+ addr = rtnl_addr_alloc();
+ OSMO_ASSERT(addr);
+ rtnl_addr_set_ifindex(addr, ifindex);
+ OSMO_ASSERT(rtnl_addr_set_local(addr, local) == 0);
+
+ if (add)
+ rc = rtnl_addr_add(nlsk, addr, 0);
+ else
+ rc = rtnl_addr_delete(nlsk, addr, 0);
+
+ rtnl_addr_put(addr);
+
+ return rc;
+}
+
+int netdev_add_addr(struct nl_sock *nlsk, int ifindex, const struct sockaddr_storage *ss)
+{
+ return _netdev_addr(nlsk, ifindex, ss, true);
+}
+
+int netdev_del_addr(struct nl_sock *nlsk, int ifindex, const struct sockaddr_storage *ss)
+{
+ return _netdev_addr(nlsk, ifindex, ss, false);
+}
+
+int netdev_set_link(struct nl_sock *nlsk, int ifindex, bool up)
+{
+ struct rtnl_link *link, *change;
+ int rc;
+
+ rc = rtnl_link_get_kernel(nlsk, ifindex, NULL, &link);
+ if (rc < 0)
+ return rc;
+
+ change = rtnl_link_alloc();
+ OSMO_ASSERT(change);
+
+ if (up)
+ rtnl_link_set_flags(change, IFF_UP);
+ else
+ rtnl_link_unset_flags(change, IFF_UP);
+
+ rc = rtnl_link_change(nlsk, link, change, 0);
+
+ rtnl_link_put(change);
+ rtnl_link_put(link);
+
+ return rc;
+}
+
+int netdev_add_defaultroute(struct nl_sock *nlsk, int ifindex, uint8_t family)
+{
+ struct rtnl_route *route = rtnl_route_alloc();
+ struct rtnl_nexthop *nhop = rtnl_route_nh_alloc();
+ struct nl_addr *dst, *gw;
+ uint8_t buf[16];
+ int rc;
+
+ OSMO_ASSERT(route);
+ OSMO_ASSERT(nhop);
+
+ /* destination address of route: all-zero */
+ memset(buf, 0, sizeof(buf));
+ dst = nl_addr_build(family, buf, family == AF_INET ? 4 : 16);
+ OSMO_ASSERT(dst);
+ nl_addr_set_prefixlen(dst, 0);
+
+ /* gateway address of route: also all-zero */
+ gw = nl_addr_clone(dst);
+ OSMO_ASSERT(gw);
+
+ /* nexthop for route */
+ rtnl_route_nh_set_ifindex(nhop, ifindex);
+ rtnl_route_nh_set_gateway(nhop, gw);
+
+ /* tie everything together in the route */
+ rtnl_route_set_dst(route, dst);
+ rtnl_route_set_family(route, family);
+ rtnl_route_add_nexthop(route, nhop);
+
+ rc = rtnl_route_add(nlsk, route, NLM_F_CREATE);
+
+ //rtnl_route_nh_free(nhop);
+ nl_addr_put(gw);
+ nl_addr_put(dst);
+ rtnl_route_put(route);
+
+ return rc;
+}
diff --git a/daemon/netns.c b/daemon/netns.c
new file mode 100644
index 0000000..b0ec254
--- /dev/null
+++ b/daemon/netns.c
@@ -0,0 +1,273 @@
+#warning "Merge netns.c from osmo-ggsn and osmo-gtpu-daemon"
+/*
+ * Copyright (C) 2014-2017, Travelping GmbH <info@travelping.com>
+ * Copyright (C) 2020, Harald Welte <laforge@gnumonks.org>
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#if defined(__linux__)
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sched.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/mount.h>
+#include <sys/param.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <osmocom/core/utils.h>
+
+#include "netns.h"
+
+#define NETNS_PATH "/var/run/netns"
+
+/*! default namespace of the GGSN process */
+static int default_nsfd = -1;
+
+/*! switch to a (non-default) namespace, store existing signal mask in oldmask.
+ * \param[in] nsfd file descriptor representing the namespace to whch we shall switch
+ * \param[out] oldmaks caller-provided memory location to which old signal mask is stored
+ * \ returns 0 on success or negative (errno) in case of error */
+int switch_ns(int nsfd, sigset_t *oldmask)
+{
+ sigset_t intmask;
+ int rc;
+
+ OSMO_ASSERT(default_nsfd >= 0);
+
+ if (sigfillset(&intmask) < 0)
+ return -errno;
+ if ((rc = sigprocmask(SIG_BLOCK, &intmask, oldmask)) != 0)
+ return -rc;
+
+ if (setns(nsfd, CLONE_NEWNET) < 0) {
+ /* restore old mask if we couldn't switch the netns */
+ sigprocmask(SIG_SETMASK, oldmask, NULL);
+ return -errno;
+ }
+ return 0;
+}
+
+/*! switch back to the default namespace, restoring signal mask.
+ * \param[in] oldmask signal mask to restore after returning to default namespace
+ * \returns 0 on successs; negative errno value in case of error */
+int restore_ns(sigset_t *oldmask)
+{
+ OSMO_ASSERT(default_nsfd >= 0);
+
+ int rc;
+ if (setns(default_nsfd, CLONE_NEWNET) < 0)
+ return -errno;
+
+ if ((rc = sigprocmask(SIG_SETMASK, oldmask, NULL)) != 0)
+ return -rc;
+ return 0;
+}
+
+/*! open a file from within specified network namespace */
+int open_ns(int nsfd, const char *pathname, int flags)
+{
+ sigset_t intmask, oldmask;
+ int ret;
+ int fd = -1;
+ int rc;
+
+ OSMO_ASSERT(default_nsfd >= 0);
+
+ /* mask off all signals, store old signal mask */
+ if (sigfillset(&intmask) < 0)
+ return -errno;
+ if ((rc = sigprocmask(SIG_BLOCK, &intmask, &oldmask)) != 0)
+ return -rc;
+
+ /* associate the calling thread with namespace file descriptor */
+ if (setns(nsfd, CLONE_NEWNET) < 0) {
+ ret = -errno;
+ goto restore_sigmask;
+ }
+ /* open the requested file/path */
+ if ((fd = open(pathname, flags)) < 0) {
+ ret = -errno;
+ goto restore_defaultns;
+ }
+ ret = fd;
+
+restore_defaultns:
+ /* return back to default namespace */
+ if (setns(default_nsfd, CLONE_NEWNET) < 0) {
+ if (fd >= 0)
+ close(fd);
+ return -errno;
+ }
+
+restore_sigmask:
+ /* restore process mask */
+ if ((rc = sigprocmask(SIG_SETMASK, &oldmask, NULL)) != 0) {
+ if (fd >= 0)
+ close(fd);
+ return -rc;
+ }
+
+ return ret;
+}
+
+/*! create a socket in another namespace.
+ * Switches temporarily to namespace indicated by nsfd, creates a socket in
+ * that namespace and then returns to the default namespace.
+ * \param[in] nsfd File descriptor of the namspace in which to create socket
+ * \param[in] domain Domain of the socket (AF_INET, ...)
+ * \param[in] type Type of the socket (SOCK_STREAM, ...)
+ * \param[in] protocol Protocol of the socket (IPPROTO_TCP, ...)
+ * \returns 0 on success; negative errno in case of error */
+int socket_ns(int nsfd, int domain, int type, int protocol)
+{
+ sigset_t intmask, oldmask;
+ int ret;
+ int sk = -1;
+ int rc;
+
+ OSMO_ASSERT(default_nsfd >= 0);
+
+ /* mask off all signals, store old signal mask */
+ if (sigfillset(&intmask) < 0)
+ return -errno;
+ if ((rc = sigprocmask(SIG_BLOCK, &intmask, &oldmask)) != 0)
+ return -rc;
+
+ /* associate the calling thread with namespace file descriptor */
+ if (setns(nsfd, CLONE_NEWNET) < 0) {
+ ret = -errno;
+ goto restore_sigmask;
+ }
+
+ /* create socket of requested domain/type/proto */
+ if ((sk = socket(domain, type, protocol)) < 0) {
+ ret = -errno;
+ goto restore_defaultns;
+ }
+ ret = sk;
+
+restore_defaultns:
+ /* return back to default namespace */
+ if (setns(default_nsfd, CLONE_NEWNET) < 0) {
+ if (sk >= 0)
+ close(sk);
+ return -errno;
+ }
+
+restore_sigmask:
+ /* restore process mask */
+ if ((rc = sigprocmask(SIG_SETMASK, &oldmask, NULL)) != 0) {
+ if (sk >= 0)
+ close(sk);
+ return -rc;
+ }
+ return ret;
+}
+
+/*! initialize this network namespace helper module.
+ * Must be called before using any other functions of this file.
+ * \returns 0 on success; negative errno in case of error */
+int init_netns()
+{
+ /* store the default namespace for later reference */
+ if ((default_nsfd = open("/proc/self/ns/net", O_RDONLY)) < 0)
+ return -errno;
+ return 0;
+}
+
+/*! create obtain file descriptor for network namespace of give name.
+ * Creates /var/run/netns if it doesn't exist already.
+ * \param[in] name Name of the network namespace (in /var/run/netns/)
+ * \returns File descriptor of network namespace; negative errno in case of error */
+int get_nsfd(const char *name)
+{
+ int ret = 0;
+ int rc;
+ int fd;
+ sigset_t intmask, oldmask;
+ char path[MAXPATHLEN] = NETNS_PATH;
+
+ OSMO_ASSERT(default_nsfd >= 0);
+
+ /* create /var/run/netns, if it doesn't exist already */
+ rc = mkdir(path, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
+ if (rc < 0 && errno != EEXIST)
+ return rc;
+
+ /* create /var/run/netns/[name], if it doesn't exist already */
+ snprintf(path, sizeof(path), "%s/%s", NETNS_PATH, name);
+ fd = open(path, O_RDONLY|O_CREAT|O_EXCL, 0);
+ if (fd < 0) {
+ if (errno == EEXIST) {
+ if ((fd = open(path, O_RDONLY)) < 0)
+ return -errno;
+ return fd;
+ }
+ return -errno;
+ }
+ if (close(fd) < 0)
+ return -errno;
+
+ /* mask off all signals, store old signal mask */
+ if (sigfillset(&intmask) < 0)
+ return -errno;
+ if ((rc = sigprocmask(SIG_BLOCK, &intmask, &oldmask)) != 0)
+ return -rc;
+
+ /* create a new network namespace */
+ if (unshare(CLONE_NEWNET) < 0) {
+ ret = -errno;
+ goto restore_sigmask;
+ }
+ if (mount("/proc/self/ns/net", path, "none", MS_BIND, NULL) < 0)
+ ret = -errno;
+
+ /* switch back to default namespace */
+ if (setns(default_nsfd, CLONE_NEWNET) < 0)
+ return -errno;
+
+restore_sigmask:
+ /* restore process mask */
+ if ((rc = sigprocmask(SIG_SETMASK, &oldmask, NULL)) != 0)
+ return -rc;
+
+ /* might have been set above in case mount fails */
+ if (ret < 0)
+ return ret;
+
+ /* finally, open the created namespace file descriptor from default ns */
+ if ((fd = open(path, O_RDONLY)) < 0)
+ return -errno;
+
+ return fd;
+}
+
+#endif
diff --git a/daemon/netns.h b/daemon/netns.h
new file mode 100644
index 0000000..3b91ba3
--- /dev/null
+++ b/daemon/netns.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2014-2017, Travelping GmbH <info@travelping.com>
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __NETNS_H
+#define __NETNS_H
+
+#if defined(__linux__)
+
+int init_netns(void);
+
+int switch_ns(int nsfd, sigset_t *oldmask);
+int restore_ns(sigset_t *oldmask);
+
+int open_ns(int nsfd, const char *pathname, int flags);
+int socket_ns(int nsfd, int domain, int type, int protocol);
+int get_nsfd(const char *name);
+
+#endif
+
+#endif
diff --git a/daemon/osmo-gtpu-daemon.cfg b/daemon/osmo-gtpu-daemon.cfg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/daemon/osmo-gtpu-daemon.cfg
diff --git a/daemon/tun_device.c b/daemon/tun_device.c
new file mode 100644
index 0000000..f6553ca
--- /dev/null
+++ b/daemon/tun_device.c
@@ -0,0 +1,432 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <unistd.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <netdb.h>
+
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+
+#include <pthread.h>
+
+#include <linux/if.h>
+#include <linux/if_tun.h>
+#include <sys/ioctl.h>
+
+#include <netlink/socket.h>
+#include <netlink/route/link.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+
+#include "gtp.h"
+#include "internal.h"
+#include "netns.h"
+
+/***********************************************************************
+ * TUN Device
+ ***********************************************************************/
+
+#define LOGTUN(tun, lvl, fmt, args ...) \
+ LOGP(DTUN, lvl, "%s: " fmt, (tun)->devname, ## args)
+
+/* extracted information from a packet */
+struct pkt_info {
+ struct sockaddr_storage saddr;
+ struct sockaddr_storage daddr;
+ uint8_t proto;
+};
+
+static int parse_pkt(struct pkt_info *out, const uint8_t *in, unsigned int in_len)
+{
+ const struct iphdr *ip4 = (struct iphdr *) in;
+ const uint16_t *l4h = NULL;
+
+ memset(out, 0, sizeof(*out));
+
+ if (ip4->version == 4) {
+ struct sockaddr_in *saddr4 = (struct sockaddr_in *) &out->saddr;
+ struct sockaddr_in *daddr4 = (struct sockaddr_in *) &out->daddr;
+
+ if (in_len < sizeof(*ip4) || in_len < 4*ip4->ihl)
+ return -1;
+
+ saddr4->sin_family = AF_INET;
+ saddr4->sin_addr.s_addr = ip4->saddr;
+
+ daddr4->sin_family = AF_INET;
+ daddr4->sin_addr.s_addr = ip4->daddr;
+
+ out->proto = ip4->protocol;
+ l4h = (const uint16_t *) (in + sizeof(*ip4));
+
+ switch (out->proto) {
+ case IPPROTO_TCP:
+ case IPPROTO_UDP:
+ case IPPROTO_DCCP:
+ case IPPROTO_SCTP:
+ case IPPROTO_UDPLITE:
+ saddr4->sin_port = ntohs(l4h[0]);
+ daddr4->sin_port = ntohs(l4h[1]);
+ break;
+ default:
+ break;
+ }
+ } else if (ip4->version == 6) {
+ const struct ip6_hdr *ip6 = (struct ip6_hdr *) in;
+ struct sockaddr_in6 *saddr6 = (struct sockaddr_in6 *) &out->saddr;
+ struct sockaddr_in6 *daddr6 = (struct sockaddr_in6 *) &out->daddr;
+
+ if (in_len < sizeof(*ip6))
+ return -1;
+
+ saddr6->sin6_family = AF_INET6;
+ saddr6->sin6_addr = ip6->ip6_src;
+
+ daddr6->sin6_family = AF_INET6;
+ daddr6->sin6_addr = ip6->ip6_dst;
+
+ /* FIXME: ext hdr */
+ out->proto = ip6->ip6_nxt;
+ l4h = (const uint16_t *) (in + sizeof(*ip6));
+
+ switch (out->proto) {
+ case IPPROTO_TCP:
+ case IPPROTO_UDP:
+ case IPPROTO_DCCP:
+ case IPPROTO_SCTP:
+ case IPPROTO_UDPLITE:
+ saddr6->sin6_port = ntohs(l4h[0]);
+ daddr6->sin6_port = ntohs(l4h[1]);
+ break;
+ default:
+ break;
+ }
+ } else
+ return -1;
+
+ return 0;
+}
+
+/* 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;
+
+ /* initialize the fixed part of the GTP header */
+ gtph->flags = 0x30;
+ gtph->type = GTP_TPDU;
+
+ while (1) {
+ struct gtp_tunnel *t;
+ struct pkt_info pinfo;
+ int rc, nread, outfd;
+
+ /* 1) read from tun */
+ rc = read(tun->fd, buffer, MAX_UDP_PACKET);
+ if (rc < 0) {
+ LOGTUN(tun, LOGL_FATAL, "Error readingfrom tun device: %s\n", strerror(errno));
+ exit(1);
+ }
+ nread = rc;
+ gtph->length = htons(nread);
+
+ rc = parse_pkt(&pinfo, buffer, nread);
+ if (rc < 0) {
+ LOGTUN(tun, LOGL_NOTICE, "Error parsing IP packet: %s\n",
+ osmo_hexdump(buffer, nread));
+ continue;
+ }
+
+ if (pinfo.saddr.ss_family == AF_INET6 && pinfo.proto == IPPROTO_ICMPV6) {
+ /* 2) TODO: magic voodoo for IPv6 neighbor discovery */
+ }
+
+ /* 3) look-up tunnel based on source IP address (+ filter) */
+ pthread_rwlock_rdlock(&d->rwlock);
+ t = _gtp_tunnel_find_eua(tun, (struct sockaddr *) &pinfo.saddr, pinfo.proto);
+ if (!t) {
+ char host[128];
+ char port[8];
+ pthread_rwlock_unlock(&d->rwlock);
+ getnameinfo((const struct sockaddr *)&pinfo.saddr,
+ sizeof(pinfo.saddr), host, sizeof(host), port, sizeof(port),
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ LOGTUN(tun, LOGL_NOTICE, "No tunnel found for source address %s:%s\n", host, port);
+ 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));
+ if (rc < 0) {
+ LOGTUN(tun, LOGL_FATAL, "Error Writing to UDP socket: %s\n", strerror(errno));
+ exit(1);
+ }
+ }
+}
+
+static int tun_open(int flags, const char *name)
+{
+ struct ifreq ifr;
+ int fd, rc;
+
+ fd = open("/dev/net/tun", O_RDWR);
+ if (fd < 0) {
+ LOGP(DTUN, LOGL_ERROR, "Cannot open /dev/net/tun: %s\n", strerror(errno));
+ return fd;
+ }
+
+ memset(&ifr, 0, sizeof(ifr));
+ ifr.ifr_flags = IFF_TUN | IFF_NO_PI | flags;
+ if (name) {
+ /* if a TUN interface name was specified, put it in the structure; otherwise,
+ the kernel will try to allocate the "next" device of the specified type */
+ strncpy(ifr.ifr_name, name, IFNAMSIZ);
+ }
+
+ /* try to create the device */
+ rc = ioctl(fd, TUNSETIFF, (void *) &ifr);
+ if (rc < 0) {
+ close(fd);
+ return rc;
+ }
+
+ /* FIXME: read name back from device? */
+ /* FIXME: SIOCSIFTXQLEN / SIOCSIFFLAGS */
+
+ return fd;
+}
+
+static struct tun_device *
+_tun_device_create(struct gtp_daemon *d, const char *devname, const char *netns_name)
+{
+ struct rtnl_link *link;
+ struct tun_device *tun;
+ sigset_t oldmask;
+ int rc;
+
+ tun = talloc_zero(d, struct tun_device);
+ if (!tun)
+ return NULL;
+
+ tun->d = d;
+ tun->use_count = 1;
+ tun->devname = talloc_strdup(tun, devname);
+
+ if (netns_name) {
+ tun->netns_name = talloc_strdup(tun, netns_name);
+ tun->netns_fd = get_nsfd(tun->netns_name);
+ if (tun->netns_fd < 0) {
+ LOGTUN(tun, LOGL_ERROR, "Cannot obtain netns file descriptor: %s\n",
+ strerror(errno));
+ goto err_free;
+ }
+ }
+
+ /* temporarily switch to specified namespace to create tun device */
+ if (tun->netns_name) {
+ rc = switch_ns(tun->netns_fd, &oldmask);
+ if (rc < 0) {
+ LOGTUN(tun, LOGL_ERROR, "Cannot switch to netns '%s': %s\n",
+ tun->netns_name, strerror(errno));
+ goto err_close_ns;
+ }
+ }
+
+ tun->fd = tun_open(0, tun->devname);
+ if (tun->fd < 0) {
+ LOGTUN(tun, LOGL_ERROR, "Cannot open TUN device: %s\n", strerror(errno));
+ goto err_restore_ns;
+ }
+
+ tun->nl = nl_socket_alloc();
+ if (!tun->nl || nl_connect(tun->nl, NETLINK_ROUTE) < 0) {
+ LOGTUN(tun, LOGL_ERROR, "Cannot create netlink socket in namespace '%s'\n",
+ tun->netns_name);
+ goto err_close;
+ }
+
+ rc = rtnl_link_get_kernel(tun->nl, 0, tun->devname, &link);
+ if (rc < 0) {
+ LOGTUN(tun, LOGL_ERROR, "Cannot get ifindex for netif after create?!?\n");
+ goto err_free_nl;
+ }
+ tun->ifindex = rtnl_link_get_ifindex(link);
+ rtnl_link_put(link);
+
+ /* switch back to default namespace before creating new thread */
+ if (tun->netns_name)
+ OSMO_ASSERT(restore_ns(&oldmask) == 0);
+
+ /* bring the network device up */
+ rc = netdev_set_link(tun->nl, tun->ifindex, true);
+ if (rc < 0)
+ LOGTUN(tun, LOGL_ERROR, "Cannot set interface to 'up'\n");
+
+ if (tun->netns_name) {
+ rc = netdev_add_defaultroute(tun->nl, tun->ifindex, AF_INET);
+ if (rc < 0)
+ LOGTUN(tun, LOGL_ERROR, "Cannot add IPv4 default route\n");
+ else
+ LOGTUN(tun, LOGL_INFO, "Added IPv4 default route\n");
+
+ rc = netdev_add_defaultroute(tun->nl, tun->ifindex, AF_INET6);
+ if (rc < 0)
+ LOGTUN(tun, LOGL_ERROR, "Cannot add IPv6 default route\n");
+ else
+ LOGTUN(tun, LOGL_INFO, "Added IPv6 default route\n");
+ }
+
+ if (pthread_create(&tun->thread, NULL, tun_device_thread, tun)) {
+ LOGTUN(tun, LOGL_ERROR, "Cannot create TUN thread: %s\n", strerror(errno));
+ goto err_free_nl;
+ }
+
+ LOGTUN(tun, LOGL_INFO, "Created (in netns '%s')\n", tun->netns_name);
+ llist_add_tail(&tun->list, &d->tun_devices);
+
+ return tun;
+
+err_free_nl:
+ nl_socket_free(tun->nl);
+err_close:
+ close(tun->fd);
+err_restore_ns:
+ if (tun->netns_name)
+ OSMO_ASSERT(restore_ns(&oldmask) == 0);
+err_close_ns:
+ if (tun->netns_name)
+ close(tun->netns_fd);
+err_free:
+ talloc_free(tun);
+ return NULL;
+}
+
+struct tun_device *
+_tun_device_find(struct gtp_daemon *d, const char *devname)
+{
+ struct tun_device *tun;
+
+ llist_for_each_entry(tun, &d->tun_devices, list) {
+ if (!strcmp(tun->devname, devname))
+ return tun;
+ }
+ return NULL;
+}
+
+struct tun_device *
+tun_device_find_or_create(struct gtp_daemon *d, const char *devname, const char *netns_name)
+{
+ struct tun_device *tun;
+
+ /* talloc is not thread safe, all alloc/free must come from main thread */
+ ASSERT_MAIN_THREAD(d);
+
+ pthread_rwlock_wrlock(&d->rwlock);
+ tun = _tun_device_find(d, devname);
+ if (tun)
+ tun->use_count++;
+ else
+ tun = _tun_device_create(d, devname, netns_name);
+ pthread_rwlock_unlock(&d->rwlock);
+
+ return tun;
+}
+
+/* UNLOCKED hard/forced destroy; caller must make sure references are cleaned up */
+static void _tun_device_destroy(struct tun_device *tun)
+{
+ /* talloc is not thread safe, all alloc/free must come from main thread */
+ ASSERT_MAIN_THREAD(tun->d);
+
+ pthread_cancel(tun->thread);
+ llist_del(&tun->list);
+ if (tun->netns_name)
+ close(tun->netns_fd);
+ close(tun->fd);
+ nl_socket_free(tun->nl);
+ LOGTUN(tun, LOGL_INFO, "Destroying\n");
+ talloc_free(tun);
+}
+
+/* UNLOCKED remove all objects referencing this tun and then destroy */
+void _tun_device_deref_destroy(struct tun_device *tun)
+{
+ struct gtp_daemon *d = tun->d;
+ char *devname = talloc_strdup(d, tun->devname);
+ struct gtp_tunnel *t, *t2;
+ struct tun_device *tun2;
+
+ /* talloc is not thread safe, all alloc/free must come from main thread */
+ ASSERT_MAIN_THREAD(tun->d);
+
+ llist_for_each_entry_safe(t, t2, &g_daemon->gtp_tunnels, list) {
+ if (t->tun_dev == tun)
+ _gtp_tunnel_destroy(t);
+ }
+ /* _tun_device_destroy may already have been called via
+ * _gtp_tunnel_destroy -> _tun_device_release, so we have to
+ * check if the tun can still be found in the list */
+ tun2 = _tun_device_find(d, devname);
+ if (tun2 && tun2 == tun)
+ _tun_device_destroy(tun2);
+
+ talloc_free(devname);
+}
+
+/* UNLOCKED release a reference; destroy if refcount drops to 0 */
+bool _tun_device_release(struct tun_device *tun)
+{
+ bool released = false;
+
+ /* talloc is not thread safe, all alloc/free must come from main thread */
+ ASSERT_MAIN_THREAD(tun->d);
+
+ tun->use_count--;
+ if (tun->use_count == 0) {
+ _tun_device_destroy(tun);
+ released = true;
+ } else
+ LOGTUN(tun, LOGL_DEBUG, "Release; new use_count=%lu\n", tun->use_count);
+
+ return released;
+}
+
+/* release a reference; destroy if refcount drops to 0 */
+bool tun_device_release(struct tun_device *tun)
+{
+ struct gtp_daemon *d = tun->d;
+ bool released;
+
+ /* talloc is not thread safe, all alloc/free must come from main thread */
+ ASSERT_MAIN_THREAD(tun->d);
+
+ pthread_rwlock_wrlock(&d->rwlock);
+ released = _tun_device_release(tun);
+ pthread_rwlock_unlock(&d->rwlock);
+
+ return released;
+}
diff --git a/daemon/utility.c b/daemon/utility.c
new file mode 100644
index 0000000..1b36a24
--- /dev/null
+++ b/daemon/utility.c
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <unistd.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <netdb.h>
+
+#include "internal.h"
+
+/***********************************************************************
+ * Utility
+ ***********************************************************************/
+
+bool sockaddr_equals(const struct sockaddr *a, const struct sockaddr *b)
+{
+ const struct sockaddr_in *a4, *b4;
+ const struct sockaddr_in6 *a6, *b6;
+
+ if (a->sa_family != b->sa_family)
+ return false;
+
+ switch (a->sa_family) {
+ case AF_INET:
+ a4 = (struct sockaddr_in *) a;
+ b4 = (struct sockaddr_in *) b;
+ if (a4->sin_port != b4->sin_port)
+ return false;
+ if (a4->sin_addr.s_addr != b4->sin_addr.s_addr)
+ return false;
+ break;
+ case AF_INET6:
+ a6 = (struct sockaddr_in6 *) a;
+ b6 = (struct sockaddr_in6 *) b;
+ if (a6->sin6_port != b6->sin6_port)
+ return false;
+ if (memcmp(a6->sin6_addr.s6_addr, b6->sin6_addr.s6_addr, sizeof(b6->sin6_addr.s6_addr)))
+ return false;
+ break;
+ default:
+ assert(false);
+ }
+
+ return true;
+}
+
+struct addrinfo *addrinfo_helper(uint16_t family, uint16_t type, uint8_t proto,
+ const char *host, uint16_t port, bool passive)
+{
+ struct addrinfo hints, *result;
+ char portbuf[6];
+ int rc;
+
+ snprintf(portbuf, sizeof(portbuf), "%u", port);
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = family;
+ hints.ai_socktype = type;
+ hints.ai_protocol = proto;
+ if (passive)
+ hints.ai_flags |= AI_PASSIVE;
+
+ rc = getaddrinfo(host, portbuf, &hints, &result);
+ if (rc != 0)
+ return NULL;
+
+ return result;
+}
diff --git a/ttcn3/UECUPS_CodecPort.ttcn b/ttcn3/UECUPS_CodecPort.ttcn
new file mode 100644
index 0000000..8f9d962
--- /dev/null
+++ b/ttcn3/UECUPS_CodecPort.ttcn
@@ -0,0 +1,70 @@
+module UECUPS_CodecPort {
+
+/* (C) 2020 by Harald Welte <laforge@gnumonks.org>
+ * All rights reserved.
+ *
+ * Released under the terms of GNU General Public License, Version 2 or
+ * (at your option) any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+ import from IPL4asp_PortType all;
+ import from IPL4asp_Types all;
+ import from UECUPS_Types all;
+
+ type record UECUPS_RecvFrom {
+ ConnectionId connId,
+ HostName remName,
+ PortNumber remPort,
+ HostName locName,
+ PortNumber locPort,
+ PDU_UECUPS msg
+ };
+
+ template UECUPS_RecvFrom t_UECUPS_RecvFrom(template PDU_UECUPS msg) := {
+ connId := ?,
+ remName := ?,
+ remPort := ?,
+ locName := ?,
+ locPort := ?,
+ msg := msg
+ }
+
+ type record UECUPS_Send {
+ ConnectionId connId,
+ PDU_UECUPS msg
+ }
+
+ template UECUPS_Send t_UECUPS_Send(template ConnectionId connId, template PDU_UECUPS msg) := {
+ connId := connId,
+ msg := msg
+ }
+
+ private function IPL4_to_UECUPS_RecvFrom(in ASP_RecvFrom pin, out UECUPS_RecvFrom pout) {
+ pout.connId := pin.connId;
+ pout.remName := pin.remName;
+ pout.remPort := pin.remPort;
+ pout.locName := pin.locName;
+ pout.locPort := pin.locPort;
+ pout.msg := f_dec_PDU_UECUPS(pin.msg);
+ } with { extension "prototype(fast)" };
+
+ private function UECUPS_to_IPL4_Send(in UECUPS_Send pin, out ASP_Send pout) {
+ pout.connId := pin.connId;
+ pout.proto := { sctp := {} };
+ pout.msg := f_enc_PDU_UECUPS(pin.msg);
+ } with { extension "prototype(fast)" };
+
+ type port UECUPS_CODEC_PT message {
+ out UECUPS_Send;
+ in UECUPS_RecvFrom,
+ ASP_ConnId_ReadyToRelease,
+ ASP_Event;
+ } with { extension "user IPL4asp_PT
+ out(UECUPS_Send -> ASP_Send:function(UECUPS_to_IPL4_Send))
+ in(ASP_RecvFrom -> UECUPS_RecvFrom: function(IPL4_to_UECUPS_RecvFrom);
+ ASP_ConnId_ReadyToRelease -> ASP_ConnId_ReadyToRelease: simple;
+ ASP_Event -> ASP_Event: simple)"
+ }
+}
diff --git a/ttcn3/UECUPS_CodecPort_CtrlFunct.ttcn b/ttcn3/UECUPS_CodecPort_CtrlFunct.ttcn
new file mode 100644
index 0000000..365b7cb
--- /dev/null
+++ b/ttcn3/UECUPS_CodecPort_CtrlFunct.ttcn
@@ -0,0 +1,44 @@
+module UECUPS_CodecPort_CtrlFunct {
+
+ import from UECUPS_CodecPort all;
+ import from IPL4asp_Types all;
+
+ external function f_IPL4_listen(
+ inout UECUPS_CODEC_PT portRef,
+ in HostName locName,
+ in PortNumber locPort,
+ in ProtoTuple proto,
+ in OptionList options := {}
+ ) return Result;
+
+ external function f_IPL4_connect(
+ inout UECUPS_CODEC_PT portRef,
+ in HostName remName,
+ in PortNumber remPort,
+ in HostName locName,
+ in PortNumber locPort,
+ in ConnectionId connId,
+ in ProtoTuple proto,
+ in OptionList options := {}
+ ) return Result;
+
+ external function f_IPL4_close(
+ inout UECUPS_CODEC_PT portRef,
+ in ConnectionId id,
+ in ProtoTuple proto := { unspecified := {} }
+ ) return Result;
+
+ external function f_IPL4_setUserData(
+ inout UECUPS_CODEC_PT portRef,
+ in ConnectionId id,
+ in UserData userData
+ ) return Result;
+
+ external function f_IPL4_getUserData(
+ inout UECUPS_CODEC_PT portRef,
+ in ConnectionId id,
+ out UserData userData
+ ) return Result;
+
+}
+
diff --git a/ttcn3/UECUPS_CodecPort_CtrlFunctDef.cc b/ttcn3/UECUPS_CodecPort_CtrlFunctDef.cc
new file mode 100644
index 0000000..b069b11
--- /dev/null
+++ b/ttcn3/UECUPS_CodecPort_CtrlFunctDef.cc
@@ -0,0 +1,56 @@
+#include "IPL4asp_PortType.hh"
+#include "UECUPS_CodecPort.hh"
+#include "IPL4asp_PT.hh"
+
+namespace UECUPS__CodecPort__CtrlFunct {
+
+ IPL4asp__Types::Result f__IPL4__listen(
+ UECUPS__CodecPort::UECUPS__CODEC__PT& portRef,
+ const IPL4asp__Types::HostName& locName,
+ const IPL4asp__Types::PortNumber& locPort,
+ const IPL4asp__Types::ProtoTuple& proto,
+ const IPL4asp__Types::OptionList& options)
+ {
+ return f__IPL4__PROVIDER__listen(portRef, locName, locPort, proto, options);
+ }
+
+ IPL4asp__Types::Result f__IPL4__connect(
+ UECUPS__CodecPort::UECUPS__CODEC__PT& portRef,
+ const IPL4asp__Types::HostName& remName,
+ const IPL4asp__Types::PortNumber& remPort,
+ const IPL4asp__Types::HostName& locName,
+ const IPL4asp__Types::PortNumber& locPort,
+ const IPL4asp__Types::ConnectionId& connId,
+ const IPL4asp__Types::ProtoTuple& proto,
+ const IPL4asp__Types::OptionList& options)
+ {
+ return f__IPL4__PROVIDER__connect(portRef, remName, remPort,
+ locName, locPort, connId, proto, options);
+ }
+
+ IPL4asp__Types::Result f__IPL4__close(
+ UECUPS__CodecPort::UECUPS__CODEC__PT& portRef,
+ const IPL4asp__Types::ConnectionId& connId,
+ const IPL4asp__Types::ProtoTuple& proto)
+ {
+ return f__IPL4__PROVIDER__close(portRef, connId, proto);
+ }
+
+ IPL4asp__Types::Result f__IPL4__setUserData(
+ UECUPS__CodecPort::UECUPS__CODEC__PT& portRef,
+ const IPL4asp__Types::ConnectionId& connId,
+ const IPL4asp__Types::UserData& userData)
+ {
+ return f__IPL4__PROVIDER__setUserData(portRef, connId, userData);
+ }
+
+ IPL4asp__Types::Result f__IPL4__getUserData(
+ UECUPS__CodecPort::UECUPS__CODEC__PT& portRef,
+ const IPL4asp__Types::ConnectionId& connId,
+ IPL4asp__Types::UserData& userData)
+ {
+ return f__IPL4__PROVIDER__getUserData(portRef, connId, userData);
+ }
+
+}
+
diff --git a/ttcn3/UECUPS_Types.ttcn b/ttcn3/UECUPS_Types.ttcn
new file mode 100644
index 0000000..8c00090
--- /dev/null
+++ b/ttcn3/UECUPS_Types.ttcn
@@ -0,0 +1,91 @@
+module UECUPS_Types {
+
+import from General_Types all;
+import from Osmocom_Types all;
+
+type enumerated UECUPS_AddrType {
+ IPV4 (1),
+ IPV6 (2)
+};
+
+type enumerated UECUPS_Result {
+ OK (1),
+ ERR_INVALID_DATA (2),
+ ERR_NOT_FOUND (3)
+};
+
+type record UECUPS_SockAddr {
+ UECUPS_AddrType addr_type,
+ OCT4_16n ip,
+ uint16_t Port
+};
+
+/* 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,
+
+ /* user address (allocated inside the tunnel) */
+ 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,
+
+ /* TUN device */
+ charstring tun_dev_name,
+ charstring tun_netns_name optional
+};
+
+type record UECUPS_CreateTunRes {
+ UECUPS_Result result
+};
+
+/* Destroy an existing GTP-U tunnel in the user plane */
+type record UECUPS_DestroyTun {
+ /* local GTP endpoint + TEID are sufficient for unique identification */
+ UECUPS_SockAddr local_gtp_ep,
+ uint32_t rx_teid
+};
+
+type record UECUPS_DestroyTunRes {
+ UECUPS_Result result
+};
+
+type union PDU_UECUPS {
+ UECUPS_CreateTun create_tun,
+ UECUPS_CreateTunRes create_tun_res,
+ UECUPS_DestroyTun destroy_tun,
+ UECUPS_DestroyTunRes destroy_tun_res
+};
+
+
+
+external function f_enc_PDU_UECUPS(in PDU_UECUPS inp) return octetstring
+ with { extension "prototype(convert) encode(JSON)" }
+external function f_dec_PDU_UECUPS(in octetstring inp) return PDU_UECUPS
+ with { extension "prototype(convert) decode(JSON)" }
+
+
+private function f_get_addrtype(OCT4_16n addr) return UECUPS_AddrType
+{
+ if (lengthof(addr) == 4) {
+ return IPV4;
+ } else {
+ return IPV6;
+ }
+}
+
+private const integer GTP1U_PORT := 2152;
+
+template (value) UECUPS_SockAddr
+ts_UECUPS_SockAddr(OCT4_16n ip, uint16_t Port := GTP1U_PORT) := {
+ addr_type := f_get_addrtype(ip),
+ ip := ip,
+ Port := Port
+}
+
+
+} with { encode "JSON" };