pespin has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmocom-bb/+/30994 )
Change subject: layer23: Introduce tun.{c,h} ...................................................................... layer23: Introduce tun.{c,h} This module provides several operations on tun devices (interfaces). These functionalitites will be used by the "modem" app to set up tunnels for each APN in a follow-up patch. The code is basically an import from osmo-ggsn.git 97f60e3dca581797007524e0006ca9fafad59713 src/lib/tun.{c,h}, with small modifications to make use of osmo_sockaddr instead of osmo-ggsn.git specific in46_addr. Related: OS#5503 Change-Id: Id8611b720ada17a3c602872fe095bb91eeb17bcd --- M src/host/layer23/include/osmocom/bb/common/Makefile.am M src/host/layer23/include/osmocom/bb/common/apn.h A src/host/layer23/include/osmocom/bb/common/tun.h M src/host/layer23/src/common/Makefile.am M src/host/layer23/src/common/apn.c A src/host/layer23/src/common/tun.c 6 files changed, 555 insertions(+), 2 deletions(-) git pull ssh://gerrit.osmocom.org:29418/osmocom-bb refs/changes/94/30994/1 diff --git a/src/host/layer23/include/osmocom/bb/common/Makefile.am b/src/host/layer23/include/osmocom/bb/common/Makefile.am index 12be730..67dc21c 100644 --- a/src/host/layer23/include/osmocom/bb/common/Makefile.am +++ b/src/host/layer23/include/osmocom/bb/common/Makefile.am @@ -18,5 +18,6 @@ sim.h \ subscriber.h \ support.h \ + tun.h \ vty.h \ $(NULL) diff --git a/src/host/layer23/include/osmocom/bb/common/apn.h b/src/host/layer23/include/osmocom/bb/common/apn.h index 18dc16f..7a6215b 100644 --- a/src/host/layer23/include/osmocom/bb/common/apn.h +++ b/src/host/layer23/include/osmocom/bb/common/apn.h @@ -21,6 +21,7 @@ #include <osmocom/core/select.h> struct osmocom_ms; +struct tun_t; #define APN_TYPE_IPv4 0x01 /* v4-only */ #define APN_TYPE_IPv6 0x02 /* v6-only */ @@ -54,7 +55,7 @@ /* corresponding tun device */ struct { - //struct tun_t *tun; + struct tun_t *tun; struct osmo_fd fd; } tun; @@ -66,3 +67,6 @@ void apn_free(struct osmobb_apn *apn); int apn_start(struct osmobb_apn *apn); int apn_stop(struct osmobb_apn *apn); + +#define LOGPAPN(level, apn, fmt, args...) \ + LOGP(DTUN, level, "APN(%s): " fmt, (apn)->cfg.name, ## args) diff --git a/src/host/layer23/include/osmocom/bb/common/tun.h b/src/host/layer23/include/osmocom/bb/common/tun.h new file mode 100644 index 0000000..703d1bd --- /dev/null +++ b/src/host/layer23/include/osmocom/bb/common/tun.h @@ -0,0 +1,64 @@ +/* + * TUN interface functions. + * Copyright (C) 2002, 2003 Mondru AB. + * Copyright (C) 2017-2018 by Harald Welte <lafo...@gnumonks.org> + * Copyright (C) 2023 by sysmocom - s.m.f.c. GmbH <i...@sysmocom.de> + * + * The contents of this file may be used under the terms of the GNU + * General Public License Version 2, provided that the above copyright + * notice and this permission notice is included in all copies or + * substantial portions of the software. + * + */ + +#ifndef _TUN_H +#define _TUN_H + +#include <stdbool.h> +#include <net/if.h> + +#include <osmocom/core/socket.h> +#include <osmocom/bb/common/netdev.h> + +#define PACKET_MAX 8196 /* Maximum packet size we receive */ +#define TUN_SCRIPTSIZE 256 +#define TUN_ADDRSIZE 128 + +/* *********************************************************** + * Information storage for each tun instance + *************************************************************/ + +struct tun_t { + int fd; /* File descriptor to tun interface */ + struct osmo_sockaddr addr; + struct osmo_sockaddr dstaddr; + struct in_addr netmask; + int addrs; /* Number of allocated IP addresses */ + int routes; /* One if we allocated an automatic route */ + char devname[IFNAMSIZ]; /* Name of the tun device */ + int (*cb_ind) (struct tun_t * tun, void *pack, unsigned len); + /* to be used by callers/users (to attach their own private state) */ + void *priv; +}; + +extern int tun_new(struct tun_t **tun, const char *dev_name, bool use_kernel, int fd0, int fd1u); +extern int tun_free(struct tun_t *tun); +extern int tun_decaps(struct tun_t *this); +extern int tun_encaps(struct tun_t *tun, void *pack, unsigned len); + +extern int tun_addaddr(struct tun_t *this, struct osmo_sockaddr *addr, + struct osmo_sockaddr *dstaddr, size_t prefixlen); + +extern int tun_set_cb_ind(struct tun_t *this, + int (*cb_ind) (struct tun_t * tun, void *pack, + unsigned len)); + +extern int tun_runscript(struct tun_t *tun, char *script); + +int tun_ip_local_get(const struct tun_t *tun, struct in46_prefix *prefix_list, + size_t prefix_size, int flags); + +#define LOGTUN(level, tun, fmt, args...) \ + LOGP(DTUN, level, "TUN(%s): " fmt, (tun)->devname, ## args) + +#endif /* !_TUN_H */ diff --git a/src/host/layer23/src/common/Makefile.am b/src/host/layer23/src/common/Makefile.am index 3a6ba37..cc6f3f5 100644 --- a/src/host/layer23/src/common/Makefile.am +++ b/src/host/layer23/src/common/Makefile.am @@ -30,6 +30,7 @@ subscriber.c \ support.c \ sysinfo.c \ + tun.c \ utils.c \ vty.c \ $(NULL) diff --git a/src/host/layer23/src/common/apn.c b/src/host/layer23/src/common/apn.c index bd0eb25..f21892e 100644 --- a/src/host/layer23/src/common/apn.c +++ b/src/host/layer23/src/common/apn.c @@ -18,12 +18,14 @@ #include <stdint.h> #include <errno.h> #include <string.h> -#include <arpa/inet.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> #include <talloc.h> #include <osmocom/bb/common/logging.h> #include <osmocom/bb/common/apn.h> +#include <osmocom/bb/common/tun.h> #include <osmocom/bb/common/ms.h> struct osmobb_apn *apn_alloc(struct osmocom_ms *ms, const char *name) @@ -49,12 +51,98 @@ talloc_free(apn); } +/* Local network-originated IP packet, needs to be sent via SNDCP/LLC (GPRS) towards GSM network */ +static int cb_tun_ind(struct tun_t *tun, void *pack, unsigned len) +{ + struct osmobb_apn *apn = tun->priv; + struct osmo_sockaddr dst; + struct iphdr *iph = (struct iphdr *)pack; + struct ip6_hdr *ip6h = (struct ip6_hdr *)pack; + uint8_t pref_offset; + + switch (iph->version) { + case 4: + if (len < sizeof(*iph) || len < 4*iph->ihl) + return -1; + dst.u.sin.sin_family = AF_INET; + dst.u.sin.sin_addr.s_addr = iph->daddr; + break; + case 6: + /* Due to the fact that 3GPP requires an allocation of a + * /64 prefix to each MS, we must instruct + * ippool_getip() below to match only the leading /64 + * prefix, i.e. the first 8 bytes of the address. If the ll addr + * is used, then the match should be done on the trailing 64 + * bits. */ + pref_offset = IN6_IS_ADDR_LINKLOCAL(&ip6h->ip6_dst) ? 8 : 0; + memcpy(&dst.u.sin6.sin6_addr, ((uint8_t*)&ip6h->ip6_dst) + pref_offset, 8); + break; + default: + LOGTUN(LOGL_NOTICE, tun, "non-IPv%u packet received\n", iph->version); + return -1; + } + + LOGPAPN(LOGL_DEBUG, apn, "system wants to transmit IPv%u pkt to %s (%u bytes)\n", + iph->version == 4 ? '4' : '6', osmo_sockaddr_to_str(&dst), len); + + /* TODO: prepare & transmit SNDCP UNITDATA.req */ + + return 0; +} + +/* callback for tun device osmocom select loop integration */ +static int apn_tun_fd_cb(struct osmo_fd *fd, unsigned int what) +{ + struct osmobb_apn *apn = fd->data; + + OSMO_ASSERT(what & OSMO_FD_READ); + + return tun_decaps(apn->tun.tun); +} + int apn_start(struct osmobb_apn *apn) { + if (apn->started) + return 0; + + LOGPAPN(LOGL_INFO, apn, "Opening TUN device %s\n", apn->cfg.dev_name); + if (tun_new(&apn->tun.tun, apn->cfg.dev_name, false, -1, -1)) { + LOGPAPN(LOGL_ERROR, apn, "Failed to configure tun device\n"); + return -1; + } + LOGPAPN(LOGL_INFO, apn, "Opened TUN device %s\n", apn->tun.tun->devname); + + /* Register with libosmcoore */ + osmo_fd_setup(&apn->tun.fd, apn->tun.tun->fd, OSMO_FD_READ, apn_tun_fd_cb, apn, 0); + osmo_fd_register(&apn->tun.fd); + + /* Set TUN library callback */ + tun_set_cb_ind(apn->tun.tun, cb_tun_ind); + + /* set back-pointer from TUN device to APN */ + apn->tun.tun->priv = apn; + + /* TODO: set IP addresses on the tun device once we receive them from GGSN. See + osmo-ggsn.git's apn_start() */ + + LOGPAPN(LOGL_NOTICE, apn, "Successfully started\n"); + apn->started = true; return 0; } int apn_stop(struct osmobb_apn *apn) { + LOGPAPN(LOGL_NOTICE, apn, "Stopping\n"); + + /* shutdown whatever old state might be left */ + if (apn->tun.tun) { + /* release tun device */ + LOGPAPN(LOGL_INFO, apn, "Closing TUN device %s\n", apn->tun.tun->devname); + osmo_fd_unregister(&apn->tun.fd); + tun_free(apn->tun.tun); + apn->tun.tun = NULL; + } + + apn->started = false; return 0; } \ No newline at end of file diff --git a/src/host/layer23/src/common/tun.c b/src/host/layer23/src/common/tun.c new file mode 100644 index 0000000..98d1927 --- /dev/null +++ b/src/host/layer23/src/common/tun.c @@ -0,0 +1,395 @@ +/* + * TUN interface functions. + * Copyright (C) 2002, 2003, 2004 Mondru AB. + * Copyright (C) 2017-2018 by Harald Welte <lafo...@gnumonks.org> + * Copyright (C) 2023 by sysmocom - s.m.f.c. GmbH <i...@sysmocom.de> + * + * The contents of this file may be used under the terms of the GNU + * General Public License Version 2, provided that the above copyright + * notice and this permission notice is included in all copies or + * substantial portions of the software. + * + */ + +/* + * tun.c: Contains all TUN functionality. Is able to handle multiple + * tunnels in the same program. Each tunnel is identified by the struct, + * which is passed to functions. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> + +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <errno.h> +#include <net/route.h> +#include <net/if.h> + +#if defined(__linux__) +#include <linux/if_tun.h> + +#elif defined (__FreeBSD__) +#include <net/if_tun.h> +#include <net/if_var.h> +#include <netinet/in_var.h> + +#elif defined (__APPLE__) +#include <net/if.h> + +#else +#error "Unknown platform!" +#endif + +#include <osmocom/core/logging.h> +#include <osmocom/core/socket.h> +#include <osmocom/bb/common/logging.h> +#include <osmocom/bb/common/tun.h> + +#define SYS_ERR(sub, pri, en, fmt, args...) \ + if (en) { \ + logp2(sub, pri, __FILE__, __LINE__, 0, \ + "errno=%d/%s " fmt "\n", en, strerror(en), \ + ##args); \ + } else { \ + logp2(sub, pri, __FILE__, __LINE__, 0, \ + fmt "\n", ##args); \ + } + +/* FIXME: import gtp-kernel.{c,h} from osmo-ggsn.git. */ +static int gtp_kernel_create(int dest_ns, const char *devname, int fd0, int fd1u) +{ + return -ENOTSUP; +} +static void gtp_kernel_stop(const char *devname) +{ + (void)devname; +} + +static int tun_setaddr4(struct tun_t *this, struct in_addr *addr, + struct in_addr *dstaddr, struct in_addr *netmask) +{ + int rc; + rc = netdev_setaddr4(this->devname, addr, dstaddr, netmask); + if (rc < 0) + return rc; + + if (addr) { + this->addr.u.sin.sin_family = AF_INET; + this->addr.u.sin.sin_addr.s_addr = addr->s_addr; + } + if (dstaddr) { + this->dstaddr.u.sin.sin_family = AF_INET; + this->dstaddr.u.sin.sin_addr.s_addr = dstaddr->s_addr; + } + if (netmask) + this->netmask.s_addr = netmask->s_addr; + this->addrs++; +#if defined(__FreeBSD__) || defined (__APPLE__) + this->routes = 1; +#endif + + return rc; +} + +static int tun_setaddr6(struct tun_t *this, struct in6_addr *addr, struct in6_addr *dstaddr, + size_t prefixlen) +{ + int rc; + rc = netdev_setaddr6(this->devname, addr, dstaddr, prefixlen); + if (rc < 0) + return rc; + if (dstaddr) { + this->dstaddr.u.sin6.sin6_family = AF_INET6; + memcpy(&this->dstaddr.u.sin6.sin6_addr, dstaddr, sizeof(*dstaddr)); + } + this->addrs++; +#if defined(__FreeBSD__) || defined (__APPLE__) + this->routes = 1; +#endif + + return rc; +} + +static int tun_addaddr4(struct tun_t *this, struct in_addr *addr, + struct in_addr *dstaddr, struct in_addr *netmask) +{ + int rc; + + /* TODO: Is this needed on FreeBSD? */ + if (!this->addrs) /* Use ioctl for first addr to make ping work */ + return tun_setaddr4(this, addr, dstaddr, netmask); /* TODO dstaddr */ + + rc = netdev_addaddr4(this->devname, addr, dstaddr, netmask); + if (rc < 0) + return rc; + + this->addrs++; + + return rc; +} + +static int tun_addaddr6(struct tun_t *this, + struct in6_addr *addr, + struct in6_addr *dstaddr, int prefixlen) +{ + int rc; + + if (!this->addrs) /* Use ioctl for first addr to make ping work */ + return tun_setaddr6(this, addr, dstaddr, prefixlen); + + rc = netdev_addaddr6(this->devname, addr, dstaddr, prefixlen); + if (rc < 0) + return rc; + + this->addrs++; + + return rc; +} + +int tun_addaddr(struct tun_t *this, struct osmo_sockaddr *addr, struct osmo_sockaddr *dstaddr, size_t prefixlen) +{ + struct in_addr netmask; + switch (addr->u.sa.sa_family) { + case AF_INET: + netmask.s_addr = htonl(0xffffffff << (32 - prefixlen)); + return tun_addaddr4(this, &addr->u.sin.sin_addr, dstaddr ? &dstaddr->u.sin.sin_addr : NULL, &netmask); + case AF_INET6: + return tun_addaddr6(this, &addr->u.sin6.sin6_addr, dstaddr ? &dstaddr->u.sin6.sin6_addr : NULL, prefixlen); + default: + return -1; + } +} + +int tun_new(struct tun_t **tun, const char *dev_name, bool use_kernel, int fd0, int fd1u) +{ + +#if defined(__linux__) + struct ifreq ifr; + +#elif defined(__FreeBSD__) || defined (__APPLE__) + char devname[IFNAMSIZ + 5]; /* "/dev/" + ifname */ + int devnum; + struct ifaliasreq areq; + int fd; +#endif + + if (!(*tun = calloc(1, sizeof(struct tun_t)))) { + SYS_ERR(DTUN, LOGL_ERROR, errno, "calloc() failed"); + return EOF; + } + + (*tun)->cb_ind = NULL; + (*tun)->addrs = 0; + (*tun)->routes = 0; + +#if defined(__linux__) + if (!use_kernel) { + /* Open the actual tun device */ + if (((*tun)->fd = open("/dev/net/tun", O_RDWR)) < 0) { + SYS_ERR(DTUN, LOGL_ERROR, errno, "open() failed"); + goto err_free; + } + + /* Set device flags. For some weird reason this is also the method + used to obtain the network interface name */ + memset(&ifr, 0, sizeof(ifr)); + if (dev_name) + strcpy(ifr.ifr_name, dev_name); + ifr.ifr_flags = IFF_TUN | IFF_NO_PI; /* Tun device, no packet info */ + if (ioctl((*tun)->fd, TUNSETIFF, (void *)&ifr) < 0) { + SYS_ERR(DTUN, LOGL_ERROR, errno, "ioctl() failed"); + goto err_close; + } + + strncpy((*tun)->devname, ifr.ifr_name, IFNAMSIZ); + (*tun)->devname[IFNAMSIZ - 1] = 0; + + /* Disable checksums */ + if (ioctl((*tun)->fd, TUNSETNOCSUM, 1) < 0) { + SYS_ERR(DTUN, LOGL_NOTICE, errno, "could not disable checksum on %s", (*tun)->devname); + } + return 0; + } else { + strncpy((*tun)->devname, dev_name, IFNAMSIZ); + (*tun)->devname[IFNAMSIZ - 1] = 0; + (*tun)->fd = -1; + + if (gtp_kernel_create(-1, dev_name, fd0, fd1u) < 0) { + LOGP(DTUN, LOGL_ERROR, "cannot create GTP tunnel device: %s\n", + strerror(errno)); + return -1; + } + LOGP(DTUN, LOGL_NOTICE, "GTP kernel configured\n"); + return 0; + } + +#elif defined(__FreeBSD__) || defined (__APPLE__) + + if (use_kernel) { + LOGP(DTUN, LOGL_ERROR, "No kernel GTP-U support in FreeBSD!\n"); + return -1; + } + + /* Find suitable device */ + for (devnum = 0; devnum < 255; devnum++) { /* TODO 255 */ + snprintf(devname, sizeof(devname), "/dev/tun%d", devnum); + if (((*tun)->fd = open(devname, O_RDWR)) >= 0) + break; + if (errno != EBUSY) + break; + } + if ((*tun)->fd < 0) { + SYS_ERR(DTUN, LOGL_ERROR, errno, + "Can't find tunnel device"); + goto err_free; + } + + snprintf((*tun)->devname, sizeof((*tun)->devname), "tun%d", devnum); + (*tun)->devname[sizeof((*tun)->devname)-1] = 0; + + /* The tun device we found might have "old" IP addresses allocated */ + /* We need to delete those. This problem is not present on Linux */ + + memset(&areq, 0, sizeof(areq)); + + /* Set up interface name */ + strncpy(areq.ifra_name, (*tun)->devname, IFNAMSIZ); + areq.ifra_name[IFNAMSIZ - 1] = 0; /* Make sure to terminate */ + + /* Create a channel to the NET kernel. */ + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + SYS_ERR(DTUN, LOGL_ERROR, errno, "socket() failed"); + goto err_close; + } + + /* Delete any IP addresses until SIOCDIFADDR fails */ + while (ioctl(fd, SIOCDIFADDR, (void *)&areq) != -1) ; + + close(fd); + return 0; +#endif + +err_close: + close((*tun)->fd); +err_free: + free(*tun); + *tun = NULL; + return -1; +} + +int tun_free(struct tun_t *tun) +{ + + if (tun->routes) { + netdev_delroute4(&tun->dstaddr.u.sin.sin_addr, &tun->addr.u.sin.sin_addr, &tun->netmask); + } + + if (tun->fd >= 0) { + if (close(tun->fd)) { + SYS_ERR(DTUN, LOGL_ERROR, errno, "close() failed"); + } + } + + gtp_kernel_stop(tun->devname); + + /* TODO: For solaris we need to unlink streams */ + + free(tun); + return 0; +} + +int tun_set_cb_ind(struct tun_t *this, + int (*cb_ind) (struct tun_t * tun, void *pack, unsigned len)) +{ + this->cb_ind = cb_ind; + return 0; +} + +int tun_decaps(struct tun_t *this) +{ + unsigned char buffer[PACKET_MAX]; + int status; + + if ((status = read(this->fd, buffer, sizeof(buffer))) <= 0) { + SYS_ERR(DTUN, LOGL_ERROR, errno, "read() failed"); + return -1; + } + + if (this->cb_ind) + return this->cb_ind(this, buffer, status); + + return 0; +} + +int tun_encaps(struct tun_t *tun, void *pack, unsigned len) +{ + int rc; + rc = write(tun->fd, pack, len); + if (rc < 0) { + SYS_ERR(DTUN, LOGL_ERROR, errno, "TUN(%s): write() failed", tun->devname); + } else if (rc < len) { + LOGTUN(LOGL_ERROR, tun, "short write() %d < %u\n", rc, len); + } + return rc; +} + +int tun_runscript(struct tun_t *tun, char *script) +{ + + char buf[TUN_SCRIPTSIZE]; + char snet[TUN_ADDRSIZE]; + char smask[TUN_ADDRSIZE]; + int rc; + + snet[0] = '\0'; + smask[0] = '\0'; + + /* system("ipup /dev/tun0 192.168.0.10 255.255.255.0"); */ + snprintf(buf, sizeof(buf), "%s %s %s %s", + script, tun->devname, + osmo_sockaddr_ntop(&tun->addr.u.sa, snet), + inet_ntop(AF_INET, &tun->netmask, smask, sizeof(smask))); + buf[sizeof(buf) - 1] = 0; + rc = system(buf); + if (rc == -1) { + SYS_ERR(DTUN, LOGL_ERROR, errno, + "Error executing command %s", buf); + return -1; + } + return 0; +} + +/*! Obtain the local address of the tun device. + * \param[in] tun Target device owning the IP + * \param[out] prefix_list List of prefix structures to fill with each IPv4/6 and prefix length found. + * \param[in] prefix_size Amount of elements allowed to be fill in the prefix_list array. + * \param[in] flags Specify which kind of IP to look for: IP_TYPE_IPv4, IP_TYPE_IPv6_LINK, IP_TYPE_IPv6_NONLINK + * \returns The number of ips found following the criteria specified by flags, -1 on error. + * + * This function will fill prefix_list with up to prefix_size IPs following the + * criteria specified by flags parameter. It returns the number of IPs matching + * the criteria. As a result, the number returned can be bigger than + * prefix_size. It can be used with prefix_size=0 to get an estimate of the size + * needed for prefix_list. + */ +int tun_ip_local_get(const struct tun_t *tun, struct in46_prefix *prefix_list, size_t prefix_size, int flags) +{ + return netdev_ip_local_get(tun->devname, prefix_list, prefix_size, flags); +} -- To view, visit https://gerrit.osmocom.org/c/osmocom-bb/+/30994 To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings Gerrit-Project: osmocom-bb Gerrit-Branch: master Gerrit-Change-Id: Id8611b720ada17a3c602872fe095bb91eeb17bcd Gerrit-Change-Number: 30994 Gerrit-PatchSet: 1 Gerrit-Owner: pespin <pes...@sysmocom.de> Gerrit-MessageType: newchange