From: Kirill Isakov Date: Wed, 23 Mar 2022 05:52:51 +0000 (+0600) Subject: Add support for meson build system X-Git-Url: https://www.tinc-vpn.org/git/?a=commitdiff_plain;h=df716df33af8e9a5b93d573a023ecd7fc24d9a03;p=tinc Add support for meson build system --- diff --git a/.astylerc b/.astylerc index dc381a2c..4b89b18a 100644 --- 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 + diff --git a/.gitignore b/.gitignore index 40094c48..7dd71eb6 100644 --- a/.gitignore +++ b/.gitignore @@ -48,9 +48,11 @@ /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 index 00000000..2e221e77 --- /dev/null +++ b/bash_completion.d/meson.build @@ -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 index 00000000..fefb1b8c --- /dev/null +++ b/doc/meson.build @@ -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 index 00000000..9f55c9e6 --- /dev/null +++ b/meson.build @@ -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 index 00000000..bb463ce1 --- /dev/null +++ b/meson_options.txt @@ -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 index 00000000..690e7373 --- /dev/null +++ b/src/bsd/meson.build @@ -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 index 00000000..d8fd74cc --- /dev/null +++ b/src/chacha-poly1305/meson.build @@ -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, +) + diff --git a/src/dropin.h b/src/dropin.h index 14783ffb..2aa6df10 100644 --- a/src/dropin.h +++ b/src/dropin.h @@ -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 index 00000000..9542c0fc --- /dev/null +++ b/src/ed25519/meson.build @@ -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, +) + diff --git a/src/event.c b/src/event.c index 899b62a0..5d0bcef0 100644 --- a/src/event.c +++ b/src/event.c @@ -25,7 +25,6 @@ #include #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 index 00000000..ac93c809 --- /dev/null +++ b/src/gcrypt/meson.build @@ -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) + diff --git a/src/getopt.c b/src/getopt.c index 5d5fb599..a8c5b492 100644 --- a/src/getopt.c +++ b/src/getopt.c @@ -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 diff --git a/src/getopt1.c b/src/getopt1.c index 3ccb150a..be2280ee 100644 --- a/src/getopt1.c +++ b/src/getopt1.c @@ -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. */ -#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 index 00000000..25677e29 --- /dev/null +++ b/src/git_tag.sh @@ -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 index 00000000..55da167b --- /dev/null +++ b/src/include/meson.build @@ -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 index 00000000..5725b4ad --- /dev/null +++ b/src/linux/meson.build @@ -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 index 00000000..dafe5b20 --- /dev/null +++ b/src/linux/uml_device.c @@ -0,0 +1,315 @@ +/* + device.c -- UML network socket + Copyright (C) 2002-2005 Ivo Timmermans, + 2002-2022 Guus Sliepen + + 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 + +#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 index 00000000..c9f5d594 --- /dev/null +++ b/src/meson.build @@ -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 + #include +''', 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', '') + elif cc.has_header('lzo1x.h', dependencies: dep_lzo) + cdata.set('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 index 00000000..796d62b9 --- /dev/null +++ b/src/mingw/meson.build @@ -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) + diff --git a/src/net_packet.c b/src/net_packet.c index 459b2413..eb438b08 100644 --- a/src/net_packet.c +++ b/src/net_packet.c @@ -25,7 +25,6 @@ #ifdef HAVE_ZLIB #define ZLIB_CONST #include -#include #endif @@ -33,8 +32,8 @@ #include LZO1X_H #endif -#ifdef LZ4_H -#include LZ4_H +#ifdef HAVE_LZ4 +#include #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 index 00000000..323a8314 --- /dev/null +++ b/src/nolegacy/meson.build @@ -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 index 00000000..34c4fff7 --- /dev/null +++ b/src/openssl/meson.build @@ -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 index 00000000..f657428b --- /dev/null +++ b/src/solaris/meson.build @@ -0,0 +1,2 @@ +src_tincd += files('device.c') + diff --git a/src/sptps_keypair.c b/src/sptps_keypair.c index 22433b93..d762724f 100644 --- a/src/sptps_keypair.c +++ b/src/sptps_keypair.c @@ -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; diff --git a/src/sptps_speed.c b/src/sptps_speed.c index cc05b865..53cff4df 100644 --- a/src/sptps_speed.c +++ b/src/sptps_speed.c @@ -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; diff --git a/src/sptps_test.c b/src/sptps_test.c index 0f62af01..1da0571c 100644 --- a/src/sptps_test.c +++ b/src/sptps_test.c @@ -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" diff --git a/src/system.h b/src/system.h index b3d9e0ab..0224fbea 100644 --- a/src/system.h +++ b/src/system.h @@ -21,7 +21,7 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../config.h" +#include "config.h" #include "have.h" diff --git a/src/tincd.c b/src/tincd.c index 7c30a26f..4310181c 100644 --- a/src/tincd.c +++ b/src/tincd.c @@ -33,8 +33,8 @@ #include LZO1X_H #endif -#ifdef LZ4_H -#include LZ4_H +#ifdef HAVE_LZ4 +#include #endif #ifndef HAVE_MINGW diff --git a/src/uml_device.c b/src/uml_device.c deleted file mode 100644 index f35ae0d2..00000000 --- a/src/uml_device.c +++ /dev/null @@ -1,315 +0,0 @@ -/* - device.c -- UML network socket - Copyright (C) 2002-2005 Ivo Timmermans, - 2002-2022 Guus Sliepen - - 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 - -#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/version.c b/src/version.c index d0af1cca..31b04d79 100644 --- a/src/version.c +++ b/src/version.c @@ -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 index 00000000..ef3ed6c4 --- /dev/null +++ b/src/version_git.h.in @@ -0,0 +1,2 @@ +#define GIT_DESCRIPTION "@VCS_TAG@" + diff --git a/subprojects/lz4.wrap b/subprojects/lz4.wrap new file mode 100644 index 00000000..c151ad0e --- /dev/null +++ b/subprojects/lz4.wrap @@ -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 index 00000000..2b100f43 --- /dev/null +++ b/subprojects/lzo2.wrap @@ -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 index 00000000..274b544c --- /dev/null +++ b/subprojects/openssl.wrap @@ -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 index 00000000..37737815 --- /dev/null +++ b/subprojects/zlib.wrap @@ -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 index 00000000..f2b4d043 --- /dev/null +++ b/systemd/meson.build @@ -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) + diff --git a/test/algorithms.test b/test/algorithms.test index d0690101..9dd722c9 100755 --- a/test/algorithms.test +++ b/test/algorithms.test @@ -1,6 +1,7 @@ #!/bin/sh -. ./testlib.sh +# shellcheck disable=SC1090 +. "$TESTLIB_PATH" echo [STEP] Initialize two nodes diff --git a/test/basic.test b/test/basic.test index 87c57842..31f27552 100755 --- a/test/basic.test +++ b/test/basic.test @@ -1,6 +1,7 @@ #!/bin/sh -. ./testlib.sh +# shellcheck disable=SC1090 +. "$TESTLIB_PATH" echo [STEP] Initialize and test one node diff --git a/test/command-fsck.test b/test/command-fsck.test index 8457baa2..daf9291d 100755 --- a/test/command-fsck.test +++ b/test/command-fsck.test @@ -1,6 +1,7 @@ #!/bin/sh -. ./testlib.sh +# shellcheck disable=SC1090 +. "$TESTLIB_PATH" foo_dir=$(peer_directory foo) foo_host=$foo_dir/hosts/foo diff --git a/test/commandline.test b/test/commandline.test index ba3d7171..800dbfcf 100755 --- a/test/commandline.test +++ b/test/commandline.test @@ -1,6 +1,7 @@ #!/bin/sh -. ./testlib.sh +# shellcheck disable=SC1090 +. "$TESTLIB_PATH" echo [STEP] Initialize one node diff --git a/test/compression.test b/test/compression.test index f0952a4f..ac925417 100755 --- a/test/compression.test +++ b/test/compression.test @@ -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 </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" diff --git a/test/sptps-basic.test b/test/sptps-basic.test index 56e3cd00..0eb285b1 100755 --- a/test/sptps-basic.test +++ b/test/sptps-basic.test @@ -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 index 00000000..3d0f990b --- /dev/null +++ b/test/testlib.sh @@ -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" <"$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" <>"$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?}" </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 index c63d27d1..00000000 --- a/test/testlib.sh.in +++ /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" <"$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" <>"$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?}" </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 diff --git a/test/variables.test b/test/variables.test index 897059de..fffbd8ce 100755 --- a/test/variables.test +++ b/test/variables.test @@ -1,6 +1,7 @@ #!/bin/sh -. ./testlib.sh +# shellcheck disable=SC1090 +. "$TESTLIB_PATH" echo [STEP] Initialize one node