osmo-uecups/daemon/tun_device.c

482 lines
13 KiB
C

/* 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 <linux/netlink.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;
}
static void tun_device_pthread_cleanup_routine(void *data)
{
struct tun_device *tun = data;
LOGTUN(tun, LOGL_DEBUG, "pthread_cleanup\n");
int rc = osmo_it_q_enqueue(tun->d->itq, &tun->itq_msg, list);
OSMO_ASSERT(rc == 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;
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
* holding the logging mutex, hence causing deadlock with main (or other)
* thread. */
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) {
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_cancelst_unused);
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) {
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_cancelst_unused);
LOGTUN(tun, LOGL_NOTICE, "Error parsing IP packet: %s\n",
osmo_hexdump(buffer, nread));
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old_cancelst_unused);
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);
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_cancelst_unused);
LOGTUN(tun, LOGL_NOTICE, "No tunnel found for source address %s:%s\n", host, port);
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));
if (rc < 0) {
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_cancelst_unused);
LOGTUN(tun, LOGL_FATAL, "Error Writing to UDP socket: %s\n", strerror(errno));
exit(1);
}
}
pthread_cleanup_pop(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 */
osmo_strlcpy(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 "
"(rc=%d): %s\n", rc, nl_geterror(rc));
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 "
"(rc=%d): %s\n", rc, nl_geterror(rc));
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;
}
/* find the first tun device within given named netns */
struct tun_device *
tun_device_find_netns(struct gtp_daemon *d, const char *netns_name)
{
struct tun_device *tun;
pthread_rwlock_rdlock(&d->rwlock);
llist_for_each_entry(tun, &d->tun_devices, list) {
if (!strcmp(tun->netns_name, netns_name)) {
pthread_rwlock_unlock(&d->rwlock);
return tun;
}
}
pthread_rwlock_unlock(&d->rwlock);
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, and tun thread is stopped beforehand by calling
* _tun_device_{deref_}release */
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);
LOGTUN(tun, LOGL_INFO, "Destroying\n");
if (tun->netns_name)
close(tun->netns_fd);
close(tun->fd);
nl_socket_free(tun->nl);
talloc_free(tun);
}
/* UNLOCKED remove all objects referencing this tun and then start async tun release procedure */
void _tun_device_deref_release(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_release(tun2);
talloc_free(devname);
}
/* UNLOCKED release a reference; start async tun release procedure 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) {
LOGTUN(tun, LOGL_INFO, "Releasing\n");
llist_del(&tun->list);
tun->itq_msg.tun_released.tun = tun;
tun->d->reset_all_state_tun_remaining++;
/* We cancel the thread: the pthread_cleanup routing will send a message
* back to us (main thread) when finally cancelled. */
pthread_cancel(tun->thread);
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;
}