--indent=tab=8
--convert-tabs
---exclude=src/lib
+--exclude=subprojects
+--exclude=build
+--recursive
-i
-j
-f
-xg
-k3
-w
+--formatted
+
/test/*.log
/test/*.trs
/test/splice
-/test/testlib.sh
Makefile
Makefile.in
core*
heaptrack.*
*.tar.gz*
+/subprojects/*
+!/subprojects/*.wrap
+
--- /dev/null
+dir_compl = dir_data / 'bash-completion' / 'completions'
+
+install_data(
+ 'tinc',
+ install_dir: dir_compl,
+)
+
--- /dev/null
+man_pages = [
+ 'tinc-gui.8.in',
+ 'tinc.8.in',
+ 'tinc.conf.5.in',
+ 'tincd.8.in',
+]
+
+info_pages = [
+ 'tinc.texi',
+]
+
+info_includes = [
+ 'tincinclude.texi.in',
+]
+
+man_conf = configuration_data()
+man_conf.set_quoted('PACKAGE', meson.project_name())
+man_conf.set_quoted('VERSION', meson.project_version())
+man_conf.set_quoted('localstatedir', dir_local_state)
+man_conf.set_quoted('runstatedir', dir_run_state)
+man_conf.set_quoted('sysconfdir', dir_sysconf)
+
+foreach man_src : man_pages
+ man = configure_file(
+ input: man_src,
+ output: '@BASENAME@',
+ configuration: man_conf,
+ )
+ install_man(man)
+endforeach
+
+prog_makeinfo = find_program('makeinfo', required: opt_docs)
+if not prog_makeinfo.found()
+ subdir_done()
+endif
+
+foreach inc : info_includes
+ configure_file(
+ input: inc,
+ output: '@BASENAME@',
+ configuration: man_conf,
+ )
+endforeach
+
+info_cmd = [
+ prog_makeinfo,
+ '-P', '@BUILD_ROOT@/doc',
+ '@INPUT@',
+ '--output', '@OUTPUT@',
+]
+
+foreach page : info_pages
+ custom_target(
+ 'info-page-' + page,
+ input: page,
+ output: '@BASENAME@.info',
+ command: info_cmd,
+ install: true,
+ install_dir: dir_info,
+ )
+endforeach
+
--- /dev/null
+project('tinc', 'c',
+ version: run_command('./src/git_tag.sh', check: true).stdout().strip(),
+ license: 'GPL-2.0-or-later',
+ meson_version: '>=0.51',
+ default_options: [
+ 'c_std=c11',
+ 'warning_level=3',
+ 'buildtype=debugoptimized',
+ ],
+)
+
+dir_run_state = get_option('runstatedir')
+opt_crypto = get_option('crypto')
+opt_curses = get_option('curses')
+opt_docs = get_option('docs')
+opt_harden = get_option('hardening')
+opt_jumbograms = get_option('jumbograms')
+opt_lz4 = get_option('lz4')
+opt_lzo = get_option('lzo')
+opt_miniupnpc = get_option('miniupnpc')
+opt_readline = get_option('readline')
+opt_static = get_option('static')
+opt_systemd = get_option('systemd')
+opt_tests = get_option('tests')
+opt_tunemu = get_option('tunemu')
+opt_uml = get_option('uml')
+opt_vde = get_option('vde')
+opt_zlib = get_option('zlib')
+
+meson_version = meson.version()
+
+cc = meson.get_compiler('c')
+os_name = host_machine.system()
+cc_name = cc.get_id()
+
+cc_defs = ['-D_GNU_SOURCE']
+cc_flags = [cc_defs]
+ld_flags = []
+
+if opt_static.auto()
+ static = os_name == 'windows'
+else
+ static = opt_static.enabled()
+endif
+
+if static
+ ld_flags += '-static'
+endif
+
+if opt_harden
+ cc_flags += [
+ '-D_FORTIFY_SOURCE=2',
+ '-fwrapv',
+ '-fno-strict-overflow',
+ '-Wreturn-type',
+ '-Wold-style-definition',
+ '-Wmissing-declarations',
+ '-Wmissing-prototypes',
+ '-Wstrict-prototypes',
+ '-Wredundant-decls',
+ '-Wbad-function-cast',
+ '-Wwrite-strings',
+ '-fdiagnostics-show-option',
+ '-fstrict-aliasing',
+ '-Wmissing-noreturn',
+ ]
+ if cc_name == 'clang'
+ cc_flags += '-Qunused-arguments'
+ endif
+ ld_flags += ['-Wl,-z,relro', '-Wl,-z,now']
+ if os_name == 'windows'
+ ld_flags += ['-Wl,--dynamicbase', '-Wl,--nxcompat']
+ endif
+endif
+
+cc_flags = cc.get_supported_arguments(cc_flags)
+ld_flags = cc.get_supported_link_arguments(ld_flags)
+
+add_project_arguments(cc_flags, language: 'c')
+add_project_link_arguments(ld_flags, language: 'c')
+
+build_root = meson.current_build_dir()
+src_root = meson.current_source_dir()
+
+prefix = get_option('prefix')
+dir_bin = prefix / get_option('bindir')
+dir_data = prefix / get_option('datadir')
+dir_info = prefix / get_option('infodir')
+dir_lib = prefix / get_option('libdir')
+dir_local_state = prefix / get_option('localstatedir')
+dir_locale = prefix / get_option('localedir')
+dir_man = prefix / get_option('mandir')
+dir_sbin = prefix / get_option('sbindir')
+dir_sysconf = prefix / get_option('sysconfdir')
+
+if dir_run_state == ''
+ dir_run_state = dir_local_state / 'run'
+endif
+
+if not opt_docs.disabled()
+ subdir('doc')
+endif
+
+subdir('src')
+
+if not opt_tests.disabled()
+ subdir('test')
+endif
+
+subdir('bash_completion.d')
+
+if os_name == 'linux' and not opt_systemd.disabled()
+ subdir('systemd')
+endif
+
+prog_reformat = find_program('astyle', native: true, required: false)
+if prog_reformat.found()
+ run_target('reformat', command: [
+ prog_reformat,
+ '--options=@SOURCE_ROOT@/.astylerc',
+ '@SOURCE_ROOT@/*.c', '@SOURCE_ROOT@/*.h',
+ ])
+endif
+
--- /dev/null
+option('runstatedir',
+ type: 'string',
+ value: '',
+ description: 'state directory for sockets, PID files')
+
+option('docs',
+ type: 'feature',
+ value: 'auto',
+ description: 'generate documentation')
+
+option('tests',
+ type: 'feature',
+ value: 'auto',
+ description: 'enable tests')
+
+option('hardening',
+ type: 'boolean',
+ value: true,
+ description: 'add compiler and linker hardening flags')
+
+option('static',
+ type: 'feature',
+ value: 'auto',
+ description: 'statically link dependencies (auto: YES on Windows, NO everywhere else)')
+
+option('systemd',
+ type: 'feature',
+ value: 'auto',
+ description: 'install systemd service files')
+
+option('systemd_dir',
+ type: 'string',
+ value: '',
+ description: 'systemd service directory (defaults to $prefix/lib/systemd/system)')
+
+option('crypto',
+ type: 'combo',
+ choices: ['openssl', 'gcrypt', 'nolegacy'],
+ value: 'openssl',
+ description: 'which cryptographic library to use')
+
+option('miniupnpc',
+ type: 'feature',
+ value: 'disabled',
+ description: 'miniupnpc support')
+
+option('lzo',
+ type: 'feature',
+ value: 'auto',
+ description: 'lzo compression support')
+
+option('lz4',
+ type: 'feature',
+ value: 'auto',
+ description: 'lz4 compression support')
+
+option('curses',
+ type: 'feature',
+ value: 'auto',
+ description: 'curses support')
+
+option('readline',
+ type: 'feature',
+ value: 'auto',
+ description: 'readline support')
+
+option('zlib',
+ type: 'feature',
+ value: 'auto',
+ description: 'zlib compression support')
+
+option('uml',
+ type: 'boolean',
+ value: false,
+ description: 'User Mode Linux support')
+
+option('tunemu',
+ type: 'feature',
+ value: 'auto',
+ description: 'support for the tunemu driver')
+
+option('vde',
+ type: 'feature',
+ value: 'auto',
+ description: 'support for Virtual Distributed Ethernet')
+
+option('jumbograms',
+ type: 'boolean',
+ value: false,
+ description: 'support for jumbograms (packets up to 9000 bytes)')
+
--- /dev/null
+check_headers += [
+ 'net/if_tap.h',
+ 'net/if_tun.h',
+ 'net/if_utun.h',
+ 'net/tap/if_tap.h',
+ 'net/tun/if_tun.h',
+]
+
+check_functions += [
+ 'devname',
+ 'fdevname',
+]
+
+src_tincd += files('device.c')
+
+if os_name == 'darwin'
+ dep_tunemu = dependency('tunemu', required: opt_tunemu, static: static)
+ dep_pcap = dependency('pcap', required: opt_tunemu, static: static)
+
+ if dep_tunemu.found() and dep_pcap.found()
+ deps_tincd += [dep_tunemu, dep_pcap]
+ src_tincd += files('tunemu.c')
+ cdata.set('ENABLE_TUNEMU', 1)
+ endif
+endif
+
--- /dev/null
+src_chacha_poly = files(
+ 'chacha-poly1305.c',
+ 'chacha.c',
+ 'poly1305.c',
+)
+
+lib_chacha_poly = static_library(
+ 'chacha_poly',
+ sources: src_chacha_poly,
+ implicit_include_directories: false,
+ include_directories: inc_conf,
+ build_by_default: false,
+)
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "system.h"
-
#ifndef HAVE_DAEMON
extern int daemon(int, int);
#endif
--- /dev/null
+src_ed25519 = files(
+ 'ecdh.c',
+ 'ecdsa.c',
+ 'ecdsagen.c',
+ 'fe.c',
+ 'ge.c',
+ 'key_exchange.c',
+ 'keypair.c',
+ 'sc.c',
+ 'sha512.c',
+ 'sign.c',
+ 'verify.c',
+)
+
+lib_ed25519 = static_library(
+ 'ed25519',
+ sources: src_ed25519,
+ implicit_include_directories: false,
+ include_directories: inc_conf,
+ build_by_default: false,
+)
+
#include <sys/epoll.h>
#endif
-#include "dropin.h"
#include "event.h"
#include "utils.h"
#include "net.h"
--- /dev/null
+src_lib_crypto = files(
+ 'cipher.c',
+ 'crypto.c',
+ 'digest.c',
+ 'pem.c',
+ 'prf.c',
+ 'rsa.c',
+ 'rsagen.c',
+)
+
+# Under current MinGW, flags specified in libgcrypt.pc fail on static build
+if static and os_name == 'windows'
+ dep_crypto = []
+ foreach lib : ['libgcrypt', 'gpg-error']
+ dep_crypto += cc.find_library(lib, static: true)
+ endforeach
+else
+ dep_crypto = dependency('libgcrypt', static: static)
+endif
+
+cdata.set('HAVE_LIBGCRYPT', 1)
+
#define _NO_PROTO
#endif
-#ifdef HAVE_CONFIG_H
-#include "../config.h"
-#endif
+#include "config.h"
#if !defined (__STDC__) || !__STDC__
/* This is a separate conditional since some stdc systems
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
\f
-#ifdef HAVE_CONFIG_H
-#include "../config.h"
-#endif
+#include "config.h"
#include "getopt.h"
--- /dev/null
+#!/bin/sh
+
+git describe --always --tags --match='release-*' "$@" | sed 's/release-//'
--- /dev/null
+configure_file(output: 'config.h', configuration: cdata)
+
+src_lib_tinc += vcs_tag(
+ command: './git_tag.sh',
+ fallback: 'unknown',
+ input: '../version_git.h.in',
+ output: 'version_git.h',
+)
+
--- /dev/null
+check_headers += [
+ 'linux/if_tun.h',
+ 'sys/epoll.h',
+ 'netpacket/packet.h',
+]
+
+check_functions += 'recvmmsg'
+
+src_tincd += files('device.c')
+
+if opt_uml
+ src_tincd += files('uml_device.c')
+ cdata.set('ENABLE_UML', 1)
+endif
+
--- /dev/null
+/*
+ device.c -- UML network socket
+ Copyright (C) 2002-2005 Ivo Timmermans,
+ 2002-2022 Guus Sliepen <guus@tinc-vpn.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "../system.h"
+
+#include <sys/un.h>
+
+#include "../conf.h"
+#include "../device.h"
+#include "../names.h"
+#include "../net.h"
+#include "../logger.h"
+#include "../utils.h"
+#include "../route.h"
+#include "../xalloc.h"
+
+static int listen_fd = -1;
+static int request_fd = -1;
+static int data_fd = -1;
+static int write_fd = -1;
+static int state = 0;
+static const char *device_info = "UML network socket";
+
+enum request_type { REQ_NEW_CONTROL };
+
+static struct request {
+ uint32_t magic;
+ uint32_t version;
+ enum request_type type;
+ struct sockaddr_un sock;
+} request;
+
+static struct sockaddr_un data_sun = {
+ .sun_family = AF_UNIX,
+};
+
+static bool setup_device(void) {
+ struct sockaddr_un listen_sun = {
+ .sun_family = AF_UNIX,
+ };
+ static const int one = 1;
+ struct {
+ char zero;
+ int pid;
+ int usecs;
+ } name;
+ struct timeval tv;
+
+ if(!get_config_string(lookup_config(&config_tree, "Device"), &device)) {
+ xasprintf(&device, RUNSTATEDIR "/%s.umlsocket", identname);
+ }
+
+ get_config_string(lookup_config(&config_tree, "Interface"), &iface);
+
+ if((write_fd = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Could not open write %s: %s", device_info, strerror(errno));
+ event_exit();
+ return false;
+ }
+
+#ifdef FD_CLOEXEC
+ fcntl(write_fd, F_SETFD, FD_CLOEXEC);
+#endif
+
+ setsockopt(write_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+
+ if(fcntl(write_fd, F_SETFL, O_NONBLOCK) < 0) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno));
+ event_exit();
+ return false;
+ }
+
+ if((data_fd = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Could not open data %s: %s", device_info, strerror(errno));
+ event_exit();
+ return false;
+ }
+
+#ifdef FD_CLOEXEC
+ fcntl(data_fd, F_SETFD, FD_CLOEXEC);
+#endif
+
+ setsockopt(data_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+
+ if(fcntl(data_fd, F_SETFL, O_NONBLOCK) < 0) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno));
+ event_exit();
+ return false;
+ }
+
+ name.zero = 0;
+ name.pid = getpid();
+ gettimeofday(&tv, NULL);
+ name.usecs = (int) tv.tv_usec;
+ memcpy(&data_sun.sun_path, &name, sizeof(name));
+
+ if(bind(data_fd, (struct sockaddr *)&data_sun, sizeof(data_sun)) < 0) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Could not bind data %s: %s", device_info, strerror(errno));
+ event_exit();
+ return false;
+ }
+
+ if((listen_fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s: %s", device_info,
+ strerror(errno));
+ return false;
+ }
+
+#ifdef FD_CLOEXEC
+ fcntl(device_fd, F_SETFD, FD_CLOEXEC);
+#endif
+
+ setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+
+ if(fcntl(listen_fd, F_SETFL, O_NONBLOCK) < 0) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno));
+ return false;
+ }
+
+ if(strlen(device) >= sizeof(listen_sun.sun_path)) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "UML socket filename %s is too long!", device);
+ return false;
+ }
+
+ strncpy(listen_sun.sun_path, device, sizeof(listen_sun.sun_path));
+
+ if(bind(listen_fd, (struct sockaddr *)&listen_sun, sizeof(listen_sun)) < 0) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Could not bind %s to %s: %s", device_info, device, strerror(errno));
+ return false;
+ }
+
+ if(listen(listen_fd, 1) < 0) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Could not listen on %s %s: %s", device_info, device, strerror(errno));
+ return false;
+ }
+
+ device_fd = listen_fd;
+ state = 0;
+
+ logger(DEBUG_ALWAYS, LOG_INFO, "%s is a %s", device, device_info);
+
+ if(routing_mode == RMODE_ROUTER) {
+ overwrite_mac = true;
+ }
+
+ return true;
+}
+
+static void close_device(void) {
+ if(listen_fd >= 0) {
+ close(listen_fd);
+ listen_fd = -1;
+ }
+
+ if(request_fd >= 0) {
+ close(request_fd);
+ request_fd = -1;
+ }
+
+ if(data_fd >= 0) {
+ close(data_fd);
+ data_fd = -1;
+ }
+
+ if(write_fd >= 0) {
+ close(write_fd);
+ write_fd = -1;
+ }
+
+ unlink(device);
+
+ free(device);
+ device = NULL;
+
+ free(iface);
+ iface = NULL;
+
+ device_info = NULL;
+}
+
+static bool read_packet(vpn_packet_t *packet) {
+ ssize_t inlen;
+
+ switch(state) {
+ case 0: {
+ struct sockaddr sa;
+ socklen_t salen = sizeof(sa);
+
+ request_fd = accept(listen_fd, &sa, &salen);
+
+ if(request_fd < 0) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Could not accept connection to %s %s: %s", device_info, device, strerror(errno));
+ return false;
+ }
+
+#ifdef FD_CLOEXEC
+ fcntl(request_fd, F_SETFD, FD_CLOEXEC);
+#endif
+
+ if(fcntl(listen_fd, F_SETFL, O_NONBLOCK) < 0) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno));
+ event_exit();
+ return false;
+ }
+
+ close(listen_fd);
+ listen_fd = -1;
+ device_fd = request_fd;
+ state = 1;
+
+ return false;
+ }
+
+ case 1: {
+ if((inlen = read(request_fd, &request, sizeof(request))) != sizeof(request)) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading request from %s %s: %s", device_info,
+ device, strerror(errno));
+ event_exit();
+ return false;
+ }
+
+ if(request.magic != 0xfeedface || request.version != 3 || request.type != REQ_NEW_CONTROL) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Unknown magic %x, version %d, request type %d from %s %s",
+ request.magic, request.version, request.type, device_info, device);
+ event_exit();
+ return false;
+ }
+
+ if(connect(write_fd, (const struct sockaddr *)&request.sock, sizeof(request.sock)) < 0) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Could not bind write %s: %s", device_info, strerror(errno));
+ event_exit();
+ return false;
+ }
+
+ if(write(request_fd, &data_sun, sizeof(data_sun)) != sizeof(data_sun)) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Error while responding to request from %s %s: %s", device_info, device, strerror(errno));
+ event_exit();
+ return false;
+ }
+
+ device_fd = data_fd;
+
+ logger(DEBUG_ALWAYS, LOG_INFO, "Connection with UML established");
+
+ state = 2;
+ return false;
+ }
+
+ case 2: {
+ if((inlen = read(data_fd, DATA(packet), MTU)) <= 0) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info,
+ device, strerror(errno));
+ event_exit();
+ return false;
+ }
+
+ packet->len = inlen;
+
+ logger(DEBUG_TRAFFIC, LOG_DEBUG, "Read packet of %d bytes from %s", packet->len,
+ device_info);
+
+ return true;
+ }
+
+ default:
+ logger(DEBUG_ALWAYS, LOG_ERR, "Invalid value for state variable in " __FILE__);
+ abort();
+ }
+}
+
+static bool write_packet(vpn_packet_t *packet) {
+ if(state != 2) {
+ logger(DEBUG_TRAFFIC, LOG_DEBUG, "Dropping packet of %d bytes to %s: not connected to UML yet",
+ packet->len, device_info);
+ return false;
+ }
+
+ logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s",
+ packet->len, device_info);
+
+ if(write(write_fd, DATA(packet), packet->len) < 0) {
+ if(errno != EINTR && errno != EAGAIN) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno));
+ event_exit();
+ }
+
+ return false;
+ }
+
+ return true;
+}
+
+const devops_t uml_devops = {
+ .setup = setup_device,
+ .close = close_device,
+ .read = read_packet,
+ .write = write_packet,
+};
--- /dev/null
+inc_conf = include_directories('include')
+
+cdata = configuration_data()
+
+cdata.set_quoted('PACKAGE', meson.project_name())
+cdata.set_quoted('VERSION', meson.project_version())
+cdata.set_quoted('CONFDIR', dir_sysconf)
+cdata.set_quoted('RUNSTATEDIR', dir_run_state)
+cdata.set_quoted('LOCALSTATEDIR', dir_local_state)
+cdata.set_quoted('SBINDIR', dir_sbin)
+
+cdata.set('HAVE_' + os_name.to_upper(), 1)
+
+foreach attr : ['malloc', 'nonnull', 'warn_unused_result']
+ cc.has_function_attribute(attr)
+endforeach
+
+check_headers = [
+ 'arpa/inet.h',
+ 'arpa/nameser.h',
+ 'dirent.h',
+ 'getopt.h',
+ 'inttypes.h',
+ 'net/ethernet.h',
+ 'net/if.h',
+ 'net/if_arp.h',
+ 'net/if_types.h',
+ 'netdb.h',
+ 'netinet/icmp6.h',
+ 'netinet/if_ether.h',
+ 'netinet/in.h',
+ 'netinet/in6.h',
+ 'netinet/in_systm.h',
+ 'netinet/ip.h',
+ 'netinet/ip6.h',
+ 'netinet/ip_icmp.h',
+ 'netinet/tcp.h',
+ 'resolv.h',
+ 'stddef.h',
+ 'sys/file.h',
+ 'sys/ioctl.h',
+ 'sys/mman.h',
+ 'sys/param.h',
+ 'sys/resource.h',
+ 'sys/socket.h',
+ 'sys/stat.h',
+ 'sys/time.h',
+ 'sys/types.h',
+ 'sys/un.h',
+ 'sys/wait.h',
+ 'syslog.h',
+ 'termios.h',
+]
+
+check_functions = [
+ 'asprintf',
+ 'daemon',
+ 'fchmod',
+ 'fork',
+ 'gettimeofday',
+ 'mlockall',
+ 'putenv',
+ 'strsignal',
+ 'unsetenv',
+]
+
+check_types = [
+ 'struct arphdr',
+ 'struct ether_arp',
+ 'struct ether_header',
+ 'struct icmp',
+ 'struct icmp6_hdr',
+ 'struct ip',
+ 'struct ip6_hdr',
+ 'struct nd_neighbor_solicit',
+ 'struct nd_opt_hdr',
+]
+
+subdir('ed25519')
+subdir('chacha-poly1305')
+
+src_lib_tinc = [
+ 'conf.c',
+ 'dropin.c',
+ 'keys.c',
+ 'list.c',
+ 'names.c',
+ 'netutl.c',
+ 'script.c',
+ 'splay_tree.c',
+ 'sptps.c',
+ 'subnet_parse.c',
+ 'utils.c',
+ 'version.c',
+ 'xoshiro.c',
+ 'logger.c',
+]
+
+src_tinc = [
+ 'fsck.c',
+ 'ifconfig.c',
+ 'info.c',
+ 'invitation.c',
+ 'tincctl.c',
+ 'top.c',
+]
+
+src_tincd = [
+ 'address_cache.c',
+ 'autoconnect.c',
+ 'buffer.c',
+ 'compression.h',
+ 'conf_net.c',
+ 'connection.c',
+ 'control.c',
+ 'dummy_device.c',
+ 'edge.c',
+ 'event.c',
+ 'fd_device.c',
+ 'graph.c',
+ 'meta.c',
+ 'multicast_device.c',
+ 'net.c',
+ 'net_packet.c',
+ 'net_setup.c',
+ 'net_socket.c',
+ 'node.c',
+ 'process.c',
+ 'protocol.c',
+ 'protocol_auth.c',
+ 'protocol_edge.c',
+ 'protocol_key.c',
+ 'protocol_misc.c',
+ 'protocol_subnet.c',
+ 'raw_socket_device.c',
+ 'route.c',
+ 'subnet.c',
+ 'tincd.c',
+]
+
+cc_flags_tincd = cc_flags
+
+deps_common = []
+deps_tinc = []
+deps_tincd = [cc.find_library('m', required: false)]
+
+if os_name in ['linux', 'android']
+ subdir('linux')
+elif os_name.endswith('bsd') or os_name in ['dragonfly', 'darwin']
+ subdir('bsd')
+elif os_name == 'sunos'
+ subdir('solaris')
+elif os_name == 'windows'
+ subdir('mingw')
+endif
+
+foreach h : check_headers
+ if cc.has_header(h)
+ cdata.set('HAVE_' + h.to_upper().underscorify(),
+ 1,
+ description: '#include <' + h + '>')
+ endif
+endforeach
+
+confdata = configuration_data()
+confdata.merge_from(cdata)
+configure_file(output: 'meson_config.h', configuration: confdata)
+
+have_prefix = '''
+ #include "@0@/src/meson_config.h"
+ #include "@1@/have.h"
+'''.format(build_root, meson.current_source_dir())
+
+foreach f : check_functions
+ if f == 'fork' and os_name == 'windows'
+ message('MinGW does not have correct definition for fork()')
+ else
+ if cc.has_function(f, prefix: have_prefix, args: cc_defs)
+ cdata.set('HAVE_' + f.to_upper(),
+ 1,
+ description: 'function ' + f)
+ endif
+ endif
+endforeach
+
+if cc.has_function('res_init', prefix: '''
+ #include <netinet/in.h>
+ #include <resolv.h>
+''', args: cc_defs)
+ cdata.set('HAVE_DECL_RES_INIT', 1)
+endif
+
+foreach type : check_types
+ if cc.has_type(type, prefix: have_prefix, args: cc_defs)
+ name = 'HAVE_' + type.to_upper().underscorify()
+ cdata.set(name, 1, description: type)
+ endif
+endforeach
+
+if not cdata.has('HAVE_GETOPT_H') or not cc.has_function('getopt_long', prefix: have_prefix, args: cc_defs)
+ src_lib_tinc += ['getopt.c', 'getopt1.c']
+endif
+
+if not opt_miniupnpc.disabled()
+ dep_miniupnpc = dependency('miniupnpc', required: false, static: static)
+ if not dep_miniupnpc.found()
+ # No pkg-config files on MinGW
+ dep_miniupnpc = cc.find_library('miniupnpc', required: opt_miniupnpc, static: static)
+ endif
+ if dep_miniupnpc.found()
+ src_tincd += 'upnp.c'
+ deps_tincd += [
+ dependency('threads', static: static),
+ dep_miniupnpc,
+ ]
+ if static
+ cc_flags_tincd += '-DMINIUPNP_STATICLIB'
+ endif
+ cdata.set('HAVE_MINIUPNPC', 1)
+ endif
+endif
+
+if opt_curses.auto() and os_name == 'windows'
+ message('curses does not link under MinGW')
+else
+ # The meta-dependency covers more alternatives, but is only available in 0.54+
+ curses_name = meson_version.version_compare('>=0.54') ? 'curses' : 'ncurses'
+ dep_curses = dependency(curses_name, required: opt_curses, static: static)
+ if dep_curses.found()
+ cdata.set('HAVE_CURSES', 1)
+ deps_tinc += dep_curses
+ endif
+endif
+
+# Some distributions do not supply pkg-config files for readline
+if opt_readline.auto() and os_name == 'windows'
+ message('readline does not link under MinGW')
+else
+ dep_readline = dependency('readline', required: opt_readline, static: static)
+ if not dep_readline.found()
+ dep_readline = cc.find_library('readline', required: opt_readline, static: static)
+ endif
+ if dep_readline.found() and \
+ cc.has_header('readline/readline.h', dependencies: dep_readline) and \
+ cc.has_header('readline/history.h', dependencies: dep_readline)
+ cdata.set('HAVE_READLINE', 1)
+ deps_tinc += dep_readline
+ endif
+endif
+
+dep_zlib = dependency('zlib',
+ required: opt_zlib,
+ static: static,
+ fallback: ['zlib', 'zlib_dep'])
+if dep_zlib.found()
+ cdata.set('HAVE_ZLIB', 1)
+ deps_tincd += dep_zlib
+endif
+
+if not opt_lzo.disabled()
+ dep_lzo = dependency('lzo2', required: false, static: static)
+ if not dep_lzo.found()
+ dep_lzo = cc.find_library('lzo2', required: opt_lzo, static: static)
+ endif
+ if not dep_lzo.found()
+ dep_lzo = dependency('lzo2',
+ required: false,
+ static: static,
+ fallback: ['lzo2', 'lzo2_dep'])
+ endif
+ if dep_lzo.found()
+ if dep_lzo.type_name() == 'internal' or cc.has_header('lzo/lzo1x.h', dependencies: dep_lzo)
+ cdata.set('LZO1X_H', '<lzo/lzo1x.h>')
+ elif cc.has_header('lzo1x.h', dependencies: dep_lzo)
+ cdata.set('LZO1X_H', '<lzo1x.h>')
+ else
+ msg = 'lzo1x.h not found'
+ if opt_lzo.auto()
+ warning(msg)
+ else
+ error(msg)
+ endif
+ endif
+ if cdata.has('LZO1X_H')
+ cdata.set('HAVE_LZO', 1)
+ deps_tincd += dep_lzo
+ endif
+ endif
+endif
+
+dep_lz4 = dependency('liblz4',
+ required: opt_lz4,
+ static: static,
+ fallback: ['lz4', 'liblz4_dep'])
+if dep_lz4.found()
+ deps_tincd += dep_lz4
+ cdata.set('HAVE_LZ4', 1)
+endif
+
+dep_vde = dependency('vdeplug', required: opt_vde, static: static)
+dep_dl = cc.find_library('dl', required: opt_vde)
+if dep_vde.found() and dep_dl.found()
+ cdata.set('ENABLE_VDE', 1)
+ src_tincd += 'vde_device.c'
+ deps_tincd += [dep_dl, dep_vde]
+endif
+
+if opt_jumbograms
+ cdata.set('ENABLE_JUMBOGRAMS', 1)
+endif
+
+subdir(opt_crypto)
+
+if opt_crypto != 'nolegacy'
+ src_lib_crypto += ['cipher.c', 'digest.c']
+endif
+
+subdir('include')
+
+lib_crypto = static_library(
+ 'tinc_crypto',
+ sources: src_lib_crypto,
+ dependencies: dep_crypto,
+ implicit_include_directories: false,
+ include_directories: inc_conf,
+ build_by_default: false,
+)
+
+deps_lib_tinc = [deps_common, dep_crypto]
+
+lib_tinc = static_library(
+ 'tinc',
+ sources: src_lib_tinc,
+ dependencies: deps_lib_tinc,
+ link_with: [lib_ed25519, lib_chacha_poly, lib_crypto],
+ implicit_include_directories: false,
+ include_directories: inc_conf,
+ build_by_default: false,
+)
+
+exe_tinc = executable(
+ 'tinc',
+ sources: src_tinc,
+ dependencies: [deps_lib_tinc, deps_tinc],
+ link_with: lib_tinc,
+ implicit_include_directories: false,
+ include_directories: inc_conf,
+ install: true,
+ install_dir: dir_sbin,
+)
+
+exe_tincd = executable(
+ 'tincd',
+ sources: src_tincd,
+ dependencies: [deps_lib_tinc, deps_tincd],
+ link_with: lib_tinc,
+ c_args: cc_flags_tincd,
+ implicit_include_directories: false,
+ include_directories: inc_conf,
+ install: true,
+ install_dir: dir_sbin,
+)
+
+exe_sptps_test = executable(
+ 'sptps_test',
+ sources: 'sptps_test.c',
+ dependencies: deps_lib_tinc,
+ link_with: lib_tinc,
+ implicit_include_directories: false,
+ include_directories: inc_conf,
+ build_by_default: false,
+)
+
+exe_sptps_keypair = executable(
+ 'sptps_keypair',
+ sources: 'sptps_keypair.c',
+ dependencies: deps_lib_tinc,
+ link_with: lib_tinc,
+ implicit_include_directories: false,
+ include_directories: inc_conf,
+ build_by_default: false,
+)
+
+if os_name == 'linux'
+ dep_rt = cc.find_library('rt')
+
+ exe_sptps_speed = executable(
+ 'sptps_speed',
+ sources: 'sptps_speed.c',
+ dependencies: [deps_lib_tinc, dep_rt],
+ link_with: lib_tinc,
+ implicit_include_directories: false,
+ include_directories: inc_conf,
+ build_by_default: false,
+ )
+
+ benchmark('sptps_speed', exe_sptps_speed, timeout: 90)
+endif
+
--- /dev/null
+win_common_libs = ['ws2_32', 'iphlpapi', 'winpthread']
+
+if opt_harden
+ win_common_libs += 'ssp'
+endif
+
+foreach libname : win_common_libs
+ deps_common += cc.find_library(libname)
+endforeach
+
+src_tincd += files('device.c')
+
+cdata.set('HAVE_MINGW', 1)
+
#ifdef HAVE_ZLIB
#define ZLIB_CONST
#include <zlib.h>
-#include <assert.h>
#endif
#include LZO1X_H
#endif
-#ifdef LZ4_H
-#include LZ4_H
+#ifdef HAVE_LZ4
+#include <lz4.h>
#endif
#include "address_cache.h"
static char lzo_wrkmem[LZO1X_999_MEM_COMPRESS > LZO1X_1_MEM_COMPRESS ? LZO1X_999_MEM_COMPRESS : LZO1X_1_MEM_COMPRESS];
#endif
+#ifdef HAVE_LZ4
+
#ifdef HAVE_LZ4_BUILTIN
static LZ4_stream_t lz4_stream;
#else
static void *lz4_state = NULL;
-#endif /* HAVE_LZ4_BUILTIN */
+#endif // HAVE_LZ4_BUILTIN
+
+#endif // HAVE_LZ4
static void send_udppacket(node_t *, vpn_packet_t *);
#ifdef HAVE_LZO
static length_t compress_packet_lzo(uint8_t *dest, const uint8_t *source, length_t len, compression_level_t level) {
- assert(level == COMPRESS_LZO_LO || level == COMPRESS_LZO_HI);
-
lzo_uint lzolen = MAXSIZE;
int result;
--- /dev/null
+src_lib_crypto = files(
+ 'crypto.c',
+ 'prf.c',
+)
+
+dep_crypto = dependency('', required: false)
+
+cdata.set('DISABLE_LEGACY', 1)
+
--- /dev/null
+src_lib_crypto = files(
+ 'cipher.c',
+ 'crypto.c',
+ 'digest.c',
+ 'log.c',
+ 'prf.c',
+ 'rsa.c',
+ 'rsagen.c',
+)
+
+# OpenBSD's 'OpenSSL' is actually LibreSSL. pkg-config on OpenBSD 7.0 reports
+# it as OpenSSL 1.0, but it has everything we need (unlike 'real' OpenSSL 1.0).
+
+if os_name == 'openbsd'
+ names = ['openssl', 'eopenssl30', 'eopenssl11']
+ min_ver = '>=1.0.0'
+else
+ names = ['openssl', 'openssl11']
+ min_ver = '>=1.1.0'
+endif
+
+if meson_version.version_compare('>=0.60')
+ dep_crypto = dependency(names, version: min_ver, static: static)
+else
+ foreach name : names
+ dep_crypto = dependency(name, version: min_ver, static: static, required: false)
+ if dep_crypto.found()
+ break
+ endif
+ endforeach
+ if not dep_crypto.found()
+ dep_crypto = dependency('', static: static, fallback: ['openssl', 'openssl_dep'])
+ endif
+endif
+
+cdata.set('HAVE_OPENSSL', 1)
+
--- /dev/null
+src_tincd += files('device.c')
+
#include "crypto.h"
#include "ecdsagen.h"
#include "logger.h"
-
-static char *program_name;
+#include "names.h"
void logger(debug_t level, int priority, const char *format, ...) {
(void)level;
(void)len;
return false;
}
-char *logfilename = NULL;
bool do_detach = false;
struct timeval now;
#include "protocol.h"
#include "sptps.h"
#include "utils.h"
+#include "names.h"
#ifndef HAVE_MINGW
#define closesocket(s) close(s)
return false;
}
-char *logfilename = NULL;
bool do_detach = false;
struct timeval now;
{NULL, 0, NULL, 0}
};
-const char *program_name;
-
static void usage(void) {
static const char *message =
"Usage: %s [options] my_ed25519_key_file his_ed25519_key_file [host] port\n"
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../config.h"
+#include "config.h"
#include "have.h"
#include LZO1X_H
#endif
-#ifdef LZ4_H
-#include LZ4_H
+#ifdef HAVE_LZ4
+#include <lz4.h>
#endif
#ifndef HAVE_MINGW
+++ /dev/null
-/*
- device.c -- UML network socket
- Copyright (C) 2002-2005 Ivo Timmermans,
- 2002-2022 Guus Sliepen <guus@tinc-vpn.org>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 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 General Public License for more details.
-
- You should have received a copy of the GNU General Public License along
- with this program; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "system.h"
-
-#include <sys/un.h>
-
-#include "conf.h"
-#include "device.h"
-#include "names.h"
-#include "net.h"
-#include "logger.h"
-#include "utils.h"
-#include "route.h"
-#include "xalloc.h"
-
-static int listen_fd = -1;
-static int request_fd = -1;
-static int data_fd = -1;
-static int write_fd = -1;
-static int state = 0;
-static const char *device_info = "UML network socket";
-
-enum request_type { REQ_NEW_CONTROL };
-
-static struct request {
- uint32_t magic;
- uint32_t version;
- enum request_type type;
- struct sockaddr_un sock;
-} request;
-
-static struct sockaddr_un data_sun = {
- .sun_family = AF_UNIX,
-};
-
-static bool setup_device(void) {
- struct sockaddr_un listen_sun = {
- .sun_family = AF_UNIX,
- };
- static const int one = 1;
- struct {
- char zero;
- int pid;
- int usecs;
- } name;
- struct timeval tv;
-
- if(!get_config_string(lookup_config(&config_tree, "Device"), &device)) {
- xasprintf(&device, RUNSTATEDIR "/%s.umlsocket", identname);
- }
-
- get_config_string(lookup_config(&config_tree, "Interface"), &iface);
-
- if((write_fd = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Could not open write %s: %s", device_info, strerror(errno));
- event_exit();
- return false;
- }
-
-#ifdef FD_CLOEXEC
- fcntl(write_fd, F_SETFD, FD_CLOEXEC);
-#endif
-
- setsockopt(write_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
-
- if(fcntl(write_fd, F_SETFL, O_NONBLOCK) < 0) {
- logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno));
- event_exit();
- return false;
- }
-
- if((data_fd = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Could not open data %s: %s", device_info, strerror(errno));
- event_exit();
- return false;
- }
-
-#ifdef FD_CLOEXEC
- fcntl(data_fd, F_SETFD, FD_CLOEXEC);
-#endif
-
- setsockopt(data_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
-
- if(fcntl(data_fd, F_SETFL, O_NONBLOCK) < 0) {
- logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno));
- event_exit();
- return false;
- }
-
- name.zero = 0;
- name.pid = getpid();
- gettimeofday(&tv, NULL);
- name.usecs = (int) tv.tv_usec;
- memcpy(&data_sun.sun_path, &name, sizeof(name));
-
- if(bind(data_fd, (struct sockaddr *)&data_sun, sizeof(data_sun)) < 0) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Could not bind data %s: %s", device_info, strerror(errno));
- event_exit();
- return false;
- }
-
- if((listen_fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s: %s", device_info,
- strerror(errno));
- return false;
- }
-
-#ifdef FD_CLOEXEC
- fcntl(device_fd, F_SETFD, FD_CLOEXEC);
-#endif
-
- setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
-
- if(fcntl(listen_fd, F_SETFL, O_NONBLOCK) < 0) {
- logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno));
- return false;
- }
-
- if(strlen(device) >= sizeof(listen_sun.sun_path)) {
- logger(DEBUG_ALWAYS, LOG_ERR, "UML socket filename %s is too long!", device);
- return false;
- }
-
- strncpy(listen_sun.sun_path, device, sizeof(listen_sun.sun_path));
-
- if(bind(listen_fd, (struct sockaddr *)&listen_sun, sizeof(listen_sun)) < 0) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Could not bind %s to %s: %s", device_info, device, strerror(errno));
- return false;
- }
-
- if(listen(listen_fd, 1) < 0) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Could not listen on %s %s: %s", device_info, device, strerror(errno));
- return false;
- }
-
- device_fd = listen_fd;
- state = 0;
-
- logger(DEBUG_ALWAYS, LOG_INFO, "%s is a %s", device, device_info);
-
- if(routing_mode == RMODE_ROUTER) {
- overwrite_mac = true;
- }
-
- return true;
-}
-
-static void close_device(void) {
- if(listen_fd >= 0) {
- close(listen_fd);
- listen_fd = -1;
- }
-
- if(request_fd >= 0) {
- close(request_fd);
- request_fd = -1;
- }
-
- if(data_fd >= 0) {
- close(data_fd);
- data_fd = -1;
- }
-
- if(write_fd >= 0) {
- close(write_fd);
- write_fd = -1;
- }
-
- unlink(device);
-
- free(device);
- device = NULL;
-
- free(iface);
- iface = NULL;
-
- device_info = NULL;
-}
-
-static bool read_packet(vpn_packet_t *packet) {
- ssize_t inlen;
-
- switch(state) {
- case 0: {
- struct sockaddr sa;
- socklen_t salen = sizeof(sa);
-
- request_fd = accept(listen_fd, &sa, &salen);
-
- if(request_fd < 0) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Could not accept connection to %s %s: %s", device_info, device, strerror(errno));
- return false;
- }
-
-#ifdef FD_CLOEXEC
- fcntl(request_fd, F_SETFD, FD_CLOEXEC);
-#endif
-
- if(fcntl(listen_fd, F_SETFL, O_NONBLOCK) < 0) {
- logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno));
- event_exit();
- return false;
- }
-
- close(listen_fd);
- listen_fd = -1;
- device_fd = request_fd;
- state = 1;
-
- return false;
- }
-
- case 1: {
- if((inlen = read(request_fd, &request, sizeof(request))) != sizeof(request)) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading request from %s %s: %s", device_info,
- device, strerror(errno));
- event_exit();
- return false;
- }
-
- if(request.magic != 0xfeedface || request.version != 3 || request.type != REQ_NEW_CONTROL) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Unknown magic %x, version %d, request type %d from %s %s",
- request.magic, request.version, request.type, device_info, device);
- event_exit();
- return false;
- }
-
- if(connect(write_fd, (const struct sockaddr *)&request.sock, sizeof(request.sock)) < 0) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Could not bind write %s: %s", device_info, strerror(errno));
- event_exit();
- return false;
- }
-
- if(write(request_fd, &data_sun, sizeof(data_sun)) != sizeof(data_sun)) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Error while responding to request from %s %s: %s", device_info, device, strerror(errno));
- event_exit();
- return false;
- }
-
- device_fd = data_fd;
-
- logger(DEBUG_ALWAYS, LOG_INFO, "Connection with UML established");
-
- state = 2;
- return false;
- }
-
- case 2: {
- if((inlen = read(data_fd, DATA(packet), MTU)) <= 0) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info,
- device, strerror(errno));
- event_exit();
- return false;
- }
-
- packet->len = inlen;
-
- logger(DEBUG_TRAFFIC, LOG_DEBUG, "Read packet of %d bytes from %s", packet->len,
- device_info);
-
- return true;
- }
-
- default:
- logger(DEBUG_ALWAYS, LOG_ERR, "Invalid value for state variable in " __FILE__);
- abort();
- }
-}
-
-static bool write_packet(vpn_packet_t *packet) {
- if(state != 2) {
- logger(DEBUG_TRAFFIC, LOG_DEBUG, "Dropping packet of %d bytes to %s: not connected to UML yet",
- packet->len, device_info);
- return false;
- }
-
- logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s",
- packet->len, device_info);
-
- if(write(write_fd, DATA(packet), packet->len) < 0) {
- if(errno != EINTR && errno != EAGAIN) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno));
- event_exit();
- }
-
- return false;
- }
-
- return true;
-}
-
-const devops_t uml_devops = {
- .setup = setup_device,
- .close = close_device,
- .read = read_packet,
- .write = write_packet,
-};
#include "version.h"
#include "version_git.h"
-#include "../config.h"
/* This file is always rebuilt (even if there are no changes) so that the following is updated */
const char *const BUILD_DATE = __DATE__;
--- /dev/null
+#define GIT_DESCRIPTION "@VCS_TAG@"
+
--- /dev/null
+[wrap-file]
+directory = lz4-1.9.3
+source_url = https://github.com/lz4/lz4/archive/v1.9.3.tar.gz
+source_filename = lz4-1.9.3.tgz
+source_hash = 030644df4611007ff7dc962d981f390361e6c97a34e5cbc393ddfbe019ffe2c1
+patch_url = https://wrapdb.mesonbuild.com/v2/lz4_1.9.3-1/get_patch
+patch_filename = lz4-1.9.3-1-wrap.zip
+patch_hash = 5a0e6e5797a51d23d5dad0009d36dfebe354b5e5eef2a1302d29788b1ee067c1
+
+[provide]
+liblz4 = liblz4_dep
+
--- /dev/null
+[wrap-file]
+directory = lzo-2.10
+source_url = https://www.oberhumer.com/opensource/lzo/download/lzo-2.10.tar.gz
+source_filename = lzo-2.10.tar.gz
+source_hash = c0f892943208266f9b6543b3ae308fab6284c5c90e627931446fb49b4221a072
+patch_filename = lzo2_2.10-1_patch.zip
+patch_url = https://wrapdb.mesonbuild.com/v2/lzo2_2.10-1/get_patch
+patch_hash = 181cf865ea5317b6e8822c71385325328863489a4e5f49177ff67fd13ea1220e
+
+[provide]
+lzo2 = lzo2_dep
+
--- /dev/null
+[wrap-file]
+directory = openssl-3.0.2
+source_url = https://www.openssl.org/source/openssl-3.0.2.tar.gz
+source_filename = openssl-3.0.2.tar.gz
+source_hash = 98e91ccead4d4756ae3c9cde5e09191a8e586d9f4d50838e7ec09d6411dfdb63
+patch_filename = openssl_3.0.2-1_patch.zip
+patch_url = https://wrapdb.mesonbuild.com/v2/openssl_3.0.2-1/get_patch
+patch_hash = 762ab4ea94d02178d6a1d3eb63409c2c4d61315d358391cdac62df15211174d4
+
+[provide]
+libcrypto = libcrypto_dep
+libssl = libssl_dep
+openssl = openssl_dep
+
--- /dev/null
+[wrap-file]
+directory = zlib-1.2.11
+source_url = http://zlib.net/fossils/zlib-1.2.11.tar.gz
+source_filename = zlib-1.2.11.tar.gz
+source_hash = c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1
+patch_filename = zlib_1.2.11-6_patch.zip
+patch_url = https://wrapdb.mesonbuild.com/v2/zlib_1.2.11-6/get_patch
+patch_hash = f7c24c5698ce787294910ad431f94088102d35ddaf88542d04add1e54afa9212
+
+[provide]
+zlib = zlib_dep
+
--- /dev/null
+dep_systemd = dependency('systemd', required: opt_systemd)
+if not dep_systemd.found()
+ subdir_done()
+endif
+
+dir_systemd = get_option('systemd_dir')
+if dir_systemd == ''
+ if meson_version.version_compare('>=0.58')
+ dir_systemd = dep_systemd.get_variable('systemdsystemunitdir', pkgconfig_define: ['prefix', prefix])
+ else
+ dir_systemd = dep_systemd.get_pkgconfig_variable('systemdsystemunitdir', define_variable: ['prefix', prefix])
+ endif
+endif
+
+systemd_conf = configuration_data()
+systemd_conf.set('sysconfdir', dir_sysconf)
+systemd_conf.set('sbindir', dir_sbin)
+
+configure_file(input: 'tinc.service.in',
+ output: 'tinc.service',
+ configuration: systemd_conf,
+ install_dir: dir_systemd)
+
+configure_file(input: 'tinc@.service.in',
+ output: 'tinc@.service',
+ configuration: systemd_conf,
+ install_dir: dir_systemd)
+
#!/bin/sh
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
echo [STEP] Initialize two nodes
#!/bin/sh
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
echo [STEP] Initialize and test one node
#!/bin/sh
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
foo_dir=$(peer_directory foo)
foo_host=$foo_dir/hosts/foo
#!/bin/sh
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
echo [STEP] Initialize one node
#!/bin/sh
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
require_root "$0" "$@"
-test -e /dev/net/tun || exit $EXIT_SKIP_TEST
-ip netns list || exit $EXIT_SKIP_TEST
-command -v socat || exit $EXIT_SKIP_TEST
+test -e /dev/net/tun || exit "$EXIT_SKIP_TEST"
+ip netns list || exit "$EXIT_SKIP_TEST"
+command -v socat || exit "$EXIT_SKIP_TEST"
ip_foo=192.168.1.1
ip_bar=192.168.1.2
wait_script foo hosts/bar-up
wait_script bar hosts/foo-up
- try_limit_time 60 sh <<EOF
+ sh <<EOF
set -eu
ip netns exec foo socat -u TCP4-LISTEN:$recv_port_foo,reuseaddr OPEN:"$tmp_file",creat &
ip netns exec bar socat -u OPEN:"$ref_file" TCP4:$ip_foo:$recv_port_foo,retry=30 &
#!/bin/sh
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
echo [STEP] Just test whether the executables work
tincd foo --help
-if [ -e "$SPTPS_TEST" ]; then
- $SPTPS_TEST --help
+if [ -e "$SPTPS_TEST_PATH" ]; then
+ "$SPTPS_TEST_PATH" --help
fi
#!/bin/sh
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
echo [STEP] Initialize three nodes
#!/bin/sh
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
echo [STEP] Initialize one node
#!/bin/sh
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
echo [STEP] Initialize one node
#!/bin/sh
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
echo [STEP] Initialize one node
#!/bin/sh
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
echo [STEP] Initialize two nodes
--- /dev/null
+tests = [
+ 'basic.test',
+ 'commandline.test',
+ 'executables.test',
+ 'import-export.test',
+ 'invite-join.test',
+ 'invite-offline.test',
+ 'invite-tinc-up.test',
+ 'scripts.test',
+ 'security.test',
+ 'variables.test',
+]
+
+if opt_crypto != 'nolegacy'
+ tests += 'algorithms.test'
+ tests += 'legacy-protocol.test'
+endif
+
+if os_name != 'windows'
+ tests += 'sptps-basic.test'
+endif
+
+if os_name == 'linux'
+ tests += 'ns-ping.test'
+endif
+
+exe_splice = executable(
+ 'splice',
+ sources: 'splice.c',
+ dependencies: deps_common,
+ implicit_include_directories: false,
+ include_directories: inc_conf,
+ build_by_default: false,
+)
+
+env = environment()
+env.set('TINC_PATH', exe_tinc.full_path())
+env.set('TINCD_PATH', exe_tincd.full_path())
+env.set('SPTPS_TEST_PATH', exe_sptps_test.full_path())
+env.set('SPTPS_KEYPAIR_PATH', exe_sptps_keypair.full_path())
+env.set('SPLICE_PATH', exe_splice.full_path())
+env.set('TESTLIB_PATH', src_root / 'test' / 'testlib.sh')
+
+deps_test = [
+ exe_tinc,
+ exe_tincd,
+ exe_splice,
+ exe_sptps_test,
+ exe_sptps_keypair,
+]
+
+test_wd = meson.current_build_dir()
+
+foreach test_name : tests
+ target = find_program(test_name, native: true)
+ test(test_name,
+ target,
+ timeout: 5 * 60,
+ env: env,
+ depends: deps_test,
+ workdir: test_wd)
+endforeach
+
#!/bin/sh
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
require_root "$0" "$@"
-test -e /dev/net/tun || exit $EXIT_SKIP_TEST
-ip netns list || exit $EXIT_SKIP_TEST
+test -e /dev/net/tun || exit "$EXIT_SKIP_TEST"
+ip netns list || exit "$EXIT_SKIP_TEST"
ip_foo=192.168.1.1
ip_bar=192.168.1.2
#!/bin/sh
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
echo [STEP] Initializing server node
#!/bin/sh
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
echo [STEP] Skip this test if tools are missing
-command -v nc >/dev/null || exit $EXIT_SKIP_TEST
-command -v timeout >/dev/null || exit $EXIT_SKIP_TEST
+command -v nc >/dev/null || exit "$EXIT_SKIP_TEST"
+command -v timeout >/dev/null || exit "$EXIT_SKIP_TEST"
foo_port=30050
bar_port=30051
# usage: splice protocol_version
splice() {
- ./splice foo localhost $foo_port bar localhost $bar_port "$1" &
+ "$SPLICE_PATH" foo localhost $foo_port bar localhost $bar_port "$1" &
sleep 10
}
) | timeout 10 nc localhost $foo_port
) && exit 1
- test $? = $EXIT_TIMEOUT
+ test $? = "$EXIT_TIMEOUT"
if [ -z "$expected" ]; then
test -z "$result"
#!/bin/sh
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
echo [STEP] Skip this test if we did not compile sptps_test
-test -e "$SPTPS_TEST" -a -e "$SPTPS_KEYPAIR" || exit $EXIT_SKIP_TEST
+test -e "$SPTPS_TEST" -a -e "$SPTPS_KEYPAIR_PATH" || exit "$EXIT_SKIP_TEST"
port=30080
echo [STEP] Generate keys
mkdir -p "$DIR_FOO"
-$SPTPS_KEYPAIR "$server_priv" "$server_pub"
-$SPTPS_KEYPAIR "$client_priv" "$client_pub"
+"$SPTPS_KEYPAIR_PATH" "$server_priv" "$server_pub"
+"$SPTPS_KEYPAIR_PATH" "$client_priv" "$client_pub"
echo [STEP] Test transfer of a simple file
(
sleep 3
- $SPTPS_TEST -4 -q "$client_priv" "$server_pub" localhost $port <"$reference"
+ "$SPTPS_TEST_PATH" -4 -q "$client_priv" "$server_pub" localhost $port <"$reference"
) &
-$SPTPS_TEST -4 "$server_priv" "$client_pub" $port >"$DIR_FOO/out1"
+"$SPTPS_TEST_PATH" -4 "$server_priv" "$client_pub" $port >"$DIR_FOO/out1"
diff -w "$DIR_FOO/out1" "$reference"
-$SPTPS_TEST -4 -q "$server_priv" "$client_pub" $port <"$reference" &
+"$SPTPS_TEST_PATH" -4 -q "$server_priv" "$client_pub" $port <"$reference" &
sleep 3
-$SPTPS_TEST -4 "$client_priv" "$server_pub" localhost $port >"$DIR_FOO/out2"
+"$SPTPS_TEST_PATH" -4 "$client_priv" "$server_pub" localhost $port >"$DIR_FOO/out2"
diff -w "$DIR_FOO/out2" "$reference"
echo [STEP] Datagram mode
-$SPTPS_TEST -4 -dq "$server_priv" "$client_pub" $port <"$reference" &
+"$SPTPS_TEST_PATH" -4 -dq "$server_priv" "$client_pub" $port <"$reference" &
sleep 3
-sleep 3 | $SPTPS_TEST -4 -dq "$client_priv" "$server_pub" localhost $port >"$DIR_FOO/out3"
+sleep 3 | "$SPTPS_TEST_PATH" -4 -dq "$client_priv" "$server_pub" localhost $port >"$DIR_FOO/out3"
diff -w "$DIR_FOO/out3" "$reference"
--- /dev/null
+#!/bin/sh
+
+set -ex
+
+echo [STEP] Initialize test library
+
+# Paths to compiled executables
+
+# realpath on FreeBSD fails if the path does not exist.
+realdir() {
+ [ -e "$1" ] || mkdir -p "$1"
+ if type realpath >/dev/null; then
+ realpath "$1"
+ else
+ readlink -f "$1"
+ fi
+}
+
+# Exit status list
+# shellcheck disable=SC2034
+EXIT_FAILURE=1
+# shellcheck disable=SC2034
+EXIT_SKIP_TEST=77
+
+# The list of the environment variables that tinc injects into the scripts it calls.
+# shellcheck disable=SC2016
+TINC_SCRIPT_VARS='$NETNAME,$NAME,$DEVICE,$IFACE,$NODE,$REMOTEADDRESS,$REMOTEPORT,$SUBNET,$WEIGHT,$INVITATION_FILE,$INVITATION_URL,$DEBUG'
+
+# Test directories
+
+# Reuse script name if it was passed in an env var (when imported from tinc scripts).
+if [ -z "$SCRIPTNAME" ]; then
+ SCRIPTNAME=$(basename "$0")
+fi
+
+# Network names for tincd daemons.
+net1=$SCRIPTNAME.1
+net2=$SCRIPTNAME.2
+net3=$SCRIPTNAME.3
+
+# Configuration/pidfile directories for tincd daemons.
+DIR_FOO=$(realdir "$PWD/$net1")
+DIR_BAR=$(realdir "$PWD/$net2")
+DIR_BAZ=$(realdir "$PWD/$net3")
+
+# Register helper functions
+
+# Alias gtimeout to timeout if it exists.
+if type gtimeout >/dev/null; then
+ timeout() { gtimeout "$@"; }
+fi
+
+# As usual, BSD tools require special handling, as they do not support -i without a suffix.
+# Note that there must be no space after -i, or it won't work on GNU sed.
+sed_cmd() {
+ sed -i.orig "$@"
+}
+
+# Are the shell tools provided by busybox?
+is_busybox() {
+ timeout --help 2>&1 | grep -q -i busybox
+}
+
+# busybox timeout returns 128 + signal number (which is TERM by default)
+if is_busybox; then
+ # shellcheck disable=SC2034
+ EXIT_TIMEOUT=$((128 + 15))
+else
+ # shellcheck disable=SC2034
+ EXIT_TIMEOUT=124
+fi
+
+# Is this msys2?
+is_windows() {
+ test "$(uname -o)" = Msys
+}
+
+# Are we running on a CI server?
+is_ci() {
+ test "$CI"
+}
+
+# Dump error message and exit with an error.
+bail() {
+ echo >&2 "$@"
+ exit 1
+}
+
+# Remove carriage returns to normalize strings on Windows for easier comparisons.
+rm_cr() {
+ tr -d '\r'
+}
+
+if is_windows; then
+ normalize_path() { cygpath --mixed -- "$@"; }
+else
+ normalize_path() { echo "$@"; }
+fi
+
+# Executes whatever is passed to it, checking that the resulting exit code is non-zero.
+must_fail() {
+ if "$@"; then
+ bail "expected a non-zero exit code"
+ fi
+}
+
+# Executes the passed command and checks two conditions:
+# 1. it must exit successfully (with code 0)
+# 2. its output (stdout + stderr) must include the substring from the first argument (ignoring case)
+# usage: expect_msg 'expected message' command --with --args
+expect_msg() {
+ message=$1
+ shift
+
+ if ! output=$("$@" 2>&1); then
+ bail 'expected 0 exit code'
+ fi
+
+ if ! echo "$output" | grep -q -i "$message"; then
+ bail "expected message '$message'"
+ fi
+}
+
+# The reverse of expect_msg. We cannot simply wrap expect_msg with must_fail
+# because there should be a separate check for tinc exit code.
+fail_on_msg() {
+ message=$1
+ shift
+
+ if ! output=$("$@" 2>&1); then
+ bail 'expected 0 exit code'
+ fi
+
+ if echo "$output" | grep -q -i "$message"; then
+ bail "unexpected message '$message'"
+ fi
+}
+
+# Like expect_msg, but the command must fail with a non-zero exit code.
+# usage: must_fail_with_msg 'expected message' command --with --args
+must_fail_with_msg() {
+ message=$1
+ shift
+
+ if output=$("$@" 2>&1); then
+ bail "expected a non-zero exit code"
+ fi
+
+ if ! echo "$output" | grep -i -q "$message"; then
+ bail "expected message '$message'"
+ fi
+}
+
+# Is the legacy protocol enabled?
+with_legacy() {
+ tincd foo --version | grep -q legacy_protocol
+}
+
+# Are we running with EUID 0?
+is_root() {
+ test "$(id -u)" = 0
+}
+
+# Executes whatever is passed to it, checking that the resulting exit code is equal to the first argument.
+expect_code() {
+ expected=$1
+ shift
+
+ code=0
+ "$@" || code=$?
+
+ if [ $code != "$expected" ]; then
+ bail "wrong exit code $code, expected $expected"
+ fi
+}
+
+# wc -l on mac prints whitespace before the actual number.
+# This is simplest cross-platform alternative without that behavior.
+count_lines() {
+ awk 'END{ print NR }'
+}
+
+# Calls compiled tinc, passing any supplied arguments.
+# Usage: tinc { foo | bar | baz } --arg1 val1 "$args"
+tinc() {
+ peer=$1
+ shift
+
+ case "$peer" in
+ foo) "$TINC_PATH" -n "$net1" --config="$DIR_FOO" --pidfile="$DIR_FOO/pid" "$@" ;;
+ bar) "$TINC_PATH" -n "$net2" --config="$DIR_BAR" --pidfile="$DIR_BAR/pid" "$@" ;;
+ baz) "$TINC_PATH" -n "$net3" --config="$DIR_BAZ" --pidfile="$DIR_BAZ/pid" "$@" ;;
+ *) bail "invalid command [[$peer $*]]" ;;
+ esac
+}
+
+# Calls compiled tincd, passing any supplied arguments.
+# Usage: tincd { foo | bar | baz } --arg1 val1 "$args"
+tincd() {
+ peer=$1
+ shift
+
+ case "$peer" in
+ foo) "$TINCD_PATH" -n "$net1" --config="$DIR_FOO" --pidfile="$DIR_FOO/pid" --logfile="$DIR_FOO/log" -d5 "$@" ;;
+ bar) "$TINCD_PATH" -n "$net2" --config="$DIR_BAR" --pidfile="$DIR_BAR/pid" --logfile="$DIR_BAR/log" -d5 "$@" ;;
+ baz) "$TINCD_PATH" -n "$net3" --config="$DIR_BAZ" --pidfile="$DIR_BAZ/pid" --logfile="$DIR_BAZ/log" -d5 "$@" ;;
+ *) bail "invalid command [[$peer $*]]" ;;
+ esac
+}
+
+# Start the specified tinc daemon.
+# usage: start_tinc { foo | bar | baz }
+start_tinc() {
+ peer=$1
+ shift
+
+ case "$peer" in
+ foo) tinc "$peer" start --logfile="$DIR_FOO/log" -d5 "$@" ;;
+ bar) tinc "$peer" start --logfile="$DIR_BAR/log" -d5 "$@" ;;
+ baz) tinc "$peer" start --logfile="$DIR_BAZ/log" -d5 "$@" ;;
+ *) bail "invalid peer $peer" ;;
+ esac
+}
+
+# Stop all tinc clients.
+stop_all_tincs() {
+ (
+ # In case these pid files are mangled.
+ set +e
+ [ -f "$DIR_FOO/pid" ] && tinc foo stop
+ [ -f "$DIR_BAR/pid" ] && tinc bar stop
+ [ -f "$DIR_BAZ/pid" ] && tinc baz stop
+ true
+ )
+}
+
+# Checks that the number of reachable nodes matches what is expected.
+# usage: require_nodes node_name expected_number
+require_nodes() {
+ echo >&2 "Check that we're able to reach tincd"
+ test "$(tinc "$1" pid | count_lines)" = 1
+
+ echo >&2 "Check the number of reachable nodes for $1 (expecting $2)"
+ actual="$(tinc "$1" dump reachable nodes | count_lines)"
+
+ if [ "$actual" != "$2" ]; then
+ echo >&2 "tinc $1: expected $2 reachable nodes, got $actual"
+ exit 1
+ fi
+}
+
+peer_directory() {
+ peer=$1
+ case "$peer" in
+ foo) echo "$DIR_FOO" ;;
+ bar) echo "$DIR_BAR" ;;
+ baz) echo "$DIR_BAZ" ;;
+ *) bail "invalid peer $peer" ;;
+ esac
+}
+
+# This is an append-only log of all scripts executed by all peers.
+script_runs_log() {
+ echo "$(peer_directory "$1")/script-runs.log"
+}
+
+# Create tincd script. If it fails, it kills the test script with SIGTERM.
+# usage: create_script { foo | bar | baz } { tinc-up | host-down | ... } 'script content'
+create_script() {
+ peer=$1
+ script=$2
+ shift 2
+
+ # This is the line that we should start from when reading the script execution log while waiting
+ # for $script from $peer. It is a poor man's hash map to avoid polluting tinc's home directory with
+ # "last seen" files. There seem to be no good solutions to this that are compatible with all shells.
+ line_var=$(next_line_var "$peer" "$script")
+
+ # We must reassign it here in case the script is recreated.
+ # shellcheck disable=SC2229
+ read -r "$line_var" <<EOF
+1
+EOF
+
+ # Full path to the script.
+ script_path=$(peer_directory "$peer")/$script
+
+ # Full path to the script execution log (one for each peer).
+ script_log=$(script_runs_log "$peer")
+ printf '' >"$script_log"
+
+ # Script output is redirected into /dev/null. Otherwise, it ends up
+ # in tinc's output and breaks things like 'tinc invite'.
+ cat >"$script_path" <<EOF
+#!/bin/sh
+(
+ cd "$PWD" || exit 1
+ SCRIPTNAME="$SCRIPTNAME" . "$TESTLIB_PATH"
+ $@
+ echo "$script,\$$,$TINC_SCRIPT_VARS" >>"$script_log"
+) >/dev/null 2>&1 || kill -TERM $$
+EOF
+
+ chmod u+x "$script_path"
+
+ if is_windows; then
+ echo "@$MINGW_SHELL '$script_path'" >"$script_path.cmd"
+ fi
+}
+
+# Returns the name of the variable that contains the line number
+# we should read next when waiting on $script from $peer.
+# usage: next_line_var foo host-up
+next_line_var() {
+ peer=$1
+ script=$(echo "$2" | sed 's/[^a-zA-Z0-9]/_/g')
+ printf "%s" "next_line_${peer}_${script}"
+}
+
+# Waits for `peer`'s script `script` to finish `count` number of times.
+# usage: wait_script { foo | bar | baz } { tinc-up | host-up | ... } [count=1]
+wait_script() {
+ peer=$1
+ script=$2
+ count=$3
+
+ if [ -z "$count" ] || [ "$count" -lt 1 ]; then
+ count=1
+ fi
+
+ # Find out the location of the log and how many lines we should skip
+ # (because we've already seen them in previous invocations of wait_script
+ # for current $peer and $script).
+ line_var=$(next_line_var "$peer" "$script")
+
+ # eval is the only solution supported by POSIX shells.
+ # https://github.com/koalaman/shellcheck/wiki/SC3053
+ # 1. $line_var expands into 'next_line_foo_hosts_bar_up'
+ # 2. the name is substituted and the command becomes 'echo "$next_line_foo_hosts_bar_up"'
+ # 3. the command is evaluated and the line number is assigned to $line
+ line=$(eval "echo \"\$$line_var\"")
+
+ # This is the file that we monitor for script execution records.
+ script_log=$(script_runs_log "$peer")
+
+ # Starting from $line, read until $count matches are found.
+ # Print the number of the last matching line and exit.
+ # GNU tail 2.82 and newer terminates by itself when the pipe breaks.
+ # To support other tails we do an explicit `kill`.
+ # FIFO is useful here because otherwise it's difficult to determine
+ # which tail process should be killed. We could stick them in a process
+ # group by enabling job control, but this results in weird behavior when
+ # running tests in parallel on some interactive shells
+ # (e.g. when /bin/sh is symlinked to dash).
+ fifo=$(mktemp)
+ rm -f "$fifo"
+ mkfifo "$fifo"
+
+ # This weird thing is required to support old versions of ksh on NetBSD 8.2 and the like.
+ (tail -n +"$line" -f "$script_log" >"$fifo") &
+
+ new_line=$(
+ sh -c "
+ grep -n -m $count '^$script,' <'$fifo'
+ " | awk -F: 'END { print $1 }'
+ )
+
+ # Try to stop the background tail, ignoring possible failure (some tails
+ # detect EOF, some don't, so it may have already exited), but do wait on
+ # it (which is required at least by old ksh).
+ kill $! || true
+ wait || true
+ rm -f "$fifo"
+
+ # Remember the next line number for future reference. We'll use it if
+ # wait_script is called again with same $peer and $script.
+ read -r "${line_var?}" <<EOF
+$((line + new_line))
+EOF
+}
+
+# Cleanup after running each script.
+cleanup() {
+ (
+ set +ex
+
+ if command -v cleanup_hook 2>/dev/null; then
+ echo >&2 "Cleanup hook found, calling..."
+ cleanup_hook
+ fi
+
+ stop_all_tincs
+ ) || true
+}
+
+# If we're on a CI server, the test requires superuser privileges to run, and we're not
+# currently a superuser, try running the test as one and fail if it doesn't work (the
+# system must be configured to provide passwordless sudo for our user).
+require_root() {
+ if is_root; then
+ return
+ fi
+ if is_ci; then
+ echo "root is required for test $SCRIPTNAME, but we're a regular user; elevating privileges..."
+ if ! command -v sudo 2>/dev/null; then
+ bail "please install sudo and configure passwordless auth for user $USER"
+ fi
+ if ! sudo --preserve-env --non-interactive true; then
+ bail "sudo is not allowed or requires a password for user $USER"
+ fi
+ exec sudo --preserve-env "$@"
+ else
+ # Avoid these kinds of surprises outside CI. Just skip the test.
+ echo "root is required for test $SCRIPTNAME, but we're a regular user; skipping"
+ exit "$EXIT_SKIP_TEST"
+ fi
+}
+
+# Generate path to current shell which can be used from Windows applications.
+if is_windows; then
+ MINGW_SHELL=$(normalize_path "$SHELL")
+fi
+
+# This was called from a tincd script. Skip executing commands with side effects.
+[ -n "$NAME" ] && return
+
+echo [STEP] Check for leftover tinc daemons and test directories
+
+# Cleanup leftovers from previous runs.
+stop_all_tincs
+
+rm -rf "$DIR_FOO" "$DIR_BAR" "$DIR_BAZ"
+
+# Register cleanup function so we don't have to call it everywhere
+# (and failed scripts do not leave stray tincd running).
+trap cleanup EXIT INT TERM
+++ /dev/null
-#!/bin/sh
-
-set -ex
-
-echo [STEP] Initialize test library
-
-# Paths to compiled executables
-
-# realpath on FreeBSD fails if the path does not exist.
-realdir() {
- [ -e "$1" ] || mkdir -p "$1"
- if type realpath >/dev/null; then
- realpath "$1"
- else
- readlink -f "$1"
- fi
-}
-
-tincd_path=$(realdir "../src/tincd@EXEEXT@")
-tinc_path=$(realdir "../src/tinc@EXEEXT@")
-
-# shellcheck disable=SC2034
-SPTPS_TEST=$(realdir "../src/sptps_test@EXEEXT@")
-# shellcheck disable=SC2034
-SPTPS_KEYPAIR=$(realdir "../src/sptps_keypair@EXEEXT@")
-
-# Exit status list
-# shellcheck disable=SC2034
-EXIT_FAILURE=1
-# shellcheck disable=SC2034
-EXIT_SKIP_TEST=77
-
-# The list of the environment variables that tinc injects into the scripts it calls.
-# shellcheck disable=SC2016
-TINC_SCRIPT_VARS='$NETNAME,$NAME,$DEVICE,$IFACE,$NODE,$REMOTEADDRESS,$REMOTEPORT,$SUBNET,$WEIGHT,$INVITATION_FILE,$INVITATION_URL,$DEBUG'
-
-# Test directories
-
-# Reuse script name if it was passed in an env var (when imported from tinc scripts).
-if [ -z "$SCRIPTNAME" ]; then
- SCRIPTNAME=$(basename "$0")
-fi
-
-# Network names for tincd daemons.
-net1=$SCRIPTNAME.1
-net2=$SCRIPTNAME.2
-net3=$SCRIPTNAME.3
-
-# Configuration/pidfile directories for tincd daemons.
-DIR_FOO=$(realdir "$PWD/$net1")
-DIR_BAR=$(realdir "$PWD/$net2")
-DIR_BAZ=$(realdir "$PWD/$net3")
-
-# Register helper functions
-
-# Alias gtimeout to timeout if it exists.
-if type gtimeout >/dev/null; then
- timeout() { gtimeout "$@"; }
-fi
-
-# As usual, BSD tools require special handling, as they do not support -i without a suffix.
-# Note that there must be no space after -i, or it won't work on GNU sed.
-sed_cmd() {
- sed -i.orig "$@"
-}
-
-# Are the shell tools provided by busybox?
-is_busybox() {
- timeout --help 2>&1 | grep -q -i busybox
-}
-
-# busybox timeout returns 128 + signal number (which is TERM by default)
-if is_busybox; then
- # shellcheck disable=SC2034
- EXIT_TIMEOUT=$((128 + 15))
-else
- # shellcheck disable=SC2034
- EXIT_TIMEOUT=124
-fi
-
-# Is this msys2?
-is_windows() {
- test "$(uname -o)" = Msys
-}
-
-# Are we running on a CI server?
-is_ci() {
- test "$CI"
-}
-
-# Dump error message and exit with an error.
-bail() {
- echo >&2 "$@"
- exit 1
-}
-
-# Remove carriage returns to normalize strings on Windows for easier comparisons.
-rm_cr() {
- tr -d '\r'
-}
-
-if is_windows; then
- normalize_path() { cygpath --mixed -- "$@"; }
-else
- normalize_path() { echo "$@"; }
-fi
-
-# Executes whatever is passed to it, checking that the resulting exit code is non-zero.
-must_fail() {
- if "$@"; then
- bail "expected a non-zero exit code"
- fi
-}
-
-# Executes the passed command and checks two conditions:
-# 1. it must exit successfully (with code 0)
-# 2. its output (stdout + stderr) must include the substring from the first argument (ignoring case)
-# usage: expect_msg 'expected message' command --with --args
-expect_msg() {
- message=$1
- shift
-
- if ! output=$("$@" 2>&1); then
- bail 'expected 0 exit code'
- fi
-
- if ! echo "$output" | grep -q -i "$message"; then
- bail "expected message '$message'"
- fi
-}
-
-# The reverse of expect_msg. We cannot simply wrap expect_msg with must_fail
-# because there should be a separate check for tinc exit code.
-fail_on_msg() {
- message=$1
- shift
-
- if ! output=$("$@" 2>&1); then
- bail 'expected 0 exit code'
- fi
-
- if echo "$output" | grep -q -i "$message"; then
- bail "unexpected message '$message'"
- fi
-}
-
-# Like expect_msg, but the command must fail with a non-zero exit code.
-# usage: must_fail_with_msg 'expected message' command --with --args
-must_fail_with_msg() {
- message=$1
- shift
-
- if output=$("$@" 2>&1); then
- bail "expected a non-zero exit code"
- fi
-
- if ! echo "$output" | grep -i -q "$message"; then
- bail "expected message '$message'"
- fi
-}
-
-# Is the legacy protocol enabled?
-with_legacy() {
- tincd foo --version | grep -q legacy_protocol
-}
-
-# Are we running with EUID 0?
-is_root() {
- test "$(id -u)" = 0
-}
-
-# Executes whatever is passed to it, checking that the resulting exit code is equal to the first argument.
-expect_code() {
- expected=$1
- shift
-
- code=0
- "$@" || code=$?
-
- if [ $code != "$expected" ]; then
- bail "wrong exit code $code, expected $expected"
- fi
-}
-
-# Runs its arguments with timeout(1) or gtimeout(1) if either are installed.
-# Usage: try_limit_time 10 command --with --args
-if type timeout >/dev/null; then
- try_limit_time() {
- time=$1
- shift
- timeout "$time" "$@"
- }
-else
- try_limit_time() {
- echo >&2 "timeout was not found, running without time limits!"
- shift
- "$@"
- }
-fi
-
-# wc -l on mac prints whitespace before the actual number.
-# This is simplest cross-platform alternative without that behavior.
-count_lines() {
- awk 'END{ print NR }'
-}
-
-# Calls compiled tinc, passing any supplied arguments.
-# Usage: tinc { foo | bar | baz } --arg1 val1 "$args"
-tinc() {
- peer=$1
- shift
-
- case "$peer" in
- foo) try_limit_time 30 "$tinc_path" -n "$net1" --config="$DIR_FOO" --pidfile="$DIR_FOO/pid" "$@" ;;
- bar) try_limit_time 30 "$tinc_path" -n "$net2" --config="$DIR_BAR" --pidfile="$DIR_BAR/pid" "$@" ;;
- baz) try_limit_time 30 "$tinc_path" -n "$net3" --config="$DIR_BAZ" --pidfile="$DIR_BAZ/pid" "$@" ;;
- *) bail "invalid command [[$peer $*]]" ;;
- esac
-}
-
-# Calls compiled tincd, passing any supplied arguments.
-# Usage: tincd { foo | bar | baz } --arg1 val1 "$args"
-tincd() {
- peer=$1
- shift
-
- case "$peer" in
- foo) try_limit_time 30 "$tincd_path" -n "$net1" --config="$DIR_FOO" --pidfile="$DIR_FOO/pid" --logfile="$DIR_FOO/log" -d5 "$@" ;;
- bar) try_limit_time 30 "$tincd_path" -n "$net2" --config="$DIR_BAR" --pidfile="$DIR_BAR/pid" --logfile="$DIR_BAR/log" -d5 "$@" ;;
- baz) try_limit_time 30 "$tincd_path" -n "$net3" --config="$DIR_BAZ" --pidfile="$DIR_BAZ/pid" --logfile="$DIR_BAZ/log" -d5 "$@" ;;
- *) bail "invalid command [[$peer $*]]" ;;
- esac
-}
-
-# Start the specified tinc daemon.
-# usage: start_tinc { foo | bar | baz }
-start_tinc() {
- peer=$1
- shift
-
- case "$peer" in
- foo) tinc "$peer" start --logfile="$DIR_FOO/log" -d5 "$@" ;;
- bar) tinc "$peer" start --logfile="$DIR_BAR/log" -d5 "$@" ;;
- baz) tinc "$peer" start --logfile="$DIR_BAZ/log" -d5 "$@" ;;
- *) bail "invalid peer $peer" ;;
- esac
-}
-
-# Stop all tinc clients.
-stop_all_tincs() {
- (
- # In case these pid files are mangled.
- set +e
- [ -f "$DIR_FOO/pid" ] && tinc foo stop
- [ -f "$DIR_BAR/pid" ] && tinc bar stop
- [ -f "$DIR_BAZ/pid" ] && tinc baz stop
- true
- )
-}
-
-# Checks that the number of reachable nodes matches what is expected.
-# usage: require_nodes node_name expected_number
-require_nodes() {
- echo >&2 "Check that we're able to reach tincd"
- test "$(tinc "$1" pid | count_lines)" = 1
-
- echo >&2 "Check the number of reachable nodes for $1 (expecting $2)"
- actual="$(tinc "$1" dump reachable nodes | count_lines)"
-
- if [ "$actual" != "$2" ]; then
- echo >&2 "tinc $1: expected $2 reachable nodes, got $actual"
- exit 1
- fi
-}
-
-peer_directory() {
- peer=$1
- case "$peer" in
- foo) echo "$DIR_FOO" ;;
- bar) echo "$DIR_BAR" ;;
- baz) echo "$DIR_BAZ" ;;
- *) bail "invalid peer $peer" ;;
- esac
-}
-
-# This is an append-only log of all scripts executed by all peers.
-script_runs_log() {
- echo "$(peer_directory "$1")/script-runs.log"
-}
-
-# Create tincd script. If it fails, it kills the test script with SIGTERM.
-# usage: create_script { foo | bar | baz } { tinc-up | host-down | ... } 'script content'
-create_script() {
- peer=$1
- script=$2
- shift 2
-
- # This is the line that we should start from when reading the script execution log while waiting
- # for $script from $peer. It is a poor man's hash map to avoid polluting tinc's home directory with
- # "last seen" files. There seem to be no good solutions to this that are compatible with all shells.
- line_var=$(next_line_var "$peer" "$script")
-
- # We must reassign it here in case the script is recreated.
- # shellcheck disable=SC2229
- read -r "$line_var" <<EOF
-1
-EOF
-
- # Full path to the script.
- script_path=$(peer_directory "$peer")/$script
-
- # Full path to the script execution log (one for each peer).
- script_log=$(script_runs_log "$peer")
- printf '' >"$script_log"
-
- # Script output is redirected into /dev/null. Otherwise, it ends up
- # in tinc's output and breaks things like 'tinc invite'.
- cat >"$script_path" <<EOF
-#!/bin/sh
-(
- cd "$PWD" || exit 1
- SCRIPTNAME="$SCRIPTNAME" . ./testlib.sh
- $@
- echo "$script,\$$,$TINC_SCRIPT_VARS" >>"$script_log"
-) >/dev/null 2>&1 || kill -TERM $$
-EOF
-
- chmod u+x "$script_path"
-
- if is_windows; then
- echo "@$MINGW_SHELL '$script_path'" >"$script_path.cmd"
- fi
-}
-
-# Returns the name of the variable that contains the line number
-# we should read next when waiting on $script from $peer.
-# usage: next_line_var foo host-up
-next_line_var() {
- peer=$1
- script=$(echo "$2" | sed 's/[^a-zA-Z0-9]/_/g')
- printf "%s" "next_line_${peer}_${script}"
-}
-
-# Waits for `peer`'s script `script` to finish `count` number of times.
-# usage: wait_script { foo | bar | baz } { tinc-up | host-up | ... } [count=1]
-wait_script() {
- peer=$1
- script=$2
- count=$3
-
- if [ -z "$count" ] || [ "$count" -lt 1 ]; then
- count=1
- fi
-
- # Find out the location of the log and how many lines we should skip
- # (because we've already seen them in previous invocations of wait_script
- # for current $peer and $script).
- line_var=$(next_line_var "$peer" "$script")
-
- # eval is the only solution supported by POSIX shells.
- # https://github.com/koalaman/shellcheck/wiki/SC3053
- # 1. $line_var expands into 'next_line_foo_hosts_bar_up'
- # 2. the name is substituted and the command becomes 'echo "$next_line_foo_hosts_bar_up"'
- # 3. the command is evaluated and the line number is assigned to $line
- line=$(eval "echo \"\$$line_var\"")
-
- # This is the file that we monitor for script execution records.
- script_log=$(script_runs_log "$peer")
-
- # Starting from $line, read until $count matches are found.
- # Print the number of the last matching line and exit.
- # GNU tail 2.82 and newer terminates by itself when the pipe breaks.
- # To support other tails we do an explicit `kill`.
- # FIFO is useful here because otherwise it's difficult to determine
- # which tail process should be killed. We could stick them in a process
- # group by enabling job control, but this results in weird behavior when
- # running tests in parallel on some interactive shells
- # (e.g. when /bin/sh is symlinked to dash).
- fifo=$(mktemp)
- rm -f "$fifo"
- mkfifo "$fifo"
-
- # This weird thing is required to support old versions of ksh on NetBSD 8.2 and the like.
- (tail -n +"$line" -f "$script_log" >"$fifo") &
-
- new_line=$(
- try_limit_time 60 sh -c "
- grep -n -m $count '^$script,' <'$fifo'
- " | awk -F: 'END { print $1 }'
- )
-
- # Try to stop the background tail, ignoring possible failure (some tails
- # detect EOF, some don't, so it may have already exited), but do wait on
- # it (which is required at least by old ksh).
- kill $! || true
- wait || true
- rm -f "$fifo"
-
- # Remember the next line number for future reference. We'll use it if
- # wait_script is called again with same $peer and $script.
- read -r "${line_var?}" <<EOF
-$((line + new_line))
-EOF
-}
-
-# Are we running tests in parallel?
-is_parallel() {
- # Grep the make flags for any of: '-j', '-j5', '-j 42', but not 'n-j', '-junk'.
- echo "$MAKEFLAGS" | grep -E -q '(^|[[:space:]])-j[[:digit:]]*([[:space:]]|$)'
-}
-
-# Cleanup after running each script.
-cleanup() {
- (
- set +ex
-
- if command -v cleanup_hook 2>/dev/null; then
- echo >&2 "Cleanup hook found, calling..."
- cleanup_hook
- fi
-
- stop_all_tincs
-
- # Ask nicely, then kill anything that's left.
- if is_ci && ! is_parallel; then
- kill_processes() {
- signal=$1
- shift
- for process in "$@"; do
- pkill -"SIG$signal" -x -u "$(id -u)" "$process"
- done
- }
- echo >&2 "CI server detected, performing aggressive cleanup"
- kill_processes TERM tinc tincd
- kill_processes KILL tinc tincd
- fi
- ) || true
-}
-
-# If we're on a CI server, the test requires superuser privileges to run, and we're not
-# currently a superuser, try running the test as one and fail if it doesn't work (the
-# system must be configured to provide passwordless sudo for our user).
-require_root() {
- if is_root; then
- return
- fi
- if is_ci; then
- echo "root is required for test $SCRIPTNAME, but we're a regular user; elevating privileges..."
- if ! command -v sudo 2>/dev/null; then
- bail "please install sudo and configure passwordless auth for user $USER"
- fi
- if ! sudo --preserve-env --non-interactive true; then
- bail "sudo is not allowed or requires a password for user $USER"
- fi
- exec sudo --preserve-env "$@"
- else
- # Avoid these kinds of surprises outside CI. Just skip the test.
- echo "root is required for test $SCRIPTNAME, but we're a regular user; skipping"
- exit $EXIT_SKIP_TEST
- fi
-}
-
-# Generate path to current shell which can be used from Windows applications.
-if is_windows; then
- MINGW_SHELL=$(normalize_path "$SHELL")
-fi
-
-# This was called from a tincd script. Skip executing commands with side effects.
-[ -n "$NAME" ] && return
-
-echo [STEP] Check for leftover tinc daemons and test directories
-
-# Cleanup leftovers from previous runs.
-stop_all_tincs
-
-if [ -d "$DIR_FOO" ]; then rm -rf "$DIR_FOO"; fi
-if [ -d "$DIR_BAR" ]; then rm -rf "$DIR_BAR"; fi
-if [ -d "$DIR_BAZ" ]; then rm -rf "$DIR_BAZ"; fi
-
-# Register cleanup function so we don't have to call it everywhere
-# (and failed scripts do not leave stray tincd running).
-trap cleanup EXIT INT TERM
#!/bin/sh
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
echo [STEP] Initialize one node