]> tinc-vpn.org Git - tinc/commitdiff
Add support for meson build system
authorKirill Isakov <bootctl@gmail.com>
Wed, 23 Mar 2022 05:52:51 +0000 (11:52 +0600)
committerKirill Isakov <bootctl@gmail.com>
Wed, 23 Mar 2022 05:52:51 +0000 (11:52 +0600)
56 files changed:
.astylerc
.gitignore
bash_completion.d/meson.build [new file with mode: 0644]
doc/meson.build [new file with mode: 0644]
meson.build [new file with mode: 0644]
meson_options.txt [new file with mode: 0644]
src/bsd/meson.build [new file with mode: 0644]
src/chacha-poly1305/meson.build [new file with mode: 0644]
src/dropin.h
src/ed25519/meson.build [new file with mode: 0644]
src/event.c
src/gcrypt/meson.build [new file with mode: 0644]
src/getopt.c
src/getopt1.c
src/git_tag.sh [new file with mode: 0755]
src/include/meson.build [new file with mode: 0644]
src/linux/meson.build [new file with mode: 0644]
src/linux/uml_device.c [new file with mode: 0644]
src/meson.build [new file with mode: 0644]
src/mingw/meson.build [new file with mode: 0644]
src/net_packet.c
src/nolegacy/meson.build [new file with mode: 0644]
src/openssl/meson.build [new file with mode: 0644]
src/solaris/meson.build [new file with mode: 0644]
src/sptps_keypair.c
src/sptps_speed.c
src/sptps_test.c
src/system.h
src/tincd.c
src/uml_device.c [deleted file]
src/version.c
src/version_git.h.in [new file with mode: 0644]
subprojects/lz4.wrap [new file with mode: 0644]
subprojects/lzo2.wrap [new file with mode: 0644]
subprojects/openssl.wrap [new file with mode: 0644]
subprojects/zlib.wrap [new file with mode: 0644]
systemd/meson.build [new file with mode: 0644]
test/algorithms.test
test/basic.test
test/command-fsck.test
test/commandline.test
test/compression.test
test/executables.test
test/import-export.test
test/invite-join.test
test/invite-offline.test
test/invite-tinc-up.test
test/legacy-protocol.test
test/meson.build [new file with mode: 0644]
test/ns-ping.test
test/scripts.test
test/security.test
test/sptps-basic.test
test/testlib.sh [new file with mode: 0644]
test/testlib.sh.in [deleted file]
test/variables.test

index dc381a2c7eeaba70ce31718bf12b811a065b4e1e..4b89b18a18936a2b6c5cb13d7ff9c9b0ba7fd007 100644 (file)
--- a/.astylerc
+++ b/.astylerc
@@ -1,6 +1,8 @@
 --indent=tab=8
 --convert-tabs
---exclude=src/lib
+--exclude=subprojects
+--exclude=build
+--recursive
 -i
 -j
 -f
@@ -10,3 +12,5 @@
 -xg
 -k3
 -w
+--formatted
+
index 40094c48daa0a89d4abda412a7e3c9ebe71be238..7dd71eb66b253770a19504bc55b4a241e1746ea6 100644 (file)
 /test/*.log
 /test/*.trs
 /test/splice
-/test/testlib.sh
 Makefile
 Makefile.in
 core*
 heaptrack.*
 *.tar.gz*
+/subprojects/*
+!/subprojects/*.wrap
+
diff --git a/bash_completion.d/meson.build b/bash_completion.d/meson.build
new file mode 100644 (file)
index 0000000..2e221e7
--- /dev/null
@@ -0,0 +1,7 @@
+dir_compl = dir_data / 'bash-completion' / 'completions'
+
+install_data(
+  'tinc',
+  install_dir: dir_compl,
+)
+
diff --git a/doc/meson.build b/doc/meson.build
new file mode 100644 (file)
index 0000000..fefb1b8
--- /dev/null
@@ -0,0 +1,62 @@
+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
+
diff --git a/meson.build b/meson.build
new file mode 100644 (file)
index 0000000..9f55c9e
--- /dev/null
@@ -0,0 +1,124 @@
+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
+
diff --git a/meson_options.txt b/meson_options.txt
new file mode 100644 (file)
index 0000000..bb463ce
--- /dev/null
@@ -0,0 +1,91 @@
+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)')
+
diff --git a/src/bsd/meson.build b/src/bsd/meson.build
new file mode 100644 (file)
index 0000000..690e737
--- /dev/null
@@ -0,0 +1,26 @@
+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
+
diff --git a/src/chacha-poly1305/meson.build b/src/chacha-poly1305/meson.build
new file mode 100644 (file)
index 0000000..d8fd74c
--- /dev/null
@@ -0,0 +1,14 @@
+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,
+)
+
index 14783ffb1699d3335f9941760a17770fa7bc756e..2aa6df106837905e5cb3427840660b81ea936efe 100644 (file)
@@ -21,8 +21,6 @@
     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
 
-#include "system.h"
-
 #ifndef HAVE_DAEMON
 extern int daemon(int, int);
 #endif
diff --git a/src/ed25519/meson.build b/src/ed25519/meson.build
new file mode 100644 (file)
index 0000000..9542c0f
--- /dev/null
@@ -0,0 +1,22 @@
+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,
+)
+
index 899b62a077abb435e5327b359bacc41402ccaaf1..5d0bcef0b265c6067157e2674b18a07f8d647e70 100644 (file)
@@ -25,7 +25,6 @@
 #include <sys/epoll.h>
 #endif
 
-#include "dropin.h"
 #include "event.h"
 #include "utils.h"
 #include "net.h"
diff --git a/src/gcrypt/meson.build b/src/gcrypt/meson.build
new file mode 100644 (file)
index 0000000..ac93c80
--- /dev/null
@@ -0,0 +1,22 @@
+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)
+
index 5d5fb5996a65a105e849d392215cabf47c93b019..a8c5b492d2db7338987138fd63cf93eb40044992 100644 (file)
@@ -30,9 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #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
index 3ccb150ade88afdff6b51a34c5ab5fd4d3e4e453..be2280ee350246889325ff1e77fae8d997fa9c5d 100644 (file)
@@ -19,9 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 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"
 
diff --git a/src/git_tag.sh b/src/git_tag.sh
new file mode 100755 (executable)
index 0000000..25677e2
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+git describe --always --tags --match='release-*' "$@" | sed 's/release-//'
diff --git a/src/include/meson.build b/src/include/meson.build
new file mode 100644 (file)
index 0000000..55da167
--- /dev/null
@@ -0,0 +1,9 @@
+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',
+)
+
diff --git a/src/linux/meson.build b/src/linux/meson.build
new file mode 100644 (file)
index 0000000..5725b4a
--- /dev/null
@@ -0,0 +1,15 @@
+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
+
diff --git a/src/linux/uml_device.c b/src/linux/uml_device.c
new file mode 100644 (file)
index 0000000..dafe5b2
--- /dev/null
@@ -0,0 +1,315 @@
+/*
+    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,
+};
diff --git a/src/meson.build b/src/meson.build
new file mode 100644 (file)
index 0000000..c9f5d59
--- /dev/null
@@ -0,0 +1,399 @@
+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
+
diff --git a/src/mingw/meson.build b/src/mingw/meson.build
new file mode 100644 (file)
index 0000000..796d62b
--- /dev/null
@@ -0,0 +1,14 @@
+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)
+
index 459b24134b1f325a6546ef9b7b6a3e04a557b7de..eb438b0878b325603a15e00d7369a39385e6dd09 100644 (file)
@@ -25,7 +25,6 @@
 #ifdef HAVE_ZLIB
 #define ZLIB_CONST
 #include <zlib.h>
-#include <assert.h>
 
 #endif
 
@@ -33,8 +32,8 @@
 #include LZO1X_H
 #endif
 
-#ifdef LZ4_H
-#include LZ4_H
+#ifdef HAVE_LZ4
+#include <lz4.h>
 #endif
 
 #include "address_cache.h"
@@ -65,11 +64,15 @@ int keylifetime = 0;
 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 *);
 
@@ -252,8 +255,6 @@ static length_t compress_packet_lz4(uint8_t *dest, const uint8_t *source, length
 
 #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;
 
diff --git a/src/nolegacy/meson.build b/src/nolegacy/meson.build
new file mode 100644 (file)
index 0000000..323a831
--- /dev/null
@@ -0,0 +1,9 @@
+src_lib_crypto = files(
+  'crypto.c',
+  'prf.c',
+)
+
+dep_crypto = dependency('', required: false)
+
+cdata.set('DISABLE_LEGACY', 1)
+
diff --git a/src/openssl/meson.build b/src/openssl/meson.build
new file mode 100644 (file)
index 0000000..34c4fff
--- /dev/null
@@ -0,0 +1,37 @@
+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)
+
diff --git a/src/solaris/meson.build b/src/solaris/meson.build
new file mode 100644 (file)
index 0000000..f657428
--- /dev/null
@@ -0,0 +1,2 @@
+src_tincd += files('device.c')
+
index 22433b93cb0e873cbaad7a9c88cc57804f97354b..d762724f8fc222ab8d99c4517cfbe17d51b58242 100644 (file)
@@ -24,8 +24,7 @@
 #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;
index cc05b8656352e8bcc0000c9abfc29c597e959533..53cff4df3193020f77d8d3d8c6c48e48ed5b35cd 100644 (file)
@@ -45,7 +45,6 @@ bool send_meta(struct connection_t *c, const void *msg, size_t len) {
        (void)len;
        return false;
 }
-char *logfilename = NULL;
 bool do_detach = false;
 struct timeval now;
 
index 0f62af01dbaf318f559597aaa14b1da3c53a457f..1da0571c6fc8b2ee1c4ad54d49b3e99046d853b0 100644 (file)
@@ -35,6 +35,7 @@
 #include "protocol.h"
 #include "sptps.h"
 #include "utils.h"
+#include "names.h"
 
 #ifndef HAVE_MINGW
 #define closesocket(s) close(s)
@@ -56,7 +57,6 @@ bool send_meta(struct connection_t *c, const void *msg, size_t len) {
        return false;
 }
 
-char *logfilename = NULL;
 bool do_detach = false;
 struct timeval now;
 
@@ -136,8 +136,6 @@ static struct option const long_options[] = {
        {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"
index b3d9e0ab4c6e5b346aac194958063e3cf695b05c..0224fbea2d0c9cd47a1e9c9886900f25c05308b2 100644 (file)
@@ -21,7 +21,7 @@
     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
 
-#include "../config.h"
+#include "config.h"
 
 #include "have.h"
 
index 7c30a26f16d40e33a75eba32a02302e97bd5b564..4310181c009b14e5ddbd2d150352802a1425fefc 100644 (file)
@@ -33,8 +33,8 @@
 #include LZO1X_H
 #endif
 
-#ifdef LZ4_H
-#include LZ4_H
+#ifdef HAVE_LZ4
+#include <lz4.h>
 #endif
 
 #ifndef HAVE_MINGW
diff --git a/src/uml_device.c b/src/uml_device.c
deleted file mode 100644 (file)
index f35ae0d..0000000
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
-    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,
-};
index d0af1ccab05f2320c97f11c0632577759b63f0da..31b04d79f7e66b5fac76f7c06d8851d48d201691 100644 (file)
@@ -19,7 +19,6 @@
 
 #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__;
diff --git a/src/version_git.h.in b/src/version_git.h.in
new file mode 100644 (file)
index 0000000..ef3ed6c
--- /dev/null
@@ -0,0 +1,2 @@
+#define GIT_DESCRIPTION "@VCS_TAG@"
+
diff --git a/subprojects/lz4.wrap b/subprojects/lz4.wrap
new file mode 100644 (file)
index 0000000..c151ad0
--- /dev/null
@@ -0,0 +1,12 @@
+[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
+
diff --git a/subprojects/lzo2.wrap b/subprojects/lzo2.wrap
new file mode 100644 (file)
index 0000000..2b100f4
--- /dev/null
@@ -0,0 +1,12 @@
+[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
+
diff --git a/subprojects/openssl.wrap b/subprojects/openssl.wrap
new file mode 100644 (file)
index 0000000..274b544
--- /dev/null
@@ -0,0 +1,14 @@
+[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
+
diff --git a/subprojects/zlib.wrap b/subprojects/zlib.wrap
new file mode 100644 (file)
index 0000000..3773781
--- /dev/null
@@ -0,0 +1,12 @@
+[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
+
diff --git a/systemd/meson.build b/systemd/meson.build
new file mode 100644 (file)
index 0000000..f2b4d04
--- /dev/null
@@ -0,0 +1,28 @@
+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)
+
index d06901012db825f0d5825bb0b1d1d168bfbb6b2e..9dd722c9a60fe2b53a21f1e757349831ef72ca6d 100755 (executable)
@@ -1,6 +1,7 @@
 #!/bin/sh
 
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
 
 echo [STEP] Initialize two nodes
 
index 87c57842b613791e656bfa9de67cab624fe3dd67..31f27552444d1f563f64429c9f40cd07c119f615 100755 (executable)
@@ -1,6 +1,7 @@
 #!/bin/sh
 
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
 
 echo [STEP] Initialize and test one node
 
index 8457baa23049d9123d5036c479bfc079eb9c77e4..daf9291d6037074da8adc2e3c09734b4dfe91293 100755 (executable)
@@ -1,6 +1,7 @@
 #!/bin/sh
 
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
 
 foo_dir=$(peer_directory foo)
 foo_host=$foo_dir/hosts/foo
index ba3d7171f9448d3de65028f21ce3c79effbfc8cf..800dbfcfa166d2f804a388faefe9ecdf83b12ca7 100755 (executable)
@@ -1,6 +1,7 @@
 #!/bin/sh
 
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
 
 echo [STEP] Initialize one node
 
index f0952a4fb935ef2eef011c228a4716e7fee7017a..ac925417e8f7bab857d03282a60e72db82467e5a 100755 (executable)
@@ -1,11 +1,12 @@
 #!/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
@@ -106,7 +107,7 @@ for level in $levels; do
   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 &
index 96aad992977a23fa243227266c2a2561c3418187..390d2c71ab4fb78327931b132b8021253f25e7cc 100755 (executable)
@@ -1,6 +1,7 @@
 #!/bin/sh
 
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
 
 echo [STEP] Just test whether the executables work
 
@@ -8,6 +9,6 @@ tinc foo --help
 
 tincd foo --help
 
-if [ -e "$SPTPS_TEST" ]; then
-  $SPTPS_TEST --help
+if [ -e "$SPTPS_TEST_PATH" ]; then
+  "$SPTPS_TEST_PATH" --help
 fi
index 9fdfa4971c4ad1b2251047d3663ef5bb1b5b769d..6fe303fd8888618c26e9c389c451c9ff61b30dfd 100755 (executable)
@@ -1,6 +1,7 @@
 #!/bin/sh
 
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
 
 echo [STEP] Initialize three nodes
 
index 791558104d1480c3ca378bbb1c06b6394582662c..be2af2be6f640568a8c1784578ad78a083a1accb 100755 (executable)
@@ -1,6 +1,7 @@
 #!/bin/sh
 
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
 
 echo [STEP] Initialize one node
 
index aea39b41110e3978bbd98250011fcbea87ce75a8..8e535139da17e9f7ed35d42032470f1a12ead994 100755 (executable)
@@ -1,6 +1,7 @@
 #!/bin/sh
 
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
 
 echo [STEP] Initialize one node
 
index a288d6e7d2317b9cb5d26b7dff6fb966d021566b..14d5a6010da16f850f310bacd121a743ea6e1e62 100755 (executable)
@@ -1,6 +1,7 @@
 #!/bin/sh
 
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
 
 echo [STEP] Initialize one node
 
index 396fc56d92bf0defb21769626ace1ed08f3bb412..9c44f7901a8527ba80b9d25a1719e0fee0d96f90 100755 (executable)
@@ -1,6 +1,7 @@
 #!/bin/sh
 
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
 
 echo [STEP] Initialize two nodes
 
diff --git a/test/meson.build b/test/meson.build
new file mode 100644 (file)
index 0000000..4b12f2e
--- /dev/null
@@ -0,0 +1,63 @@
+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
+
index 8e10d11ed064b331725ff12ce46490899aea5011..33e72708db7e579719bdff9ec89e049d39035e01 100755 (executable)
@@ -1,10 +1,11 @@
 #!/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
index a76356aeae3498a1f29dd0ecc9bc2154782022d3..ff0e565fd88351ba17c53e99e75125b0b5704e11 100755 (executable)
@@ -1,6 +1,7 @@
 #!/bin/sh
 
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
 
 echo [STEP] Initializing server node
 
index ee7bd46dd30af7b2e79da393be738ea5c05369a6..fb783b06fb0949e554e706b6e009b11cf81d4688 100755 (executable)
@@ -1,18 +1,19 @@
 #!/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
 }
 
@@ -28,7 +29,7 @@ send_with_timeout() {
     ) | timeout 10 nc localhost $foo_port
   ) && exit 1
 
-  test $? = $EXIT_TIMEOUT
+  test $? = "$EXIT_TIMEOUT"
 
   if [ -z "$expected" ]; then
     test -z "$result"
index 56e3cd00e089a601227fff00acb6bceae0e1eb6d..0eb285b1390805d7b0443f5266d6e658b5eefbb0 100755 (executable)
@@ -1,10 +1,11 @@
 #!/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
 
@@ -16,8 +17,8 @@ client_pub="$DIR_FOO/client.pub"
 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
 
@@ -25,20 +26,20 @@ reference=testlib.sh
 
 (
   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"
diff --git a/test/testlib.sh b/test/testlib.sh
new file mode 100644 (file)
index 0000000..3d0f990
--- /dev/null
@@ -0,0 +1,436 @@
+#!/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
diff --git a/test/testlib.sh.in b/test/testlib.sh.in
deleted file mode 100644 (file)
index c63d27d..0000000
+++ /dev/null
@@ -1,482 +0,0 @@
-#!/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
index 897059dec0cfc22d618684146916803e94027bea..fffbd8ced0da877e6122b042da16a87f1fd74564 100755 (executable)
@@ -1,6 +1,7 @@
 #!/bin/sh
 
-. ./testlib.sh
+# shellcheck disable=SC1090
+. "$TESTLIB_PATH"
 
 echo [STEP] Initialize one node